To begin creating our player we first start with some very basic logic. We apply a sprite to our player object and add some simple code to make our character move along the x and y axis in the game world. This is done quite simple in Monodevelope (Unity’s C# scripting language) with the call to Input.GetAxisRaw(“Horizontal”) and Input.GetAxisRaw(“Vertical”). This sets up a vector for us which we call “movement vector”.
We want the player game object to play an animation while moving, so that it looks more natural. Since our game is a 2d sprite based game, our walking animation includes 4 sprites for each direction (up, down, left, right). We can set up this animation through Unity’s animator controller, where we just apply the sprites we want in the order they should play in, and set up a transition. This transition is a Boolean (true, false) which we call isWalking. In order to decide whether our player is moving or not, is a simple matter of asking whether or not our “movement vector” (which we set up earlier) is equal to a zero vector or not. If it is not a zero vector, we can set isWalking to true, and the animator will play the animation. In order for the animator to know whether the player is moving up, down, left or right, we also send the x and y directions to the animator controller. If the player is not moving (movement vector is a zero vector) then the animator receives a command tell it that isWalking is now false, and the animator plays an idle animation, which is just a single sprite showing our player standing still, but pointing the direction we last moved in.
The final code looks like this.
So at this point we have a character which can be moved around in our world along the x and y axis, and when the character is moving, the appropriate animations are played. It is worth noting that when using Input.GetAxisRaw(), Unity knows that you are referring to the arrow keys or the WASD keys on a keyboard, or the joysticks of a controller.
So we plan on having enemies in our game, which means that our player is going to have to defend itself. We originally planned on introducing four abilities which the player would be able to use, two offensive and two defensive and then expand upon the ability arsenal from there. Each ability has a cooldown, which prevents the player from using the abilities too often. This is an ideal way of balancing the game and making sure our player does not become too powerful.
The first ability we gave our character was a fireball. This is a long range spell which shoots fire in a straight line. This is done rather easily via a script which our player game object has. If we press the fireball button on our keyboard, or gamepad, then a fireball prefab is spawned and is given a direction and force. This moves the fireball across the map depending on the direction the player is facing when he/she pressed the fireball button. The force applied to the fireball game object is applied by a number given to the script from Unity’s inspector. This will dictate how fast the fireball can travel.
If the fireball comes in contact with an enemy or an object in the environment, the fireball game object is destroyed. If the fireball came in contact with an enemy, then it deals damage to that enemy by calling on the enemy stats script and calling the function ‘DoDamage’.
Our next ability is called “Ice Shield”. This is a shield the player can put on themselves which will reduce incoming damage. Much of the code is the same as fireball. At least when we want to spawn the ice shield. We have a script connected to the player game object. If the designated ice shield button is pressed, then we spawn a new game object. The difference from fireball is that we do not apply any force to the ice shield, since we want it to remain on top of the player, and the ice shield has no physical presence in the game, unlike fireball. It presents is only visual. When the ice shield is applied, a boolean value is set to true, which is then sent to the PlayerStats scripts. In the PlayerStats script, if iceShieldOn is set to true, it means the ice shield is active and the TakeDamage script will run a different formula to calculate the amount of damage the player will take if injured.
The third ability is also a defensive ability. It is called teleport. It will teleport the player a short distance in the current direction the player is facing. When activating teleport we spawn the animations in exactly the same way we spawn the ice shield animation, only this time we spawn two versions of it. One where the player was, and one where the player ends up. This animation helps give the player a visual indication of what has happened.
One problem we had to be a bit cautious of was that we can now teleport into walls and other objects in the world. This wasn’t ideal, so we used an inbuilt Unity function called RayCastHit2D. What this does is it creates a vector from one point to another and then it finds the first object it collides with. We can then use RayCastHit2D.point to get the location of the closest object it hit, and then tell our teleport script to teleport the player to that point rather than through it or into it.
Our forth ability is called Nuke. This is an AOE (area of effect) ability, which spawns eight big fireballs around the player and causes damage to all enemies around the player. We decided to have an ability like this because we realised that our player was easily getting swamped by enemies with little means to dispose of them. Some of our enemies are fast moving, which makes them hard to hit with a basic fireball, but if you wait for them to get close then you can easily dispose of them with Nuke.
How this ability works is that when activated, it finds all enemies within a certain area around the player and then adds them to a list. We then go through the list and deal the damage to each enemy.
We also added an ability which we called Haste. This ability adds movement speed to our character for a short time. This is more often used as a sprint button to get around the world quicker but can also be used if running away from scary monsters in dungeons.
Scaling and RPG elements
We are trying to create a RPG (role-playing game) style game, with that there are some expectations that a player would expect from the game. Some of these expectations are that there should be some kind of levelling system, the player should have stats, and these stats should be changed or improved as the player progresses through the game.
So we decided to start off simple and add some basic stats to our character. Our character has health and damage stats. The health stat simply increases the maximum amount of health the player can have, and the damage stats increase the amount of damage each ability the player has can do.
The health stat was quite easy to implement. Based on the level of the player, a formula calculates how much health the player should be allowed to have. Each time the player gains another level, some amount of health is added to the player’s health pool (maximum health).
The damage stat is the one which is a little harder to add and keep track of. Basically each abilities script fetches the player’s damage from the PlayerStats script. With this variable, each ability calculates the amount of damage it should do. Again this variable from the PlayerStats script is based on the current level of the player.
The math behind the health and damage calculations is still something which is not solidly decided yet, as we have to go through balancing the game when more elements are in place and the game is closer to completion. The calculations of these stats will also have to be balanced based on the enemy’s stats, which also are based on the player’s level.
A stretch goal which we have at the moment is to implement randomly generated items which the player can equip. If this is implemented then this will affect how much the health and damage should increase with each level since the items themselves will have health and damage on them.
Now, we need a levelling system in place for our player. This is where the math gets quite scary. Basically we want the player to earn experience for doing things in the world, when something in the world yields experience, it calls the function in the PlayerStats script to increase the experience by X amount. The PlayerStats script has two variables, “needed experience” and “current experience”. “Needed experience” is how much experience the player needs before he or she can gain another level, “current experience” is how much experience the player has currently got. If “current experience” exceeds “needed experience” then the script calls the LevelUp function.
The level up function does a series of tasks. First, it increases the level of the player by one. Then it calls on a function which increases the stats, which does what we talked about earlier with increasing the amount of health and damage the player has. It then sets the current experience to be the excess amount of experience left after exceeded the needed amount. (This is so that no experience is lost, if current experience is set to zero, the player may lose out on some experience).
The function then calculates the needed experience for the next level. This is done via a scary mathematical formula. I would love to say that I came up with this formula all by myself, but the truth is that I used this formula because it is a proven formula used to calculating experience for RPG games based on how much experience is needed for the first level and how much is needed for the maximum level the player can achieve. This formula gives a nice, balanced amount of experience needed for each level between level 1 and the maximum level the player can achieve.
A representation of what the formula will return for each level if the max level is 40, and the experience needed for the first level is 1000 and the experience needed for the last level is 1000000.
The code and formula to calculate these numbers.
With these aspects of the player in place, what is left is to balance the game and make sure that these numbers are appropriate throughout all the levels. This is something that will be done right at the end of the game development when we can play through the game multiple times and make small adjustments until everything feels comfortable, meaning the game is not too hard and it is also not too easy.
That concludes the most important aspects of our player game object in our game.
P219 Development Team.