Search Unity

Entity.transform.forward equivalent to set entity velocity direction equal to that of a parent

Discussion in 'Entity Component System' started by MadboyJames, Aug 7, 2019.

  1. MadboyJames

    MadboyJames

    Joined:
    Oct 28, 2017
    Posts:
    262
    I'm trying to create a projectile spawning entity (a cannon), which shoots a projectile entity in the direction the cannon is facing. I can set the projectile entity at the target position, using the translate component, and I can set it's rotation equal to that of the cannon using the rotation component. I am struggling to set the projectile's velocity equal to that of the parent, seeing as the physics body component uses global values to move an entity, rather than local values.

    Code (CSharp):
    1. Entities.ForEach((ref SpawnShotComponent spawnShot, ref Translation translation, ref Rotation rotation) =>
    2.         {
    3.          
    4.  
    5.                 Entity newProjectile= entityManager.Instantiate(spawnShot.bulletPrefab);
    6.                
    7.  
    8.                 entityManager.SetComponentData(newProjectile, new Translation {Value = translation.Value });
    9.                 entityManager.SetComponentData(newProjectile, new Rotation { Value = rotation.Value });
    10.  
    11.                 float3 heading = spawnShot.forwardDirection;
    12.  
    13.                 //I'm not sure how to multiply the heading by the rotation to get the "real" heading.
    14.  
    15.                
    16.                 entityManager.SetComponentData(newProjectile, new PhysicsVelocity {Linear= heading });
    17.            
    18.  
    19.         });
    Currently, if heading is equal to (0,1,0), the projectile will always move up the global Y axis by 1 meter per second, regardless if the projectile is facing up with a rot of (0,0,0), or if the projectile is facing sideways with a rot of (90,0,0).
     
  2. MadboyJames

    MadboyJames

    Joined:
    Oct 28, 2017
    Posts:
    262
    On another local to global note, If one entity is the child of another entity (lets say the cannons are on a ship), how would I set the instantiation translation to be the cannons global transform, rather than it's local?
     
  3. desertGhost_

    desertGhost_

    Joined:
    Apr 12, 2018
    Posts:
    260
    To get the world space forward vector of any given entity you simply need to multiply the rotation value by your forward vector.

    Something like this (untested) should be what you are looking for

    heading = math.mul(rotation.Value, spawnShot.forwardDirection);
     
  4. MadboyJames

    MadboyJames

    Joined:
    Oct 28, 2017
    Posts:
    262
    hrm. It does not appear to be having any effect. I'll play around with that a bit. I was unsure of what math.mul was. Seems obvious now.
     
  5. MadboyJames

    MadboyJames

    Joined:
    Oct 28, 2017
    Posts:
    262
    Oh I know why. It probably works, but I'm not getting the values from rotation of the root entity. So i'll need to figure that out first.
     
  6. MadboyJames

    MadboyJames

    Joined:
    Oct 28, 2017
    Posts:
    262
    Got it to work! Although it seems that having a "rootEntityComponent" storing whatever the parent entity is, then calling that entity's translation and rotation is not very efficient. 800 of these ship+ cannon(x2) entities (so 800 ships, 1600 cannons, 2400 entities total. Only the 1600 cannons call the script) took 1560ms to complete. It looks like it did not break it into a job though. I thought it would do that automatically, but I'll have to do that. Assuming that I can break it into 4 threads, then it'll take 390ms for 1600 shots to be spawned in a frame. and that'd be even less with Burst. It still doesn't seem particularly efficient.
     
    Last edited: Aug 7, 2019
    desertGhost_ likes this.
  7. MadboyJames

    MadboyJames

    Joined:
    Oct 28, 2017
    Posts:
    262
    And here is the code I used to get the parent's components:
    Code (CSharp):
    1. //getting the new values of the root's translation and rotation. This may be very ineffecient.
    2.                 Translation rootTranslation = entityManager.GetComponentData<Translation>(rootTransform.root);
    3.                 Rotation rootRotation = entityManager.GetComponentData<Rotation>(rootTransform.root);
    Code (CSharp):
    1. public struct RootTransformComponent : IComponentData
    2. {
    3.     public Entity root;
    4. }
    Code (CSharp):
    1. public class RootTransformComponentProxy : MonoBehaviour, IConvertGameObjectToEntity
    2. {
    3.     public GameObject targetParent; //set manually in the inspector. I could use gameobject.Transform.root, but I went with this just in case we don't want the true root
    4.     public void Convert(Entity entity, EntityManager dstManager, GameObjectConversionSystem conversionSystem)
    5.     {
    6.  
    7.         var data = new RootTransformComponent
    8.         {
    9.             root= conversionSystem.GetPrimaryEntity(targetParent),
    10.         };
    11.         dstManager.AddComponentData(entity, data);
    12.     }
    13. }
     
  8. desertGhost_

    desertGhost_

    Joined:
    Apr 12, 2018
    Posts:
    260
    Mike Geig gave a really good talk at GDC about a really similar scenario. Here's the link.

    As far as I'm aware you have to explicitly write a system as a JobComponentSystem for it to take advantage of jobs / multi-threading.

    Jobifying it should definitely speed it up, but getting components does take some time.

    My project requires animation / traditional physics, so I link a pure entity to a GameObject entity. The pure entity handles all logic and holds all significant components. The results of any math / animator parameters are stored on this entity and passed to the GameObject entity in as few systems as possible at the end of the simulation.

    This kind of setup has had good performance for me; I can move 128 animated characters around with full collision handling at ~60 frames per second in the editor on a Microsoft Surface Pro 6 (Intel Core i5-8250u). The burst compiled jobs happen in less than 0.2 ms each, the animator system takes about 1.3 ms, and physics handling system takes about 3 ms with 128 characters (which I could definitely streamline if I jobified collision casting).

    If possible it would be faster to query the parent (the entity with the rotation that you are using) and to apply a relative offset to the spawn position of the projectile.

    This component could be used to determine where the firing point is relative to the parent:

    Code (CSharp):
    1. [System.Serializable]
    2. public struct FireShotComponent : IComponentData
    3. {
    4.     public float3 spawnOffset;  
    5. }
    All of that being said, if you use something like a EndSimulationEntityCommandBuffer with a JobSystemComponent, I think the entities will still be instantiated on the main thread (at the end of the simulation each frame). They will just be instantiated in a way that does not create unnecessary sync points.
     
  9. MadboyJames

    MadboyJames

    Joined:
    Oct 28, 2017
    Posts:
    262
    Okay, good video. Is there an easy way to link a pure entity to a pure entity? The issue I am coming across is that there will be a couple hundred ships active at once. To clarify the reason for this, I'm trying to build a proof-of-concept spaceship RTS. There will be bigger ships, which the player(s) may only have 50 to 100 of, but there is a "fighter" class of ship which are small, weak, and plentiful. lots of big ships will have fighter bays where they passively deploy these ships in squads of 10. That's the end goal. So I'm trying to figure out how to make the "fighters" themselves be entities, due to the fact that 100 ships, each deploying 2 squads of fighters, each squad containing 10 fighters (100*10*2) would be 2000 fighters. I can make bigger ships gameobjects, but I think the fighters should be entities. And I would want the fighters to have child objects "Cannons" that take the cannon's real world position to spawn bullets. I could make the fighters just spawn bullets at the offset, but bigger ships will have rotating guns, each which should be able to automatically swivel and independently target (think Star Destroyer). That will be hard to reuse the fighter ship solution of "fire at offset" called by the root object. Essentially I'm trying to get the cannons on the fighter to fire at their own global position (currently by getting the ship trans- local gun trans) without using gameobjects because I hope to scale that to have lots of complex guns on larger ships, each gun as their own entity, adhering to the same system.

    On the topic of jobifying, how would I implement a EndSimulationEntityCommandBuffer? I am very new to jobbing.
     
  10. MadboyJames

    MadboyJames

    Joined:
    Oct 28, 2017
    Posts:
    262
    in the video, a bunch of entities were instantiated per frame from the same gameobject. How would I apply that use of native array to many different entities spawning entities? I'm still wrapping my head around DOTS thinking patterns, so I apologize if I ask slow questions.
     
  11. MadboyJames

    MadboyJames

    Joined:
    Oct 28, 2017
    Posts:
    262
    for reference, here is my spawn shot system, without jobbing. I attempted to job it, but I cannot instantiate objects/entities in a job, so... I'm a bit confused on how I would break apart the calculations, and the spawning. I guess i'd need the jobs to return a position and rotation to apply to the newly spawned entity... but I'm not sure jobs work like methods in that way. At least I'm not sure how to make that happen.
    anyways, code.
    Code (CSharp):
    1. public class SpawnShotSystem : ComponentSystem
    2. {
    3.     private EntityManager entityManager;
    4.     private EntityArchetype projectileArchtype;
    5.     private void Start()
    6.     {
    7.       entityManager = World.Active.EntityManager; //gets the EntityManager running in the project
    8.     }
    9.     protected override void OnUpdate()
    10.     {
    11.         if(entityManager!= World.Active.EntityManager)
    12.         {
    13.             entityManager = World.Active.EntityManager;
    14.         }
    15.  
    16.  
    17.  
    18.         Entities.ForEach((ref SpawnShotComponent spawnShot, ref Translation translation, ref RootTransformComponent rootTransform, ref Rotation rotation) =>
    19.         {
    20.          
    21.             if (spawnShot.timeSinceLastShot < Time.time)
    22.             {
    23.                 spawnShot.timeSinceLastShot = Time.time + ((Time.deltaTime*60) / spawnShot.rateOfFire);
    24.                 Entity newProjectile= entityManager.Instantiate(spawnShot.bulletPrefab);
    25.  
    26.                 //getting the new values of the root's translation and rotation. This may be very ineffecient.
    27.                 Translation rootTranslation = entityManager.GetComponentData<Translation>(rootTransform.root);
    28.                 Rotation rootRotation = entityManager.GetComponentData<Rotation>(rootTransform.root);
    29.  
    30.                 //getting the rotation of the root+spawner
    31.                 Quaternion temp = rootRotation.Value;
    32.                 float3 tempEuler = temp.eulerAngles;
    33.                 temp = rotation.Value;
    34.                 float3 tempEulerTwo = temp.eulerAngles;
    35.                 temp.eulerAngles = (tempEuler - tempEulerTwo);
    36.  
    37.                 entityManager.SetComponentData(newProjectile, new Translation {Value = rootTranslation.Value- translation.Value});
    38.                 entityManager.SetComponentData(newProjectile, new Rotation { Value = temp });
    39.  
    40.                 float3 heading = math.mul(rootRotation.Value, spawnShot.forwardDirection);
    41.              
    42.                 entityManager.SetComponentData(newProjectile, new PhysicsVelocity {Linear= heading });
    43.  
    44.             }
    45.            
    46.  
    47.         });
    48.  
    49.     }
    50.  
    51. }
     
  12. desertGhost_

    desertGhost_

    Joined:
    Apr 12, 2018
    Posts:
    260
    Here is how I do it for a pure entity linked to a hybrid:
    Code (CSharp):
    1. public class EntityGameObjectLinkAuthoringComponent : MonoBehaviour, IConvertGameObjectToEntity
    2.     {
    3.         [Tooltip("The gameobject hybrid entity to track.")]
    4.         public GameObjectEntity linkedGameObject = null;
    5.  
    6.         public void Convert(Entity entity, EntityManager dstManager, GameObjectConversionSystem conversionSystem)
    7.         {
    8.             dstManager.AddComponentData(entity, new EntityLink { linkedEntity = linkedGameObject.Entity });
    9.  
    10.             dstManager.AddComponentData(linkedGameObject.Entity, new EntityLink { linkedEntity = entity });
    11.         }
    12.     }
    13.  
    14.     public struct EntityLink : IComponentData
    15.     {
    16.         public Entity linkedEntity;
    17.     }
    For a pure entity to pure entity link:

    Maybe create a component that contains an integer identifier that two entities share and a system that connects them before deleting that component on both?


    This is untested, but this should point you in the right direction:

    Code (CSharp):
    1. public class SpawnShotJobComponentSystem : JobComponentSystem
    2. {
    3.  
    4.     struct SpawnShotJob : IJobForEachWithEntity<SpawnShotComponent, Translation, Rotation, RootTransformComponent>
    5.     {
    6.         public EntityCommandBuffer.Concurrent ecb;
    7.  
    8.         [ReadOnly]
    9.         public ComponentDataFromEntity<Translation> translationLookup;
    10.  
    11.         [ReadOnly]
    12.         public ComponentDataFromEntity<Rotation> rotationLookup;
    13.  
    14.         public float time;
    15.  
    16.         public float deltaTime;
    17.  
    18.         public void Execute(Entity entity, int index, [ReadOnly] ref SpawnShotComponent spawnShot, [ReadOnly] ref Translation translation, [ReadOnly] ref Rotation rotation, [ReadOnly] ref RootTransformComponent rootTransform)
    19.         {
    20.             if (spawnShot.timeSinceLastShot < time)
    21.             {
    22.                 spawnShot.timeSinceLastShot = time + ((deltaTime * 60) / spawnShot.rateOfFire);
    23.                 var newProjectile = ecb.Instantiate(index, spawnShot.bulletPrefab);
    24.  
    25.                 //getting the new values of the root's translation and rotation.
    26.                 Translation rootTranslation = translationLookup[rootTransform.root];
    27.                 Rotation rootRotation = rotationLookup[rootTransform.root];
    28.  
    29.                 //getting the rotation of the root+spawner
    30.                 quaternion temp = rootRotation.Value;
    31.                 float3 tempEuler = GetEuler(ref temp);
    32.                 temp = rotation.Value;
    33.                 float3 tempEulerTwo = GetEuler(ref temp);
    34.                 temp = quaternion.EulerXYZ(tempEuler - tempEulerTwo);
    35.  
    36.                 ecb.SetComponent(index, newProjectile, new Translation { Value = rootTranslation.Value - translation.Value });
    37.                 ecb.SetComponent(index, newProjectile, new Rotation { Value = temp });
    38.  
    39.                 float3 heading = math.mul(rootRotation.Value, spawnShot.forwardDirection);
    40.  
    41.                 ecb.SetComponent(index, newProjectile, new PhysicsVelocity { Linear = heading });
    42.             }
    43.         }
    44.  
    45.         public float3 GetEuler(ref quaternion quaternion)
    46.         {
    47.             //algorithm used from https://en.wikipedia.org/wiki/Conversion_between_quaternions_and_Euler_angles
    48.             float4 q = quaternion.value;
    49.             double3 eulerAngles;
    50.  
    51.             double sinr_cosp = 2.0 * (q.w * q.x + q.y * q.z);
    52.             double cosr_cosp = 1.0 - 2.0 * (q.x * q.x + q.y * q.y);
    53.             eulerAngles.x = math.atan2(sinr_cosp, cosr_cosp);
    54.  
    55.             double sinp = 2.0 * (q.w * q.y - q.z * q.x);
    56.  
    57.             if (math.abs(sinp) >= 1)
    58.             {
    59.                 eulerAngles.y = (math.PI / 2) * math.sign(sinp);
    60.             }
    61.             else
    62.             {
    63.                 eulerAngles.y = math.asin(sinp);
    64.             }
    65.  
    66.             double siny_cosp = 2.0 * (q.w * q.z + q.x * q.y);
    67.             double cosy_cosp = 1.0 - 2.0 * (q.y * q.y + q.z * q.z);
    68.             eulerAngles.z = math.atan2(siny_cosp, cosy_cosp);
    69.  
    70.             return (float3)eulerAngles;
    71.         }
    72.     }
    73.  
    74.     EndSimulationEntityCommandBufferSystem endSimulationBuffer;
    75.  
    76.     protected override void OnCreate()
    77.     {
    78.         base.OnCreate();
    79.  
    80.         endSimulationBuffer = World.Active.GetOrCreateSystem<EndSimulationEntityCommandBufferSystem>();
    81.     }
    82.  
    83.     protected override JobHandle OnUpdate(JobHandle inputDeps)
    84.     {
    85.         var job = new SpawnShotJob
    86.         {
    87.             ecb = endSimulationBuffer.CreateCommandBuffer().ToConcurrent(),
    88.             translationLookup = GetComponentDataFromEntity<Translation>(true),
    89.             rotationLookup = GetComponentDataFromEntity<Rotation>(true),
    90.             time = UnityEngine.Time.time,
    91.             deltaTime = UnityEngine.Time.deltaTime
    92.         };
    93.  
    94.         var handle =  job.Schedule(this, inputDeps);
    95.  
    96.         endSimulationBuffer.AddJobHandleForProducer(handle);
    97.  
    98.         return handle;
    99.     }
    100. }
    This system should process any entity that has the SpawnShotComponent, Translation, Rotation, RootTransformComponent.
     
    Last edited: Aug 8, 2019
  13. MadboyJames

    MadboyJames

    Joined:
    Oct 28, 2017
    Posts:
    262
    Alrighty, I'll definitely start playing around with that. Thank you so much for your help so far! I'll post my findings when I figure this out. In the meantime I've been using LocalToWorld to just get the global position without needing a root object reference. I can get the position, but I'm still working on getting the rotation.