3️⃣ Unity ECS Tutorial • Input

3️⃣ Unity ECS Tutorial • Input


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.

36 thoughts to “3️⃣ Unity ECS Tutorial • Input”

  1. Great video except .. you lost me a bit with the int length field. Unity will populate it because it's called length and because there is another field with a list there, it assume by it self that length should be the length of the list? is that how it works? if yes, what would happen if i made the field "int bob" instead? would it still guess it must be the length of the list that should be the value in bob?

  2. Watch the series playlist at https://www.youtube.com/watch?v=yzhsgaFVpZY&index=2&list=PLKERDLXpXl_gABNen5aIlTVQ4AP26_J_-

    Download the project files at https://www.patreon.com/posts/code-making-part-19388349

  3. How can you make sure that your InputSystem executes before the PlayerMovementSystem? In case it doesn't, you may be processing input one frame late?

  4. I have a question.There are two different entities in the scene.They have some same components.But the same component are all included in a filter.How to distinguish the entities?

  5. It will be cool to hear how to make Initialization Systems with ECS approach. Because it isn't clear to me how can I do it if ComponentSystem base class have no any Start (OnStart) methods. For example, PlayerInitSystem or PlayerSpawnSystem, PlayerInstantiateSystem, which imply that it will be executed only once, but not on each frame in OnUpdate method. (Sorry for my English)

  6. Nice video. But im getting 2 errors, it compiles but it doesnt do anything:

    Error 1:

    ArgumentException: InputSystem:_data An [Inject] struct, supports only a specialized int storing the length of the group. ("int Length;")
    Unity.Entities.InjectComponentGroupData.CreateInjection (System.Type injectedGroupType, System.Reflection.FieldInfo groupField, Unity.Entities.ComponentSystemBase system) (at C:/Users/EQUIPO/AppData/Local/Unity/cache/packages/packages.unity.com/[email protected]/Unity.Entities/Injection/InjectComponentGroup.cs:132)
    Unity.Entities.ComponentSystemInjection.Inject (Unity.Entities.ComponentSystemBase componentSystem, Unity.Entities.World world, Unity.Entities.EntityManager entityManager, Unity.Entities.InjectComponentGroupData[]& outInjectGroups, Unity.Entities.InjectFromEntityData& outInjectFromEntityData) (at C:/Users/EQUIPO/AppData/Local/Unity/cache/packages/packages.unity.com/[email protected]/Unity.Entities/Injection/ComponentSystemInjection.cs:44)
    Unity.Entities.ComponentSystemBase.OnBeforeCreateManagerInternal (Unity.Entities.World world, System.Int32 capacity) (at C:/Users/EQUIPO/AppData/Local/Unity/cache/packages/packages.unity.com/[email protected]/Unity.Entities/ComponentSystem.cs:75)
    Unity.Entities.ComponentSystem.OnBeforeCreateManagerInternal (Unity.Entities.World world, System.Int32 capacity) (at C:/Users/EQUIPO/AppData/Local/Unity/cache/packages/packages.unity.com/[email protected]/Unity.Entities/ComponentSystem.cs:307)
    Unity.Entities.ScriptBehaviourManager.CreateInstance (Unity.Entities.World world, System.Int32 capacity) (at C:/Users/EQUIPO/AppData/Local/Unity/cache/packages/packages.unity.com/[email protected]/Unity.Entities/ScriptBehaviourManager.cs:21)
    Unity.Entities.World.CreateManagerInternal (System.Type type, System.Int32 capacity, System.Object[] constructorArguments) (at C:/Users/EQUIPO/AppData/Local/Unity/cache/packages/packages.unity.com/[email protected]/Unity.Entities/Injection/World.cs:157)
    Unity.Entities.World.GetOrCreateManagerInternal (System.Type type) (at C:/Users/EQUIPO/AppData/Local/Unity/cache/packages/packages.unity.com/[email protected]/Unity.Entities/Injection/World.cs:185)
    Unity.Entities.World.GetOrCreateManager (System.Type type) (at C:/Users/EQUIPO/AppData/Local/Unity/cache/packages/packages.unity.com/[email protected]/Unity.Entities/Injection/World.cs:238)
    Unity.Entities.DefaultWorldInitialization.GetBehaviourManagerAndLogException (Unity.Entities.World world, System.Type type) (at C:/Users/EQUIPO/AppData/Local/Unity/cache/packages/packages.unity.com/[email protected]/Unity.Entities.Hybrid/Injection/DefaultWorldInitialization.cs:21)
    UnityEngine.Debug:LogException(Exception)
    Unity.Debug:LogException(Exception) (at C:/Users/EQUIPO/AppData/Local/Unity/cache/packages/packages.unity.com/[email protected]/Unity.Entities/Stubs/Unity/Debug.cs:25)
    Unity.Entities.DefaultWorldInitialization:GetBehaviourManagerAndLogException(World, Type) (at C:/Users/EQUIPO/AppData/Local/Unity/cache/packages/packages.unity.com/[email protected]/Unity.Entities.Hybrid/Injection/DefaultWorldInitialization.cs:25)
    Unity.Entities.DefaultWorldInitialization:CreateBehaviourManagersForMatchingTypes(Boolean, IEnumerable`1, World) (at C:/Users/EQUIPO/AppData/Local/Unity/cache/packages/packages.unity.com/[email protected]/Unity.Entities.Hybrid/Injection/DefaultWorldInitialization.cs:91)
    Unity.Entities.DefaultWorldInitialization:Initialize(String, Boolean) (at C:/Users/EQUIPO/AppData/Local/Unity/cache/packages/packages.unity.com/[email protected]/Unity.Entities.Hybrid/Injection/DefaultWorldInitialization.cs:57)
    Unity.Entities.AutomaticWorldBootstrap:Initialize() (at C:/Users/EQUIPO/AppData/Local/Unity/cache/packages/packages.unity.com/[email protected]/Unity.Entities.Hybrid/Injection/AutomaticWorldBootstrap.cs:11)

    Error2:

    A Native Collection has not been disposed, resulting in a memory leak. It was allocated at C:UsersEQUIPOAppDat[email protected]0.0.9-preview.1Unity.CollectionsNativeList.cs:24.

    I just put play, and it press pause automatically

  7. I had to change all of the code to get the playerMovementSystem to work, because when i tried your methond the Entity0 in the playerMovementSystem(entity debugger) was not existing, the entity manager registered it but the playerMovementSystem did not also it said "No MonoBehavior scripts in the file, or thier names do not match the file name" above the script of the
    playerMovementSystem

  8. Thank you very much for your videos. I've just started learning Unity ECS and your videos are very helpful. But I did not really get the part about injections. Does it automatically fills variables in struct or what? In terms of "classic" Unity programming: is it kinda like making a Start() function with GetComponent<> in it?

  9. After grabbing your input using InputSystem and modifying _data, wouldn't you need to then change the PlayerMovementSystem, so that Input is taken from the entities InputComponent?
    This ECS is quite confusing to me as im just starting out, but my PlayerMovementSystem looks like this:

    using UnityEngine;
    using Unity.Entities;

    public class PlayerMovementSystem : ComponentSystem {

    private struct Filter
    {
    public Rigidbody Rigidbody;
    public InputComponent InputComponent;
    }

    protected override void OnUpdate()
    {
    foreach (var entity in GetEntities<Filter>())
    {
    var moveVector = new Vector3(entity.InputComponent.Horizontal , 0, entity.InputComponent.Vertical);

    var movePosition = entity.Rigidbody.position + moveVector.normalized * 3 * Time.deltaTime;

    entity.Rigidbody.MovePosition(movePosition);
    }
    }
    }

  10. Hi.
    Thanks for the tutorial!

    I have noticed that PlayerMovementSystemTests (availble for patreons) are failing, because during the test, the InputSystem still runs and it actually overwrites the values of the InputComponent.

    Using this article (https://connect.unity.com/p/part-1-unity-ecs-briefly-about-ecs), I figured out that it is possible to disable the system using:

    var system = World.Active.GetExistingManager<inputsystem>();
    system.Enabled = false;

    in the beginning of the test and reenable it in the end.

    In this case,

    inputComponent.Horizontal = 1;

    works correctly.

  11. Was getting all sorts of injection errors through "int Length" so I deleted it and replaced it with "_data.InputComponents.Length" in the for loop

  12. in my project the InputSystem class only worked with "GetEntity <>" mode.
      [Inject] the console reported error, in the version "Entities 0.0.12-preview 21"

  13. So what if i need to get a specific button behaviour? I need something to happen on GetButtonDown and something else on GetButtonUp?

  14. Correct me if I'm wrong, but systems update on the main thread, so you aren't getting any new parallelism with this approach, right?

  15. Wow man, what a great tutorials! You are explaining all of it so clearly! I see myself becoming a patron as soon as I get a new job 😉

  16. It is a better idea to define a constant string for get axis as using a direct string does a memory allocation on each function call which affects performance.

Leave a Reply

Your email address will not be published. Required fields are marked *