Search Unity

DOTS: How to break away from OOP mindset?

Discussion in 'Entity Component System' started by Jyskal, Sep 13, 2019.

  1. Jyskal

    Jyskal

    Joined:
    Dec 4, 2014
    Posts:
    28
    Hello,

    I have been doing a lot of reading and testing with DOTS. However practically moving from OOP to ECS I run into some minor roadblocks.

    In traditional OOP I have a project where I have Citizens who walk around, perform tasks, have needs etc... I can see how I can move the game logic to ECS systems. However converting my OOP classes to ECS is less clear. For example:

    Code (CSharp):
    1. public class Citizen
    2. {
    3.   public int id;
    4.   public string name;
    5.   public Inventory inventory;
    6.   public Vector3 destination;
    7.   public Needs needs;
    8.   ...
    9. }
    As you know you can't convert this kind of class one-on-one to ECS components because ECS components can only store Blittable types. So how to solve this?

    1. Do you create collections in your systems and link the entity ID to structs or classes within the collections to maintain data that does not fit within ECS components?
    2. Do you keep a Citizen class outside of ECS for traditional OOP and move some parts into ECS (pathing, rendering, logic, etc...)? Would this make it a "Hybrid ECS"?
    3. If opting for option 2, wouldn't it be easier to drop ECS as a whole and only use Jobs without ECS?
    4. Is there some other solution?
    I would like to hear your comments. Thanks
     
  2. digitaliliad

    digitaliliad

    Joined:
    Jul 1, 2018
    Posts:
    64
    schaefsky likes this.
  3. hippocoder

    hippocoder

    Digital Ape

    Joined:
    Apr 11, 2010
    Posts:
    29,723
    Richard Fabian if I'm not mistaken, wrote it and has a book out if anyone wants to buy it. I think it's worth a read with the feet up :)
     
    MNNoxMortem and Vacummus like this.
  4. swejk

    swejk

    Joined:
    Dec 22, 2013
    Posts:
    20
    There are multiple ways how you can arrange your data, it depends on your needs. For Citizen class you can do this:
    Code (CSharp):
    1. struct Citizen : IComponentData {
    2. public int id;
    3. public NativeString64 name;
    4. public float3 destination;
    5. }
    6.  
    It is quite popular opinion that each aspect of the entity should be its own component. This will help you make your code reusable and modular. So you can also use this arrangement of components to represent Citizen entity.
    Code (CSharp):
    1. struct Citizen : IComponentData {
    2. // empty, serves as a tag
    3. }
    4. struct Id : IComponentData {
    5. public int Value;
    6. }
    7. struct Name : IComponentData {
    8. public NativeString64 Value;
    9. }
    10. struct Destination : IComponentData {
    11. public float3 Value;
    12. }
    "Needs" and "Inventory" fields implies that we want to work with collections of data / entities. In such cases you will want to look at DynamicBuffer and IBufferElementData. In short DynamicBuffer is an component which behaves as an array.
    You can use them to represent needs/inventory like:
    Code (CSharp):
    1. struct NeedElement : IBufferElementData {
    2.   // some data
    3. }
    4.  
    5. struct InventoryElement : IBufferElementData {
    6.    public int ItemId;
    7.   // or
    8.    public Entity ItemEntity;
    9. }
    98% of cases you can get by with simple blittable types in IComponentData and IBufferElementData. Dont be afraid to use Entity references in components and ComponentDataFromEntity<> get their components in jobs. If nothing works, you can still have Dictionary<Entity, SomeClass> in your componentSystem or use EntityManager.AddComponentObject().

    We are spoiled by being able to create convoluted object graphs in OOP, no wonder we struggle to convert them to constrained ECS. But thats why I like ECS, you just have to make it simple.
     
  5. neoshaman

    neoshaman

    Joined:
    Feb 11, 2011
    Posts:
    6,493
    I think this talk from unity anzsea will help you, it touch on that with great details

     
  6. SubPixelPerfect

    SubPixelPerfect

    Joined:
    Oct 14, 2015
    Posts:
    224
    the entity itself is an ID so there is no need in duplicating it and introducing an extra variable for identifier
     
  7. Guedez

    Guedez

    Joined:
    Jun 1, 2012
    Posts:
    827
    I assume the citizen name is not actually used for any computational reason, it's just there as data and it's only there because it happens to be convenient to be "stored" on the citizen.
    Then consider putting it into a ComponentObject. Those are stored like OOP objects but are associated with the entity
    Code (CSharp):
    1. public class CitizenNameHolder: Component {
    2.     public string name;
    3. }
    The entity id/version is not constant between application runs, so if you save/load the id/version changes. it is not suitable to uniquely identifying data across multiple executions. It's better to think of it like a pointer reference, you wouldn't use an object's memory position as an ID
     
    TeagansDad, eterlan and JBR-games like this.
  8. MostHated

    MostHated

    Joined:
    Nov 29, 2015
    Posts:
    1,235
    Wait a second. I have always just been using IComponentData, I didn't even know there was something that was just Component. I am assuming that if you can load those up with data, I am guessing it doesn't get used in a job or anytihng?
     
  9. Jyskal

    Jyskal

    Joined:
    Dec 4, 2014
    Posts:
    28
  10. MostHated

    MostHated

    Joined:
    Nov 29, 2015
    Posts:
    1,235
    Yeah, I realized that after, lol. I thought for a minute that there was some other data type in the DOTS packages I might have missed.
     
  11. Guedez

    Guedez

    Joined:
    Jun 1, 2012
    Posts:
    827
    Components also can be used to attach data to entities, this data none of the normal ECS requirements like needing to be bittable
    EntityManager em = World.Active.EntityManager;

    em.AddComponentObject(entity, AnyObjectWhichExtendsComponent)

    The same object can later be queried with
    em.GetComponentObject<TheTypeOfTheObjectAddedInTheExampleAbove>(entity)
     
    andywatts likes this.
  12. Vacummus

    Vacummus

    Joined:
    Dec 18, 2013
    Posts:
    191
    I got that book a year ago when he published it on Amazon. It was a great read (especially with my feet up). However, I didn't think it was very beginner friendly.
     
    hippocoder likes this.
  13. Cynicat

    Cynicat

    Joined:
    Jun 12, 2013
    Posts:
    290
    My advice would be to start thinking of entities as the "classes" instead of the components.

    eg:
    I have a class Citizen
    - It has an identifier
    - It has a name
    - It keeps an inventory
    - It walks to a destination
    - It has basic needs

    Instead think of it like this:
    I have an Entity Citizen
    - It has an identifier
    - It has a name
    - It keeps an inventory
    - It walks to a destination
    - It has basic needs

    The above are all components. Feel free to group values that you access together often.

    For functions, the trick i'd show is this:
    The first function is basically just syntax suger for the second one. Except that using extension methods lets you define functions all over the place instead of being stuck in one file.
    Code (CSharp):
    1. void MyFunction() {
    2.     myVariable = 4;
    3. }
    4.  
    5. static void MyFunction(this MyObject obj) {
    6.     obj.myVariable = 4;
    7. }
    This isn't necessarily the endgame of how to think about data oriented, i find myself thinking far more in terms of rules instead of objects after years of using ECS systems. But this can be a good starting point to get you making progress again. =3
     
    Last edited: Oct 8, 2019
    milox777 and createtheimaginable like this.
  14. calabi

    calabi

    Joined:
    Oct 29, 2009
    Posts:
    232
    I find thinking in DOTS is much more easier than OOP for me. I was struggling in OOP with certain abstract objects and concepts. I would struggle to figure out what was an object and where I should put the actual processing code. Like when I want to transfer data from two different objects. Do I put it on one of the objects, then which one, or do I make a new object. What is even an object when I'm dealing with abstract concepts. I didn't even dare go near inheritance or polymorphism.

    DOTS, its just think of the data and what I want to do with it, it makes so much more sense to me. I've programmed much more faster and more clearly with it.
     
    hippocoder likes this.