Search Unity

Modifying a Shared Component across many Entities

Discussion in 'Entity Component System' started by TickTakashi, Jun 1, 2018.

  1. TickTakashi

    TickTakashi

    Joined:
    Mar 14, 2013
    Posts:
    31
    Hi

    Is it possible to modify a shared component across many entities? Almost like having a component stored by reference on multiple entities and being able to change that across the group.

    My use case:

    There's a weapon in my game which can only have a certain number of projectiles in play at any given time. I'd like to be able to store a reference to the Weapon component that spawned the projectile, and somehow decrement it at the point of destroying the projectile.

    I've included a simplified version of the problem here:


    Code (CSharp):
    1.     // Simplified Weapon data - Tracking the number of current bolts. If there are too many, we can't fire.
    2.     public struct Weapon : IComponentData {
    3.         public int maxConcurrentBolts;
    4.         public int currentBolts;
    5.     }
    6.  
    7.     // Spawns the Bolts if the weapon is able and we've tried to fire. Could be the AI or Player Controls triggering this event.
    8.     public class WeaponSystem : ComponentSystem {
    9.         public struct WeaponFireGroup {
    10.             public ComponentDataArray<Weapon> Weapons;
    11.             public ComponentDataArray<FireEvent> FireEvents;
    12.  
    13.             public EntityArray Entities;
    14.             public int Length;
    15.         }
    16.  
    17.         [Inject] public WeaponFireGroup GroupWeaponFire;
    18.         EntityArchetype BoltArchetype;
    19.  
    20.         protected override void OnCreateManager(int capacity) {
    21.             BoltArchetype = EntityManager.CreateArchetype(
    22.                 ComponentType.Create<Projectile>(),
    23.                 ComponentType.Create<BasicBolt>()
    24.             );
    25.         }
    26.        
    27.         protected override void OnUpdate() {
    28.             for (int i = 0; i < GroupWeaponFire.Length; i++) {
    29.                 if (GroupWeaponFire.Weapons[i].currentBolts < GroupWeaponFire.Weapons[i].maxConcurrentBolts) {
    30.                     PostUpdateCommands.CreateEntity(BoltArchetype);
    31.                     Weapon weapon = GroupWeaponFire.Weapons[i];
    32.                     weapon.currentBolts += 1;
    33.                     GroupWeaponFire.Weapons[i] = weapon;
    34.                     PostUpdateCommands.RemoveComponent<FireEvent>(GroupWeaponFire.Entities[i]);
    35.                 }
    36.             }
    37.         }
    38.     }
    39.  
    40.     // Destroys the bolts when they collide with stuff.
    41.     public class BoltCollisionSystem : ComponentSystem {
    42.         public struct BoltCollisionGroup {
    43.             public ComponentDataArray<BasicBolt> BasicBolts;
    44.             public ComponentDataArray<CollisionEvent> CollisionEvents;
    45.             public EntityArray Entities;
    46.  
    47.             public int Length;
    48.         }
    49.  
    50.         [Inject] public BoltCollisionGroup GroupBoltCollision;
    51.  
    52.         protected override void OnUpdate() {
    53.             for(int i = 0; i < GroupBoltCollision.Length; i++) {
    54.                 PostUpdateCommands.DestroyEntity(GroupBoltCollision.Entities[i]);
    55.                
    56.                 // TODO: Somehow Decrement the Weapon.currentBolts value.
    57.             }
    58.  
    59.         }
    60.     }
    61.  
    One solution I've considered, is having an Entity member in the
    BasicBolt
    , which is initialized when the bolt is created, and then injecting a
    ComponentDataFromEntity<Weapon>
    into the
    BoltCollisionSystem
    , but it feels like because there is a context where I have access to both the weapon and the bolt (when spawning) there should be a way for me to pass that component on directly instead of having to look it up?
     
  2. 5argon

    5argon

    Joined:
    Jun 10, 2013
    Posts:
    1,555
    The preferred way of "Pass the component directly" in ECS shoud be remembering the Entity as you said. It is analogous to passing references to other instances in OOP when combined with ComponentDataFromEntity.

    You cannot remember other entities's ComponentDataArray and hope that you could directly write to the correct place in the data base later from itself. The inside of ComponentDataArray is not a data, it is a struct with indexers that contains logic to access the correct memory area. The memory must be accessed using this indexers on the CDA and so if you want to keep it for use, you have to store the ComponentDataArray as a whole. Which then you cannot do, as IComponentData only allows data, therefore you cannot store ComponentDataArray together with BasicBolt IComponentData.

    (There is a pointer deep inside CDA but still you could not just peel them out, they are all internal and unsafe for obvious reason that they made CDA + indexers as a safe interface for you.)

    After using GetComponentDataFromEntity with the remembered Entity you will get the Weapon data and not CDA<Weapon>. To save the changes, you can use the same remembered entity and the same GetComponentDataFromEntity to save back the modified data. (Despite the name says "get" you can actually "SetComponentDataToEntity" back too)
     
    Last edited: Jun 1, 2018
    TickTakashi likes this.