Is there a way to disable entities?

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?

1 Like

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.

2 Likes

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, but that’s inefficient. I was hoping for a better way, but I’ll do what you suggested for now. Thanks!

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.

1 Like

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.

As SamOld said you could use SystemStateComponent to detect when Disabled component is added and removed. MonoBehavoir can be stored on entity

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

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.

public struct GameObjectContainer : ISharedComponentData
{
    public GameObject GameObject;
}

public struct GameObjectToggleData : IComponentData
{
    public bool IsActive;
}

Then we have a system to capture user input and transform the toggle data:

if (controllerStateArray[1].ButtonTwoWasPressedThisFrame == 1)
{
    if (EntityManager.HasComponent<GameObjectToggleData>( containerEntity))
    {
        var toggleData = EntityManager.GetComponentData<GameObjectToggleData>(containerEntity);
        toggleData.IsActive = !toggleData.IsActive;
        EntityManager.SetComponentData(containerEntity, toggleData);
    }
}

And finally another system to do the actual toggling:

public class GameObjectToggleSystem : ComponentSystem
{
    private EntityQuery toggledObjectsQuery;

    protected override void OnCreateManager()
    {
        toggledObjectsQuery = GetEntityQuery(new EntityQueryDesc
        {
            All = new[] {
                ComponentType.ReadOnly<GameObjectContainer>(),
                ComponentType.ReadOnly<GameObjectToggleData>()
            }
        });
    }

    protected override void OnUpdate()
    {
        var entityArray = toggledObjectsQuery.ToEntityArray(Allocator.TempJob);
        for (int i=0; i< entityArray.Length; i++)
        {
            var entity = entityArray[i];
            var container = EntityManager.GetSharedComponentData<GameObjectContainer>(entity);
            var toggleData = EntityManager.GetComponentData<GameObjectToggleData>(entity);
            container.GameObject.SetActive(toggleData.IsActive);
        }
        entityArray.Dispose();
    }
}

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.

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…

1 Like

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

/// <summary>
        /// Enabled entities are processed by systems, disabled entities are not.
        /// Adds or removes the <see cref="Disabled"/> component. By default EntityQuery does not include entities containing the Disabled component.
        ///
        /// If the entity was converted from a prefab and thus has a <see cref="LinkedEntityGroup"/> component, the entire group will enabled or disabled.
        /// </summary>
        /// <param name="entity">The entity to enable or disable</param>
        /// <param name="enabled">True if the entity should be enabled</param>
        public void SetEnabled(Entity entity, bool enabled)
        {
            if (GetEnabled(entity) == enabled)
                return;

            var disabledType = ComponentType.ReadWrite<Disabled>();
            if (HasComponent<LinkedEntityGroup>(entity))
            {
                //@TODO: AddComponent / Remove component should support Allocator.Temp
                using (var linkedEntities = GetBuffer<LinkedEntityGroup>(entity).Reinterpret<Entity>().ToNativeArray(Allocator.TempJob))
                {
                    if (enabled)
                        RemoveComponent(linkedEntities, disabledType);
                    else
                        AddComponent(linkedEntities, disabledType);
                }
            }
            else
            {
                if (!enabled)
                    AddComponent(entity, disabledType);
                else
                    RemoveComponent(entity, disabledType);
            }
        }

        public bool GetEnabled(Entity entity)
        {
            return !HasComponent<Disabled>(entity);
        }

It seems EntityManager.SetEnabled is actually more similar to GameObject.SetActive than Component.enabled, which is a little confusing.

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
[UpdateInGroup(typeof(ClientSimulationSystemGroup))]
public class UnitsSelectionSystem : ComponentSystem
{
    protected override void OnUpdate()
    {
        Entities
            .WithAll<UnitSelectionVisual>()
            .WithIncludeAll()
            .ForEach((
                Entity entity,
                ref Parent parent) =>
               {
                   var sel = EntityManager.GetComponentData<UnitSelectionState>(parent.Value).IsSelected;

                   if (sel)
                   {
                       PostUpdateCommands.AddComponent<Disabled>(entity);
                   }
                   else
                   {
                       PostUpdateCommands.RemoveComponent<Disabled>(entity);
                   }

               });

    }

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:

  BeginSimulationEntityCommandBufferSystem ECB;

    protected override void OnCreate()
    {
        base.OnCreate();

        ECB = World.GetOrCreateSystem<BeginSimulationEntityCommandBufferSystem>();

    }

    [RequireComponentTag(typeof(Disabled))]
    [BurstCompile]
    struct UnitsSelSystemJob : IJobForEachWithEntity<Parent, UnitSelectionVisual>
    {
        public EntityCommandBuffer.Concurrent CommandBuffer;
        [ReadOnly] public ComponentDataFromEntity<UnitSelectionState> us;

        public void Execute(Entity entity, int index,
            [ReadOnly] ref Parent parent,
            [ReadOnly] ref UnitSelectionVisual unitSelectionVisual)
        {

            var sel = us[parent.Value].IsSelected;
            if (!sel)
            {
                CommandBuffer.AddComponent<Disabled>(index, entity);
            }
            else
            {
                CommandBuffer.RemoveComponent<Disabled>(index, entity);
            }
        }
    }

    protected override JobHandle OnUpdate(JobHandle inputDependencies)
    {
        var job = new UnitsSelSystemJob
        {
            CommandBuffer = ECB.CreateCommandBuffer().ToConcurrent(),
            us = GetComponentDataFromEntity<UnitSelectionState>(true)
        }.Schedule(this, inputDependencies);

        ECB.AddJobHandleForProducer(job);

        // Now that the job is set up, schedule it to be run.
        return job;
    }

NOT WORKING:

    protected override JobHandle OnUpdate(JobHandle inputDeps)
    {
        var ecb = ECB.CreateCommandBuffer().ToConcurrent();
        var us = GetComponentDataFromEntity<UnitSelectionState>(true);

        return Entities
            //.WithoutBurst()
            //.WithAny<Disabled>()
            .ForEach((
         ref Entity entity,
         ref int nativeThreadIndex,
         in Parent parent,
         in UnitSelectionVisual selectionVisual) =>
        {
               //var sel = EntityManager.HasComponent<UnitSelected>(parent.Value);

               var sel = us[parent.Value].IsSelected;

               //var ecb = ECB.CreateCommandBuffer().ToConcurrent();

               // if (sel)
               {
                ecb.AddComponent<Disabled>(nativeThreadIndex, entity);
            }
               //else
               //{
               //    ecb.RemoveComponent<Disabled>(nativeThreadIndex, entity);
               //}


           }).Schedule(inputDeps);

        return default(JobHandle);
    }

Update - solved:

[UpdateInGroup(typeof(ClientSimulationSystemGroup))]
public class UnitsSelectionSystem : JobComponentSystem
{
    protected override JobHandle OnUpdate(JobHandle inputDeps)
    {
        var us = GetComponentDataFromEntity<UnitSelectionState>(true);
        var PostUpdateCommands = new EntityCommandBuffer(Allocator.Temp);

        Entities
             .WithEntityQueryOptions(EntityQueryOptions.IncludeDisabled)
             .ForEach((
          ref Entity entity,
          in Parent parent,
          in UnitSelectionVisual selectionVisual) =>
         {
             var sel = us[parent.Value].IsSelected;

             if (!sel)
             {
                 PostUpdateCommands.AddComponent<Disabled>(entity);
             }
             else
             {
                 PostUpdateCommands.RemoveComponent<Disabled>(entity);
             }
         }).Run();

        PostUpdateCommands.Playback(EntityManager);
        PostUpdateCommands.Dispose();

        return default;
    }
}
1 Like

2022.3.10f1 Is there any new way to write it?

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.

using Unity.Collections;
using Unity.Entities;
using Unity.Burst;

[BurstCompile]
public partial struct DisableSystem : ISystem
{
    public void OnUpdate(ref SystemState state)
    {
        // Query for requesting all entities, even disabled
        foreach (var piece in SystemAPI.Query<RefRO<PieceData>>().WithOptions(EntityQueryOptions.IncludeDisabledEntities))
        {
            // Do something with ALL entities
        }

        // Query for filter only enabled entities
        foreach (var piece in SystemAPI.Query<RefRO<PieceData>>())
        {
            // Do something with only enabled entities
        }

        // Job for disabling entity
        EntityCommandBuffer ecb = new(Allocator.TempJob);
        DisableJob job = new()
        {
            ecb = ecb,
        };

        job.Schedule();
        state.Dependency.Complete();

        ecb.Playback(state.EntityManager);
    }
}

public partial struct DisableJob : IJobEntity
{
    public EntityCommandBuffer ecb;

    public void Execute(ref DisableData disable)
    {
        ecb.AddComponent(disable.self, new Disabled() { });
    }
}

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

using Unity.Entities;

public struct DisableData : IComponentData
{
    public Entity self;
}

Is just a boilerplate code anyways, modify as you need. Here is how it works with a simple count variable inside the loops:
Screenshot

9669518--1377446--upload_2024-2-28_9-4-28.png

Edit: Using Allocator.Persistent and disposing it in OnUpdate is not a good idea. Corrected to Allocator.TempJob

9669518--1377446--upload_2024-2-28_9-4-28.png