Posted November 20, 2023 by 0de
If you've played Beaconfall at all up to this point, one of the things you've probably picked up on is the slippery-ness of the player's movement, especially when you get to higher speeds. This is something which will probably be addressed directly pretty soon with a rework (or at least heavy tweak) of the player's movement system - the subject of this devlog. The player's movement and collision systems have been written and rewritten many times in Beaconfall, proving to be a challenge as difficult to get correct as the LOD system discussed in the previous devlog.
Let's start from the beginning.
In the very beginning of the game's development, the 'player' was not yet implemented, and all I had was a simple, 3D first person camera, which you could rotate and move in 4 directions: forward, backward, left, and right.
Very quickly after I got the basics of the game's world loop system up and running, I started developing the player.
How to Move
The player is simply an object, with a position and velocity, that the camera is attached to, although with how my project is organized the player and camera are considered one and the same. When the player presses a directional input, like any of the WASD keys, a 'direction' vector is created, pointing in the direction the user is requesting the player to move in.
The actual velocity of the player is determined through some combination of the direction vector and the player's velocity in the previous frame, plus some other modifiers such as acceleration, maximum speed, and gravity. The coefficient that determines how much the previous velocity gets applied to the current velocity is called the friction coefficient.
There are two systems of implementing friction that I've toyed with. The first system, which is currently in use, assumes friction is a linear dragging force that subtracts from the player's current velocity. So, basically, this system subtracts a constant amount from the player's velocity, no matter the speed. So, if you're going at high speeds, it'll take longer to slow down.
The second system, which I'm considering switching to, or at least deriving from, assumes that friction is a multiplicative coefficient. This is a number between 0 and 1, where 0 just copies the previous velocity over (no friction), and 1 doesn't even consider it (total friction). This means that, no matter the speed, the player will slow down or change direction at a nearly constant pace.
If I'm going to be honest, neither of these systems make much sense to me, and yet they both do at the same time.
I've done many experiments where I've slid small objects across the table and tried to make sense of it. My immediate observations led me to the conclusion that the first system was the way to go. After all, the more force applied to a small bottle of hand sanitizer sliding across a wooden table, the farther said bottle of hand sanitizer would travel. With the second system in mind, this would technically apply, however the distance would get exponentially limited to a crazy amount, where, for example, you'd need double the speed to be moved just one more unit of space. That doesn't make any sense to me.
Or... does it?
Because while friction appears to be constant, drag force is multiplicative. From what little I've read up on in physics, higher drag applies to objects whose surface area cause it to collide with more particles when moving at higher speeds, and is the reason certain shapes and objects are considered aerodynamic. This air resistance is the reason we have terminal velocity - a speed at which you can no longer accelerate while falling.
Look... I'll keep playing with it. Didn't exactly expect to need a PhD in physics to make a weird running simulator.
When the player is in mid-air, the friction coefficient is far lower than when the player is on the ground, which is why the player has far less control of their direction while they're in mid-air. The friction coefficient also increases when going uphill, and decreases when going downhill. That's basically the one thing I'm keeping constant about the current player movement system. Basically everything besides the friction seems correct, but the friction itself is so integral to the movement that I'm not sure how to change it without rethinking the rest of it to some degree.
Collisions
Me and collisions, we have history.
Collision detection has been one of my greatest anxieties around building a game since I started building games many years ago. It would always be the major roadblock hindering any progress I could make on a game, because I could almost never implement it properly.
Let me take you through some basic examples of collision. One of the most popular and simple places to start is AABB collision. This acronym stands for Axis-Aligned Bounding Boxes.
The AABB method breaks all of the level geometry down into axis-aligned boxes, which are super simple to perform collision checks on. The important thing to remember is that not only do you need be able to test if two things are colliding, you also need to correct their positions if they are colliding, so that they aren't anymore.
AABB isn't all that useful for more realistic collisions, especially with more 'fluid' surfaces like terrain, so another approach is needed. I personally like to use point collisions.
This method is very simple. Pick a point, anywhere in 3D space, and have some function measure the distance from that point to the surface of the object. The distance's sign can then indicate if the point is inside or outside of the object, and 0 means that the point perfectly rests on the surface of the object.
Does this sound familiar? It should if you read devlog #2. This is literally what the terrain generation function does.
This is the basis for the current system of collisions. I will very likely use some form of AABB for optimization (since it's still a good and fast way of checking if a player is near enough to an object for the collision system to care about making more complicated measurements), but since the terrain is the only 'fluid' object that the player can collide with in a typical fashion, this is all I need to make the proper measurements each frame.
But how exactly does one handle a collision? It's easy to check for one by measuring if a point attached to the player produces a number below 0, and that number would even indicate the rough distance that the player would need to move in order to get out from inside the terrain, at least at that particular point you're sampling.
The system I came up with is one where there are two types of collision corrections, which I've termed "soft collisions" and "hard collisions."
You see, each frame, when the player moves, all of the movement logic is handled first, so that an 'expected position' can be generated. This 'expected position' is then sampled in multiple areas and run through the collision checker, so that the expected position can be corrected into the final position for the next frame, if need be.
A "soft" collision typically occurs when the player is moving at relatively low speeds, and just nudges the player away from the thing they're colliding with. A "hard" collision on the other hand deliberately changes the player's final movement vector, and typically occurs at high speeds as the player's range of movement gets far larger. One consequence of this system that is in need of a correction is the fact that the player often gets slowed down by a hard collision, which you may notice especially when landing after a high jump.
I think that's about all for now. Sorry this devlog took so long, I think two devlogs a week might be bit much, at least with everything going on right now. I'll try to have at least one a week, at minimum. If you haven't already, check out v0.1.3. The visual changes I made to the game in that update will be the subject of the next devlog. Thanks again for reading!
- 0de