2
\$\begingroup\$

I'm trying to implement a platformer character using a finite state machine, but I have troubles understanding what kind of logic goes into a state and what logic goes into a main player class directly.

I have a base state class and a couple of state classes that implement it, like Idle, Running, Jumping state etc. Every state has an update method. The player class has a member state_machine that handles transitioning states and other state related things.

Now, the player has some logic that applies in most states, like gravity, so I can have that logic in a main player class so it doesn't get repeated in every state. But also logic that only applies in certain states, like being able to jump or attack, changing directions etc. How do I reuse the same logic in multiple states without moving it to the player class, using a lot of if statements or duplicating it between states?

\$\endgroup\$

1 Answer 1

3
\$\begingroup\$

Keep in mind that there is not a single one way to go about this.

If the issue is how to reuse behavior. I want to encourage to separate state and behavior in your mind.

Yes, there is an state machine. And yes the behavior of your player character changes with its state. But state and behavior are not the same thing. For example, you have some behavior that does not depend on the state. And you want to have behavior shared among some states but not others.

So, I suggest to have a list of behaviors per state. Which you execute in order. And, perhaps, you have some behaviors in multiple of these lists. Yes, those are behavior objects.


I want to suggest an organization that has worked for me. It is not a silver bullet by any means. But it might be adequate for you.

If you consider physics something external to the behavior. Then it follows to not allowing the behaviors to interact directly with the physics engine. They of course, need to interact somehow. For that, you can allow them to get and set properties of the main class.

I'll tell you what I settled into. The main class update method does these steps:

  • Motion.

    Here you do physics based motion that works regardless of the state. Basically these:

    • Rotation. There is a target direction that behaviors can set, and there are rotation speed variables the the behaviors can set as well.
    • Translation. Using the velocity we got, of course. By the way, you may find useful to allow the behaviors to set a property to have player character snap to a surface.
    • Collisions. So we moved, did we collide? Did we bounce or slide? Do we need to update position or velocity?

    If I need to tell the physics engine to update something else based on what the behaviors are doing, I would add it here. For example, I suppose we could add stuff like drag and friction.

  • Physics checks.

    You know, raycasts and stuff. Gather any information about the physical surrounding that you want to be available for the behaviors to read. Including - but not limited to - ground checks.

  • Update Velocity (Gravity and other accelerations).

    Turns out I need to give accelerations a special treatment. While I could keep accelerating against an obstacle (e.g. gravity on the ground), I don't want to (you don't want the speed to build up, and have the player character tunnel through). And doing it after knowing the result of physics checks allows me that.

    To be clear, we use accelerations to update velocity. But velocity can be set by other means. Including externally or form the behaviors (running and jumping behaviors do this).

    By the way, the world controls gravity, not the behaviors. However, the behaviors can set a gravity factor.

    We can also enforce terminal velocity here.

  • Update Target Direction.

    Based on the results of physics checks, and also on the velocity after accelerations has been applied, I update the target direction. This could be done by a behavior. In fact executing behaviors is the next step, so it won't break much to do so. However, I have decided that direction is responsibility of the main class, since rotation is responsibility of the main class.

  • Execute Behaviors.

    Here is where the behaviors for the current state get to run. And yes, multiple per state. With some reused between multiple states.

    I actually let the run behavior execute in the air, so you have air control with the same logic, even if the state is jumping.

    Consequently the run behavior cannot be responsible of setting animations. In general, I have separate behaviors for setting animations and effects based on the current state.

    On a similar note, I have found useful to define parameters for air and ground… Which are not states of the state machine. Instead they are part of the results of the physics checks that the behaviors can read. And based on that the behavior picks the parameters it will use.

With this approach I have clarity of where everything goes. Also notice that most of the conditional behavior in the main class is not controlled by if statements, but by setting speeds or accelerations or factors which if set to zero would disable that behavior, but that does not require additional flow control. Snapping to a surface is the glaring excpetion.

\$\endgroup\$

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .