March 3rd 2023

Coding a Movement System for a Parkour Game

Planning

During a Code Studio session, we discussed as a group what sort of parkour mechanics we wanted within our game. The designers then created a nice design doc detailing in specific what these mechanics would be, and how they would work.

These included:

I took on the task of implementing all of these features for the game, assigned myself the task on JIRA, and got to work.

Jira task progress March 3rd

Movement and Jumping

The first task was the very basics, character movement. Kieran started by creating the basic WASD movement as well as the 1st person camera movement, so everything I did was built on top of his original code.

The first big decision to make was to decide between using a CharacterController or a RigidBody component to control the movement. I had to do some research about the two, as I was previously only aware of RigidBodys, so it was important that I understood both sides.

Through research, I found there were a few major differences between the two components:

After considering each of these differences, I decided to go with a Character Controller. This is because the movement for a parkour game is vital, and having it be as responsive and accurate as possible is the goal. Therefore, a character controller is the best way to achieve that, as opposed to a RigidBody where the movement may feel unresponsive and inconsistent. While a character controller may take more effort to get working, this is a core mechanic of the game, so it's okay if it takes extra time to get it just right. And most importantly, I like a good challenge.


Movement with a character controller is controlled with either two functions, Move or SimpleMove, both doing very simple tasks, moving the object (with SimpleMove ignoring Y velocity). With this, basic WASD movement is very simple. All you do is grab the direction they are pressing in and create a vector in that direction. Then you can apply that vector to the move function every frame. This, however, leaves the speed to be very slow.

This is because the direction the player is pressing in is only a value of one, meaning the vector can only be a maximum of 1 per frame. To increase this the vector needs to be multiplied by a value, ideally a player speed variable over a magic number, so that it can be controlled dynamically to control how fast the player is.

There is one issue with this system I noticed, and that is that the player will move faster when going diagonally compared to when moving straight in just one axis. After doing some research, I discovered that the cause of this was due to how vectors work. While it may seem like the vector length is the same diagonal as it is straight, it's actually slightly longer, meaning that it will seem slightly faster as a result. This diagram demonstrates it well.

To fix this, the vector can be normalized. What this will do is ensure that the magnitude (length) is 1. This essentially makes the vector a direction rather than motion, which is perfect for what we want, as the actual movement will be determined by the player's speed and any other external values.

With that done, there was the jumping. For this, I used a YouTube Tutorial for guidance, as I was new to character controllers and was unsure as to how to create a gravity system within the controller.

The gravity part is easy, there is a gravity variable which determines how strong the gravity is, and there is a ySpeed variable which tracks the speed at which the player is falling down. The ySpeed goes down into the negatives while the player is not on the ground, and that is subtracted from the move function's Y vector.

The jumping part was then very simple. All I had to do was change that ySpeed to be a high positive value and due to how the gravity system already worked, it would naturally make the player go up, and then down affected by gravity. I had the ySpeed be set to a jump height variable rather than a hardcoded value so that it could be easily adjusted in the editor.

With this all done, the very basics of the movement system were done!

There was one further improvement I wanted to make to the jumping, and this is called jump buffering. This works by saving the player's jump input for a set amount of time, that way the player can press space a little bit before touching the ground and the game will still register it. This makes timing jumps more forgiving, helping improve player satisfaction. It's important to balance it, however, as making it too forgiving would make the inputs feel super clunky and unresponsive, which is the complete opposite of what I want to achieve. I wasn't too sure how to do this, so I found a helpful YouTube Tutorial to guide me.

It essentially works by checking for the jump input and then setting the jump buffer value to max. This value acts as a time, counting back down to zero. When the game wants to check if the player has pressed the jump input within the last few frames, it will check if this jump buffer value is higher than zero.

This implementation is small and robust, and on top of that, it's nice and modular. So in other parts of the code I can just check the jumpBuffer value without needing a whole system for it for each part. This helps improve the cleanliness of the code.

I think this basic movement code is fine and fulfils its purpose. 

As it's so concise and simple, it's really easy to add onto it and alter it. So for example, if I wanted to add a double jump or alter the gravity, all it requires is a simple value change and other parts won't break.

Looking back at my choice to go with a character controller, I'm also glad I chose it over a RigidBody just because I am prouder of myself with a controller since it takes more work and requires more thinking to implement. You have to make your own implantations of forces like mass and gravity, and can't just rely on Unity's system for it. 

One regret I do have with this choice however was the fact that documentation for CharacterControllers was extremely scarce when compared to RigidBodys. I guess since RigidBodys are the easier and quicker option it makes them far more common, especially with beginners to programming, therefore people were less likely to have asked questions or made tutorials relating to CharacterControllers. This didn't prevent me from making anything, however, as I was able to eventually implement everything I wanted, it just made it slightly harder and more time-consuming. This is definitely something I should consider next time when choosing, as it isn't mentioned on any websites comparing the differences, so I wasn't aware of this downside and needed the actual experience before discovering it.

It's also my first ever movement system, so I'm glad that I was able to pull it off well in my first attempt with minimal issues.

I'm not really sure of how I could improve the code other than maybe adding coyote time, which is a system that lets players jump slightly after falling off an object. This allows for a bit more room for error and makes the game overall feel more responsive and forgiving and less frustrating, and would not be hard at all to integrate into the current system. It could potentially make some of the code more confusing, though, as it is extra code needing to be squashed into the current implementation.

As for how the code is actually written, I don't think there is much that can be done to improve it since it's so incredibly simple as it is.

Dodging

Next up, I wanted to implement the dodging. This was an interesting mechanic idea, as it allows players to easily dodge obstacles if they get too close, increasing the skill gap and allowing for further refinement of movement. The idea for how this would be that while running, you could press the Q key for a quick movement burst to the left, and you can press the E key for a quick movement burst to the right.

Coding this was fairly simple, at least, so I thought. All the code consisted of was checking for the inputs and then calculating a new position to the left or right which the player should move quickly towards. I then used the lerp function to control said movement from the original position when the player presses the dodge key and the final destination.

There was one issue, however, which was that the start point and end position would remain the same and wouldn't account for the player's current moving speed. This means that while they would move to the side, they would stay still in position in forwards and backward directions. To fix this, I would find the difference between the start position, and the player's new position in each frame, and then add that to the start position. That way, it would account for the player's forward movement. 

I think looking back, though, this fix combined with the actual system of moving the player is slightly janky. Rather than just lerping their position when they dodge, I instead should have added motion in that direction, which can then be applied during the regular movement section. 

This means the dodge function wouldn't interfere with the movement system, as currently it does, and this can lead to certain inconsistencies and also makes it super hard to debug when something isn't working as I might not know where the issue is being caused. It would also in turn fix the issue I was having, so all-around a much better system. 

Regardless, though, the current system works fine.

I wasn't very happy with this state of the dodging for a number of reasons. 

Firstly, there was no easing in the movement to the side, making the movement feel very unnatural.

Secondly, the camera feels way too static. Adding some tilting to the camera during the tilting motion would add much more life and energy to the movement.

The fix to the first issue was very simple. All I had to do was add the built-in Unity SmoothStep function to the Lerp function, which will ease in and out at the start and end. 

The second issue however ended up being an absolute nightmare to implement.

 The idea of tilting the screen is very simple. All you have to do is change the Z rotation of the camera back and forth, smoothly, in the direction you dodge. I used a simple coroutine which would tilt the screen, wait two seconds, and then tilt it again in the opposite direction. This part of the tilting ended up being simple to add, although getting the tilt values correct was difficult. 

After getting the tilt values correct, however, there was a major bug I noticed which ended up being the death of me.

The issue was that for some reason, while the screen was tilting to the side, the mouse movement of the player would freeze and only update once the tilting had stopped. This would cause a large stutter to occur if the player moved their mouse during a dodge. 

Fixing this bug took a lot of work, mostly due to the fact I wasn't sure what was causing it. I even made my first ever Unity forum post trying to find a solution, and while they were able to explain what may have been causing the issue, I still was unable to properly fix it.

It wasn't until the next day I was able to find a good fix, which was done by adding the screen tilt on top of the code which handled the first-person camera movement, which means they worked together preventing any of them from overwriting each other which was causing it to not work.

This method does lose a lot of readability and makes it more complex, but I think it's somewhat necessary. Maybe in the future, I could have improved upon it to where there is no repeated code, since the top and bottom sections both have a similar Euler command. This makes it harder to change in the future since I'll have to modify it in two locations.

In hindsight, I could have gone about fixing this bug in much better ways. Firstly, I spent way too long in one go trying to fix it. I should have taken a break. The main reason I didn't fix it until the next day was because the break that I took helped my mind gain clarity, allowing me to think better. 

I also should have done a better job of understanding exactly what was going wrong, rather than just trying to fix the issue. Originally, when the issue occurred, all I did was switch from a coroutine to a function and hope that was causing it. However, this was completely unrelated and just wasted my time.

With the bug fixed, however, dodging was finally implemented! 

There was one glaring issue I noticed through testing, however, and this was that the controls felt very unintuitive.

Originally, the buttons were that you would press Q or E while running to dodge either left or while. While on paper this makes sense and sounds like this would work, in practice this ended up being hard to pull off. This is because I found it not only very difficult to press the W key as well as the respective dodge and side movement key, but it was also difficult to quickly switch sides of the dodges.

All of this combined made dodging feel very unsatisfying, so I set out to look into improving upon these controls.

Eventually, I took inspiration from Mirror's Edge: Catalyst's shift ability, in which the player can right-click their mouse to gain momentum in a direction for a quick speed burst. I realised this could be repurposed for our game and the dodge ability. 

Mirrors Edge Catalyst Screenshot

This would free up the left hand and let it feel far less cluttered and easier to press the dodge key when you need, especially quickly in clutch situations, which is important since that's likely when players will be needing to use the dodge function the most. It also provides more use for the mouse, as currently there was no planned use for the mouse buttons within the parkour.

Using both left and right click would be inefficient, though, which is when it occurred to me that you wouldn't even need multiple keys for this as players will only be dodging in one direction, either left or right. This means that when they press the dodge key, only the A or D key need to be pressed. The game can just check which of those keys is currently pressed, and dodge in the correct direction according to that.

And with that, the final iteration of the control was done, using right click and the current side movement of the player to determine where the player should dodge.

This implantation does have the same issues as I just discussed with the camera fix, where I could easily reduce the number of repeated code, making it much more maintainable and clearer with fewer lines.

Grappling

For the next mechanic, I took on the task of the grappling mechanic, which would pull you up a ledge if you just didn't make it up to the top. As this was a completely foreign concept to me, I watched a YouTube Dev Log for a game which had a similar vault mechanic and looked into how they went about implementing it.

After this, I had two ideas of how it could work.

The first option is to have a big trigger object in front of the player, and when that collides with an object, then I know that they are in front of it.

The second option is to perform a raycast in front of the player, and then check for the object it hits. A raycast is basically just a straight line starting from an object, travelling forward until it hits its max length or another object.

At first, I wasn't sure of which to go with, as the differences in performance between the two seemed negligible. However, one Reddit comment pointed out that a trigger could actually be in multiple objects at once, therefore leading to certain issues if the player tried to vault in between two objects. With that, I decided a raycast would be the best option, as it eliminated that issue due to it only being able to collide with one object at once.

To do this, first, it will need to do a short raycast (using Physics.Raycast) forward from the camera and check for any grapple-able objects in front of the player. If the line hits an object, then the object is stored inside a variable.

The object which is stored inside a variable is crucial, as this is what we will use to be able to determine whether the obstacle is able to be grappled onto or not. 

Many different values need to be stored, such as the object's width, height, the raycast's height, length and more. Calculations are then done with these values and the output should be a simple float value signifying the distance of the player from the top of the object.

With this value, I'm able to check if they are high enough to be able to grapple up to the top, or if they're too low and it should not allow them. If they are high enough, then their position to adjusted to be where the end of the ray is, as well as some additional height.

Overall I'm happy with this code, it's nice and concise and fits its purpose well. The grappling itself feels satisfying to use, and I think the mechanic does a good job at being forgiving for those who mess up a jump timing, while still punishing them by slowing them down slightly and having them lose their current momentum

I do think however a lot can be done to improve the readability, as currently it's just a chunk of code. Perhaps better more detailed comments would help explain what each section does, or just simply spacing out each line would help. I might look back at this code weeks later and be completely confused at what it's meant to be doing.

It is slightly unfinished, however, as the transition from pressing the grapple key and getting moved to the top of the object happens within one frame. I left this intentionally, though, as there was no point trying to time it smoothly since there was no animation yet, so it would be better to wait for that before doing so.

Wallrunning

Now it was time for the hardest of the mechanic, and that was the wallrunning.

I had dreaded the implementation of this one, as I knew it would likely be the most difficult one to add due to its complexity. It was made even more tough by the fact that there were no good tutorials for the topic that used CharacterControllers, they all used RigidBodys. As I result, I found a good RigidBody Tutorial and did my best to copy the ideas and translate them to my CharacterController component instead.


The way the wallrunning works is split into 3 steps.

The first step is checking if the player even can wall run. This consists of just doing 2 raycasts to the left and right of the player and checking if it hits a wallrun-able wall, and storing the output of each in a boolean variable. I chose a raycast over a collision trigger for the same reasons as listed for the grappling.

After that's done, the player needs to switch into the wallrunning state for the next step to commence.

This part of the step is to calculate the direction that the player will be wall running in. This will be done using the Vector3.Cross function and passing in the normal of the wall (which is the line pointing away from it) as well as the up transform of the player. 

The cross function outputs the cross product of two vectors. The Unity Documentation explains it well:

The cross product of two vectors results in a third vector which is perpendicular to the two input vectors. The result's magnitude is equal to the magnitudes of the two inputs multiplied together and then multiplied by the sine of the angle between the inputs. You can determine the direction of the result vector using the "left-hand rule". - Official Unity Documentation

The final step is to just make sure the player moves along the wall during the wall run. For this, the built-in CharacterController move function works fine, supplied with the direction vector calculated from the cross-product earlier. 

There also needed to be a way to jump off the wall, which was very simple. It was done by using the wall normal, combined with the height of a regular jump and a new wallJumpForce variable boost, and using all that and applying it as motion.

And with that, the wall running basics are done...

Except it was very slow, so the speed needed to be adjustable by multiple factors.

Firstly, it needed to be affected by if the player was sprinting or not because if the player was sprinting the wall run would have to be faster as it would feel very janky to have it slow down as they enter to wall run. Secondly, it should have an overall speed that is controlled by a variable in the editor, so that it can be easily adjusted by developers so that they can get the speeds right.

One last final thing to add before the wall running was finished was an additional screen tilt for some extra flare. 

Luckily, I had already added a screen tilt effect with the dodging mechanic, so I could build upon the implementation for that. Unluckily for me, as the last implementation had caused a lot of bugs and took a very long time to make, it was incredibly messy and poorly coded. As a result, I ended up massively improving upon it and creating a proper modular system that could be very easily reused at any point in the code for any sort of tilt effect, for any duration.

While doing this did take quite a while to do, in the long run, it will be very much appreciated by me and other programmers and will ultimately save a lot of time as next time someone wants to add any tilting it will be very simple to do. In hindsight, I should have added a system like it when I originally added the dodge, however, I got lazy and impatient due to the frustration I felt due to the bugs I was experiencing. Luckily, the second time around, I didn't have the same issues and had a better state of mind.

With the tilting done, that was wall running implemented, and ready to be tested.

After testing with other members of the team on the Monday during Code Studio, however, it was discussed that the wall running controls could be improved upon.

The implementation I made had the user holding the space key to latch unto the wall. Letting go of space would let them latch off the wall, and instead letting go and quickly pressing space again would jump off the wall. Through testing this, I found this to be intuitive enough to keep, and I became quickly used to this.

I favoured the idea of holding space to latch onto the wall, as it gave the player more control. They could hold onto the wall for as long as they wanted to, and if they wanted to get off the wall, then they had other alternatives than just jumping off. They could just let go of space, and would just slide off rather than jump off. This would be helpful in the instance that they accidentally start wallrunning and want to quickly get off the wall without jumping too far, for example.

When Irving (the team producer) tested this, however, he felt as though the controls weren't satisfying enough and the extra control was unnecessary. As a result, I reworked it and went for controls in the completely opposite direction.

Now instead, it will automatically latch you onto a wall if you're near it mid-air, and pressing jump while on the wall will jump off.

Testing this new system took some getting used to, but overall it feels far more satisfying to use and feels more natural to use, especially when compared to other games. 

In the future, it may come across some issues that the other system didn't have. Such as accidentally latching onto walls when you didn't mean to, or not being able to get off a wall without jumping. At the moment though, those issues don't seem bad enough to worry about.

Conclusion

Overall, it has been quite a productive month.

While I wasn't able to get all the mechanics done, (such as the sliding, vaulting and rolling), I did get all the main ones, and I am very happy with how they turned out.

Running around the little playground and just jumping on all the pink cubes is really fun, and that fun is only going to increase with more mechanics, objectives and obstacles as the game grows in scale.

There are certainly some aspects that could be improved for better readability and performance, most of which I pointed out earlier, however, at the moment these aren't dire issues and don't affect the actual functionality of the system.

If there was one thing I were to improve with my work in the last month, it would be how I handled documenting my code. During the development of these mechanics, I did not add comments to my code. This means that passing the code onto my peers was very difficult for them to understand and makes me not a very good teammate. I ended up going through all my code after and adding the comments, however, in the future, I should learn to be actively commenting while I code to save time. This will also prevent me from forgetting how something works after I make it, as this did happen multiple times and I found myself having to go over my code trying to figure out what it does.

All in all, though, I'm excited to work on the last few mechanics over the next month.

Media