Welcome to part 3 of this Unity tutorial series where we’re creating a survival shooter
game using Unity’s new implementation of the Entity Component System pattern, or Unity
ECS for short.
In part 2 we got the player character moving around the scene using mock input data that
we entered manually in the editor.
In this video we’ll start consuming actual input data and using that to move the character
instead.
So, without further ado, let’s get right into it.
The goal here is to transform player input into data that we can use within the context
of Unity ECS.
More specifically we wanna grab values from the controller’s horizontal and vertical
axes and store them within our entities’ InputComponents.
you may be wondering why we should bother storing these values at all.
Why not just access the player input right here in the player movement system?
Well one of the strengths of the Entity Component System pattern is that it makes it easy to
stick to the single responsibility principle.
The single responsibility principle states that every class should be responsible for
one function of the program.
The player movement system is responsible for, well, moving the player.
Anything more would just add layers of complexity that would make the system more error prone
and harder to test.
The single responsibility principle can, however, create a shift in complexity that’s important
to be aware of.
Medium to large scale games could potentially end up with hundreds of systems.
That would add an entirely new layer of complexity to the project in the form of massive directories
that are hard to navigate.
The solution is either to compromise and allow systems to perform more than one function
or to come up with a well thought out and documented folder structure that separates
your class files into sensible groups.
For now, we won’t worry about it.
We need to implement our new behavior.
So let’s create a system.
I’ll call it “input system”.
Now the first thing we need to do is get access to the right entities.
The input system’s sole responsibility will be to transform player input into component
data.
So we’re gonna need a reference to each entity that has an input component.
In the player movement system we accomplished this using the GetEntities method, but now
we’re gonna explore an alternative approach called Injection.
Let’s create a struct that’ll hold all of the data our system needs to operate.
Now let’s expose the struct as a field, and mark it with the inject attribute.
The inject attribute tells Unity that this field needs a value.
At startup, Unity will do it’s best to provide an object that meets this field’s requirements.
If you’ve ever used Zenject or any form dependency injection, then this part should
look familiar.
How we form the rest of struct, however, will not.
Adding fields to the struct that have specific types and/or names will tell Unity how to
populate those fields with certain predictable values.
For instance, adding a field of the type ComponentArray and setting it’s generic type to InputComponent
tells Unity to scan the scene for entities that have an InputComponent.
Unity then takes those entities’ InputComponents and uses them to populate the ComponentArray
object.
This concept is known as convention over configuration, or coding by convention.
Convention over configuration is a design paradigm used by frameworks that tries to
decrease the number of decisions the developer has to make.
Following certain conventions is supposed to make it easier to get things done by not
requiring you to configure the framework or make decisions about how it should work underneath
the covers.
There are a few other special types and reserved names that we can include which’ll instruct
Unity to inject certain values into the struct instance.
For example, an int called “Length” will be given a value that correlates to the number
of entities Unity finds based on the other fields in the struct.
We’ll use the length field now to loop through our entities and access each one’s InputComponent.
Now we can use Unity’s Input class and the GetAxis method to populate the horizontal
and vertical fields of each one.
And there’s actually a slight optimization we can make before test this in the editor.
We’re going to move the call to Input outside of the for loop.
You may have noticed that I did this for delta time in the player movement system, as well.
And there we have it!
Back in the editor we can press play and check the Entity Debugger to make sure the input
system loaded correctly.
There it is!
And there’s Entity 0 which represents the player GameObject in the scene.
Now we can test the functionality.
Pressing left and right should result in the player avatar moving left and right in the
scene.
And pressing up and down should result in the player avatar moving up and down in the
scene.
Beautiful!
Just a heads up, the Unity project used in this video is available to my tier two patrons.
If you’d like to download the project files and support my content, please consider becoming
a patron today.
I include bonus content in each package.
Today’s bonus content includes integration tests that assert the correctness of the logic
used in the input system.
So now we’ve seen two different ways to to access entities from within component systems.
Which one’s better?
Well, I’ll leave that up to you and your particular use case.
In the next video, we'll continue adding functionality to the character by adding rotation that’s
based on the current mouse position.
We’ll be using a lot of the concepts we’ve covered so far so it’ll be a nice little
review before we start moving on to more advanced topics.
If you found Unity tutorial helpful, please leave a like and a comment letting me know
what you thought.
And for more Unity tutorials just like this one don’t forget to subscribe with notifications
on.
I’ll catch you in the next video.