Search Unity

Is there a way to disable entities?

Discussion in 'Entity Component System' started by aeldron, May 2, 2019.

  1. aeldron

    aeldron

    Joined:
    Feb 12, 2013
    Posts:
    32
    Hello.

    I'd like to disable a group of entities, something akin to GameObject.SetActive(false)

    I need to turn off rendering, collision detection, and any other behaviour added by systems like interpolation, etc - without destroying the entity data - so they can be toggled back on again.

    I am using a hybrid system with GameObjectEntity, but it seems that if I disable those game objects the entities get destroyed, which means I can no longer use an EntityQuery to retrieve and re-enable them.

    What's the best way to achieve that?
     
  2. julian-moschuering

    julian-moschuering

    Joined:
    Apr 15, 2014
    Posts:
    529
    Add the 'Disabled' component to the entities. This will result in the entity not getting returned by any EntityQueries except when explicitly querying for 'Disabled' component.
     
    rafal_b likes this.
  3. aeldron

    aeldron

    Joined:
    Feb 12, 2013
    Posts:
    32
    That's pretty much what I'm doing but since this is a hybrid system, Monobehaviour scripts on those gameObjects will continue running.

    I suppose I could get the GameObject reference and turn things off with a standard GetComponent<T>, but that's inefficient. I was hoping for a better way, but I'll do what you suggested for now. Thanks!
     
  4. SamOld

    SamOld

    Joined:
    Aug 17, 2018
    Posts:
    333
    You could have a system dedicated to detecting when
    Disabled
    is added and removed, and only updating the
    GameObjects
    as necessary. You may run into some tricky behaviours with hierarchies, I'm not sure.
     
    starikcetin likes this.
  5. aeldron

    aeldron

    Joined:
    Feb 12, 2013
    Posts:
    32
    What I really need is a way to do a gameObject.SetActive(false) and still somehow keep a reference to that gameobject so I can turn it back on.

    I could just slap a Dictionary on my toggle system, but that's kind of ugly and it breaks the ECS paradigm.

    If I could use a pure ECS system this would not be a problem but unfortunately we still need to use Animator components and other things which are still not covered by the ECS.
     
  6. Micz84

    Micz84

    Joined:
    Jul 21, 2012
    Posts:
    451
    As SamOld said you could use SystemStateComponent to detect when Disabled component is added and removed. MonoBehavoir can be stored on entity
     
  7. Ahlundra

    Ahlundra

    Joined:
    Apr 13, 2017
    Posts:
    47
    cant you drop the monobehaviours and transform them in componentsystems? Componentsystem is designed to run in the mainthread for this kind of situation, just give the gameobject a name and make a component with his name as a string then search for it using it's name inside the componentsystem

    or you could use a static class wich lists gameobjects by id and give every object a component with it's id as int

    dunno how fast the code would be, but that's what i'm doing for now to exchange information between game objects and pure ecs
     
  8. aeldron

    aeldron

    Joined:
    Feb 12, 2013
    Posts:
    32
    My workaround is actually simple. I've nested the game objects I wanted to toggle in a container. The parent is a GameObjectEntity that has a SharedComponentData referencing the gameObject I want to toggle. The parent GameObjectEntity never gets disabled, so the entity itself is always alive.

    Code (CSharp):
    1. public struct GameObjectContainer : ISharedComponentData
    2. {
    3.     public GameObject GameObject;
    4. }
    5.  
    6. public struct GameObjectToggleData : IComponentData
    7. {
    8.     public bool IsActive;
    9. }
    Then we have a system to capture user input and transform the toggle data:

    Code (CSharp):
    1. if (controllerStateArray[1].ButtonTwoWasPressedThisFrame == 1)
    2. {
    3.     if (EntityManager.HasComponent<GameObjectToggleData>( containerEntity))
    4.     {
    5.         var toggleData = EntityManager.GetComponentData<GameObjectToggleData>(containerEntity);
    6.         toggleData.IsActive = !toggleData.IsActive;
    7.         EntityManager.SetComponentData(containerEntity, toggleData);
    8.     }
    9. }
    And finally another system to do the actual toggling:

    Code (CSharp):
    1. public class GameObjectToggleSystem : ComponentSystem
    2. {
    3.     private EntityQuery toggledObjectsQuery;
    4.  
    5.     protected override void OnCreateManager()
    6.     {
    7.         toggledObjectsQuery = GetEntityQuery(new EntityQueryDesc
    8.         {
    9.             All = new[] {
    10.                 ComponentType.ReadOnly<GameObjectContainer>(),
    11.                 ComponentType.ReadOnly<GameObjectToggleData>()
    12.             }
    13.         });
    14.     }
    15.  
    16.     protected override void OnUpdate()
    17.     {
    18.         var entityArray = toggledObjectsQuery.ToEntityArray(Allocator.TempJob);
    19.         for (int i=0; i< entityArray.Length; i++)
    20.         {
    21.             var entity = entityArray[i];
    22.             var container = EntityManager.GetSharedComponentData<GameObjectContainer>(entity);
    23.             var toggleData = EntityManager.GetComponentData<GameObjectToggleData>(entity);
    24.             container.GameObject.SetActive(toggleData.IsActive);
    25.         }
    26.         entityArray.Dispose();
    27.     }
    28. }

    That seems to work well, with the advantage that when I toggle any child game object off its entity disappears, so there's no risk of systems doing any unintended behaviour while the original game object is disabled.
     
  9. Nyanpas

    Nyanpas

    Joined:
    Dec 29, 2016
    Posts:
    406
    This seems like such a roundabout way to do something that should be trivial for a developer to implement. Guess I'll hold on a lot longer before I make the switch to ECS...
     
  10. eizenhorn

    eizenhorn

    Joined:
    Oct 17, 2016
    Posts:
    2,685
    In EntityManager you can see SetEnabled method, which doing this thing - adding specific component (Disabled) which excluded from EntityQuery by default, which means this data will be filtered from processing by systems
    Code (CSharp):
    1. /// <summary>
    2.         /// Enabled entities are processed by systems, disabled entities are not.
    3.         /// Adds or removes the <see cref="Disabled"/> component. By default EntityQuery does not include entities containing the Disabled component.
    4.         ///
    5.         /// If the entity was converted from a prefab and thus has a <see cref="LinkedEntityGroup"/> component, the entire group will enabled or disabled.
    6.         /// </summary>
    7.         /// <param name="entity">The entity to enable or disable</param>
    8.         /// <param name="enabled">True if the entity should be enabled</param>
    9.         public void SetEnabled(Entity entity, bool enabled)
    10.         {
    11.             if (GetEnabled(entity) == enabled)
    12.                 return;
    13.  
    14.             var disabledType = ComponentType.ReadWrite<Disabled>();
    15.             if (HasComponent<LinkedEntityGroup>(entity))
    16.             {
    17.                 //@TODO: AddComponent / Remove component should support Allocator.Temp
    18.                 using (var linkedEntities = GetBuffer<LinkedEntityGroup>(entity).Reinterpret<Entity>().ToNativeArray(Allocator.TempJob))
    19.                 {
    20.                     if (enabled)
    21.                         RemoveComponent(linkedEntities, disabledType);
    22.                     else
    23.                         AddComponent(linkedEntities, disabledType);
    24.                 }
    25.             }
    26.             else
    27.             {
    28.                 if (!enabled)
    29.                     AddComponent(entity, disabledType);
    30.                 else
    31.                     RemoveComponent(entity, disabledType);
    32.             }
    33.         }
    34.  
    35.         public bool GetEnabled(Entity entity)
    36.         {
    37.             return !HasComponent<Disabled>(entity);
    38.         }
     
  11. kingstone426

    kingstone426

    Joined:
    Jun 21, 2013
    Posts:
    44
    It seems EntityManager.SetEnabled is actually more similar to GameObject.SetActive than Component.enabled, which is a little confusing.
     
  12. Danielpunct

    Danielpunct

    Joined:
    May 9, 2013
    Posts:
    16
    Hey guys,

    I have this solution for disabling/enabling entities by checking its parent that is working:
    * it is with netcode sintax, but I guess that can be ignored

    Code (CSharp):
    1. [UpdateInGroup(typeof(ClientSimulationSystemGroup))]
    2. public class UnitsSelectionSystem : ComponentSystem
    3. {
    4.     protected override void OnUpdate()
    5.     {
    6.         Entities
    7.             .WithAll<UnitSelectionVisual>()
    8.             .WithIncludeAll()
    9.             .ForEach((
    10.                 Entity entity,
    11.                 ref Parent parent) =>
    12.                {
    13.                    var sel = EntityManager.GetComponentData<UnitSelectionState>(parent.Value).IsSelected;
    14.  
    15.                    if (sel)
    16.                    {
    17.                        PostUpdateCommands.AddComponent<Disabled>(entity);
    18.                    }
    19.                    else
    20.                    {
    21.                        PostUpdateCommands.RemoveComponent<Disabled>(entity);
    22.                    }
    23.  
    24.                });
    25.  
    26.     }
    Can anyone please tell me how to convert it to job system ? and better yet to the new 'foreach' written jobsystem ?

    My failed approaches:
    NOT WORKING:
    Code (CSharp):
    1.   BeginSimulationEntityCommandBufferSystem ECB;
    2.  
    3.     protected override void OnCreate()
    4.     {
    5.         base.OnCreate();
    6.  
    7.         ECB = World.GetOrCreateSystem<BeginSimulationEntityCommandBufferSystem>();
    8.  
    9.     }
    10.  
    11.     [RequireComponentTag(typeof(Disabled))]
    12.     [BurstCompile]
    13.     struct UnitsSelSystemJob : IJobForEachWithEntity<Parent, UnitSelectionVisual>
    14.     {
    15.         public EntityCommandBuffer.Concurrent CommandBuffer;
    16.         [ReadOnly] public ComponentDataFromEntity<UnitSelectionState> us;
    17.  
    18.         public void Execute(Entity entity, int index,
    19.             [ReadOnly] ref Parent parent,
    20.             [ReadOnly] ref UnitSelectionVisual unitSelectionVisual)
    21.         {
    22.  
    23.             var sel = us[parent.Value].IsSelected;
    24.             if (!sel)
    25.             {
    26.                 CommandBuffer.AddComponent<Disabled>(index, entity);
    27.             }
    28.             else
    29.             {
    30.                 CommandBuffer.RemoveComponent<Disabled>(index, entity);
    31.             }
    32.         }
    33.     }
    34.  
    35.     protected override JobHandle OnUpdate(JobHandle inputDependencies)
    36.     {
    37.         var job = new UnitsSelSystemJob
    38.         {
    39.             CommandBuffer = ECB.CreateCommandBuffer().ToConcurrent(),
    40.             us = GetComponentDataFromEntity<UnitSelectionState>(true)
    41.         }.Schedule(this, inputDependencies);
    42.  
    43.         ECB.AddJobHandleForProducer(job);
    44.  
    45.         // Now that the job is set up, schedule it to be run.
    46.         return job;
    47.     }

    NOT WORKING:
    Code (CSharp):
    1.     protected override JobHandle OnUpdate(JobHandle inputDeps)
    2.     {
    3.         var ecb = ECB.CreateCommandBuffer().ToConcurrent();
    4.         var us = GetComponentDataFromEntity<UnitSelectionState>(true);
    5.  
    6.         return Entities
    7.             //.WithoutBurst()
    8.             //.WithAny<Disabled>()
    9.             .ForEach((
    10.          ref Entity entity,
    11.          ref int nativeThreadIndex,
    12.          in Parent parent,
    13.          in UnitSelectionVisual selectionVisual) =>
    14.         {
    15.                //var sel = EntityManager.HasComponent<UnitSelected>(parent.Value);
    16.  
    17.                var sel = us[parent.Value].IsSelected;
    18.  
    19.                //var ecb = ECB.CreateCommandBuffer().ToConcurrent();
    20.  
    21.                // if (sel)
    22.                {
    23.                 ecb.AddComponent<Disabled>(nativeThreadIndex, entity);
    24.             }
    25.                //else
    26.                //{
    27.                //    ecb.RemoveComponent<Disabled>(nativeThreadIndex, entity);
    28.                //}
    29.  
    30.  
    31.            }).Schedule(inputDeps);
    32.  
    33.         return default(JobHandle);
    34.     }
     
  13. Danielpunct

    Danielpunct

    Joined:
    May 9, 2013
    Posts:
    16
    Update - solved:

    Code (CSharp):
    1.  
    2. [UpdateInGroup(typeof(ClientSimulationSystemGroup))]
    3. public class UnitsSelectionSystem : JobComponentSystem
    4. {
    5.     protected override JobHandle OnUpdate(JobHandle inputDeps)
    6.     {
    7.         var us = GetComponentDataFromEntity<UnitSelectionState>(true);
    8.         var PostUpdateCommands = new EntityCommandBuffer(Allocator.Temp);
    9.  
    10.         Entities
    11.              .WithEntityQueryOptions(EntityQueryOptions.IncludeDisabled)
    12.              .ForEach((
    13.           ref Entity entity,
    14.           in Parent parent,
    15.           in UnitSelectionVisual selectionVisual) =>
    16.          {
    17.              var sel = us[parent.Value].IsSelected;
    18.  
    19.              if (!sel)
    20.              {
    21.                  PostUpdateCommands.AddComponent<Disabled>(entity);
    22.              }
    23.              else
    24.              {
    25.                  PostUpdateCommands.RemoveComponent<Disabled>(entity);
    26.              }
    27.          }).Run();
    28.  
    29.         PostUpdateCommands.Playback(EntityManager);
    30.         PostUpdateCommands.Dispose();
    31.  
    32.         return default;
    33.     }
    34. }
     
    Moecia likes this.
  14. zb737472783

    zb737472783

    Joined:
    Nov 8, 2018
    Posts:
    19
    2022.3.10f1 Is there any new way to write it?
     
  15. Kahlis

    Kahlis

    Joined:
    Jan 14, 2017
    Posts:
    6
    In this case you can use EntityCommandBuffer for disable inside an IJobEntity and use SystemAPI to query the values using WithOptions to query even disabled and default query for only enabled.

    Code (CSharp):
    1.  
    2. using Unity.Collections;
    3. using Unity.Entities;
    4. using Unity.Burst;
    5.  
    6. [BurstCompile]
    7. public partial struct DisableSystem : ISystem
    8. {
    9.     public void OnUpdate(ref SystemState state)
    10.     {
    11.         // Query for requesting all entities, even disabled
    12.         foreach (var piece in SystemAPI.Query<RefRO<PieceData>>().WithOptions(EntityQueryOptions.IncludeDisabledEntities))
    13.         {
    14.             // Do something with ALL entities
    15.         }
    16.  
    17.         // Query for filter only enabled entities
    18.         foreach (var piece in SystemAPI.Query<RefRO<PieceData>>())
    19.         {
    20.             // Do something with only enabled entities
    21.         }
    22.  
    23.         // Job for disabling entity
    24.         EntityCommandBuffer ecb = new(Allocator.Persistent);
    25.         DisableJob job = new()
    26.         {
    27.             ecb = ecb,
    28.         };
    29.  
    30.         job.Schedule();
    31.         state.Dependency.Complete();
    32.  
    33.         ecb.Playback(state.EntityManager);
    34.         ecb.Dispose();
    35.     }
    36. }
    37.  
    38. public partial struct DisableJob : IJobEntity
    39. {
    40.     public EntityCommandBuffer ecb;
    41.  
    42.     public void Execute(ref DisableData disable)
    43.     {
    44.         ecb.AddComponent(disable.self, new Disabled() { });
    45.     }
    46. }
    47.  
    In my code my component is PieceData that runs into PieceSystem
    To disable an Entity just add Disabled to it, so you will need a reference of the entity, that why I created a "public Entity self" parameter inside of DisableData

    Code (CSharp):
    1. using Unity.Entities;
    2.  
    3. public struct DisableData : IComponentData
    4. {
    5.     public Entity self;
    6. }
    7.  
    Is just a boilerplate code anyways, modify as you need. Here is how it works with a simple count variable inside the loops:
    upload_2024-2-28_9-5-9.png
     

    Attached Files:

    Last edited: Feb 28, 2024