Search Unity

Apply Damage on Collision

Discussion in 'Data Oriented Technology Stack' started by MadboyJames, Sep 7, 2019.

  1. MadboyJames

    MadboyJames

    Joined:
    Oct 28, 2017
    Posts:
    132
    Right now I have a system which successfully detects when EntityA (projectile) hits EntityB (target). I am struggling to make it so the system finds if entity B has a Health Component, and applies 10 damage (currently hardcoded, I'll make it so EntityA has a "damage" component). I was trying to use a Buffer From Entity to make it work, But I'm hoping there is a better solution, because this one (posted below) does not really work, and it is super slow (because it is not burst compatible).
    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. using Unity.Burst;
    9.  
    10. [UpdateBefore(typeof(ShotDestroyAtRangeSystem))]
    11. public class DestroyOnContactWithDamageableSystem : JobComponentSystem
    12. {
    13.     private EndSimulationEntityCommandBufferSystem endSimulationBufferSystem;
    14.     private BuildPhysicsWorld physicsWorld;
    15.  
    16.  
    17.     protected override void OnCreate()
    18.     {
    19.         endSimulationBufferSystem = World.GetOrCreateSystem<EndSimulationEntityCommandBufferSystem>();
    20.         physicsWorld = World.GetOrCreateSystem<BuildPhysicsWorld>();
    21.  
    22.     }
    23.  
    24.    
    25.  
    26.     //[RequireComponentTag(typeof(DestroyOnContactWithDamageableComponent))]
    27.     [BurstCompile]
    28.     public unsafe struct HitDetectionJob : IJobForEachWithEntity<ProjectileData, Translation>
    29.     {
    30.         [ReadOnly] public float deltaTime;
    31.         [ReadOnly] public CollisionWorld collisionWorld;
    32.         public EntityCommandBuffer.Concurrent Buffer;
    33.         [NativeDisableParallelForRestriction] public BufferFromEntity<HitEvent> hitsBufferLookup;
    34.         //[ReadOnly] public ComponentDataFromEntity<HealthComponent> healthLookup;
    35.  
    36.         public void Execute(Entity enityProjectile, int index, ref ProjectileData projectileData, ref Translation translation)
    37.         {
    38.             var Hits = new NativeList<Unity.Physics.RaycastHit>(Allocator.Temp);
    39.  
    40.             var pos = translation.Value;
    41.            
    42.  
    43.             // RayCast Input
    44.             RaycastInput raycastInput = new RaycastInput()
    45.             {
    46.                 Start = translation.Value,
    47.                 End = pos,
    48.                 Filter = CollisionFilter.Default
    49.             };
    50.             // Check if Collided With Something
    51.             if (collisionWorld.CastRay(raycastInput, ref Hits))
    52.             {
    53.                 float closestHitDistance = 1000;
    54.  
    55.                 // Assigning an initial value
    56.                 Unity.Physics.RaycastHit hit = Hits[0];
    57.  
    58.                 for (var i = 0; i < Hits.Length; i++)
    59.                 {
    60.                     if (Hits[i].Fraction - closestHitDistance <= 0.0001f)
    61.                     {
    62.                         hit = Hits[i];
    63.                         closestHitDistance = Hits[i].Fraction;
    64.  
    65.                        
    66.                     }
    67.                    
    68.                 }
    69.  
    70.  
    71.                 if (hitsBufferLookup.Exists(entityCollided))
    72.                 {
    73.                     var myBuffer = hitsBufferLookup[entityCollided];
    74.                     myBuffer.Add(new HitEvent { Damage = 10 });
    75.                 }
    76.                /else
    77.                 {
    78.                    var myBuffer = Buffer.AddBuffer<HitEvent>(index, entityCollided);
    79.                    
    80.                     myBuffer.Add(new HitEvent { Damage = 10 });
    81.                  
    82.                 }
    83.  
    84.  
    85.                 Hits.Dispose();
    86.  
    87.                 // Hit Effect should be Instantiated in this Point
    88.                 var hitPosition = hit.Position;
    89.                 Buffer.DestroyEntity(index, enityProjectile);
    90.  
    91.              
    92.              
    93.  
    94.  
    95.                
    96.  
    97.  
    98.        
    99.              
    100.  
    101.  
    102.             }
    103.         }
    104.        
    105.     }
    106.     protected override JobHandle OnUpdate(JobHandle inputDeps)
    107.     {
    108.  
    109.  
    110.         var job = new HitDetectionJob
    111.         {
    112.  
    113.             deltaTime = Time.deltaTime,
    114.             collisionWorld = physicsWorld.PhysicsWorld.CollisionWorld,
    115.             Buffer = endSimulationBufferSystem.CreateCommandBuffer().ToConcurrent(),
    116.             hitsBufferLookup = GetBufferFromEntity<HitEvent>(false),
    117.             //healthLookup =GetComponentDataFromEntity<HealthComponent>(false)
    118.            
    119.  
    120.  
    121.         }.Schedule(this, inputDeps);
    122.         job.Complete();
    123.         return job;
    124.  
    125.     }
    126. }
    127.  
    128. [InternalBufferCapacity(16)]
    129. public struct HitEvent : IBufferElementData
    130. {
    131.     public ushort Damage;
    132. }
    133.  
    Anyone know how to make a projectile apply damage to the hit object on contact (either by this system or another)?
     
  2. MadboyJames

    MadboyJames

    Joined:
    Oct 28, 2017
    Posts:
    132
    Bump? I could use a ComponentDataFromEntity<HealthComponent> healthLookup; to make it see what health it has, but that would have to be a [ReadOnly] in a parallel job. Anyone have an idea on how to write to a healthcomponent on collision if an event system wouldn't work?
     
  3. DreamingImLatios

    DreamingImLatios

    Joined:
    Jun 3, 2017
    Posts:
    436
    I'd start with writing the damages to a NativeStream and then having a single-threaded job rush through the NativeStream and apply the damages.

    If you really need to make the damage application parallel, that's a much more complex problem and I would suggest trying to make your collision detection work in a parallel-safe fashion. My multi-box pruner can dispatch hits in a thread-safe manner (same access rules as IJFEWE except you get a pair of entities and and other collision data), so it's definitely possible.
     
    desertGhost_ likes this.
  4. desertGhost_

    desertGhost_

    Joined:
    Apr 12, 2018
    Posts:
    74
    I use a componentsystem (main thread only) to apply damage from collisions.

    The issue with trying to apply damage in a parallel way is the case where multiple projectiles have hit the same entity and your job tries to do concurrent writes to the same buffer / component. In my case there are are never enough collisions happening in a frame to make it practical to switch to a parallel safe way of handling this.

    Spatial partitioning based collision processing would allow for better parallelization.

    Here is an example project of how to check for collision this using an IJobChunk.
     
  5. DreamingImLatios

    DreamingImLatios

    Joined:
    Jun 3, 2017
    Posts:
    436
    Don't use a ComponentSystem. Use a JobComponentSystem with ScheduleSingle.
     
  6. MadboyJames

    MadboyJames

    Joined:
    Oct 28, 2017
    Posts:
    132
    Okay, That all makes sense. I see how Unity uses chunks to apply damage in the example. How would I use a NativeStream? I am unfamiliar with the syntax and implementation.
     
  7. MadboyJames

    MadboyJames

    Joined:
    Oct 28, 2017
    Posts:
    132
    So in order to use the Chunk job structure, I put the collision detection code in it (because I'm not sure how I would pipe the collisions from one job into the damage allocations in another). I have this code. It seems to delete all my entities systematically. I'm not sure what's wrong, or if I should be doing something completely different in order to get collisions and damage allocations. The code is as follows.
    Code (CSharp):
    1. private EndSimulationEntityCommandBufferSystem endSimulationBufferSystem;
    2.     private BuildPhysicsWorld physicsWorld;
    3.     private EntityQuery damageables;
    4.  
    5.  
    6.     protected override void OnCreate()
    7.     {
    8.         endSimulationBufferSystem = World.GetOrCreateSystem<EndSimulationEntityCommandBufferSystem>();
    9.         physicsWorld = World.GetOrCreateSystem<BuildPhysicsWorld>();
    10.         damageables = GetEntityQuery(typeof(HealthComponent), ComponentType.ReadOnly<Translation>());// ComponentType.ReadOnly<DamagableTag>()
    11.  
    12.     }
    13.  
    14.     [BurstCompile]
    15.     public struct CollisionJob : IJobChunk
    16.     {
    17.         public ArchetypeChunkComponentType<HealthComponent> healthType;
    18.         [ReadOnly] public ArchetypeChunkComponentType<Translation> position;
    19.         [ReadOnly] public NativeArray<Entity> entites;
    20.        [ReadOnly] public CollisionWorld collisionWorld;
    21.         public EntityCommandBuffer.Concurrent buffer;
    22.  
    23.         public void Execute(ArchetypeChunk chunk, int chunkIndex, int firstEntityIndex)
    24.         {
    25.             var chunkHealths = chunk.GetNativeArray(healthType);
    26.             var chunkPos = chunk.GetNativeArray(position);
    27.            
    28.  
    29.             int count = chunk.Count;
    30.             for (int i = 0; i < count; i++)
    31.             {
    32.                 float damage = 0;
    33.                 HealthComponent health = chunkHealths[i];
    34.                 Translation pos = chunkPos[i];
    35.                 var hits = new NativeList<Unity.Physics.RaycastHit>(Allocator.Temp);
    36.                
    37.  
    38.                 RaycastInput raycastInput = new RaycastInput()
    39.                 {
    40.                     Start = pos.Value,
    41.                     End = pos.Value,
    42.                     Filter = CollisionFilter.Default
    43.                 };
    44.  
    45.                 if (collisionWorld.CastRay(raycastInput, ref hits))
    46.                 {
    47.                     float closestHitDistance = 1000;
    48.  
    49.                     // Assigning an initial value
    50.                     Unity.Physics.RaycastHit hit = hits[0];
    51.  
    52.                     for (var n = 0; n < hits.Length; n++)
    53.                     {
    54.                         if (hits[n].Fraction - closestHitDistance <= 0.0001f) //.0001f is Epsilon
    55.                         {
    56.                             hit = hits[n];
    57.                             closestHitDistance = hits[n].Fraction;
    58.  
    59.  
    60.                         }
    61.  
    62.                     }
    63.  
    64.                     hits.Dispose();
    65.  
    66.                     // Hit Effect should be Instantiated in this Point
    67.                     var hitPosition = hit.Position;
    68.                     buffer.DestroyEntity(chunkIndex,entites[i]);
    69.                 }
    70.  
    71.             }
    72.            
    73.         }
    74.     }
    75.  
    76. protected override JobHandle OnUpdate(JobHandle inputDeps)
    77.     {
    78.         var healthType = GetArchetypeChunkComponentType<HealthComponent>(false);
    79.         var translationType = GetArchetypeChunkComponentType<Translation>(true);
    80.         var damageablesArray = damageables.ToEntityArray(Allocator.TempJob);
    81.  
    82.         var job = new CollisionJob
    83.         {
    84.             healthType = healthType,
    85.             position = translationType,
    86.             entites = damageablesArray,
    87.             collisionWorld = physicsWorld.PhysicsWorld.CollisionWorld,
    88.             buffer = endSimulationBufferSystem.CreateCommandBuffer().ToConcurrent()
    89.  
    90.         }.Schedule(damageables, inputDeps);
    91.         job.Complete();
    92.         damageablesArray.Dispose();
    93. }
     
  8. DreamingImLatios

    DreamingImLatios

    Joined:
    Jun 3, 2017
    Posts:
    436
    Oh. You are using Unity.Physics. I was thinking you had written a custom collision detection solution. I believe in Unity.Physics, if a ray starts inside a collider, it counts as a hit. So you need to check if the entity the ray hit isn't the caster. Or even better, use CalculateDistance queries instead of CastRay queries. And maybe even better yet is you can use ITrggerEventsJob or ICollisionEventsJob and have that data already stored and extracted in what is effectively a NativeStream for you.
     
  9. eizenhorn

    eizenhorn

    Joined:
    Oct 17, 2016
    Posts:
    1,428
  10. MadboyJames

    MadboyJames

    Joined:
    Oct 28, 2017
    Posts:
    132
    @Eizenh I looked at the Nordeus demo, and I vaguely understood what the code was doing, though I cannot say I understand it enough to implement it. As for @DreamingImLatios, I put together an ICollisionEventsJob, but it does not do anything. If you have any suggestions on how to debug the system, or in general what I am doing incorrectly, it would be greatly appreciated.

    Code (CSharp):
    1. using Unity.Entities;
    2. using Unity.Jobs;
    3. using Unity.Physics;
    4. using Unity.Collections;
    5. using Unity.Burst;
    6. using Unity.Physics.Systems;
    7.  
    8. public class ApplyDamageSystem : JobComponentSystem
    9. {
    10.     EndSimulationEntityCommandBufferSystem endSimulationEntityCommandBufferSystem;
    11.     StepPhysicsWorld stepPhysicsWorld;
    12.     BuildPhysicsWorld buildPhysicsWorld;
    13.  
    14.     protected override void OnCreate()
    15.     {
    16.         endSimulationEntityCommandBufferSystem = World.GetOrCreateSystem<EndSimulationEntityCommandBufferSystem>();
    17.         stepPhysicsWorld = World.GetOrCreateSystem<StepPhysicsWorld>();
    18.         buildPhysicsWorld = World.GetOrCreateSystem<BuildPhysicsWorld>();
    19.     }
    20.  
    21.  
    22.     [BurstCompile]
    23.     public struct DamageOnCollision : ICollisionEventsJob
    24.     {
    25.         [ReadOnly] public ComponentDataFromEntity<ProjectileComponent> projectileTag;
    26.         public ComponentDataFromEntity<HealthComponent> healthData;
    27.         public EntityCommandBuffer buffer;
    28.         public void Execute(CollisionEvent collisionEvent)
    29.         {
    30.  
    31.             if(!ApplyDamage(collisionEvent.Entities.EntityA, collisionEvent.Entities.EntityB));
    32.                 ApplyDamage(collisionEvent.Entities.EntityB, collisionEvent.Entities.EntityA);
    33.  
    34.  
    35.  
    36.  
    37.         }
    38.         public bool ApplyDamage(Entity entityA, Entity entityB)
    39.         {
    40.             bool bothdeleted=false;
    41.  
    42.             if (projectileTag.Exists(entityA))
    43.             {
    44.                 if (healthData.Exists(entityB))
    45.                 {
    46.                     var health = healthData[entityB];
    47.                     health.currentHealth -= 10;
    48.                     healthData[entityB] = health;
    49.                     if (health.currentHealth <= 0)
    50.                     {
    51.                         buffer.DestroyEntity(entityB);
    52.                         bothdeleted = true;
    53.                     }
    54.                     buffer.DestroyEntity(entityA);
    55.                 }
    56.             }
    57.             return bothdeleted;
    58.         }
    59.  
    60.     }
    61.  
    62.  
    63.  
    64.     protected override JobHandle OnUpdate(JobHandle inputDeps)
    65.     {
    66.         var damOnCollJob = new DamageOnCollision
    67.         {
    68.             projectileTag =GetComponentDataFromEntity<ProjectileComponent>(),
    69.             healthData =GetComponentDataFromEntity<HealthComponent>(),
    70.             buffer =endSimulationEntityCommandBufferSystem.CreateCommandBuffer()
    71.         };
    72.  
    73.       JobHandle damOnCollhandle= damOnCollJob.Schedule(stepPhysicsWorld.Simulation, ref buildPhysicsWorld.PhysicsWorld, inputDeps);
    74.         damOnCollhandle.Complete();
    75.  
    76.         return damOnCollhandle;
    77.  
    78.        
    79.     }
    80. }
     
  11. DreamingImLatios

    DreamingImLatios

    Joined:
    Jun 3, 2017
    Posts:
    436
    Can't help you much with regards to Unity.Physics. I don't personally use it as is and don't totally understand its layers, trigger classification, and authoring code. But I bet one of those isn't set up correctly with your job.

    As for debugging, you can do [BurstCompile(Debug = true)] and instead of .Schedule() you do a .Run(). That gives you the ability to set breakpoints and investigate.
     
  12. thelebaron

    thelebaron

    Joined:
    Jun 2, 2013
    Posts:
    277
    Is the problem that your system doesn't work or that it doesn't work with burst/fast enough? How many entities are colliding at a time for there to be performance issues? I use ComponentDataFromEntity, BufferFromEntity quite a lot and in a number of my jobs I cant use burst yet, but the performance from these systems is good enough for my uses(and so far projectile collision detection isn't one of the heavier systems). While I do have a decent amount of gc from ecb(which I am assuming Unity will clean up at some point) when the numbers are substantial, the actual system that detects collisions(raycasts in my case) is not itself a performance sink.
     
  13. MadboyJames

    MadboyJames

    Joined:
    Oct 28, 2017
    Posts:
    132
    The problem is that the logic does not execute. Or that it is not detecting collisions.
    Additionally I'm not getting any "burst incompatible" errors, although that could just be due to the logic condition never being met.
     
    Last edited: Sep 12, 2019
  14. tim_jones

    tim_jones

    Unity Technologies

    Joined:
    May 2, 2019
    Posts:
    19
    @MadboyJames if you remove the [BurstCompile] attribute (or change it to [BurstCompile(Debug = true)] as @DreamingImLatios suggested) does the problem still happen? Which version of Burst are you using?
     
  15. MadboyJames

    MadboyJames

    Joined:
    Oct 28, 2017
    Posts:
    132
    @tim_jones thank you for the reply. I am using Burst 1.1.2. The problem persists with the removal of the BurstCompile attribute. Both objects have kinematic physics bodies. In terms of efficiency I am only testing one collision per frame, so I have not stress tested my system, seeing as it currently does not execute logic. To confirm this, I put
    Debug.Log("there was a collision event");
    as the first line in the Execute brackets. It does not run when there is a collision.

    If I change the body of my launched projectile to Dynamic, it stops on collision with my target object, meaning that the collision detection and physics system are working, but still no logic exicutes. I am not creating CollisionEvents. I assmue that they are created dynamically when a collision is detected. Is this assumption wrong, or am I leveraging the CollisionEvents job incorrectly?

    Would the issue have something to do with the stepPhysicsWorld and buildPhysicsWorld? I do not really know what those two worlds are doing (what a "world" really is). So those specifics of the code are just me parroting syntax.

    Additionally I'm trying to avoid using EntityCommandBuffer.Concurrent because I do not know how to get the index from a CollisionEvent job.
     
    Last edited: Sep 12, 2019
  16. thelebaron

    thelebaron

    Joined:
    Jun 2, 2013
    Posts:
    277
    You cant get an index from CollisionEvent job, afaik it doesnt have a parallel equivalent yet. For the first system you posted, was the issue of finding how to get "entityCollided"? If that was the case it would be
    var entityCollided = collisionWorld.Bodies[Hits[0].RigidBodyIndex].Entity;
     
    steveeHavok likes this.
  17. MadboyJames

    MadboyJames

    Joined:
    Oct 28, 2017
    Posts:
    132
    The issue was I could not get the hit events to properly apply damage. Essentially I couldn't make a system that applied the damage stored in the HitEvents. Additionally it was giving me errors associated with adding a new buffer to an entity.
     
  18. steveeHavok

    steveeHavok

    Joined:
    Mar 19, 2019
    Posts:
    110
    On the Physics Shape Authoring component, make sure you have the Raises Collision Events flag toggled: upload_2019-9-13_16-58-4.png
    This equates to the
    Unity.Physics.Material.MaterialFlags.EnableCollisionEvents
    at runtime.
     
    MadboyJames and tim_jones like this.
  19. MadboyJames

    MadboyJames

    Joined:
    Oct 28, 2017
    Posts:
    132
    That was my issue! It now collides and deals damage! Thank you!
     
    steveeHavok likes this.