Search Unity

Handling collisions

Discussion in 'Entity Component System' started by schaefsky, Aug 25, 2019.

  1. schaefsky

    schaefsky

    Joined:
    Aug 11, 2013
    Posts:
    90
    I spawn bullets that are set to be triggers and objects that can be hit (not set to trigger). When they hit something I want to handle this, so deal damage, create an effect etc. I am using an ITriggerEventsJob.
    So I guess I want to do three things:
    1. add a "destroy" tag to the bullet
    2. add a "damage" component to the hit object
    3. get the location where the object was hit to apply effects
    For 1 and 2 I guess I need to figure out which entity is which? How to do that?
    For 3 I have no idea how to get that information.

    Also I did some crude debugging code and it seems to me that I will get two "collsion events" per collision? Is that correct?
    Code (CSharp):
    1. using Unity.Collections;
    2. using Unity.Entities;
    3. using Unity.Jobs;
    4. using Unity.Physics;
    5. using UnityEngine;
    6. using Unity.Physics.Systems;
    7.  
    8. [UpdateInGroup(typeof(SimulationSystemGroup))]
    9. public class CollisionSystem : JobComponentSystem
    10. {
    11.  
    12.     //private EndSimulationEntityCommandBufferSystem _entityCommandBuffer;
    13.     private BuildPhysicsWorld _buildPhysicsWorldSystem;
    14.     private StepPhysicsWorld _stepPhysicsWorldSystem;
    15.     //private EndFramePhysicsSystem _endFramePhysicsSystem;
    16.  
    17.     protected override void OnCreate()
    18.     {
    19.         //_entityCommandBuffer = World.GetOrCreateSystem<EndSimulationEntityCommandBufferSystem>();
    20.         _buildPhysicsWorldSystem = World.GetOrCreateSystem<BuildPhysicsWorld>();
    21.         _stepPhysicsWorldSystem = World.GetOrCreateSystem<StepPhysicsWorld>();
    22.         //_endFramePhysicsSystem = World.GetOrCreateSystem<EndFramePhysicsSystem>();
    23.     }
    24.  
    25.     struct CollisionJob : ITriggerEventsJob
    26.     {
    27.         public NativeList<Entity> L;
    28.  
    29.         public void Execute(TriggerEvent triggerEvent)
    30.         {
    31.             L.Add(triggerEvent.Entities.EntityA);
    32.             L.Add(triggerEvent.Entities.EntityB);
    33.         }
    34.  
    35.     }
    36.  
    37.     protected override JobHandle OnUpdate(JobHandle inputDeps)
    38.     {
    39.         var L = new NativeList<Entity>(Allocator.TempJob);
    40.         var job = new CollisionJob
    41.         {
    42.             L = L
    43.         }.Schedule(_stepPhysicsWorldSystem.Simulation, ref _buildPhysicsWorldSystem.PhysicsWorld, inputDeps);
    44.  
    45.         job.Complete();
    46.         for (int i = 0; i < L.Length; i++)
    47.         {
    48.             Debug.Log(L[i].ToString());
    49.         }
    50.         L.Dispose();
    51.  
    52.         return job;
    53.     }
    54. }
    Which geives me something like this per shot:
    Entity(7:1)
    Entity(0:1)
    Entity(7:1)
    Entity(0:1)
     
  2. TRS6123

    TRS6123

    Joined:
    May 16, 2015
    Posts:
    246
    If you have a tag IComponentData called Bullet and another called Damageable, you can use ComponentDataFromEntity.Exists to check the trigger event is between a Bullet entity and a Damageable entity. Just declare the ComponentDataFromEntity fields in job and initialize them before the job is scheduled.
     
    Ryuuguu likes this.
  3. TRS6123

    TRS6123

    Joined:
    May 16, 2015
    Posts:
    246
    For 1: You probably don't even need to add a Destroy tag to the entity. You can just use EntityCommandBuffer.DestroyEntity to destroy the Bullet entity.
     
    Last edited: Aug 26, 2019
    schaefsky likes this.
  4. TRS6123

    TRS6123

    Joined:
    May 16, 2015
    Posts:
    246
    For 2: You can use the same EntityCommandBuffer to add the Damage component to the Damageable entity
     
    schaefsky likes this.
  5. TRS6123

    TRS6123

    Joined:
    May 16, 2015
    Posts:
    246
    For 3: You can use ComponentDataFromEntity<LocalToWorld> to get the world position of the bullet entity at the time of impact.
     
    schaefsky likes this.
  6. TRS6123

    TRS6123

    Joined:
    May 16, 2015
    Posts:
    246
    Alternately, if you use IContactsJob you can use contact.Position to get the contact point of the collision
     
    schaefsky likes this.
  7. schaefsky

    schaefsky

    Joined:
    Aug 11, 2013
    Posts:
    90
    I saw that approach in another context, but I wondered if it would not be inefficient, since there might be lots of Entities with these components but only a few that are involved in collisions per frame? Or is this the way to do it? Seems a little counter-intuitive, but then again DOTS is done a little different than OOP.

    Will try the IContactsJob.
     
  8. schaefsky

    schaefsky

    Joined:
    Aug 11, 2013
    Posts:
    90
    Okay, I’ve tried the approach with ComponentDataFromEntity. This seems to work ok, but the problem is that the code runs twice for every collision. If I destroy the bullet entity, this will give an error.
    Why does the ITriggerEventsJob run twice per collision with the exact same data (see end of first post)? Is that a bug or what am I doing wrong?
     
  9. TRS6123

    TRS6123

    Joined:
    May 16, 2015
    Posts:
    246
    I'm not encountering the same issue with ITriggerEventsJob running twice per event.
     
  10. Rory_Havok

    Rory_Havok

    Joined:
    Jun 25, 2018
    Posts:
    70
    Examine the other TriggerEvent properties - my bet is that the colliderKey's are different. Trigger and collision events are raised per leaf collider pair (i.e. per Jacobian), not per body pair. E.g. If a sphere collider collides with a mesh collider you will get one event per hit triangle. So you probably need some post-process merging/filtering if you only want them per body pair.
     
  11. schaefsky

    schaefsky

    Joined:
    Aug 11, 2013
    Posts:
    90
    The ColliderKey.Values seem to be the same?
    Code (CSharp):
    1. using Unity.Collections;
    2. using Unity.Entities;
    3. using Unity.Jobs;
    4. using Unity.Physics;
    5. using UnityEngine;
    6. using Unity.Physics.Systems;
    7. using Unity.Transforms;
    8.  
    9. [UpdateInGroup(typeof(SimulationSystemGroup))]
    10. public class CollisionSystem : JobComponentSystem
    11. {
    12.  
    13.     private EndSimulationEntityCommandBufferSystem _entityCommandBuffer;
    14.     private BuildPhysicsWorld _buildPhysicsWorldSystem;
    15.     private StepPhysicsWorld _stepPhysicsWorldSystem;
    16.     //private EndFramePhysicsSystem _endFramePhysicsSystem;
    17.  
    18.     protected override void OnCreate()
    19.     {
    20.         _entityCommandBuffer = World.GetOrCreateSystem<EndSimulationEntityCommandBufferSystem>();
    21.         _buildPhysicsWorldSystem = World.GetOrCreateSystem<BuildPhysicsWorld>();
    22.         _stepPhysicsWorldSystem = World.GetOrCreateSystem<StepPhysicsWorld>();
    23.         //_endFramePhysicsSystem = World.GetOrCreateSystem<EndFramePhysicsSystem>();
    24.     }
    25.  
    26.     struct CollisionJob : ITriggerEventsJob
    27.     {
    28.         public NativeList<Unity.Physics.ColliderKey> L;
    29.  
    30.         [ReadOnly] public ComponentDataFromEntity<LocalToWorld> LTW;
    31.         [ReadOnly] public ComponentDataFromEntity<BulletComponent> Bullets;
    32.         [ReadOnly] public ComponentDataFromEntity<HealthComponent> Healths;
    33.         public EntityCommandBuffer CommandBuffer;
    34.  
    35.         public void Execute(TriggerEvent triggerEvent)
    36.         {
    37.             Entity bullet = Entity.Null;
    38.             Entity health = Entity.Null;
    39.             if (Bullets.Exists(triggerEvent.Entities.EntityA))
    40.             {
    41.                 bullet = triggerEvent.Entities.EntityA;
    42.                 //health = triggerEvent.Entities.EntityB;
    43.             }
    44.             else if (Bullets.Exists(triggerEvent.Entities.EntityB))
    45.             {
    46.                 bullet = triggerEvent.Entities.EntityB;
    47.                 //health = triggerEvent.Entities.EntityA;
    48.             }
    49.  
    50.             if (Healths.Exists(triggerEvent.Entities.EntityA))
    51.             {
    52.                 health = triggerEvent.Entities.EntityA;
    53.             }
    54.             else if (Healths.Exists(triggerEvent.Entities.EntityB))
    55.             {
    56.                 health = triggerEvent.Entities.EntityB;
    57.             }
    58.  
    59.             if (health != Entity.Null)
    60.             {
    61.                 var h = Healths[health];
    62.                 h.health -= Bullets[bullet].damage;
    63.                 CommandBuffer.SetComponent(health, h);
    64.             }
    65.  
    66.             if (bullet != Entity.Null)
    67.             {
    68.                 //CommandBuffer.DestroyEntity(bullet);
    69.             }
    70.  
    71.             //L.Add(triggerEvent.Entities.EntityA.ToString());
    72.             //L.Add(triggerEvent.Entities.EntityB.ToString());
    73.             L.Add(triggerEvent.ColliderKeys.ColliderKeyA);
    74.             L.Add(triggerEvent.ColliderKeys.ColliderKeyB);
    75.  
    76.         }
    77.  
    78.     }
    79.  
    80.     protected override JobHandle OnUpdate(JobHandle inputDeps)
    81.     {
    82.         var LTW = GetComponentDataFromEntity<LocalToWorld>(isReadOnly: true);
    83.         var Bullets = GetComponentDataFromEntity<BulletComponent>(isReadOnly: true);
    84.         var Healths = GetComponentDataFromEntity<HealthComponent>(isReadOnly: true);
    85.  
    86.         var L = new NativeList<Unity.Physics.ColliderKey>(Allocator.TempJob);
    87.         var job = new CollisionJob
    88.         {
    89.             L = L,
    90.             LTW = LTW,
    91.             Bullets = Bullets,
    92.             Healths = Healths,
    93.             CommandBuffer = _entityCommandBuffer.CreateCommandBuffer()
    94.         }.Schedule(_stepPhysicsWorldSystem.Simulation, ref _buildPhysicsWorldSystem.PhysicsWorld, inputDeps);
    95.  
    96.         job.Complete();
    97.         for (int i = 0; i < L.Length; i++)
    98.         {
    99.             Debug.Log(L[i].Value.ToString());
    100.         }
    101.         L.Dispose();
    102.  
    103.  
    104.         return job;
    105.     }
    106. }
    gives:
    2147483647
    4294967295
    2147483647
    4294967295


    As for post-processing to merge, how would I go about that? Sorry, I am fairly new to Unity and game programming.
     
  12. tertle

    tertle

    Joined:
    Jan 25, 2011
    Posts:
    3,761
    You're not manually updating your system in fixedupdate or something are you?
     
  13. schaefsky

    schaefsky

    Joined:
    Aug 11, 2013
    Posts:
    90
    Oh, yes I am. The whole group
     
  14. tertle

    tertle

    Joined:
    Jan 25, 2011
    Posts:
    3,761
    So it's been auto updated in the loop and you're also calling to update it so it's being executed twice.

    You need to disable manual creation of system , create it yourself and update it yourself. Or take it out of the update loop
     
    Rory_Havok and schaefsky like this.
  15. schaefsky

    schaefsky

    Joined:
    Aug 11, 2013
    Posts:
    90
    Hmm, I am doing it like this though, should that not have done the trick?
    Code (CSharp):
    1. using System.Collections.Generic;
    2. using Unity.Entities;
    3. using UnityEngine;
    4. public class PhysicsRunner : MonoBehaviour
    5. {
    6.     private IEnumerable<ComponentSystemBase> simSystems;
    7.     void Start()
    8.     {
    9.         World.Active.GetOrCreateSystem<SimulationSystemGroup>().Enabled = false;
    10.         simSystems = World.Active.GetOrCreateSystem<SimulationSystemGroup>().Systems;
    11.     }
    12.     void FixedUpdate()
    13.     {
    14.         foreach (var sys in simSystems)
    15.         {
    16.             sys.Update();
    17.         }
    18.     }
    19. }
     
  16. tertle

    tertle

    Joined:
    Jan 25, 2011
    Posts:
    3,761
    I haven't actualy tried but from my knowledge of the code I doubt disabling a group will disable systems in it. If you're manually updating a system you probably just need to remove it from the default game loop.
     
    Last edited: Aug 29, 2019
  17. schaefsky

    schaefsky

    Joined:
    Aug 11, 2013
    Posts:
    90
    So now I did [DisableAutoCreation] on the system, World.Active.GetOrCreateSystem<CollisionSystem>() and manual Update() of the system in my PhysicsRunner.
    Still the same behaviour.

    Edit:
    I saw a reply that that would not do it, I guess it was deleted. Anyway, I tried not calling the Update() by hand (and had removed the UpdateInSystemGroup()) but had the PhysicsRunner still active, I disabled my PhysicsRunner completely, whatever I do I seem to get the same behavior
     
    Last edited: Aug 30, 2019
  18. schaefsky

    schaefsky

    Joined:
    Aug 11, 2013
    Posts:
    90
    So the TriggerEventJob not executes twice, it executes n-times depending on the angle and speed of the projectile (e.g. slower speed, runs more often). Sometimes it does not run at all when my bullet (cylinder) passes through the edge of my target (cube). In the debug window it clearly passes through the target with it's whole body though.

    So,
    1. How can I make it to only handle the trigger once to apply damage and destroy the bullet?
    2. Why does the TriggerEvent seems to be quite inaccurate when I shoot near the edge?
     
  19. lclemens

    lclemens

    Joined:
    Feb 15, 2020
    Posts:
    761
    Is it being called twice in the exact same frame, or is it more like once when it first collides, another in the next frame, another in the frame after that, etc?
     
  20. schaefsky

    schaefsky

    Joined:
    Aug 11, 2013
    Posts:
    90
    Sorry, this was two years ago. I have since abandoned the project and moved on / back to GameObject world with a new one.
    Given the SNAFU Dots is in currently I am glad I did, maybe I will come back in a year or two, if Dots is still around then.
    But thanks for your reply and best of luck to you.
     
  21. lclemens

    lclemens

    Joined:
    Feb 15, 2020
    Posts:
    761
    In case anyone is wondering... If the duplicates are happening in the same frame and the collider keys are different, it's probably the issue Rory Havok was referring to - the ITriggerEventsJob gets called once for each intersecting triangle and not for each collision body. This is unfortunate for two reasons. One is that a per-body trigger would be much more useful for common scenarios and it matches OnTriggerEnter() in OOP Unity. The other reason is that there are a bunch of examples online that use ITriggerEventsJob and they ignore the fact that it's getting called per-triangle instead of per collision body.

    I thought using DynamicBufferTriggerEventAuthoring.cs from the example physics project instead of using ITriggerEventsJob directly would filter out the duplicate results due to the same collision key, but it does not. It seems like everyone using unity physics triggers will have to write their own filtering functions. Comparing the old way of a couple of lines of code via OnTriggerEnter() to this new spaghetti boilerplate... @Rory_Havok Am I doing something wrong or is this really the intended approach for doing something as simple as finding out when a mesh collides with a trigger mesh?
     
    Lukas_Kastern likes this.