Coding Best Practices in Unity: Why You Should be Hiding Your Data
Writing code is a difficult process that can be made even worse when you write code that ends up a massive ball of spaghetti. Spaghetti code is bad. Spaghetti code means you can fix a bug only to end up introducing five more. Spaghetti code means adding a new feature might result in rewriting a large part of your code base. Spaghetti code for developers working in a team means you can be constantly getting in each other’s way spending endless amounts of time dealing with things such as merge conflicts.
Like I said, spaghetti code is bad. Which is why I am writing articles specifically for Unity on how to improve your code by introducing and explaining coding best practices. By reading these articles and incorporating the ideas covered into your own game development projects, you should hopefully be spending a lot less time fixing and cleaning up code and a lot more time working on making your game.
In this first article we will look at the concept of information hiding and how it can be applied to your games.
What is Information Hiding
Information hiding was a principle created by David Parnas back in 1972 to describe a way of programming which involves hiding the internal workings of code behind a well-defined interface. In terms of scripting in Unity, an interface is the public methods and properties we create in our script that other scripts would call. The inner workings refer to everything that sits behind our interface that we don’t want to expose to anything else, private and protected functions and variables that aren’t accessible to other scripts.
There are two main advantages to hiding the inner workings of our scripts behind a well-defined interface. First, it can limit the amount of code we would have to change if we changed how a script worked internally and second, we also avoid causing unwanted changes to our internal data that may cause unexpected behaviour.
Information Hiding Car Example
If you are sat here still not quite sure how information hiding is beneficial then think about how we interact, or interface with a car. The interface for a car would be the pedals that allow us to accelerate and brake, a steering wheel that allows us to turn the car or the ignition system that lets us start and stop a car’s engine.
This well-defined interface provides a simple way for us to drive a car without us requiring any knowledge about how a car works internally. Also, the internal parts of the car can be changed without breaking the interface and requiring the driver learn something new. Putting in a bigger more powerful engine or changing the brake pads on a car doesn’t have any effect on how we would interface with a car other than that it might drive faster and come to a stop quicker.
Finally, you could argue that cars don’t do a good job of hiding their internals. Someone who owns a car can pop open the hood and start poking around with the engine even if they have no idea what they are doing, possibly breaking their car in the process.
Information Hiding Problem Coding Example
I hope by this point you understand the concept of information hiding and are eager to see how it works with some actual code.
public class Player : MonoBehaviour{public int _maxHealth = 100;public int _currentHealth = 100;List<Weapon> _weapons = new List<Weapon>();public void TakeDamage(int damage){_currentHealth = _currentHealth — damage;if(_currentHealth <= 0){Animator deathAnimation = GetComponent<Animator>();deathAnimation.SetTrigger(“Death”);}}}public class World : MonoBehaviour{public Player player;private void Update(){if (player._currentHealth <= 0){Instantiate(player._weapons[0].gameObject, player.transform.position, Quaternion.identity); player._weapons.Remove(player._weapons[0]);}}}
In the above code we have a player script which does player stuff like track and manage the players health and it also stores a list of all the weapons the player currently owns. As you can see everything about the player is currently made public. Everything internally about how it stores weapons, and health is made public.
The World script simply checks if the player is dead, spawns the weapon the player was carrying, which we make to be the first item in the list and removes it from the players inventory. In our imaginary game we are giving players the penalty that they lose whatever they were carrying in their hands when they die.
Now let’s start digging into some of the problems with these scripts related to the ideas of information hiding.
First, if we wanted to change part of the design of our game so that players only ever carry one weapon at a time, we would likely update our Player script so that we no longer store a list of weapons, instead having a single variable that stores the weapon the player is currently carrying. If we were to do this with the code, we would need to change all the code within the World script because it relies on the fact that Player was using a list. Our World script knows how Player works internally and now Player has changed World also needs to change. Not good.
Secondly, in our Player script we always want to play a death animation in our TakeDamage function if the players current health is less than or equal to zero. Assuming we have other developers working on the game and one of them decides they need to kill the Player, and instead of using the TakeDamage function they simply change the currentHealth value to 0. Well now our death animation doesn’t play when the player now dies when being called from the other developers script.
The biggest issue here though is that if the developers followed the principle of information hiding all these problems could have been easily avoided.
public class Player : MonoBehaviour{private int _maxHealth = 100;private int _currentHealth = 100;private List<Weapon> _weapons = new List<Weapon>();public int CurrentHealth{get { return _currentHealth; }}public Weapon EquippedWeapon{get { return _weapons[0]; }}public void TakeDamage(int damage){_currentHealth = _currentHealth — damage;if(_currentHealth <= 0){Animator deathAnimation = GetComponent<Animator>();deathAnimation.SetTrigger(“Death”);}}public void DropEquippedWeapon(){_weapons.Remove(_weapons[0]);}}public class World : MonoBehaviour{Player _player;private void Update(){if (_player.CurrentHealth <= 0){Instantiate(_player.EquippedWeapon.gameObject, _player.transform.position, Quaternion.identity);_player.DropEquippedWeapon();}}}
As you can see most of what we did was make the variables private in Player and have them only accessible through the interface we have defined. In this way no other developer can affect the health of the player without calling TakeDamage which means our death animation will always get played now. Additionally, we have allowed access to the data using properties so that the World script can now get access to the weapon the player has and tell them to drop it without needing to know how many weapons the player has or how they are stored. Additionally, scripts have access to the players _currentHealth but aren’t allowed to change it.
We have effectively written code that hides the inner workings of our script, defining how our script is interacted with through the interface we have created which has solved all of the issues we were having before we started hiding information.
…
To summarise we looked at the coding best practice of information hiding that allows us to hide how our scripts work internally. If you incorporate this concept whilst developing games you can potentially avoid having to rewrite large parts of your code when you want to make internal changes to a single script and avoid the potential of introducing bugs.
If you still unsure or have any questions, feel free to send me a message @gamedevunboxed😊
To read me more check out my blog at: www.gamedevunboxed.com
Further Reading