Hello once again and welcome to the Blightmare development blog! We’re in the middle of a multi-part discussion of the evolution of Blightmare’s code which will be a look back at why we rewrote the game twice. In part 1 I introduced the series and set us up with a plan that allows the game logic to be separated from the visual logic which helps keep the game stable without sacrificing smooth animations. Today I’ll introduce the first few components and get into how we implemented mechanics for the prototype.
It’s a topic that comes up a lot here, not just because I like it. Blightmare is a platformer after all, and in order to feel good and natural, we need to do something that simulates what humans expect in terms of physics. Unity has a built-in physics system that implements all kinds of features like friction and torque. Not only is this overkill for what we need for Blightmare, it’s also very difficult to control and tune in the way that we want. This last reason is the important part because it changes the feel of the game which we can’t have. Some people may be able to achieve a good feel from a game that uses realistic physics and therefore may disagree with that approach, and that’s great. I am in no way trying to say that this is the best way – in fact I’m actually saying the opposite (that’s why we rewrote it!) – but it has the very desirable properties of simplicity and absolute control.
The first real game component was called “PhysicsObject” because Physics is already taken in Unity. PhysicsObject had the task of implementing our equations of motion, doing collision detection, and handling collision response as it pertained to the equations of motion. In particular, there were fields for position, velocity, and acceleration for anyone wishing to add some motion, and when an object was determined to have collided into another object, we moved them as far as possible and then removed all velocity in the direction of the collision. This means that perfectly inelastic collision response was implemented which is definitely what we wanted for the player so that she didn’t bounce around when landing on a platform.
We made use of the built-in collision detection system that Unity provides – through Collider2D’s concrete sub-classes – which was really the most complex part of the system at the time. The documentation about how this system works is quite sparse so I had to spend quite a bit of time reverse engineering it through observation and testing. Eventually I got something working to the point where we could keep things from passing through the terrain and I moved on.
To test various scenarios with PhysicsObject, I added some input handling to move left and right, or to do a rudimentary jump. The problem with this is that it was hard to keep track of where the input code actually was and what it should be doing depending on the larger game context. For example, we don’t want the player moving when the pause menu is up, but we can’t just disable the arrow keys because they need to be options for menu traversal. In order to tackle this, I created a couple of components to deal with input management. Essentially there would be two phases to input: reading the actual input from the player and converting it into an intention, and then deciding if we were going to allow that intention which would convert it to an action. Input is something that we read once per frame, every frame, so it can’t be done in our normal game logic processing. This is because there are cases when we can run a frame without running any game logic if the timing is just right – this is much more common in high FPS scenarios such as 120 or 240 Hz. This means that the input handling has to bridge between variable (per-frame) updates and fixed (logical) updates. Blightmare does this with a simple queuing mechanism that buffers intentions until the next logical update. Essentially what this looks like is a set of flags for things like requesting a jump, trying to move left, or (eventually) trying to swing the net.
This first design was intended to have an Input component on every object that was controllable and then whatever object specific logic could be done within the logical update. For the player we ended up with some additional input related components: LateralMovement and Jump were the first ones. Jump was pretty simple, if the player is requesting a jump and the player’s character is on the ground, then we can add some acceleration in the up direction to the character and set a flag indicating that we need to observe a release of the jump button before we can jump again. For Lateral Movement (left or right) the implementation was a bit more complicated. The behavior that we want is to have some acceleration period that takes the player from stopped to max speed and then some – possibly different – period where the player will come to a stop when input stops. There are 3 different input states to handle for left/right motion: Left, Right, and None. It would be convenient to have an implementation where we could just change a single value depending on the input state and have the system automatically react. We also need to be able to handle max speeds in either direction with a bit of acceleration. Modeling the lateral motion with drag gives us all the properties we desire. The basic idea is to use an equation for motion that has a built-in way of opposing the direction of movement. This is essentially a simplified version of linear drag which is where we apply a force every frame in the opposite direction of movement that is proportional to the speed of movement. This way, we can implement left or right input as an acceleration and we get the maximum speed for free when our movement acceleration matches the drag force.