Search Unity

Scripted Entity Archetypes?

Discussion in 'Entity Component System' started by colinthe26, Jul 2, 2019.

  1. colinthe26

    colinthe26

    Joined:
    May 25, 2019
    Posts:
    7
    Does anyone know (or have examples of) the syntax for creating entity archetypes in code?

    I strongly prefer that style of workflow and organization over converting GameObject prefabs into entities, but haven't been able to find any references in which this was done.

    For some context, I'm instantiating a projectile entity within a job. The entity is being passed within component data, and can be one of a number of different projectile entities.
     
  2. tertle

    tertle

    Joined:
    Jan 25, 2011
    Posts:
    3,761
    EntityManager.CreateArchetype?
     
    Last edited: Jul 2, 2019
  3. colinthe26

    colinthe26

    Joined:
    May 25, 2019
    Posts:
    7
    I should have specified, I'm curious about the syntax for defining an EntityArchetype struct. If I can, I'd like a single script that just defines an entity archetype (and its components). -- Just the data, not yet worrying about run-time creation.

    I ran cross this bit in the documentation but couldn't find any use examples.

    Maybe I'm misinterpreting the specifics for how Unity handles entities, and am more than happy for suggestions.
     
  4. tertle

    tertle

    Joined:
    Jan 25, 2011
    Posts:
    3,761
    EntityArchetype is just a collection of types. There is no data in it you have to set it after and you should only create it via EntityManager.CreateArchetype. This was the classic way of defining entities before conversion was a thing.

    Code (CSharp):
    1. var archetype = EntityManager.CreateArchetype(typeof(A), typeof(B));
    2. var entity1 = EntityManager.CreateEntity(archetype);
    3. var entity2 = EntityManager.CreateEntity(archetype);
    4.  
    5. EntityManager.SetComponentData(entity1, new A {value = 1});
    6. EntityManager.SetComponentData(entity2, new A {value = 2});
    Conversion is actually a bit less efficient as it doesn't actually use archetypes, just adds components to the entity but it's still the recommended approach for convenience.
     
    Last edited: Jul 3, 2019
    colinthe26 likes this.
  5. colinthe26

    colinthe26

    Joined:
    May 25, 2019
    Posts:
    7
    Okay, that's super helpful, thank you!

    I'm curious as well, could I define a public entity/archetype type outside of a function or method? This bit of syntax has me super curious:

    public struct EntityArchetype : IEquatable<EntityArchetype>
     
    Last edited: Jul 3, 2019
  6. Arowx

    Arowx

    Joined:
    Nov 12, 2009
    Posts:
    8,194
    Is there any way we could 'directly' change the entities data e.g:

    Code (CSharp):
    1. var archetype = EntityManager.CreateArchetype(typeof(float), typeof(int));
    2. var entity1 = EntityManager.CreateEntity(archetype);
    3. var entity2 = EntityManager.CreateEntity(archetype);
    4. entity1.float = 1f;
    5. entity2.int = 2;
    Ideally allowing for some kind of struct like variable naming convention e.g.

    Code (CSharp):
    1. var archetype = EntityManager.CreateArchetype(
    2. {
    3. float health,
    4. int actionPoints
    5. });
    6.  
    7. var human = EntityManager.CreateEntity(archetype);
    8. var elf = EntityManager.CreateEntity(archetype);
    9.  
    10. human.health = 100f;
    11. human.actionPoints = 10;
    12.  
    13. elf.health = 50f;
    14. elf.actionPoints = 15;
     
  7. tertle

    tertle

    Joined:
    Jan 25, 2011
    Posts:
    3,761
    You can already write extension methods if you want

    EntityManager.SetHealth(entity, 100f);
    EntityManager.SetActionPoints(entity, 15f);

    But what you're suggesting isn't possible in c#. Extension properties are coming in c# 8.0 which would let you do syntax closer to what you want but won't really help. You'd need to store the EM in the Entity which would cause a lot more issues than it's worth for some pretty useless syntax sugar.
     
    Last edited: Jul 3, 2019
    xVergilx and tarahugger like this.
  8. JesOb

    JesOb

    Joined:
    Sep 3, 2012
    Posts:
    1,109
    Entity is new type of GameObject so it has no fields.

    Like with GameObject you need to get component from it and do what ever you want with it.
    e.g. humanEntity.GetComponent<Health>( ).Value = 5;
     
  9. Arowx

    Arowx

    Joined:
    Nov 12, 2009
    Posts:
    8,194
    Lets get back to basics here, our entity in this example is a block of memory with a float32 and an int32 in that order.

    So if I were to make a struct with the same format and name the variables and point it to the memory address of the entity it would map perfectly onto that memory footprint of the entity. And provide field name access to the data.

    C# has pointers therefore it should be doable IMHO.
     
  10. sngdan

    sngdan

    Joined:
    Feb 7, 2014
    Posts:
    1,154
    Did you read the memory architecture?
     
  11. Arowx

    Arowx

    Joined:
    Nov 12, 2009
    Posts:
    8,194
    What impact does this have on mapping variables to memory data via named fields?
     
  12. sngdan

    sngdan

    Joined:
    Feb 7, 2014
    Posts:
    1,154
    I was commenting on back to the basics...

    Your point is, can we make it less verbose, simpler and my understanding is that this is the goal of Unity (a) within the standard c# context, (b) better editor support, (c) a visual language

    What you are talking about is looking like c# but it’s not c# anymore and reminds me of why not lua, f#, etc discussions. They just moved tiny to c# - find piece with it...
     
  13. sngdan

    sngdan

    Joined:
    Feb 7, 2014
    Posts:
    1,154
  14. Arowx

    Arowx

    Joined:
    Nov 12, 2009
    Posts:
    8,194
    Remember that a variable or field is just a token that points to a memory address. So jumping through a convoluted set of function and method calls just to change a variable is a massive step back IMHO.
     
  15. JesOb

    JesOb

    Joined:
    Sep 3, 2012
    Posts:
    1,109
    No you are wrong :)

    Your entity in memory is something like this:

    Entity[]
    float32[]
    int32[]

    all three arrays consume slightly less then 16KB of memory

    so you can not create struct that will point to entity.

    You can point only into separate component of entoity because it is pointer to element of array.
     
  16. sngdan

    sngdan

    Joined:
    Feb 7, 2014
    Posts:
    1,154
    I am not arguing against simplicity, but if there would be the 1 size fits all solution, we would not have different languages, etc.

    Let’s hope they make it as easy as possible but without voodoo
     
  17. sngdan

    sngdan

    Joined:
    Feb 7, 2014
    Posts:
    1,154
    That was my point earlier on memory layout, etc with some pointer voodoo you could make it work.... it’s the same old discussion, let me imagine my game and the code is done automatically- yes we all would like this
     
  18. Arowx

    Arowx

    Joined:
    Nov 12, 2009
    Posts:
    8,194
    LOL you do realise that programming languages and compilers were created to allow machine code programmers to be able to work with easier to use tools.

    You should realise that an API or Framework is clunky when you see complex structures used to do simple things. In your own code you will probably use getters and setters to make working with it easier and simpler.

    It's just applying the Keep It Simple Stupid approach to all things. Or the Don't Make Me Think approach.

    Code (CSharp):
    1. // So you want to have to type...
    2. EntityManager.SetComponentData(human, new float {value = 100});
    3.  
    4. // Instead of...
    5. human.health = 100f;
    6.  
    7.  
     
  19. Arowx

    Arowx

    Joined:
    Nov 12, 2009
    Posts:
    8,194
    So why not call Enity.human[entityIndex] = 100f;

    Even it would simpler than EntityManager.SetComponentData(human, new float {value = 100});
     
  20. Antypodish

    Antypodish

    Joined:
    Apr 29, 2014
    Posts:
    10,775
    You are aware, there is something like worlds as well? That is why we got EntityManager (s).
    And then, you can not have
    Code (CSharp):
    1. Enity.human[entityIndex] = 100f;
    , because human entity can have multiple components with floats, ints, and other data types.
     
  21. colinthe26

    colinthe26

    Joined:
    May 25, 2019
    Posts:
    7
    Follow-up question. I'm trying to follow the HelloCube SpawnFromEntity example, but using manually scripted entities instead of prefab conversion. Right now, I'm creating an entity archetype and a public entity from it, on awake.
    Code (CSharp):
    1. public class Bootstrap : MonoBehaviour
    2. {
    3.     public Entity projectile;
    4.  
    5.     private static Bootstrap instance;
    6.     public static Bootstrap GetInstance()
    7.     {
    8.         return instance;
    9.     }
    10.  
    11.     private void Awake()
    12.     {
    13.         instance = this;
    14.  
    15.         EntityManager entityManager = World.Active.EntityManager;
    16.  
    17.         EntityArchetype projectileArchetype = entityManager.CreateArchetype(
    18.             typeof(Translation),
    19.             typeof(Projectile),
    20.             typeof(LocalToWorld),
    21.             typeof(RenderMesh),
    22.             typeof(TranslationMovement),
    23.             typeof(Scale),
    24.             typeof(EntityLifeTimer)
    25.             );
    26.  
    27.         projectile = entityManager.CreateEntity(projectileArchetype);
    28.     }
    29. }
    In a job, I'm assigning the entity to component data:
    Code (CSharp):
    1. public struct AttackStats : IComponentData
    2. {
    3.     public Entity WeaponProjectile;
    4. }
    5.  
    6. public struct AssignProjectileJob : IJobForEach<AttackStats>
    7. {
    8.     public void Execute(ref AttackStats attackStats)
    9.     {
    10.         attackStats.WeaponProjectile = Bootstrap.GetInstance().projectile;
    11.     }
    12. }
    and in a later job, using that passed entity to instantiate copies.
    Code (CSharp):
    1. public struct PlayerAttackJob : IJobForEachWithEntity<Translation, PlayerInputs, AttackStats>
    2. {
    3.     public float dT;
    4.  
    5.     public EntityCommandBuffer.Concurrent CommandBuffer;
    6.  
    7.     public void Execute(Entity entity, int index, [ReadOnly] ref Translation translation, [ReadOnly] ref PlayerInputs playerInputs, ref AttackStats attackStats)
    8.     {
    9.         attackStats.LastAttackTime += dT;
    10.         if (playerInputs.isFiring)
    11.         {
    12.             if (attackStats.LastAttackTime > attackStats.AttackSpeed)
    13.             {
    14.                 attackStats.LastAttackTime = 0f;
    15.  
    16.                 var instance = CommandBuffer.Instantiate(index, attackStats.WeaponProjectile);
    17.             }
    18.         }
    19.     }
    20. }
    The problem I'm running into is in a final system:
    Code (CSharp):
    1. public void Execute(Entity entity, int index, ref EntityLifeTimer entityLifeTimer)
    2. {
    3.     entityLifeTimer.Age += dT;
    4.  
    5.     if (entityLifeTimer.Age > entityLifeTimer.Life)
    6.     {
    7.         CommandBuffer.DestroyEntity(index, entity);
    8.     }
    9. }
    I'm counting up the age of the entities, and destroying them after a certain amount of time has passed, but instead of having independent timers, they all share that initial entity "age" timer, and are all destroyed at once (throwing an error).
    Should I be passing a reference to the archetype instead of the entity, and if so, how do I use CommandBuffer.Instantiate from that archetype when it wants an entity as input?
     
  22. tertle

    tertle

    Joined:
    Jan 25, 2011
    Posts:
    3,761
    Are you setting the EntityLifeTimer anywhere.

    You need to set all the shared values before you Instantiate and need to set all instance values after you Instantiate the projectile prefab.

    I'm actually surprised what you're doing will work at all. The initial 'Bootstrap.projectile' entity will be destroyed by your cleanup job and become invalid. You need to give it the Prefab or Disabled components.
     
    colinthe26 likes this.
  23. colinthe26

    colinthe26

    Joined:
    May 25, 2019
    Posts:
    7
    Ahh-ha! That's precisely what I was doing wrong. I was setting the timer on awake, but not after instantiation. Additional note: I had to end up adding the component with the timer after instantiation instead of passing it as part of the entity, otherwise the reference entity would be destroyed as well.

    Thanks again for the swift feedback!
     
  24. JesOb

    JesOb

    Joined:
    Sep 3, 2012
    Posts:
    1,109
    If you have some primary or the only EntityManager
    You can make it look like this:

    Code (CSharp):
    1. //C#7
    2. entity.SetHealth( someVal );
    3. entity.SetAABB( new AABB( 23, 123, 213, 231 ) )
    4. entity.SetAvatar( EAvatar.Baby )
    5.  
    6. //C#8
    7. entity.Health  = someVal;
    8. entity.AABB   = new AABB( 23, 123, 213, 231 );
    9. entity.Avatar = EAvatar.Baby;
    Note that Unity 2019.3a have bug fixes in IL2CPP related to C# 8
     
  25. tertle

    tertle

    Joined:
    Jan 25, 2011
    Posts:
    3,761
    Just because you can do something does not mean you should.
     
    Last edited: Jul 4, 2019
  26. Arowx

    Arowx

    Joined:
    Nov 12, 2009
    Posts:
    8,194
    Code (CSharp):
    1. //C#8
    2. entity.Health  = someVal;
    3. entity.AABB   = new AABB( 23, 123, 213, 231 );
    4. entity.Avatar = EAvatar.Baby;
    That's what I'm talking about where ECS systems and entities start appearing as 'first class' elements within the programming language. This can massively reduce boilerplate code.
     
  27. JesOb

    JesOb

    Joined:
    Sep 3, 2012
    Posts:
    1,109
    All this is cool stuff but it all good only for MainThread Code, and not usefull for Jobified code.
     
  28. DreamingImLatios

    DreamingImLatios

    Joined:
    Jun 3, 2017
    Posts:
    4,266
    For jobified code you can use a strongly archityped entity wrapper and factory to help reduce the boilerplate cluttering your game logic. Whatever your component access pattern is, (ComponentDataFromEntity or a chunk with NativeArrays), you construct your factory using these accessors for the desired archetype. Then that factory has a Create(Entity entity) method that creates a struct containing the Entity and properties for reading and/or modifying the components.

    In practice this technique is only really worthwhile if your project is more logic-heavy than computationally-heavy.