6 Important Things I Learned from Half a Year Using Unity
28-09-2021
🌿
6 minutes
the Game Dev Iceberg
My Programming Experience Before Unity
A bit of context to my programming experience, I haven't had too much experience with statically typed languages, I used mostly javascript and python, though I had one class in University on Java.
It was a tough decision to decide which route to take to build my 2d game. Godot was really rising in popularity, but I ended up going with Unity due to familiarity and the strong user base. I was also hesitant with how much could get done with a dynamic language like Godot, unity being written with C# meant that it was static, and in my understanding, static languages can handle bigger loads of information more efficiently.
Through the 8 months of using unity at the time of writting this article, there are some new concepts I've learned and I've seen becoming important with Unity, and OOP.
Important Unity / C# / OOP Concepts
Monobehavior - Unity
Singleton design pattern - OOP
State Machines - Unity
Delegate - C#
Event System - Unity
Scriptable Objects - Unity
1 - Monobehavior
Honestly I never thought about how important it was to take a dive into Monobehavior until writing this article. Monohevaior is what most of your scripts will inherit from.
When I looked into the Monobehavior I notice some Methods I had no idea that existed and could be very useful, for instance, OnMouseDrag, OnMouseDown, and OnMouseEnter, these methods can help when dealing with input from the mouse to develop some really intricate systems.
take some time to look at more of the methods that come with Monobehavior here, it will help you undertstand why its important and the full power your scripts that inherit from Monobehavior have.
2 - Singleton Design Pattern
now this is a concept that comes when you start diving into OOP deeper. If you're a beginner programmer, it might not be the best time to understand this concept, but continue reading to see whats ahead of the horizon. A singleton is a Design Pattern. What is a design pattern you may ask?
For example, the Singleton design pattern is the best way to implement something that should only have one instance. For instance in my game, there is only one skillbook. And how the code works is that it only instantiates the class once. The code for a singleton makes it very safe, because if we accidently instantiate two instances and update 1 and then read the other, you can see how this can get very messy.
Its very simple to lookup singleton demonstrations all over the web so I'll leave it to you, just some important features I want to highlight is the fact that the instance variable inside of the singleton is static this is important because it can be easily accessed all throughout your code, and you know you're accessing the correct class because there is only instance of it.
3 - State Machines
I've been unexcusably hesitant to try out Unity State Machines. I finally threw in the towel and got cracking on it when I was programming the AI for an enemy in my game.
The first thing I realized was that you create State Machine Scripts differently from normal scripts in unity. In order to create State Machine (SM) scripts you must open the animator tab, and click on an animation, then in the inspector window click [Add Behavior] once you open up the created script you'll realize that the script no longer inherits from MonoBehavior but from StateMachineBehaviour. There should be some commented out functions: OnStateEnter(), OnStateUpdate(), OnStateExit(), OnStateMove() and OnStateIK(). For now the first 3 are the most important and are pretty self explanatory. The first [OneStateEnter] is called once the animation state is entered, the second [OnStateUpdate] is a loop that is called at each Update frame except for the first and last frame. Finally [OnStateExit] is called on the last update frame when a state machine evaluates this state.
I don't know about you but I've used way too many Coroutines and unnecessary code controlling when the state changes, and when to change using methods like, animator.GetBool("state") or animator.SetBool("state", false) peppered throughout my code. Its nice to have a central place that controls the animation state just to clean up and compartmentalize things.
Another neat thing about state machines is that in the function it already supplys you with that gameobjects animator so you don't need to fetch it in a variable.
For my first use case, I used state machine to control the enemy in my game through 4 state,
- Patrolling State - When the enemy chooses a random location and walks to that random position.
- Follow State - when the enemy detects me and starts following me.
- Attack State - after the enemy follows me and is within range to attack.
- Idle State - after the enemy gets to the patrol states random position, and it waits in that position for around 4 seconds.
4 - Delegate
Delegates are actually something very small to understand, I say this because I held off learning them because the description was confusing to me and in tern I became intimidated, but let me try to explain it so its simple and easy to understand. You know whenever you make a new script and you need to declare all the variables close to the top of the script? well a delegate is a variable that holds a function, so something like:
public delegate int PerformCalculation(int x, int y);
PerformCalculation add;
so all we did was made a random function that accepts 2 arguments, at this point we havent defined what the function/delegate does, all we did was say it needs 2 variables. Then we gave the delegate a variable name, add.
so when you want to use it, you need to assign a function to this delegate:
static bool adding(int number1, int number2)
{
return number1 + number2;
}
add = adding;
now our delegate add has the function value adding, we did this in 2 steps, we could have named the delegate and assigned its value in one step by doing this:
PerformCalculation add = adding;
now our add delegate becomed that function that adds 2 numbers, and in the future I can even assign our add delegate to a different function. now if I wanted to use the add delegate its like this
int value = add(1,2);
it works the same as a normal function. And thats all there is to delegates! Its super easy and its so important in Unity and Gamedev to pass functions to different scripts, expecially when you want to separate front and backend.
5 - Event System
This concept is very interesting. You could produce a whole game without using this, but what it does, is it makes your code a lot better. The reason why you should use the Event System is because it allows you to compartmentalize logic more efficiently. Your Front-end Code should be separate from the backend, with the event system you can write the backend once and have subscribers attached to it. These subscribers do not effect the backend code at all, the backend doesn't need to know who these subscribers are or what they're doing. For instance, I can have backend code that sets off an event when the user clicks a particular key, lets say "TAB", when the user hits tab, the event is fired, and either no variables or some variables are passed to that event.
// Firing of event looks like this
if(Input.GeyKeyDown(KeyCode.Tab))
{
OnClickTab?.Invoke(); <--- Fired Event
}
I'm not going to go into the code, there's more to event than this little if statement, but before you get into that, we must understand the concept first, Events are very tricky to understand, but once you get it, you see that it was actually simple, the syntax is whats difficult to get the hang of.
The subscribers then activate when that event is fired and you can create methods inside the script with the subscribers to perform certain methods when that event is activated.
Events
Some events that monobehavior has access to are as follows:
Start- called when the GameObject begins to exist (either when the Scene is loaded, or the GameObject is instantiated).
Update - called every frame.
Fixed Update- called every physics timestep.
OnBecameVisible and OnBecameInvisible- called when a GameObject’s renderer enters or leaves a camera's view.
OnCollisionEnter nd OnTriggerEnter - called when physics collisions or triggers occur.
OnDestroy- called when the GameObject is destroyed.
6 - Scriptable Objects
some methods that inherit from scriptable objects type, compared to monobehavior
Scriptable Objects Vs Monobehaviors
As you can see the only new method you get through using Scriptable Objects is OnDestoy() and you don't have access to the game loop through functions like update and start. This is an important attribute!
Scriptable objects can not be attached to components as a gameobject instead they are created as project asset files.
Monobehaviors always live on a game object, so by design their bound to an instance of a gameobject and because of that when you want to access the data for a prefab, each instance will have a copy of the transform and the gameobject which is a waste of space.
- moral of the story, if your prefab doesn't need access to gameobject or transform, and you want to save memory, you should use Scriptable Objects instead of Monobehaviors. If you'd like more information on this topic here is a video created by Unity on the topic.