Search Unity

  1. Welcome to the Unity Forums! Please take the time to read our Code of Conduct to familiarize yourself with the forum rules and how to post constructively.
  2. We have updated the language to the Editor Terms based on feedback from our employees and community. Learn more.
    Dismiss Notice

Resolved Unity ECS Spawn entity from Prefab in a MonoBehaviour and keep a reference to it

Discussion in 'Entity Component System' started by OMGeeky, Jan 3, 2022.

  1. OMGeeky

    OMGeeky

    Joined:
    Sep 5, 2018
    Posts:
    17
    I have a GameObject that has a spawner MonoBehaviour attached. from this I want to instantiate a Prefab as an Entity (multiple times) and save a list of spawned entities.

    My prefab currently has a ConvertToEntity component attached and is turning itself into an Entity.

    The Entity will Destroy itself at some point and then i want the Spawner MB to spawn another the same way as before.

    My problem is how i could get a reference to the spawned entity. The only way I found online to get a reference was using the GameObjectConversionUtility which doesn't work anymore (or I dont have the right packages installed idk, but I think it was removed).



    I also tried converting the spawner to be an Entity and a System that is spawning stuff. This way I already have an entity after I input a GameObject into the AuthoringComponent of the spawner, the problem here is how I would keep a list/count of Entities that have been spawned and let the spawner respawn if not enough are spawned.
    There would be a way with the entity Query but I cannot put Custom Tags on each different prefab to identify it (because im generating the prefabs from some other data and would have to create the code of each tag with code), so I don't really know how to approach this.
     
  2. DreamingImLatios

    DreamingImLatios

    Joined:
    Jun 3, 2017
    Posts:
    3,993
    For a large amount of entities per spawner, you can use shared components. You can create a shared component that has an entity field with the entity being the spawner, and then attach that shared component to every spawned entity generated from that spawner at runtime.

    Otherwise, attach a dynamic buffer to each spawner, and store the spawned entities in that buffer.
     
  3. OMGeeky

    OMGeeky

    Joined:
    Sep 5, 2018
    Posts:
    17
    Its mostly just 2-5 per Spawner in rare cases up to 30 but I don't think more. Should I use the Shared component or work with the buffer in this case? and if I work with the buffer, how would I go about removing an entity from the buffer if it is destroyed? Don't I need a reference to the spawner on every spawned entity to find it and then remove itself from it? or can the spawner remove destroyed entities easily?
     
  4. DreamingImLatios

    DreamingImLatios

    Joined:
    Jun 3, 2017
    Posts:
    3,993
    No. Use dynamic buffer for this. The shared component is for cases where there is at least 200 entities per spawner.

    You could do this reactively by storing a reference to the spawner, or you could just iterate through the buffers and check if each entity exists. Unfortunately checking if an entity exists in a job is harder than it should be because Unity hasn't exposed proper API for it yet. The workaround is to ensure each entity has some tag component, and to use ComponentDataFromEntity<>.HasComponent() which will return false if the entity does not exist. You can add the tag component in the spawner authoring's conversion logic.
     
    MaNaRz likes this.
  5. MaNaRz

    MaNaRz

    Joined:
    Aug 24, 2017
    Posts:
    117
    For the reactive Solution you should look into ISystemStateComponent. That's the place where you should store the reference to the Spawner. Your Spawner System is then able to pick up the destruction of an entity and can see which Spawner should spawn a new one.
     
  6. OMGeeky

    OMGeeky

    Joined:
    Sep 5, 2018
    Posts:
    17
    Ok, Thanks for all the Help.
    Is there something that works in a ScheduleParallel foreach like ComponentDataFromEntity<>.HasComponent() ? Also wouldnt the EntityManager.Exists method work for checking if it exists, that seems like the whole point of it?
     
  7. DreamingImLatios

    DreamingImLatios

    Joined:
    Jun 3, 2017
    Posts:
    3,993
    HasComponent does work in a parallel foreach. The EntityManager.Exists would work, and it is possible to make work in a parallel job using ExclusiveEntityTransaction, but it is likely more effort to make work and has some other JobHandle pitfalls.
     
  8. OMGeeky

    OMGeeky

    Joined:
    Sep 5, 2018
    Posts:
    17
    I found my error there, I had to use it ReadOnly to work:
    GetComponentDataFromEntity<>(isReadOnly:true) 


    now Im getting another error.
    Code (csharp):
    1. System.ArgumentException: All entities created using EntityCommandBuffer.CreateEntity must be realized via playback(). One of the entities is still deferred (Index: {0}).
    2. This Exception was thrown from a job compiled with Burst, which has limited exception support.
    Its probably since the entity just got created or deleted and isnt ready?
    Can I just check if the index of the entity == -1 and if it does skip it or are there other problems attached to doing it that way?
     
  9. DreamingImLatios

    DreamingImLatios

    Joined:
    Jun 3, 2017
    Posts:
    3,993
    Use EntityCommandBuffer to append the buffer element containing the newly generated entity. That will allow the buffer element to be remapped correctly.
     
  10. OMGeeky

    OMGeeky

    Joined:
    Sep 5, 2018
    Posts:
    17
    How would I do that? I thought I did, but i probably don't quite understand the commandBuffer and how it works.

    Here is my code trying to spawn an entity every time there are not enough entities spawned and keeping a list of spawned entities:
    Code (CSharp):
    1. protected override void OnUpdate()
    2.         {
    3.             var entityCommandBuffer = endSimulationEcbSystem.CreateCommandBuffer().AsParallelWriter();
    4.             Entities.ForEach( ( Entity entity , int entityInQueryIndex ,
    5.                 DynamicBuffer<EntityBuffer> entityBuffer ,
    6.                 ref NPCSpawnerComponent npcSpawnerComponent ) =>
    7.             {
    8.                 for ( int i = 0; i < entityBuffer.Length; i++ )
    9.                 {
    10.                     Entity spawnedEntity = entityBuffer[i].entity;
    11.                     if ( !GetComponentDataFromEntity<SpawnedEntityComponent>(isReadOnly:true).HasComponent( spawnedEntity ) )
    12.                     {
    13.                         entityBuffer.RemoveAt( i );
    14.                     }
    15.                      
    16.                 }
    17.                 if ( entityBuffer.Length < npcSpawnerComponent.amount )
    18.                 {
    19.                     var spawnedEntity = entityCommandBuffer.Instantiate( entityInQueryIndex , npcSpawnerComponent.prefab );
    20.                     entityCommandBuffer.AddComponent<SpawnedEntityComponent>( entityInQueryIndex , spawnedEntity );
    21.                     entityCommandBuffer.SetComponent( entityInQueryIndex , spawnedEntity , new SpawnedEntityComponent() { test = 2 } );
    22.                     entityBuffer.Add( new EntityBuffer() { entity = spawnedEntity } );
    23.                 }
    24.             } ).ScheduleParallel();
    25.          
    26.             endSimulationEcbSystem.AddJobHandleForProducer( this.Dependency );
    27.         }
    Do I have to use the command buffer differently or am I missing something? I basically took how I did it from the sample in the documentation
     
  11. DreamingImLatios

    DreamingImLatios

    Joined:
    Jun 3, 2017
    Posts:
    3,993
    Code (CSharp):
    1. entityCommandBuffer.AppendToBuffer(entity, new EntityBuffer() { entity = spawnedEntity });
     
  12. OMGeeky

    OMGeeky

    Joined:
    Sep 5, 2018
    Posts:
    17
    Thanks that did it. (and I understand the system a bit better now I think) :)
     
  13. OMGeeky

    OMGeeky

    Joined:
    Sep 5, 2018
    Posts:
    17
    Is my assumption right that I was trying to add an entity to the buffer that wasn't created yet, because it was still just in the buffer that had not run?
     
  14. DreamingImLatios

    DreamingImLatios

    Joined:
    Jun 3, 2017
    Posts:
    3,993
    Kinda, but maybe not quite.

    Even if you waited to for ECB playback before reading the dynamic buffer, the entity handle would still be invalid. ECB can only rewrite invalid handles to be valid which are stored within the ECB itself. AppendToBuffer stores the EntityBuffer struct in the ECB so the ECB can rewrite the entity handle during playback.
     
  15. OMGeeky

    OMGeeky

    Joined:
    Sep 5, 2018
    Posts:
    17
    Ok, Thanks a lot for your help and explanations. Helped a lot.
    Have a great Day :)