Search Unity

Parenting Enitities from a System script

Discussion in 'Entity Component System' started by SecondCobra, Mar 7, 2019.

  1. SecondCobra

    SecondCobra

    Joined:
    Jan 25, 2017
    Posts:
    31
    I've been modifying the
    EntityComponentSystemSamples (specifically the HelloSpawnACube scene) to learn ECS. I've got it generating a grid of cubes by adding additional parameters to the HelloSpawner and expanding the HelloSpawnerSystem to create the grid of cubes (entities).

    The spawn proxy:

    Code (CSharp):
    1. public struct HelloSpawner : ISharedComponentData
    2. {
    3.     public int spawnCountX;
    4.     public int spawnCountY;
    5.     public int spawnCountZ;
    6.  
    7.     public float separationDist;
    8.     public GameObject prefab;
    9.     public GameObject parent;
    10. }
    But I want to also place all of the created entities under a single parent entity so that the entire grid can also be rotated. I have a project I want to convert to ECS and this behaviour is essential.

    The way I'm attempting this is to have an additional prefab that is just an empty GameObject with GameObjectEntity, TranslationProxy (the new name for PositionProxy) etc, basically the same things the BasicCubeArchtype has. This is the parent prefab, the idea being that all cubes are parented to it and then it is rotated.
    Then in the spawner, I create a single copy of the parent prefab:

    Code (CSharp):
    1.  
    2.     // Convert the parent GameObject into an Entity
    3.     var parentEntity = EntityManager.Instantiate(spawner.parent);
    And in the loop where I create the grid, after creating each cube entity, I try to add the parent:

    Code (CSharp):
    1.  
    2.     EntityManager.AddComponent(entity, typeof(Parent));
    3.     EntityManager.SetComponentData<Parent>(entity, new Parent { Value = parentEntity });
    However here is the issue: when I try to add the Parent object, the cubes no longer appear when run. They are there according to the Entity Debugger since I can see all of their rotations changing, but they no longer render.
    I'm lost as to why this is.

    Note that I can't simply add a Parent component to the BasicCubeArchtype as it throws errors due to not having a parent set at that point!

    I'll add the whole spawn system OnUpdate code here in case that helps:

    Code (CSharp):
    1.  
    2. protected override void OnUpdate()
    3. {
    4.     DateTime start = DateTime.Now;
    5.     // Get all the spawners in the scene.
    6.     using (var spawners = m_Spawners.ToEntityArray(Allocator.TempJob))
    7.     {
    8.         // Need this to attach child objects to parent object
    9.         EntityManager entityManager = World.GetOrCreateManager<EntityManager>();
    10.  
    11.         foreach (var spawnerGroup in spawners)
    12.         {
    13.             // Create an entity from the prefab set on the spawner component.
    14.             var spawner = EntityManager.GetSharedComponentData<HelloSpawner>(spawnerGroup);
    15.             Translation spawnerPosition = EntityManager.GetComponentData<Translation>(spawnerGroup);
    16.  
    17.             var parentEntity = EntityManager.Instantiate(spawner.parent);
    18.  
    19.             var prefab = spawner.prefab;
    20.             var separation = spawner.separationDist;
    21.             float3 startingPos = new float3(
    22.                 -(separation * spawner.spawnCountX) / 2f,
    23.                 -(separation * spawner.spawnCountY) / 2f,
    24.                 -(separation * spawner.spawnCountZ) / 2f);
    25.  
    26.             var x = startingPos.x;
    27.             var y = startingPos.y;
    28.             var z = startingPos.z;
    29.  
    30.             for (var xIndex = 0; xIndex < spawner.spawnCountX; xIndex++)
    31.             {
    32.                 for (var yIndex = 0; yIndex < spawner.spawnCountY; yIndex++)
    33.                 {
    34.                     for (var zIndex = 0; zIndex < spawner.spawnCountZ; zIndex++)
    35.                     {
    36.                         var entity = EntityManager.Instantiate(prefab);
    37.  
    38.                         // Copy the position of the spawner to the new entity.
    39.                         Translation position = EntityManager.GetComponentData<Translation>(spawnerGroup);
    40.                         Translation adjPosition = position;
    41.                         adjPosition.Value = new float3(x, y, z);
    42.  
    43.                         EntityManager.AddComponent(entity, typeof(Parent));
    44.                         EntityManager.SetComponentData<Parent>(entity, new Parent { Value = parentEntity });
    45.  
    46.                         EntityManager.SetComponentData(entity, adjPosition);
    47.                         z += separation;
    48.                     }
    49.  
    50.                     z = startingPos.z;
    51.                     y += separation;
    52.                 }
    53.  
    54.                 z = startingPos.z;
    55.                 y = startingPos.y;
    56.                 x += separation;
    57.             }
    58.  
    59.             // Destroy the spawner so this system only runs once.
    60.             EntityManager.DestroyEntity(spawnerGroup);
    61.         }
    62.     }
    63.  
    64.     Debug.Log("Cube creation time: " + (DateTime.Now - start).TotalMilliseconds.ToString() + "\n");
    65. }
     
  2. Joachim_Ante

    Joachim_Ante

    Unity Technologies

    Joined:
    Mar 16, 2005
    Posts:
    5,203
    You need to also add a LocalToWorld component.
     
  3. Joachim_Ante

    Joachim_Ante

    Unity Technologies

    Joined:
    Mar 16, 2005
    Posts:
    5,203
    We currently don't have the concept of dependent components that automatically get added at runtime. Agree that this combined with no warnings / debug tooling for is not good UX.
     
    FROS7 and NotaNaN like this.
  4. SecondCobra

    SecondCobra

    Joined:
    Jan 25, 2017
    Posts:
    31
    Thanks for the swift reply. I added a LocalToWorld to the BasicCubeArchtype and to the parent archetype but still get the same issue. Should have said that what I'm getting is either one cube drawn or they are being drawn at one point rather than in a grid - I think the latter since I am seeing all entities in the Debugger and their rotations are changing, however the single cube I can see is not rotating.

    Could we get the samples project updated to show a parent child example?
    The performance boost when working (just the grid of cubes rotating) is huge, with GameObjects: 60-70 FPS for 27000 cubes, with ECS: 250 FPS, so I really want to understand how this works.

    The archetypes now look like this:

    BasicCubeArchetype:
    basic_cube_archetype.PNG

    Parent Archetype
    parent_archetype.PNG
     

    Attached Files:

  5. Joachim_Ante

    Joachim_Ante

    Unity Technologies

    Joined:
    Mar 16, 2005
    Posts:
    5,203
  6. Joachim_Ante

    Joachim_Ante

    Unity Technologies

    Joined:
    Mar 16, 2005
    Posts:
    5,203
    In fact the sample has a hierarchy. All Hello cube have two parented renderers.
    There is also the spawner sample creating 10.000 of them by instantiating a prefab containing parented renderers.
     
  7. SecondCobra

    SecondCobra

    Joined:
    Jan 25, 2017
    Posts:
    31
    I'm adding to this as I still do not understand how to parent one entity to another from script. Let me be clear here, I am not, as the samples do, simply recreating a GameObject hierarchy that has been made in the editor.

    In my very basic sample project I'm trying to make, I have the same setup as in the HelloCube_01_ForEach example, i.e. a parent GO with a child GO under it. On the parent I have the RotationRateProxy (and the component and system behind it), but I also have a ChildAllocationProxy, which takes a GameObject (prefab) parameter. The proxy looks like this:

    Code (CSharp):
    1. public class ChildAllocationProxy : MonoBehaviour, IConvertGameObjectToEntity
    2. {
    3.     public GameObject childPrefab;
    4.  
    5.     public void Convert(Entity entity, EntityManager dstManager, GameObjectConversionSystem conversionSystem)
    6.     {
    7.         // Get the child as an entity
    8.         var childEntity = conversionSystem.GetPrimaryEntity(childPrefab);
    9.  
    10.         var parentComponent = new Parent
    11.         {
    12.             Value = entity
    13.         };
    14.  
    15.         dstManager.AddComponentData(childEntity, parentComponent);
    16.     }
    17. }
    So it is supposed to create an entity based on the prefab (just a cylinder GO with the Convert to Entity script added) and add the Parent component, with the parent being Entity. What I get in the Entity Debugger is this: upload_2019-3-21_21-25-51.png
    I.e. It has created an empty entity. The parent index is correct but nothing else has been created from the prefab. Why is this? I'm clearly not doing this the correct way but there seems to be zero documentation, forum posts or anything on creating parent child relationships programmatically.
     
  8. M_R

    M_R

    Joined:
    Apr 15, 2015
    Posts:
    559
    you need to add
    IDeclareReferencedPrefabs
    . one of the spawner samples show that (05 or 06)
     
  9. joseph-t83

    joseph-t83

    Joined:
    Mar 28, 2014
    Posts:
    22
    I think you will also have to add a LocalToParent component for it to get picked up by the parenting system.
     
    lclemens and GilCat like this.
  10. SecondCobra

    SecondCobra

    Joined:
    Jan 25, 2017
    Posts:
    31
    M_R: Adding IDeclareReferencedPrefabs does fill in the components, however the cylinder does not appear (in editor or game window).

    joseph-t83: Adding LocalToParent did not change anything, although this may be because it's not displaying. I'm guessing this allows it to get moved with its parent - so it may be working, it just doesn't show.

    Code (CSharp):
    1. using System.Collections.Generic;
    2. using Unity.Entities;
    3. using Unity.Transforms;
    4. using UnityEngine;
    5.  
    6. public class ChildAllocationProxy : MonoBehaviour, IDeclareReferencedPrefabs, IConvertGameObjectToEntity
    7. {
    8.     public GameObject childPrefab;
    9.  
    10.     public void DeclareReferencedPrefabs(List<GameObject> gameObjects)
    11.     {
    12.         gameObjects.Add(childPrefab);
    13.     }
    14.  
    15.     public void Convert(Entity entity, EntityManager dstManager, GameObjectConversionSystem conversionSystem)
    16.     {
    17.         // Get the child as an entity
    18.         var childEntity = conversionSystem.GetPrimaryEntity(childPrefab);
    19.  
    20.         var parentComponent = new Parent
    21.         {
    22.             Value = entity
    23.         };
    24.  
    25.         LocalToParent lp = new LocalToParent();
    26.  
    27.         dstManager.AddComponentData(childEntity, parentComponent);
    28.         dstManager.AddComponentData(childEntity, lp);
    29.     }
    30. }
    31.  
    Added child entity:
    upload_2019-3-22_8-5-42.png
     
  11. joseph-t83

    joseph-t83

    Joined:
    Mar 28, 2014
    Posts:
    22
    You still need to instantiate the prefab.
     
  12. lclemens

    lclemens

    Joined:
    Feb 15, 2020
    Posts:
    761
    When the user clicks one of the moving entities in the scene, I want an "icon" to attach to it and follow it around. I thought that I could simply reparent it. So I made an "adopt" function that goes something like:

    1) If the child currently has a parent, then the old parent removes the child from its list of children.
    2) Set the child/'s parent to the new parent
    3) Add the child to the new parent's list of children

    In this case, the parent is "the thing the player clicked", and the "child" is the icon that needs to follow the object. That function I wrote doesn't work FYI.

    I did not realize that the parent system could take care of steps 1 and 3 for me (handle adding/removing from child lists). HOWEVER, that ONLY works if the child has a LocalToParent component. So thanks joseph-t83... adding LocalToParent really simplified my code.

    With GameObject this is so easy... just a single line call - child.transform.SetParent(parent), but the ECS equivalent is not nearly as obvious.
     
  13. eizenhorn

    eizenhorn

    Joined:
    Oct 17, 2016
    Posts:
    2,685
    Looking at your components - you missed RenderBounds. And don't forget to set it properly and just by default.
     
    lclemens likes this.
  14. lclemens

    lclemens

    Joined:
    Feb 15, 2020
    Posts:
    761
    Your symptoms do seem to fit what eizenhorn is saying... your entities are there, but they're not rendering. I bet he's right.