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.