Learning Unity
First Things First
I was not new to programming when I started this project. I have a Bachelor’s in computer science, and have been programming and making games since I was but a wee lad. I was, however, new to C# and Unity, which both felt like insurmountable obstacles, especially Unity. It turns out that it’s really not that bad.
C#
I figured that I would learn C# as I go, especially since it is very similar to typescript, in both syntax and async calls. I had just completed a project in TS, so I was confident that I could pick C# up as I learnt Unity. There are a couple Unity-specific quirks that I immediately noticed, though, and they only really make sense in the context of Unity. I’ll try to explain them below, in the Unity section.
Unity
The first thing I did was follow this tutorial by Game Maker’s Toolkit on YouTube. It’s amazing, and actually teaches you the basics of Unity instead of getting you to make a game while learning nothing. There’s some great advice peppered in there.
Entity-Component Systems
Unity uses a system called Entity-Component-System (ECS) instead of your default object-oriented programming. Although we still use objects, the way that behaviours and data and methods are attached to them is quite different from OOP. I learnt about this in a fourth-year course at my university, thankfully, so I am well aware of the benefits of using ECS. I will try to explain that here. But first, what is ECS?
ECS has three main parts, with a hidden fourth part:
- Entities, which are barebones objects, typically with no data or methods attached1. This is like a skeleton.
- Components, which are like distilled classes of data, functions, etc., that can be attached to any entity. Think
healthComponent,positionComponent,movePlayerComponent, etc. (not all components will haveComponentappended to their names) - Systems, which provide the gameplay elements and game engine. Think of the collision system, the tick system, physics, rendering, etc.
- Registry, which is like the backend that maps entities to their components. Unity handles this for us, so we’ll ignore this part.
Basically, objects are defined by their components, not by their class (in fact, their class is just the basic Entity class). There are several benefits of this. The most glaring benefit is that often you will have an object that will have many things in common with other objects (health, level, etc.), but will have many things not in common with other objects (vehicle vs. player vs. gun). If you wanted to make a “HealthObject” interface, and then have the subclasses (vehicle, player, etc.) inherit from that, that’s all well and good, but then what if you want to share a property, such as health, between objects that must not share other properties? What if you have several types of vehicles, some with health, some without (i.e., static background assets vs. drivable vehicles)? This is where OOP fails, and where ECS shines.
With ECS, you can pick and choose what components you want for an entity, and all entities exist peacefully together. Obviously, coupling increases, because entities can just snatch whatever components they want, but the overall complexity of the system goes way down, and some things once impossible are now possible. For example, we can have a vehicleComponent separate from a healthComponent, so you can have some vehicles with health and some without. It also allows for significant optimizaiton. For example, if we have a renderable component that stores renderTexture and renderLayer, when rendering, we can immediately filter our registry to only include these entities to avoid iterating over all of our entities.
ECS & Unity
Okay, now how does this connect to Unity? Well, this is where the quirks I mentioned earlier come into play.
Entities are GameObjects. Any GameObject you have created in your scene is immediately and fundamentally an entity.
Components are either builtin, such as RigidBody2D, or user-defined scripts, where a component is defined as a class that extends MonoBehaviour. I’m not sure why they call it MonoBehaviour instead of Component, since it’s just a really abstract and non-self-explanatory word, but I can kind of see the vision, since you want all components to really only do one single thing.
Important MonoBehaviour Built-in Methods
Components are more than just data holders or single-action methods. They have access to a whole suite of built-in methods, all very useful for making games.
Update()
This function runs every frame, and you can access the time elapsed since the last frame with Time.deltaTime. This is incredibly useful for a metric ton of things. For example, let’s say we want a rain particle effect on the screen to change every frame. Done. Let’s say we want it to change every 50ms. Just keep a counter variable summing up Time.deltaTime until counter > 0.05, then counter -= 0.05 and do your thing. You could apply a similar principle for updating AI pathfinding, making a tower in a tower defense game shoot a dart, etc.
Note that you won’t always use Update(). Sometimes, you just want your monobehaviour to be used by other monobehaviours, or sometimes you want the methods to trigger upon an action, like getting hit or moving through a doorway.
Awake()
This function runs every time the GameObject is enabled, which includes when the game is first run. This should be used for the entity (GameObject)‘s internal setup. Linking components, setting initial position of the GameObject, etc.
Start()
This function runs after every GameObject’s Awake() is complete. This should be used for connecting GameObjects with each other. For example, you could have the AI component of a group of enemies target the closest player. That player’s position needs to be correctly set in Awake(), then Start() will run and the position will be accurate. For this reason, you should not do any internal setup in Start()
Scriptable Objects
Okay, then what is a scriptable object? How does that fit into ECS?
Okay, so, scriptable objects are great, but they fulfill a more niche subset of requirements when making a game. You could, if you wanted, just use GameObjects and never use scriptable objects. However, that is a waste, and could potentially lead to confusion. Think of GameObjects as objects with infinite potential and infinite possibilities. You can do anything. A GameObject could be a player, or it could be a tilemap, or it could be a map generator. If you want it to have these behaviours, or hold data, you need to make a MonoBehaviour component and attach it to the GameObject. However, these MonoBehaviour components have a bunch of tools that we sometimes don’t want. Let’s say I’m storing the data for each tile in the TileMap. If I made the variables holding this data into a MonoBehaviour script and attached it to a GameObject, not only would this MonoBehaviour now be accessible to all other game objects, which is weird because it has a very specific purpose, it would also have access to Awake(), Start(), Update(), etc., all functions that I really shouldn’t have for just data. Plus, I need to attach it to a GameObject. You cannot simply have a component free floating, because by definition in ECS, components are attached to entities. So now I have a transform on my TileMap data. Now, if I want to save the TileMap data, I have to save the GameObject it’s attached to, the GameObject’s posiiton, etc. And what if I wanted to use this data elsewhere? I would have to have a reference to this one specific GameObject’s TileMap Data component. There’s a lot of room for developer error everywhere in this process, and it’s also unintuitive. This is getting ridiculous.
Enter scriptable objects. They are actual assets, existing in your files instead of in your scene. They are built for storing data, and keeping it persistent across game launches and editor restarts. A scriptable object can be referenced by many GameObject components while still existing on its own. You use these when you want to defined data that should be shared across multiple GameObjects and when you want to store data that is independent of any GameObject or scene. The best part is that they can still emit (and listen to other scriptable object) events. A downside is that they cannot hold references to components, since components can be disabled or enabled or the game can be turned off, whereas scriptable objects are always “awake”
Events
Okay, if you don’t know about events, then this isn’t the place where you’ll learn about them. Go look up the Observer design pattern immediately. Breifly, events are a way to invert the dependency between classes. You want to know when the TileMap data changes? Instead of holding an instance of it, and calling it every frame to see when it has changed, you give it a function that you have (let’s say UpdateVisualTilemap(Tiles[])), and this function needs to match the TileMap Data’s event function signature (e.g., update(Tiles[] modifiedTiles)), and then when SetTiles() is called, it will then, after execution but before return, invoke its event, passing the relevant Tiles[] that have changed, and all “listeners“‘s update functions will be run automatically. I like to describe it as a newsboy yelling “Wow! TileMap Data has changed!” and a smoldering detective in a trench coat is listening and then does something about it (updates the visual tilemap).
Events are amazing, and I use them a lot. Maybe too much. This ties into the Model-View-Presenter design pattern which I will explain in a later devlog. Do not underutilize events. If you don’t understand them, research them until you do.
Conclusion
That’s it for the first devlog. I’m already way past this since it took a while to setup the website, so I’ll be getting to the good stuff soon. See you next time.
Footnotes
-
In Unity, all entities always have a
transformcomponent, no matter what. You cannot remove it. ↩