To give a basic understanding of what I will be covering with this dev log are what my role is within our group, ChillForge and what we have been doing to develop our prototype, Soulstice.
I work as a producer who manages the team with additional roles in scripting systems and design, and my current task is to remake the character controller as well as complete or reassign any miscellaneous tasks that pop up and we haven’t planned for.
What I have been working on to those ends are a few things. First off would be the reimplementation of the character controller, which has been a placeholder taken from the internet who’s author is Sebastien Lague. It is now reimplemented and based off of Unity’s physics engine rather than transforms and this is beneficial for Soulstice for a few reasons:
Interactions with enemies can be more easily standardized
There will not be an emphasis on platforming
It is easier to implement
It can be read without difficulty
We know exactly how to modify it
It is a standard Character Controller by any means, but I made it extremely friendly and able to be hooked up easily with its various scripts. I will be going over the structure of this new character controller while focusing on the key aspects of readability, functionality, and future modification.
The character controller is split up into various scripts depending on the functionality.
Player Input 2D Class
Physics Player Controller 2D Class
Player Attack Class
We will Go over the input class and the controller class.
The Player Input 2D class takes in all the inputs and passes events onto the other classes to reduce coupling and to make it easy to add functionality to each action in the future. It accepts the button inputs from the Unity Input manager and sends them off to the appropriate events in the other scripts. The system utilizes Unity’s serialized events and can be viewed and hooked up in the inspector:
You can notice a few things in that image. First of all would be the public fields for the name of the inputs. In the Input Manager I list all of the key inputs as the actual key you press to fire that input and this is because it makes it simpler to set up control schemes. The second thing is that the event system has reference to methods within other scripts. This is what mitigates coupling, as it reduces the amount of references I would need in my actual script from 2 to 0.
As it can be seen, there are no references to the other scripts within this one making it clear for a reader as well as extending its functionality.
One thing to note here is that I am using attributes to change my variables. I make it a habit to only use public variables when I know another class will be accessing it, so the variable horizontalMovementInput is marked as public because its used in another script to identify direction. The attribute [HideInInspector] removes it from the inspector view to remove clutter and display only the variables that need to be changed based on structure/design iterations. The same goes for jumpInput and attackInput, as those are variables that are only needed within this class, yet might need to be modified for structure later on, so the attribute [SerializeField] is used so they can be modified in the inspector and saved onto disk.
Moving on to the other parts of the Character Controller, we have the Physics Player Controller 2D Class. This script manages the player movement and is also set up in a way that provides readability and modification. What I did to ensure that was to have the functionality segmented into different functions. For example, pressing the jump button and releasing it both perform unique actions. The pressed action gives the player an upwards velocity, and the released action reduces that velocity so that it emulates a pressure sensitive jump (not in the ideal way, but it serves its purpose). But because they have these unique functions, I was able to easily put them into 2 separate functions that could be handled through events.
To speak specifically about what this script does, it handles lateral movement and the jumping mechanic. In order to enforce maximum usability of this script, I did a few things. The first of which would be to have the actual value that is inputted as a characteristic of movement meaningfully represent that movement. Something that I find frustrating to deal with and bad practise is to have a public variable represent something such as jump height, and all that means to the person iterating on it is that the bigger the number, the higher the jump, with no visible correlation between the actual value and the height of the jump. Because of this, I ensured that if I inputted 10 as the jump height, that character would jump 10 units high, and if I inputted movement speed as 5, the character would move at 5 units per second. I will provide a brief explanation of each:
In order to determine this, I had to calculate ballistic trajectory. Velocity is being used here as ballistic trajectory relies on an elevation and initial velocity so we can disregard mass in this situation. That leaves these factors to take into account:
Gravity of the world
Gravity Scale of the object
Desired distance for the object to travel
Velocity to be applied
When calculating ballistic trajectory, you would normally split the vector into its x and y components. This is an upwards jump however, so we can focus on only the y component. Because the y velocity eventually becomes 0 as it pushes against gravity, it’ll have 0 velocity when its at the peak of its jump. With this we can describe:
y0 = √(-2gd)
Now we have the gravity scale to factor in, resulting in an amplification (or reduction) of the gravity. Converting that into code results in:
float jumpVelocity = Mathf.Sqrt (2f * -Physics2D.gravity.y * rigidbody2DComp.gravityScale* jumpHeight);
This is the applied force on the character as they jump resulting in them jumping whoever many units jumpHeight dictates.
Credit to Aldo Naletto for this solution.
In contrast to jump height, movement speed was fairly straightforward. There was some confusion with it however. Setting the value for horizontal velocity does not have to be modified with Time.deltatime, as velocity is already independent of frame rate, and multiplying by deltatime would only make it dependant. So just by setting the velocity you get a consistent velocity of movementSpeed units per second.
Once again, in order to promote readability I took advantage of attributes, this time in the form of tooltips, and I did this to let whoever use it have a clear understanding of the value that they were changing. Tooltips are an attribute that you can give a variable and it allows you to give that variable a description. So in syntax it would be look like this:
And it would display this in the inspector:
What this does is allow an extra dimension of communication as it tucks away information. I believe that there is a certain limit that being verbose stops at where its possible for what you’re describing to physically have too many characters and begin getting in the way and that’s what tooltip are helpful with.