Kinematics System Concept


INTRODUCTION TO THE SYSTEM

I've always learned that abstraction is a man's best friend when it comes to video games. Don't make everything too rigid. Everything has to be adaptable. I realized that I've come up with my own system that handles the player movement for a platformer quite well. I aptly name it the "Godot Kinematic System" (GKS). It's just a node that can be attached to any KinematicBody2D (NPC or player) that holds the following variables/constants:

  • walking speed - minimum (for animation purposes), maximum
  • acceleration - modify if you want momentum-based, iced mechanics, or snapping to full speed
  • friction - how fast do you slow down
  • wall friction - same thing for walls
  • air resistance - essentially what the friction is set to in the air
  • maximum slope angle - how tall of a slope can you climb?


IMPLEMENTATION OF PHYSICS (AND "MODIFYING" GODOT)

There are some further improvements I have made since my experience is in physics. The jump kinematics are based on the basic trajectory equations you'd see in an introduction physics course.  The two noticeable quantities that we care about in terms of a platformer is how high we jump and how long it takes us to do so. 

Normally, you have the same time from the ground to the peak as you do the peak to the ground. However, if we want to allow for longer hang-time, we can set a longer fall time. So how do we go about setting gravity and jump speed from the times and heights we want? Just use the kinematic equations.


We see that the equations are simple and only rely on the height and time, respectively. Note that how I implement this is as follows:

  • jump_impulse = 2.0 * jump_height / time_to_peak
  • jump_gravity = -2.0 * jump_height / pow(time_to_peak, 2.0)
  • fall_gravity = -2.0 * jump_height / power(time_to_fall, 2.0)

How do I handle which gravity is applied? Well, if we are at a positive velocity, apply jump_gravity. Otherwise, apply fall_gravity.


ISSUE WITH PHYSICS VS. GODOT

There is one small issue in terms of applying this system as is - typical kinematic equations assume that positive y-axis is upward, where Godot inverts this. There is an easy solution to this.


The only place where it matters that the y-velocity's sign matters is when applying the actual movement. Store the velocity as expected in physics, BUT apply it to the move_and_slide methods with the sign flipped. I implement this as follows:

var godot_velocity = Vector2(velocity.x, -velocity.y)

velocity.y = -body.move_and_slide_with_snap(godot_velocity, ...).y


APPLYING FORCES/ACCELERATION AND GRAVITY

We want to be able to apply forces rather than just setting the velocity (in most cases). This is rather simple. From Newton's second law (F=ma), we know that force is related to acceleration by mass. In my games, weight/mass don't really have any importance, so we just allow for an acceleration to go in and be added to the velocity vector. My method has the option to force the value rather than add it on, if necessary.

What about slowing down? We cheat it a bit and just lerp the velocity to zero with the friction as the time step. For gravity, I have two methods - 1) the method that handles which gravity value I use, 2) the one that applies gravity. I have a special addition to this method that applies gravity because of a project I never finished. The game holds an integer named gravity_direction (+1 = normal gravity, 0 = no gravity, -1 = anti-gravity). This allows for anti-gravity or turning off gravity in a game. I learned in an old project that if you want to hang on the ceiling, it might actually be useful to handle ceiling slopes by applying anti-gravity as appropriate. It's a bit complicated and would be better seen in a demo (which I unfortunately am not doing here). 

Here's a question though - if I allow for all this, how do I handle collisions?


RAYCAST SUBSYSTEM

To make the collisions with the floor, ceiling, walls, platforms, etc. work properly, I have SIX raycasts included in the system. 

  • Floorcast - self-explanatory
  • Ceilingcast - also self-explanatory
  • Leftcast - checks for collisions with walls on the left side
  • Rightcast - checks for collisions with walls on... well, you can figure it out
  • OneWaycast - checks for collisions with one-way platforms; allows for fall-through platforms
  • Platformcast - checks for collisions with moving platforms (might be on a different layer than the floor)

This is all great, "But what if you're applying anti-gravity? The floor is now the ceiling and vice versa!" I'm glad you bring this up because I've already accounted for this. Rather than using the standard "is_on_floor()" and similar methods, I have my own methods that I created for walls, floor, and ceiling. The way this works is it takes into account the gravity_direction variable. As a sample, I do the following:

func on_floor() -> bool:

             return (floorcast.is_colliding() and gravity_direction == 1) or (ceilingcast.is_colliding() and gravity_direction == -1)

This is a simple enough solution and I don't see a way to make it simpler. I do recommend having Globals to represent +/- 1 as GRAVITY and ANTI_GRAVITY, respectively, for readability. 


TRIGGERS AND INPUT

Well, this is all great, but how do we apply it? It is intended for NPCs (in my games) to have a handle_input() method that do whatever is necessary and then they pass the input into its kinematic system. Each entity (either within the kinematic system or itself) should have the following triggers:

  • input_active
  • animations_active
  • gravity_active

These handle necessary elements within the classes and I have a state system which handles them as necessary. 


SUMMARY

The great thing about this system is while this seems like a lot, this system will be easily transferable between projects and I can use it again for other projects. Rather than add a bunch of code to a new KinematicBody2D, all I have to do is copy over a single .tscn and accompanying .gd file and I am good to go. I recommend if you're considering making a platformer to try to make your own version of this system. It seems like it's a great timesaver at the end of the day.

Leave a comment

Log in with itch.io to leave a comment.