Implementing player movement in a Finite State Machine

Questions about the LÖVE API, installing LÖVE and other support related questions go here.
Forum rules
Before you make a thread asking for help, read this.
User avatar
Eroica
Prole
Posts: 9
Joined: Sat Feb 28, 2015 12:38 pm

Implementing player movement in a Finite State Machine

Post by Eroica »

Hello everyone,

first of all, I'm really grateful for having found LÖVE and this forum. Game development has always been my favorite hobby, and with LÖVE it feels like the game I'm day-dreaming about (like everyone else here probably :awesome: ) could be really achieved with enough effort and time.

Right now, I'm trying to prototype a basic platformer---walking, jumping, and some combat later. I've attached a .love file that represents the current state. If you open it, you'll be able to control the player using A/D (left/right) and W (a single jump). (Big thanks to kikito for his bump.lua (and other libraries) that I'm using here. Most of the movement methods are from his bump.lua example game.)

(I've stripped away some unnecessary stuff like the component system so that only the important parts about player movement remain. These are mainly located in the player.lua file and in the love.update(dt) function.)

So far I'm quite satisfied with the current mechanics--for instance, the player has to release W if he (she, in this case) wants to jump again. (In the beginning it was the case that while holding W, the player would immediately start another jump after she touched the ground again.)

However, I'm a little bit concerned about how the game logic processes the player movement. If you look at the .lua files, player movement is split into two parts: love.update(dt) checks whether a specific key is pressed, but love.keypressed(key) is also processing the W key---giving the player a little bit of a "jump" velocity.

I was under the impression that it's best to keep player movement uniform in a single update() function. Especially since my next goal would be to process player movement using a finite state machine---something that I've read about here: http://gameprogrammingpatterns.com/state.html

This is where I'm currently finding myself at a loss. My first idea would be to use love.keypressed() to change the player stage, e.g. like this:

Code: Select all

function love.keypressed(key)
    if key == "a" then
        player.state = "WALKING_LEFT"
    elseif key == "a" then
        player.state = "WALKING_RIGHT"
    elseif key == "w" then
        player.state = "JUMPING"
    else
        player.state = "STANDING"
    end
end
And using love.keyreleased() in a similar manner, for instance to set the player state to "FALLING" (meaning that the player cannot jump during this time until he touches the ground again):

Code: Select all

function love.keyreleased(key)
    if key == "w" and player.isJumpingOrFlying then
        player.state = "FALLING"
    end
end
The player:update(dt) function could then look something like (this time in pseudo-code):

Code: Select all


if player.state == "STANDING" then
    goto STANDING
end

if player.state == "WALKING_LEFT" then
    goto WALKING_LEFT
end

if player.state == "WALKING_RIGHT" then
    goto WALKING_RIGHT
end

if player.state == "JUMPING" then
    goto JUMPING
end

::STANDING::
    applyFriction()
    goto END_MOVEMENT

::WALKING_LEFT::
    player.vx = player.vx - player.movementSpeed
    goto END_MOVEMENT

::WALKING_RIGHT::
    player.vx = player.vx + player.movementSpeed
    goto END_MOVEMENT

::JUMPING::
    player.vy = player.vy + player.jumpVelocity
    goto END_MOVEMENT

::END_MOVEMENT::
    if checkIfNoCollisions() then
        player.left = player.vx
        player.top = player.vy
    end
I'm using goto and labels here because of another pattern that I've found in "Programming in Lua" (something about State Machines being easy to implement using goto).

However, I find it terribly hard to reconcile this state machine with all the booleans the game has to check if it wants to allow the player to jump, or allowing movement while jumping (e.g. jumping to the right in an arc-like shape).

That's why I wanted to ask here first if you think that there's a better approach to this. If I shouldn't use a FSM---do you think it's alright to have movement logic both in love.update() and love.keypressed()?
Attachments
love-movement.love
(26.27 KiB) Downloaded 364 times
User avatar
ArchAngel075
Party member
Posts: 319
Joined: Mon Jun 24, 2013 5:16 am

Re: Implementing player movement in a Finite State Machine

Post by ArchAngel075 »

Iremember implementing player states like "Walking" etc in the past using the vector of player movement.

Take positive X linear velocity as right, and Positive Y as upwards

If you check X > 0 then "MovementRight"
If you check X < 0 then "MovementLeft"
If you check Y > 0 then "MovementUp"
If you check Y > 0 then "MovementDown"

This also lets you use a physics system, either self made or using Love.physics (what i used)

Also if using Love Physics, for contact checking on ground, use the contact object from the world callbacks and get its normal, if the normal is above 0 then it ontop of platform, if its negative its below (or reversed, i cant test now atm)

The method worked amazingly well for a small platformer setup i made awhile back(when i was fiddling with multi-player)


Ive never used a state engine in the past i believe, so im not sure i ca assist regarding question about using finite states...
User avatar
s-ol
Party member
Posts: 1077
Joined: Mon Sep 15, 2014 7:41 pm
Location: Cologne, Germany
Contact:

Re: Implementing player movement in a Finite State Machine

Post by s-ol »

ArchAngel075 wrote:Iremember implementing player states like "Walking" etc in the past using the vector of player movement.

Take positive X linear velocity as right, and Positive Y as upwards

If you check X > 0 then "MovementRight"
If you check X < 0 then "MovementLeft"
If you check Y > 0 then "MovementUp"
If you check Y > 0 then "MovementDown"

This also lets you use a physics system, either self made or using Love.physics (what i used)

Also if using Love Physics, for contact checking on ground, use the contact object from the world callbacks and get its normal, if the normal is above 0 then it ontop of platform, if its negative its below (or reversed, i cant test now atm)

The method worked amazingly well for a small platformer setup i made awhile back(when i was fiddling with multi-player)


Ive never used a state engine in the past i believe, so im not sure i ca assist regarding question about using finite states...
From the code he posted, OP is not going to use a physics engine; his states are more like "input states" and he wants a FSM, not how to figure out which way you are moving.

@OP: First of all, I would never use gotos because they tear your code apart and basically make a mess. What I have used in the past was a setup like this:

Code: Select all

function player:update(dt)
  if self.state == "jumping" then
    self:updateJump(dt)
  elseif self.state == "falling" then
    self:updateFalling(dt)
   else
     self:updateRunning(dt)
   end
   self.x, self.y = self.x + self.vx*dt, self.y + self.vy*dt
end

player:updateRunning(dt)
  local xaccel = 0
  if love.keyboard.isDown("a") then
    xaccel = xaccel - PLYR_ACCEL
  end -- intentionally no elseif here so they cancel each other out
  if love.keyboard.isDown("d") then
    xaccel = xaccel + PLYR_ACCEL
  end
  self.xvel = self.xvel + xaccel*dt
end
-- etc.

s-ol.nu /blog  -  p.s-ol.be /st8.lua  -  g.s-ol.be /gtglg /curcur

Code: Select all

print( type(love) )
if false then
  baby:hurt(me)
end
Sosolol261
Party member
Posts: 125
Joined: Wed Nov 26, 2014 6:43 am

Re: Implementing player movement in a Finite State Machine

Post by Sosolol261 »

I looked through your code and I think, this could possibly help you.

Code: Select all

-- in love.keypressed()

    if key == 'f1' then
        if DEBUG then
            DEBUG = false
        else
            DEBUG = true
        end
    end
User avatar
Jasoco
Inner party member
Posts: 3727
Joined: Mon Jun 22, 2009 9:35 am
Location: Pennsylvania, USA
Contact:

Re: Implementing player movement in a Finite State Machine

Post by Jasoco »

This will help even more compactly

Code: Select all

if key == "f1" then
    DEBUG = not DEBUG
end
Sosolol261
Party member
Posts: 125
Joined: Wed Nov 26, 2014 6:43 am

Re: Implementing player movement in a Finite State Machine

Post by Sosolol261 »

Jasoco wrote:

Code: Select all

if key == "f1" then
    DEBUG = not DEBUG
end
wow.. unbelievable how I NEVER thought of that...
User avatar
nfey
Citizen
Posts: 63
Joined: Tue Feb 18, 2014 9:50 am
Location: Romania

Re: Implementing player movement in a Finite State Machine

Post by nfey »

Regarding this little snippet and others like it:

Code: Select all

if key == "a" then
        player.state = "WALKING_LEFT"
The idea of using a FSM is to have all the conditional logic encapsulated inside the machine and invisible to the outside modules. The FSM will know how the different states work and what the transitions between them are. In order for this to work, you need to let the machine hold it's state and decide when to change it. Your input to the FSM should not be

Code: Select all

machine.state = NEW_STATE
but rather

Code: Select all

machine.execute(ACTION)
and then let the machine decide, based on it's current state and action entered, if a transition is possible and how to go about with executing it.

Just nitpicking on your original code, but I think it's an important detail to keep in mind if you want to obtain well organized code.
Last edited by nfey on Sun Mar 01, 2015 4:37 pm, edited 1 time in total.
User avatar
s-ol
Party member
Posts: 1077
Joined: Mon Sep 15, 2014 7:41 pm
Location: Cologne, Germany
Contact:

Re: Implementing player movement in a Finite State Machine

Post by s-ol »

nfey wrote:Regarding this little snippet and others like it:

Code: Select all

if key == "a" then
        player.state = "WALKING_LEFT"
The idea of using a FSM is to have all the conditional logic encapsulated inside the machine and invisible to the outside modules. The FSM will know how the different states work and what the tranzitions between them are. In order for this to work, you need to let the machine hold it's state and decide when to change it. Your input to the FSM should not be

Code: Select all

machine.state = NEW_STATE
but rather

Code: Select all

machine.execute(ACTION)
and then let the machine decide, based on it's current state and action entered, if a tranzition is possible and how to go about with executing it.

Just nitpicking on your original code, but I think it's an important detail to keep in mind if you want to obtain well organized code.
That's basically like my example (except I didn't encapsulate it away in another "machine" object, the player object is the FSM).

Also your states are way too specific, they are what you would draw in theory for a FSM, but in programming we have much more optuons, and a vanilla FSM is just wasting stuff here (WALK_LEFT and WALK_RIGHT can very easibly be one state).

s-ol.nu /blog  -  p.s-ol.be /st8.lua  -  g.s-ol.be /gtglg /curcur

Code: Select all

print( type(love) )
if false then
  baby:hurt(me)
end
User avatar
nfey
Citizen
Posts: 63
Joined: Tue Feb 18, 2014 9:50 am
Location: Romania

Re: Implementing player movement in a Finite State Machine

Post by nfey »

S0lll0s wrote: That's basically like my example (except I didn't encapsulate it away in another "machine" object, the player object is the FSM).
Yes, it is, I just wanted to explain why it should look like that.
Also, I know you put the FSM logic inside the player class just for convenience, for this example, but it might be a good idea to stress that the FSM should be a separate class. Mostly because the player will probably also need to fire weapons, interact with the environment, manage an inventory, etc. - it's best to have specialised classes to take care of these actions or the player class will become a mess.
User avatar
Eroica
Prole
Posts: 9
Joined: Sat Feb 28, 2015 12:38 pm

Re: Implementing player movement in a Finite State Machine

Post by Eroica »

Hi, thanks for all your replies so far. I guess my original post was a little bit cluttered because it basically asked two things separately---how best to implement a finite state machine, and how best to split up movement logic in love.update(dt) and love.keypressed(key).

Regarding the FSM:
nfey wrote:Regarding this little snippet and others like it:

Code: Select all

if key == "a" then
        player.state = "WALKING_LEFT"
The idea of using a FSM is to have all the conditional logic encapsulated inside the machine and invisible to the outside modules. The FSM will know how the different states work and what the transitions between them are. In order for this to work, you need to let the machine hold it's state and decide when to change it. Your input to the FSM should not be

Code: Select all

machine.state = NEW_STATE
but rather

Code: Select all

machine.execute(ACTION)
and then let the machine decide, based on it's current state and action entered, if a transition is possible and how to go about with executing it.

Just nitpicking on your original code, but I think it's an important detail to keep in mind if you want to obtain well organized code.
This seems clear to me, I'll try to implement movement this way and let you guys know. By the way, you were right that player movement is currently in player = {} out of convienience---in the original code I'm already using an entity component system in which a "playerUpdateSystem" (in a manner of speaking) updates the player's position, a "playerDrawSystem" draws the sprite, etc.

If I understand you correctly, a system like those should fire an "ACTION" based on which key was pressed, and update the player's state in its methods. Which brings me to my other point: Is there any inconvenience in having "key-press logic" both in love.update() and love.keypressed()?

I hope I don't sound too confusing with this, perhaps I'm still lacking the "a-ha!" moment before I see all of this clearly. Nevertheless, I'm already happy that you had the patience to look over my code and improve it (thanks for the DEBUG hint too, by the way!). If there is anything else that you think might help me, feel free to correct me.
Post Reply

Who is online

Users browsing this forum: Bing [Bot] and 5 guests