Search Unity

  1. Welcome to the Unity Forums! Please take the time to read our Code of Conduct to familiarize yourself with the forum rules and how to post constructively.
  2. We have updated the language to the Editor Terms based on feedback from our employees and community. Learn more.
    Dismiss Notice

Migrating Mindset from OOP to DoD/ECS

Discussion in 'Entity Component System' started by Timotius, Nov 2, 2018.

  1. Timotius

    Timotius

    Joined:
    Mar 30, 2017
    Posts:
    6
    Hi,

    During these couple of days I've been learning about DOD and ECS, watching videos, view tutorials and some examples. And while I'm already convinced that ECS is the "correct" way for the game development at least, I've yet to see concrete help how to change my problem solving mindset from OOP that I'm familiar with for longest time (and I admit, I might not even that good on it), into DOD mindset. Watching Mike Acton videos on how he designed the data feels like ethereal to my puny brain.

    So here's some points of confusion during my learning:
    1. Single Object (like player or BGM manager) - If I know I will make games with one player only, how to design it for ECS? Usually on OOP I make it Singleton for ease access, and put some Player behaviour and capabilities there. Alright, some of you might groaning about this, but yeah, how do you guys usually break it down?
    2. Events & Trigger - I used a lot of events previously, and I'm confused on how to translate that to pure ECS. From Collider, to Animation Events, and many other triggerable conditions. ECS seems on quite contained loop by themselves, and events seems crashing into that philosophy. How do you guys handle events?
    3. State Machine - Usually I make a lot of if and conditions about state, and do appropriate behavior for particular state. But from what I read (I forgot where), for ECS you need to reduce branching and testing. How can we did that?
    4. Sprite Renderer - the built in renderer is only for mesh. If my game is 2D, what should I do (I also have very limited understanding about shaders)
    5. Job Schedule size - This code is quite common in ECS (
      Code (CSharp):
      1.  JobHandle moveHandle = moveJob.Schedule(this, 64, inputDeps);
      ), so the number 64 if I'm not wrong, is the job size. How to determine that?
    6. Unity UI - How to combine ECS with that? With all of the components seems still tied into GameObjects, like RectTransform, Layout, etc.
    7. (Almost) No GameObject on Scene? - Is this the target to achieve with pure ECS?
    8. Game Flow - Usually all in ECS are parallel, or if sequential is needed, that's for specific contained system. I've yet to see for big interconnected things like game flow (on main menu, char select, then main game, score screen, and such), which usually handled by game manager. How can we achieve this in ECS?
    9. Physics - Just like UI, how to do ECS with physics? Seems unity is not supporting this yet, but if I want to change my mindset, how should I approach Physics in my game?
    10. Inject vs InjectAttributes (and then chunks?) - Sometimes I stumbled upon some examples that use Inject, and sometimes InjectAttributes. What's the difference? Should I care? Because later on I read somewhere that Unity will drop this, and replace it with chunks? What about that?

    Sorry for lots of points, and some of it might seem dumb questions. But you can see that these were leftover OOP mindset that I have, and I have no idea how to transition to think like DOD. Personally, I don't think using Hybrid ECS is a good way to change the mindset. I tried it with my game demo, and still has no idea how to make the full game with pure ECS. Lots of stumbling blocks are unanswered by self learning, I don't know if my question problems are DOD (principal) or Unity ECS (implementation syntax) things, and if I see my final build of the demo, a lot of it actually has no difference than usual OOP.

    So here's hoping you guys can help :). Thanks.
     
    Antypodish and hippocoder like this.
  2. dartriminis

    dartriminis

    Joined:
    Feb 3, 2017
    Posts:
    157
    Hope this helps a bit:

    1. I usually stick with ECS. Even for my player (which there is only one of) there is an entity with some components that are unique to that entity, and so I also have some systems which usually only transform one component.
    2. I usually handle events by either: a) attaching a component to the effected entity and later removing it or b) using a simple queue that a system reads from and others enqueue to.
    3. Dont have a good answer yet :)
    4. ECS is only in preview. I'm sure a Sprite Renderer is coming at some point.
    5. That's the batch size, its how many IJobParallelFor.Execute(i)s are run sequentially on a single thread. 64 is a good starting place if you have many components and your data transformations are small. I have a couple jobs that do a LOT of work on only a handful of components, so I have the batch count on that down to 1.
    6. Loaded topic, but you have some options. Your UI objects could be entities and thus used by systems. Your UI objects could reference the entity manager and query for entities it needs. etc. I don't think there's a good practice for this yet.
    7. Yes, pure ECS is just entities and components, not GameObjects and MonoBehaviours. However, there's not a whole lot of pure ECS functionality, yet. So hybrid is usually the route I take for most things.
    8. Check out the FPS sample: Specifically the Game and ClientGameLoop files.
    9. See 4.
    10. I believe the Inject attribute is going away. If you can use it, I strongly recommend using IJobProcessComponentData, because it's simple to use and has very little boilerplate. If it doesn't suite your needs, I believe the next best thing is to use GetComponentGroup() and IJobChunk.

    Also, here's a link to the ECS docs on GitHub if you haven't stumbled over it yet.
     
  3. Razmot

    Razmot

    Joined:
    Apr 27, 2013
    Posts:
    346
    firstly, it's currently not possible to do a full game with ECS, there is no ecs mesh renderer ! Only instanced meshes writing directly to the graphics, no equivalent of a unique meshrenderer/filter.

    For your point #1 - the player, your objective should be to design the behaviours and the data that can affect a player ( it can move -> has a transform component, it can receive damage -> health component, it can eat a pizza -> hunger/ satiety component ... ) and the systems that work on these components. So you could reuse the same behaviour on your player and on your NPCs, and add new data and behaviour without breaking the existing ones.

    But I think, like a lot of people, you're falling into the trap of wanting to use the brand new "gold hammer" named ecs, that is extremely performant at hammering lots of nails in parallel, ... to screw a bunch of screws :) And gameobjects/monobehaviours are really good to ... screw around ;)
     
    dadude123 and fholm like this.
  4. MatthieuPr

    MatthieuPr

    Joined:
    May 4, 2017
    Posts:
    56
    I haven't had the chance yet to make large attempts at ECS projects, but from all the reading I have been doing the easiest way I have found to to transition from an OOP mindset to a DoD mindset is the following:

    1) start by making up your OOP object, all the data it has, all the functionality you think it will need. For your player it would most likely be Movement, take damage, equip armour, unequip,...

    2) see all those functionalities as behaviours and apply the composition pattern (move behaviour to separate script and you add an object of that type to your Player).

    3 ) break down your data that is left into small chunks individual to your behaviour, take damage would use health, so make a health component that keeps track of your player's health, movement needs a position, maybe a speed and heading, equipping armor might need reference to strength to see if can carry it or not... and put all of those into separate structs. Mesh rendered needs position so might want 1 component for position and 1 separate component that holds speed and heading for the movement part.

    4) Those structs you just created for your Player are the components you most likely would use in DoD, delete all the behaviours you have added to your player in 2 and just keep the structs in there. You have just converted your Player into a DoD entity.

    5) all those separate behaviour scripts you wrote in 2 can be converted to jobs that utilise the components generated in 4.

    From what I have seen DoD is a much further abstraction between data and behaviour. When I was studying long time ago my teacher loved to speak on proper code structure and layering of the code. For those that do not know, there are supposed to be 3 layers in your code:
    1) Data Access layer
    2) logic
    3) presentation
    Layers can only know about themselves and the layer right under them. presentation never knows about how data is accessed, only knows that it receives information from the logic part and it has to show it.

    DoD breaks all of that, layers only know about themselves, no longer know about what others do.

    For 2, and 3 you can most likely handle that by modifying the components attached to your entity at runtime... based on the state of an entity I would change the components that are present on it.

    for 4, 6, 9 we just have to wait on the implementation in the ECS system, I would assume that there will be components offered for these by Unity ECS and like the mesh renderer for 3D would just have to add a 2d renderer and provide the correct sprite for it.

    I have noticed in latests test I made in preview 18 that batch size is no there, changed to
    Code (CSharp):
    1.  JobHandle moveHandle = moveJob.Schedule(this, inputDeps);
    7 yes, there is separate entity debugger (rough still) that will be used to debug and work with those. I think by default in Unity it was a bad behaviour to keep all your game objects in the scene... but that is personal for me.
     
  5. Lurking-Ninja

    Lurking-Ninja

    Joined:
    Jan 20, 2015
    Posts:
    9,991
    The entire ECS is a big state machine if you think about it that way.
    You have components on entities, which determinates if the particular entity will be passed in the running system or not. You can carry extra data if you want.
    Depending on what you actually want to do, you may dissolve your FSM into your ECS implementation. Just plain and simple.Or you can use a system to advance your own state machine depending on some components. And you hide your FSM in systems and maybe components.
     
    SubPixelPerfect and dartriminis like this.
  6. eizenhorn

    eizenhorn

    Joined:
    Oct 17, 2016
    Posts:
    2,655
  7. Razmot

    Razmot

    Joined:
    Apr 27, 2013
    Posts:
    346
    Yeah I've read your post, and liked it very much thanks ! I mean it's not possible to do 100% pure without any gameobject.
     
  8. eizenhorn

    eizenhorn

    Joined:
    Oct 17, 2016
    Posts:
    2,655
    And again I disagree :) All depend on difficulty of game, but anyway is possible, you just use your own colision and other system writed on ECS, base systems exists - Rendering (With culling and LOD), Transform (with hierarchy), last needed - physics which can be writen by you own and two GO which wil be represented in game being Camera and Light :)
     
  9. DreamingImLatios

    DreamingImLatios

    Joined:
    Jun 3, 2017
    Posts:
    3,993
    I'll throw in my two cents since I find these questions interesting:

    1. Using a singleton for the player is generally not a good idea to begin with, even in classic GameObject land. However, for singletons that actually should be singletons (like a game manager), I have an entity with a few components unique to only it. Don't worry about splitting up all the different components if those components are only going to be used for a small number of entities. I've found that starting with large structs and refactoring as I develop, the data will split apart to an optimal form.

    2. I try not to rely heavily on events. I instead run query jobs and then run jobs that process those queries to determine behavior output. If I do need events, I use event queues. Attaching and Removing components doesn't scale well enough for my goals.

    3. For compile-time state machines, I use enums and a switch statement. For runtime or designer-driven state machines, I have been playing with the idea of using an entity for each state and having those entities' components reference each other. Then the NPC keeps track of its current state by storing a reference to a state entity. (Note that by "reference" I mean using the Entity struct type.) This approach is not cache-efficient which sucks, but it is Burst-able and avoids an command buffer sync.

    4. Create an SCD type that takes a sprite object (we'll call this SpriteComponent). Write a system that checks for SpriteComponent additions or changes. Give this system an internal Dictionary<Sprite, MeshInstanceRenderer>. For each added or changed entity, check if the sprite is in the dictionary. If it is, add or update the MeshInstanceRenderer from the dictionary to the entity. Otherwise, build a mesh using the Sprite's traingles, uvs, and vertices. Then create and assign a sprite material with the sprite's texture or texture atlas. Assign those to the MeshInstanceRenderer in the Dictionary and copy that to the the entity. That'll get basic sprites drawing on screen. The newer 2D features will probably be broken though.

    5. See dartriminis answer.

    6. You would do this the same way you do cameras or lights. Either you use hybrid for these, or you store indices to lists of GameObjects inside IComponentData. I prefer the latter.

    7. Yes. But practically speaking, "almost" is good enough. It is ok to have a small handful of Game Objects. The MegaCity demo did just that. It may not be perfectly pure, but it is "pure enough" to get all of the performance benefits.

    8. I personally use classical Unity Scenes and Scene changes for these things. I then have a Game Manager in each scene that sets up the ECS player loop that the scene needs.

    9. There's a few libraries and prototypes floating about in these forums for physics. However, do you really need physics? Or just collision detection? For the latter, it is now much easier to roll your own solution optimized for your game needs, whether that be a fixed grid, octtree, or something else.

    10. IJobProcessComponentData{WithEntity} and chunk iteration are the only current methods sticking around. This breaks it all down pretty cleanly:


    And for the record, you can actually do completely pure ECS right now. No camera or light class components necessary. But you do need a custom or modified ScriptableRenderPipeline to do it. But again, close enough is good enough to see the massive performance improvements ECS promises. So saying "we are stuck with hybrid" is a pessimistic view of things.
     
  10. Timotius

    Timotius

    Joined:
    Mar 30, 2017
    Posts:
    6
    Thank you guys. Wow there's a lot of discussion and insight here. At least now I got some direction to start. There are still others on that I need to get further explanation (like, how to write my own renderer or collision? I don't know how to do it here). But for the moment I'll try some of the suggestion here.

    I'll get back here when I'm stumped again.
     
  11. Timotius

    Timotius

    Joined:
    Mar 30, 2017
    Posts:
    6
    Hello, sorry for getting back again so soon.

    So I tried to make my player (a 2D game), into pure ECS. For behind the scene stuff (like processing health or movement) this is quite straightforward. The problem starts when I tried to put them on screen.

    See, my usual OOP workflow is that I have Player GameObject, and it has child PlayerSprite, to put my SpriteRenderer there and any animations. So any movement that I did will be on main Player gameobject, and any sprite animation on the child will follow in relation to that.

    Since Unity still doesn't have built in SpriteRenderer and animator for ECS, I was thinking of combining Pure and Hybrid (writing my own renderer is way out of my league). So I tried converting Player to pure ECS, and the PlayerSprite will be processed by Hybrid. I figure that if I can do this, I can also do it for enemies and other stuffs.

    However, I cannot find any (clean) way to reference the Entity that should be the parents (Player) to the child (PlayerSprite). Should the parent hold the child? If so, what do I need to use, since IComponentData cannot reference GameObject. Should the child hold the parent? Or should the child query the parent all the time? <- this doesn't seem efficient for multiple objects.

    What kind of thinking should I approach this?
     
  12. Chiv

    Chiv

    Joined:
    Mar 6, 2014
    Posts:
    10
    Could you elaborate a little on this, maybe with a few code lines? It sounds like something good, but unfortunately I do not really get what you mean by storing "the indices to lists" and where are the lists of GameObjects made and stored?
     
  13. DreamingImLatios

    DreamingImLatios

    Joined:
    Jun 3, 2017
    Posts:
    3,993
    Here's one example. I have not tested this version as I am very short on time at the moment. I store the ECSPrefabPools in singleton ISharedComponentData, but you could just as easily store them in a ComponentSystem. My actual implementation is broken right now, but it has features for list compaction and indirect indices remapping so that I can jobify transform component manipulation.

    Code (CSharp):
    1. public struct GameObjectReferenceExample : ISystemStateComponentData
    2.     {
    3.         public int Index;
    4.     }
    5.  
    6.     public class ECSPrefabPool
    7.     {
    8.         //Any standard pooling system you prefer. I use one built off the pooling classes from SRP.
    9.         PrefabPool Pool;
    10.  
    11.         //This implementation does not require entity access, but you could also use a Dictionary<Entity, GameObject> and a tag ISSCD instead.
    12.         //The implementation I actually use is a bit more complex.
    13.         List<GameObject> ActiveObjects;
    14.         Stack<int> freeIndices;
    15.  
    16.         public int GetIndex()
    17.         {
    18.             var go = Pool.Get();
    19.             if (freeIndices.Count > 0)
    20.             {
    21.                 var result = freeIndices.Pop();
    22.                 ActiveObjects[result] = go;
    23.                 return result;
    24.             }
    25.             else
    26.             {
    27.                 var result = ActiveObjects.Count;
    28.                 ActiveObjects.Add(go);
    29.                 return result;
    30.             }
    31.         }
    32.  
    33.         public void ReleaseIndex(int index)
    34.         {
    35.             freeIndices.Push(index);
    36.             Pool.Release(ActiveObjects[index]);
    37.             ActiveObjects[index] = null;
    38.         }
    39.     }
     
    Chiv likes this.
  14. Ofx360

    Ofx360

    Joined:
    Apr 30, 2013
    Posts:
    155
    I might do this:

    Code (CSharp):
    1. [Serializable]
    2. public struct PlayerState : IComponentData
    3. {
    4.     public enum State
    5.     {
    6.         Idle = 0,
    7.         Walking = 1,
    8.         Running = 2
    9.     };
    10.     public State state;
    11. }
    12.  
    13. public class PlayerStateComponent : ComponentDataWrapper<PlayerState> { }
    14.  
    File name for this would be: PlayerStateComponent.cs

    This allows you to put the component on your GameObjects like normal components but you get easier access to them from ECS land

    You can then access the component with GetComponentDataArray<>(). From there you can change your data in ECS land, and read that data in GameObject land. Like this:

    Code (CSharp):
    1. [UpdateBefore(typeof(UnityEngine.Experimental.PlayerLoop.Update))]
    2. class PlayerSystem : ComponentSystem
    3. {
    4.     ComponentGroup stateGroup;
    5.  
    6.     protected override void OnCreateManager()
    7.     {
    8.         stateGroup = GetComponentGroup(typeof(PlayerState));
    9.     }
    10.  
    11.     protected override void OnUpdate()
    12.     {
    13.         var stateArray = stateGroup.GetComponentDataArray<PlayerState>();
    14.         var count = stateArray.Length;
    15.  
    16.         for (var i = 0; i < count; i++)
    17.         {
    18.             var playerStates = stateArray[i];
    19.  
    20.             if (Input.GetKeyDown(KeyCode.Alpha1))
    21.             {
    22.                 playerStates.state = PlayerState.State.Idle;
    23.             }
    24.             else if (Input.GetKeyDown(KeyCode.Alpha2))
    25.             {
    26.                 playerStates.state = PlayerState.State.Walking;
    27.             }
    28.             else if (Input.GetKeyDown(KeyCode.Alpha3))
    29.             {
    30.                 playerStates.state = PlayerState.State.Running;
    31.             }
    32.  
    33.             stateArray[i] = playerStates;
    34.         }
    35.     }
    36. }
    When you're eventually ready to switch to full ECS, I imagine it'll be pretty easy. All your systems should basically just work with pure ECS entities - most of the work will be recreating the ecs representations of your objects, i'd think

    One thing: It looked like the system was updating after the GameObject's update loop. So i added [UpdateBefore(typeof(UnityEngine.Experimental.PlayerLoop.Update))] so that the system will update the state data before all your visuals updates that'll happen in the GO update loop. You can change when you update your systems by adding [UpdateBefore(typeof())] or [UpdateAfter(typeof())] (typeof() taking the type of the System you want to update before or after)
     
  15. Ofx360

    Ofx360

    Joined:
    Apr 30, 2013
    Posts:
    155
    Oh i just remembered

    You could do this too:

    Code (CSharp):
    1. protected override void OnCreateManager()
    2.     {
    3.         stateGroup = GetComponentGroup(typeof(GameObjectEntity), typeof(PlayerState));
    4.     }
    5.  
    6.     protected override void OnUpdate()
    7.     {
    8.         var gameObjectEntityArray = stateGroup.GetComponentArray<GameObjectEntity>();
    9.         var stateArray = stateGroup.GetComponentDataArray<PlayerState>();
    10.         var count = stateArray.Length;
    11.  
    12.         for (var i = 0; i < count; i++)
    13.         {
    14.             var playerStates = stateArray[i];
    15.             var go = gameObjectEntityArray[i];
    16.  
    17. (...)
    And just access your GameObjectEntity. It has a reference to gameObject, so you can do all you normal gameObject stuff from there if you wanted
     
    Deleted User likes this.