Welcome, Guest. Please login or register. Did you miss your activation email?

Author Topic: Re:creation - a top down action adventure about undeads [hiatus]  (Read 443262 times)

0 Members and 1 Guest are viewing this topic.

Elias Daler

  • Hero Member
  • *****
  • Posts: 599
    • View Profile
    • Blog
    • Email
Re:creation - a top down action adventure about undeads
« Reply #615 on: July 05, 2016, 11:50:43 pm »
Decent work so far!

So what you basically do is store a callback function together with a button/key and invoke that callback when the appropriate inputEvent is catched? What if you want multiple callbacks to be invoked on the same keypress?
Yep. Right now I have basic input events like key press or key release but probably will create something more complex if needed.
Right now I'm doing this:
callbacks["PRESS_A"] = { type = ButtonPressedAction(keys.A), f = f1 }
callbacks["PRESS_A2"] = { type = ButtonPressedAction(keys.A), f = f2}
As you can see, both "PRESS_A" and "PRESS_A2" callbacks will be called on one button press.

I don't use vectors as Mario proposed because removing specific callbacks from that vector will be kinda tricky. Suppose I did something like this:
callbacks[ButtonPressedAction(keys.A)] = { PRESS_A = f1, PRESS_A2 = f2}
And now I want to remove PRESS_A callback. The problem is that I can't find table which contains it because doing this:
local callbacksWithA = callbacks[ButtonPressedAction(keys.A)]
won't work, because ButtonPressedAction(keys.A) will create another table and so it won't be the same as the one that was used as key at callback registration. I don't have solution to this problem at the moment. The only thing that comes to my mind is doing this:
callbacks.onButtonPress[keys.A] = { PRESS_A = f1, PRESS_A2 = f2}
So that I can later do this:
local callbacksWithA = callbacks.onButtonPress[keys.A]

Btw I think I saw you posting at Sol2 on GitHub (the world is small), is that right? I recently started using it and I think its pretty decent. Do you use it?
Yep, I've created several issues there. I'm using it and I've written a bit about it in the latest dev log on my blog. It's very good :)
Tomb Painter, Re:creation dev (abandoned, doing other things) | edw.is | @EliasDaler

Ungod

  • Newbie
  • *
  • Posts: 44
    • View Profile
Re:creation - a top down action adventure about undeads
« Reply #616 on: July 06, 2016, 08:04:35 am »
Use something capable of storing multiple entries, such as a vector? :)

The result would be a datastructure where just (in most cases) very few vectors have more than one element. Not that aesthectic. Just wondered how one could solve that "better".  :)


Elias Daler

  • Hero Member
  • *****
  • Posts: 599
    • View Profile
    • Blog
    • Email
Re:creation - a top down action adventure about undeads
« Reply #617 on: July 09, 2016, 01:41:11 am »
I was recently inteviewed on CppCast! Listen to the episode here. If you've been following this thread, you probably know most of the stuff, but still, it may be interesting for you. :)

I've also mentioned SFML several times, so it's pretty good too, I think! ;D
Tomb Painter, Re:creation dev (abandoned, doing other things) | edw.is | @EliasDaler

Hapax

  • Hero Member
  • *****
  • Posts: 3351
  • My number of posts is shown in hexadecimal.
    • View Profile
    • Links
Re:creation - a top down action adventure about undeads
« Reply #618 on: July 09, 2016, 04:25:33 pm »
I just wanted to point out that, since you've redrawn the player graphics, you should probably update the ones on the original post. ;)
Selba Ward -SFML drawables
Cheese Map -Drawable Layered Tile Map
Kairos -Timing Library
Grambol
 *Hapaxia Links*

Tank

  • SFML Team
  • Hero Member
  • *****
  • Posts: 1486
    • View Profile
    • Blog
    • Email
Re:creation - a top down action adventure about undeads
« Reply #619 on: July 09, 2016, 10:01:18 pm »
Nice CppCast, Elias. :)

Elias Daler

  • Hero Member
  • *****
  • Posts: 599
    • View Profile
    • Blog
    • Email
Re:creation - a top down action adventure about undeads
« Reply #620 on: July 10, 2016, 10:29:36 am »
I just wanted to point out that, since you've redrawn the player graphics, you should probably update the ones on the original post. ;)
Yup, these animations aren't drawn yet, but I'll update it once I redraw them, thanks :)

Nice CppCast, Elias. :)
Thank you!
Tomb Painter, Re:creation dev (abandoned, doing other things) | edw.is | @EliasDaler

Elias Daler

  • Hero Member
  • *****
  • Posts: 599
    • View Profile
    • Blog
    • Email
Re:creation - a top down action adventure about undeads
« Reply #621 on: July 25, 2016, 03:14:34 pm »
I'm back! Spent some time with family and taking a rest from all that study stuff. But now I'm back, though I don't have much time to work on the game, so I'll work on some things that I really need to implement which may not be really that interesting.

* Multiple tile maps on the same level. Useful for creating indoor maps which won't require any loading. Much easier to use and implement than having them on the same tile map just far-far away.
* Better collision detection. First of all, I need to use some space partitioning and I'll probably go with the simplest approach: uniform grid. I'll implement quad tree if there's a need for it (probably won't need it, as there's probably won't be lots of entities on the screen at the same time).
I also want to implement diagonal tiles but I don't have any idea how to do them at the moment, so any suggestions are welcome!
* Making entity descriptions pure data. I've talked a bit previously about this, but now it's clearer to me why moving functions out of entity descriptions is awesome.

1) I can easily reuse them. Most functions inside entity description scripts are collision responses and different triggers. Some collision responses may be the same, for example, entities saying "watch where you're going". Copying even one line of the same behavior can be dangerous and lead to some hard to fix bugs (DRY!)
2) Serialization. I want to make entity descriptions serializable so I can edit them inside some Entity Editor (kinda like Animation Editor). I can't save Lua functions, I can only save data.

So, what is the alternative to storing collision response in script and having CollisionSystem call that function on collision? Easy, I use events. CollisionSystem sends OnCollisionEvent and then state machine of the entity catches it and the current state reacts to that event.

This is cooler than I thought at first. Imagine if I want to implement some guy which can carry some precious item and drop it on collision with another entity if it was moving faster than SOME_BIG_SPEED. If he doesn't carry anything, he just swears.

Here's how old approach looks:
some_guy = {
    ...
    CollisionComponent = {
        onCollision = function(this, second)
            local currentState = stateManager.getCurrentState(this)
            if currentState == State.IdleState then
                this:swear()
            elseif currentState == States.CarryingItemState then
                if Vector2f.normalize(second:getVelocity()) > SOME_BIG_SPEED then
                    this:dropItem()
                end
            end
        end
    }
}
 
This is bad, because if/else branches may become pretty big this way. Here's how it will look with my state machine system:
local SomeGuyStates = {
    IdleState = {
        [EventType.CollisionEvent] = {
            callback = function(this, event)
                this:swear()
            end
        }
    },
    CarryingItemState = {
        [EventType.CollisionEvent] = {
            callback = function(this, event)
                if Vector2f.normalize(second:getVelocity()) > SOME_BIG_SPEED then
                    this:dropItem()
                end
            end
        }
    }
}

Okay, that may not look as straightforward as the first approach, but it's much more scalable and let's me easily add intended behavior which varies depending entity's current state.

* Input. I've made some progress in this area. Now I have InputManager in Lua which has stuff like this (some things are simplified:
local playerCallbacks = {
    UseItem = {
        type = InputType.Player,
        callback = function()
            local player = getPlayer()
            player:usePrimaryItem()
        end
    },
    ...
}

function InputManager:initialize()
    self.onPressCallbacks = {}
    self.oneReleaseCallbacks = {}
    ...
end
And then I set input like this.
inputManager.setPressedCallback(Button.PrimaryAction, playerCallbacks.UsePrimaryItem)

* Reloading changes in scripts. Previously I could reload entities by changing their scripts and then typing "reload <ENTITY_NAME>" in in-game console. But this wasn't very efficient, because most changes are local to one component. So now I want to implement something like "reload <ENTITY_NAME>.<COMPONENT_NAME>" (e.g. "reload hero.CollisionComponent")
I can even take it further sometime and make some systems watch over currently used script files and reload them automatically if they detect any changes. Determining the change will be the hardest thing and I just plan to compare component tables inside entity description scripts. If some things changed, reloading system will just re-make the component in each entity of this type and give them copies of new one. Trying to detect changes INSIDE the component is much harder and I won't spend time on that. Though it would be pretty awesome to be able to change one thing inside the script, hit CTRL+S and have it changed in C++ without creating any new objects.

But that's pretty hard to make! Having "reload <ENTITY_NAME>.<COMPONENT_NAME>" will probably be enough for me for now.

* Unbreaking stuff. There are some things that I break with system changes and then don't have time to fix them. For example: some actions lists and complete levels are broken and need to be rewritten. That's what I'm going to do in the next weeks. :D

* Some refactoring and improving code. One thing that I dislike about my code base is that I use raw pointers quite a lot in some places. For example, Entity::get<ComponentType> returns a raw pointer to component. This is not bad, because the component can not be present and then I'll just return nullptr, but I hardly check for that anywhere, so references are the way to go. If I'm really want to know if the component is present, I can call Entity::has<ComponentType> anyway.
There are some small changes that I have to make and I'll write them there if I find them good enough.

* Lua. In recent days I've ported all my Lua/C++ code to sol2 and now LuaBridge is not used anywhere. This feels really good. I also really want to keep Lua/C++ stuff in one place, it's pretty hard to do now (it keeps getting better with events and moving some C++ stuff to action lists though). I wish there was some good example on how to keep Lua/C++ stuff in one place, but I haven't seen this anywhere, so I'll see what works for me.

* Combat. Okay, that one is the coolest. I'll just work on combat, draw some battle animation and see what works and what doesn't. The combat won't be complex, but I want it to be cooler than ALttP. The player won't have a shield in his undead body, so he'll only be able to dodge attacks. Let's see how it works, there aren't many top down action adventure games which use this system.
« Last Edit: July 25, 2016, 03:16:44 pm by Elias Daler »
Tomb Painter, Re:creation dev (abandoned, doing other things) | edw.is | @EliasDaler

Elias Daler

  • Hero Member
  • *****
  • Posts: 599
    • View Profile
    • Blog
    • Email
Re:creation - a top down action adventure about undeads
« Reply #622 on: July 26, 2016, 07:37:29 pm »
Worked with input today, made some cool progress.

First of all, I started to tie input to player states. For example, consider moving around. Player can only move when the hero is in IdleState and MoveState (when you walk around and change the direction without stopping. I could write it like this:
local currState = player:getState()
if currState == State.IdleState or currState == State.MoveState then
    if Input.isPressed(Button.Up) then
        player:moveUp()
    elseif ...
end
First of all, this is not scalable and can lead to spaghetti code and lots of if/else states.  Secondly, the input is tightly coupled to game logic which goes against the separation I try to make. Okay, I can rewrite it like this:
local playerCallbacks = {
    MoveUp = {
        isActive = function()
            return (currState == State.IdleState or currState == State.MoveState)
        end,
        callback = function()
            player:moveUp()
        end
    },
    ...
}

...

inputManager:setPressedCallback(Button.Up, playerCallbacks.MoveUp)
Okay, that's a bit better, because now I can easily redefine input and not tie it too tightly with game logic. isActive function is used as additional check after Input.isPressed(<SOME_BUTTON>) to see if the callback should be called or not. But that solution wasn't good enough for me (suppose that I have X states in which player can move and Y states when he can only move up and down), so I went with more readable and scalable approach, I just do this:
local playerCallbacks = {
    MoveUp = {
        callback = function()
            player:moveUp()
        end
    },
    ...
}

...

local HeroStates = {
    IdleState = {
        enter = function()
            ...
            inputManager:setPressedCallback(Button.Up, playerCallbacks.MoveUp)
        end,
        exit = function()
            ...
            inputManager:setPressedCallback(Button.Up, nil) -- disable this input
            ...
        end
    },
    ... -- same for MoveState
}
Why do I have to disable the callback when I exit the state? That's because each state has to define controls in it. In some cases you only get ability to attack, in some cases "Up" now means another thing.
Awesome! Let me show you how easily expandable this is. Suppose that someone hits the hero. If the hit was too heavy, the controls become a bit reversed! (This is just an example, won't happen in the game). I can easily express it in code without adding new if/else conditions and breaking anything along the way!
local HeroStates = {
    ...
    HurtState = {
        ... -- same controls as IdleState, same code
    },
    HurtBadState = {
        enter = function()
            ...
            inputManager:setPressedCallback(Button.Up, playerCallbacks.MoveDown)
        end,
        exit = function()
            ...
            inputManager:setPressedCallback(Button.Up, nil)
            ...
        end
    }
}
And that's how it works. Maybe I'm overcomplicating stuff, but I'm very satisfied with the end result.
Another thing about input. Right now I use previous and current frame inputs to check if the button was just pressed or just released, but now I think that I'll just use events for that because the same can be achieved with them. Are there any stuff which can't be solved with events and info about the button press at the current frame? I can't think of the situation which isn't solvable with this approach.

And the last thing are events. I think that I need to have some events which can be sent directly to some objects instead of having just one global event queue.
Here's an example where I need that. Suppose two entities collided and I don't want to call collision response callbacks immideately. So I create CollisionEvent which has some data, like ids of first and second entities and some other stuff.
Now entities can either subscribe to particular event and either recieve messages from everyone or subscribe to particular entity. In this case, there's no clear sender and even if there was one, it would still feel kinda awkward. I want to do something like this:
-- event in Lua
-- suppose that 'first' and 'second' collided
local e1 = {
    sender = first,
    recipient = second,
    data = {
        ... -- additional data
    }
}
local e2 = {
 ... -- almost the same with sender and recipient switched around
}
eventManager:queueEvent(e1)
eventManager:queueEvent(e2)
Neat! Now the events will be send from 'first' to 'second' and vice versa instead of telling about it to everyone and then checking if entity is interested in that collision or not (which is really slow). What do you think of that approach?
Tomb Painter, Re:creation dev (abandoned, doing other things) | edw.is | @EliasDaler

Elias Daler

  • Hero Member
  • *****
  • Posts: 599
    • View Profile
    • Blog
    • Email
Re:creation - a top down action adventure about undeads
« Reply #623 on: July 31, 2016, 02:06:18 pm »
Three days of suffering and now I have this:

Yup, now I can handle diagonal tiles! And they can have different angles!
First of all, I didn't use any algorithm or tutorial because I haven't found any which explained it good enough. Most of them are poorly explained, have bugs and fail in lots of cases or have to many limitations (like having to add special "END OF HILL" tile).
So, here's how my method works.
First of all, I have to resolve the collision after I detect it.

First of all, I check one of the points of the AABB is inside the triangle. The point which is checked depends on the rotation of triangle. In the case shown on the picture, I just use p.x and then calculate yTest like this: yTest = k * p.x + b, where y=kx+b is line equation. Then I check if c.y < yTest (if the y coord of point is below the line). If it is, then it means that the point is inside the triangle.

Then I find dx and dy - intersection depth. If the entity was moving down, I push it by "dy" up. If it was moving left, I push it by "dx" to the right as if it never crossed the boundaries of the triangle.
The most complex situation is pushing it out if it was moving down AND left. In 45 degree case, it's pretty easy and is shown on the picture. For other cases it's harder and so far I haven't found the easy way to push it out perfectly for other angles.

But just pushing it out of the triangle isn't enough. Suppose that entity moves down (player holds the "DOWN" button). It's expected that entity will slide down-right along the wall, so just pushing it out is not enough, I'll have to move it a bit down and right as if it was sliding along the wall.
There are some other considerations to be had, but after all the method works.
After I clean up the code, I'll probably open source it and maybe someone will help me make it even better.

One more thing, I've noticed that my AABB collision test wasn't perfect and didn't detect a collision when it was very small, so I've remade the algorithm and now it looks like this.

sf::Vector2f getIntersectionDepth(const sf::FloatRect& rectA, const sf::FloatRect& rectB)
{
    if (rectA.width == 0.0f || rectA.height == 0.0f || rectB.width == 0.0f || rectB.height == 0.0f) { // rectA or rectB are empty
        return sf::Vector2f();
    }

    sf::Vector2f depth;

    if (rectA.left < rectB.left) {
        depth.x = rectA.left + rectA.width - rectB.left;
    } else {
        depth.x = rectA.left - rectB.left - rectB.width;
    }

    if (rectA.top < rectB.top) {
        depth.y = rectA.top + rectA.height - rectB.top;
    } else {
        depth.y = rectA.top - rectB.top - rectB.height;
    }
    return depth;
}

Previosly, it looked like this:
sf::Vector2f getIntersectionDepth(const sf::FloatRect& rectA, const sf::FloatRect& rectB) {
    if (rectA.width == 0.0f || rectA.height == 0.0f || rectB.width == 0.0f || rectB.height == 0.0f) { // rectA or rectB are empty
        return sf::Vector2f();
    }
    // get centers of rectangles
    sf::Vector2f centerA = sf::Vector2f(rectA.left + rectA.width / 2.0f, rectA.top + rectA.height / 2.0f);
    sf::Vector2f centerB = sf::Vector2f(rectB.left + rectB.width / 2.0f, rectB.top + rectB.height / 2.0f);

    float dx = centerB.x - centerA.x;
    float dy = centerB.y - centerA.y;
    float min_dx = (rectA.width + rectB.width) / 2.0f;
    float min_dy = (rectA.height + rectB.height) / 2.0f;

    if (std::abs(dx) >= min_dx || std::abs(dy) >= min_dy) { // no intersection;
        return sf::Vector2f();
    }

    float depthX = dx > 0 ? min_dx - dx : -min_dx - dx;
    float depthY = dy > 0 ? min_dy - dy : -min_dy - dy;

    return sf::Vector2f(depthX, depthY);
}
This code was used in XNA Platformer example and worked good enough, but after inspecting it, I noticed that there was no need to find centers of each AABB!

There was one small bug with my implementation, though.
At first I wrote one line like this:
depth.x = rectA.left - (rectB.left + rectB.width);
Now, let's say the entity had pos.x == 20.f and I pushed it by some v * dt. The problem was that after resolution, the pos wasn't 20.f, it was something like 19.999999998f; or something. Writing this interestingly enough fixed the problem:
depth.x = rectA.left - rectB.left - rectB.width;
Now the resolution is perfect!

That's it for now. What do you think of the method? Maybe you have some method that is easier to implement? :D
I think that instead of moving it along the slope myself, I need to add something to velocity so the entity will move at the next frame by the needed amount, but I found it too hard to implement unfortunately. :S

(BTW, I think I will only use 45 angle tiles because they're easier to draw and work the best, he-he)
« Last Edit: July 31, 2016, 02:39:56 pm by Elias Daler »
Tomb Painter, Re:creation dev (abandoned, doing other things) | edw.is | @EliasDaler

Mortal

  • Sr. Member
  • ****
  • Posts: 284
    • View Profile
Re:creation - a top down action adventure about undeads
« Reply #624 on: July 31, 2016, 09:29:50 pm »
interesting, from demo, it looks work perfectly.  :)

i just want to draw your attention at the condition in getIntersectionDepth(). the condition statement ( if (rectA.width == 0.0f ... ) will never be true due to the floating point. better to round it with some epsilon value.

btw, since you are using AABB collision, sf::Rect can return the intersect rect and i think this what you're want. am i right?
« Last Edit: July 31, 2016, 09:33:31 pm by Mortal »

Elias Daler

  • Hero Member
  • *****
  • Posts: 599
    • View Profile
    • Blog
    • Email
Re:creation - a top down action adventure about undeads
« Reply #625 on: July 31, 2016, 10:25:34 pm »
interesting, from demo, it looks work perfectly.  :)
Yep, it looks like this, but there are some edge cases when it fails. It also doesn't work correctly with platformers where your have gravity pushing entities downwards... I hope I'll be able to fix it.
This isn't a problem with top-down thankfully, but I want to be sure that the algorithm works no matter what.

i just want to draw your attention at the condition in getIntersectionDepth(). the condition statement ( if (rectA.width == 0.0f ... ) will never be true due to the floating point. better to round it with some epsilon value.
Hmm, that's not true. :P
Check this out: http://ideone.com/bCbpwX
At some point, there's a possibility that you'll lose some precision and (x - x) won't be exactly equal to zero, yes. But if you assign 0.0f to float, you can be sure that this will be equal to 0.0f.
So, I'm just testing if each rect actually has non-zero size. This is rare though and this condition can be probably thrown out. :D

btw, since you are using AABB collision, sf::Rect can return the intersect rect and i think this what you're want. am i right?
Testing for the collision is not enough, I'm also getting intersection depth which is then used for collision resolution. :)
Tomb Painter, Re:creation dev (abandoned, doing other things) | edw.is | @EliasDaler

Elias Daler

  • Hero Member
  • *****
  • Posts: 599
    • View Profile
    • Blog
    • Email
Re:creation - a top down action adventure about undeads
« Reply #626 on: August 10, 2016, 11:44:20 pm »
I drew new walking animation and made some slight changes to Renatus' design.

I think it's much better than all previous iterations. :D

It looks very clean. :D
The coolest thing about it is that it was pretty cheap to make. 2nd and 4th frames are idle frames and 1st and 3rd just mirror each other.

I've also made some random tile tests to figure out how much "nature-like" I can make roads and the result looks pretty good:

That's only 5 road-grass transition tile variations (some tiles are just mirrored and rotated). Now I'll have to draw something like that for Re:creation :D

Don't have much time to work on the game atm but hopefully I'll soon get some!
« Last Edit: August 11, 2016, 08:22:29 am by Elias Daler »
Tomb Painter, Re:creation dev (abandoned, doing other things) | edw.is | @EliasDaler

Xoodar

  • Newbie
  • *
  • Posts: 4
    • View Profile
    • Email
Re: Re:creation - a top down action adventure about undeads
« Reply #627 on: August 12, 2016, 12:38:22 am »
The game looks awesome!
I started following you some weeks ago on Twitter and on your blog, you are capable of inspire others, congratulations!
I have one question about the level saving/loading.
How do you handle them? I mean, how do you handle different tilesets in one level-room?

Mr_Blame

  • Full Member
  • ***
  • Posts: 192
    • View Profile
    • Email
Re:creation - a top down action adventure about undeads
« Reply #628 on: August 12, 2016, 02:16:58 pm »
For no reason the new main character animation reminds me Earthbound characters walk cycle.:) Looks like I played to much Earthbound last week :P
« Last Edit: August 14, 2016, 09:57:25 am by Mr_Blame »

Elias Daler

  • Hero Member
  • *****
  • Posts: 599
    • View Profile
    • Blog
    • Email
Re: Re:creation - a top down action adventure about undeads
« Reply #629 on: August 20, 2016, 11:48:01 pm »
The game looks awesome!
I started following you some weeks ago on Twitter and on your blog, you are capable of inspire others, congratulations!
I have one question about the level saving/loading.
How do you handle them? I mean, how do you handle different tilesets in one level-room?
Thank you! And I'm very glad to hear that I inspire people. :)

Level consists of stuff stored in txt (mostly tile info) and some additional Lua scripts which contain some data like music path, ambient path, area info, etc. and functions which are executed when you enter different areas on the level.

And what do you mean by level saving? Do you mean in-game saving or saving level after it has been edited in level editor?

Different tilesets are handled with multiple vertex arrays. :)

For no reason the new main character animation reminds me Earthbound characters walk cycle.:) Looks like I played to much Earthbound last week :P
Ha-ha, Mother 3 walking cycles actually inspired me a lot, so it's no coincidence :D



A bit off-topic: I've just finished preparing/taking exams for my Master's degree (which is why I didn't do anything about the game for a long time). I'm finally going to get my degree in HSE which was the best option for me, so yay!
And also, that means that I'll finally get some free time to work on the game again. (But who knows, maybe this new study will be pretty demanding, I'll see).
Tomb Painter, Re:creation dev (abandoned, doing other things) | edw.is | @EliasDaler