Search Unity

Collision Detection projectiles

Discussion in 'Data Oriented Technology Stack' started by MadboyJames, Aug 28, 2019.

  1. MadboyJames

    MadboyJames

    Joined:
    Oct 28, 2017
    Posts:
    159
    I am looking to make any entity with the component tag "DestroyOnContactWithDamageableComponent" be destroyed on contact with any entity which has the "DamagableComponent". This will mainly be used for bullets. As it is, I am struggling to figure out how to detect collisions, and how I can destroy the projectile after the detection occurs. I was poking around this thread, but I do not know what half of the code is doing, let alone how to modify it for my own purposes. My current (unworking) system is below. Any and all help will be appreciated.

    Code (CSharp):
    1. public class DestroyOnContactWithDamageableSystem : JobComponentSystem
    2. {
    3.     private EndSimulationEntityCommandBufferSystem endSimulationBufferSystem;
    4.     private BuildPhysicsWorld _buildPhysicsWorldSystem;
    5.     private StepPhysicsWorld _stepPhysicsWorldSystem;
    6.    
    7.  
    8.     protected override void OnCreate()
    9.     {
    10.         endSimulationBufferSystem = World.GetOrCreateSystem<EndSimulationEntityCommandBufferSystem>();
    11.         _buildPhysicsWorldSystem = World.GetOrCreateSystem<BuildPhysicsWorld>();
    12.         _stepPhysicsWorldSystem = World.GetOrCreateSystem<StepPhysicsWorld>();
    13.        
    14.     }
    15.  
    16.  
    17.     [RequireComponentTag(typeof(DestroyOnContactWithDamageableComponent))]
    18.     public struct CollisionJob : ITriggerEventsJob
    19.     {
    20.  
    21.         public EntityCommandBuffer.Concurrent Buffer;
    22.         public NativeList<ColliderKey> L;
    23.  
    24.         public void Execute(TriggerEvent triggerEvent)
    25.         {
    26.             if()//triggerEvent.Entities.EntityB has component tag of Destructable
    27.             {
    28.                 Buffer.DestroyEntity(,triggerEvent.Entities.EntityA);//no index with this job type?
    29.             }
    30.         }
    31.     }
    32.  
    33.  
    34.     protected override JobHandle OnUpdate(JobHandle inputDeps)
    35.     {
    36.         var L = new NativeList<ColliderKey>(Allocator.TempJob);
    37.  
    38.         var job = new CollisionJob
    39.         {
    40.             Buffer = endSimulationBufferSystem.CreateCommandBuffer().ToConcurrent(),
    41.             L = L
    42.  
    43.         }.Schedule(_stepPhysicsWorldSystem.Simulation, ref _buildPhysicsWorldSystem.PhysicsWorld, inputDeps);
    44.         job.Complete();
    45.         L.Dispose();
    46.         return job;
    47.        
    48.     }
    49. }
     
  2. Opeth001

    Opeth001

    Joined:
    Jan 28, 2017
    Posts:
    292
    i suggest you using ECS Tags instead of Components,

    Code (CSharp):
    1.  
    2.         [BurstCompile]
    3.         public unsafe struct HitDetectionJob : IJobForEachWithEntity<ProjectileData, Translation>
    4.         {
    5.             [ReadOnly] public float deltaTime;  
    6.             [ReadOnly] public CollisionWorld collisionWorld;
    7.  
    8.             public void Execute(Entity enityProjectile, int index, ref ProjectileData projectileData, ref Translation translation)
    9.             {
    10.                 var Hits = new NativeList<Unity.Physics.RaycastHit>(Allocator.Temp);
    11.  
    12.                 // Process Cooldown
    13.                 projectileData.countDown -= deltaTime;
    14.  
    15.                 var nextFramePosition = translation.Value + projectileData.cachedVelocity * deltaTime;
    16.  
    17.  
    18.                 // RayCast Input
    19.                 RaycastInput raycastInput = new RaycastInput()
    20.                 {
    21.                     Start = translation.Value,
    22.                     End = nextFramePosition,
    23.                     Filter = CollisionFilter.Default
    24.                 };
    25.                 // Check if Collided With Something
    26.                 if (collisionWorld.CastRay(raycastInput, ref Hits))
    27.                 {
    28.                     float closestHitDistance = 1000;
    29.  
    30.                     // Assigning an initial value
    31.                     Unity.Physics.RaycastHit hit = Hits[0];
    32.  
    33.                     for (var i = 0; i < Hits.Length; i++)
    34.                     {
    35.                         if (Hits[i].Fraction - closestHitDistance <= _MathUtils.Epsilon)
    36.                         {
    37.                             hit = Hits[i];
    38.                             closestHitDistance = Hits[i].Fraction;
    39.                         }
    40.                     }
    41.  
    42.                     Hits.Dispose();
    43.  
    44.                     // Hit Effect should be Instantiated in this Point
    45.                     var hitPosition = hit.Position;
    46.  
    47.                     // The Physics Entity hit by the Projectile
    48.  
    49.                     var body = collisionWorld.Bodies[hit.RigidBodyIndex];
    50.  
    51.                     var entityCollided = body.Entity;
    52.  
    53.                     var filter = body.Collider->Filter;
    54.                     var physicsLayer = FilterToLayerIndex(filter.BelongsTo);
    55.  
    56.  
    57.                     // 1) Apply Impact Effects
    58.                     switch (physicsLayer)
    59.                     {
    60.                         case ECSPhysicsLayer.Players:
    61.                             break;
    62.                         case ECSPhysicsLayer.Environement:
    63.                               break;
    64.                         case ECSPhysicsLayer.Destructables:
    65.                             //TODO: Handle Destructables (Only Server Side)
    66.                         default:
    67.                             // Debug.Log("Projetile Collided an unknown Collder Layer");
    68.                             break;
    69.                     }
    70.  
    71.              
    72.             }
    73.         }
    74.  
    in case u need to get ComponentData From the Collided Entity, you can use the GetComponentDataFromEntity it works inside jobs, also you can use the [BurstCompile] when u are only using the EntityCommandBuffer.DestroyEntity .
     
    MadboyJames likes this.
  3. MadboyJames

    MadboyJames

    Joined:
    Oct 28, 2017
    Posts:
    159
    Actually I think I may have figured out my collision stuff. I am still stuck on the entity deletion though. Here is my new and improved code.
    Code (CSharp):
    1. public class DestroyOnContactWithDamageableSystem : JobComponentSystem
    2. {
    3.     private EndSimulationEntityCommandBufferSystem endSimulationBufferSystem;
    4.     private BuildPhysicsWorld _buildPhysicsWorldSystem;
    5.     private StepPhysicsWorld _stepPhysicsWorldSystem;
    6.    
    7.  
    8.     protected override void OnCreate()
    9.     {
    10.         endSimulationBufferSystem = World.GetOrCreateSystem<EndSimulationEntityCommandBufferSystem>();
    11.         _buildPhysicsWorldSystem = World.GetOrCreateSystem<BuildPhysicsWorld>();
    12.         _stepPhysicsWorldSystem = World.GetOrCreateSystem<StepPhysicsWorld>();
    13.        
    14.     }
    15.  
    16.  
    17.     [RequireComponentTag(typeof(DestroyOnContactWithDamageableComponent))]
    18.     public struct CollisionJob : ITriggerEventsJob
    19.     {
    20.        
    21.         public EntityCommandBuffer.Concurrent Buffer;
    22.  
    23.         [ReadOnly] public ComponentDataFromEntity<DamageableTagComponent> damagable;
    24.  
    25.         public void Execute(TriggerEvent triggerEvent)
    26.         {
    27.             if(damagable.Exists(triggerEvent.Entities.EntityB))
    28.             {
    29.                 Buffer.DestroyEntity(index?,triggerEvent.Entities.EntityA);//no index with this job type?
    30.             }
    31.         }
    32.     }
    33.  
    34.  
    35.     protected override JobHandle OnUpdate(JobHandle inputDeps)
    36.     {
    37.        
    38.  
    39.         var job = new CollisionJob
    40.         {
    41.            
    42.             Buffer = endSimulationBufferSystem.CreateCommandBuffer().ToConcurrent(),
    43.             damagable =GetComponentDataFromEntity<DamageableTagComponent>(true)
    44.            
    45.  
    46.         }.Schedule(_stepPhysicsWorldSystem.Simulation, ref _buildPhysicsWorldSystem.PhysicsWorld, inputDeps);
    47.         return job;
    48.        
    49.     }
    50. }
     
  4. MadboyJames

    MadboyJames

    Joined:
    Oct 28, 2017
    Posts:
    159
    I did not know I could use burst compile with destroy. Is using raycasts like that my best option? I'll see if I can get it to work, Thanks!
     
  5. Opeth001

    Opeth001

    Joined:
    Jan 28, 2017
    Posts:
    292
    yes try to use CollisionWorld.CastRay it's super performant ^_^
     
  6. schaefsky

    schaefsky

    Joined:
    Aug 11, 2013
    Posts:
    27
    Had the same problem, I think you have to use a "normal" CommandBuffer, not a concurrent one.
    I am a total beginnre though, so take it with a grain of salt.

    Also, which part of my thread you mentioned did you not understand? Your code seems t be pretty similar. I copied most of my code from bits and pieces from the web.
    Especially the ".Schedule(_stepPhysicsWorldSystem.Simulation, ref _buildPhysicsWorldSystem.PhysicsWorld, inputDeps);" bit, I don't have any idea if this is the correct way to do it, I got it from http://blog.phonesoftware.hu/2019/05/17/detecting-collisions.html

    Nice to see someone else is trying to do the same thing I do!

    Oh and please also let me know if you got it to work, because I have the problem that the job runs twice per collision, which gives an error if I delete the entity.
     
  7. MadboyJames

    MadboyJames

    Joined:
    Oct 28, 2017
    Posts:
    159
    @Opeth001 Could you post what you have for the ProjectileData component and any system you use that in?
     
  8. MadboyJames

    MadboyJames

    Joined:
    Oct 28, 2017
    Posts:
    159
    Okay, so for right now all I'm looking for is if the projectile hits a damagable, it deletes itself. I'll deal with dealing damage and animations and effects later. With that, I stripped down to code a bit, but I'm not sure what a collisionWorld is or how to use one. I also do not have FilterToLayerIndex, _MathUtils, and ECSPhysicsLayer. Are those extensions you made, or libraries/ packages that intellisense is not sensing?

    Code (CSharp):
    1. public class DestroyOnContactWithDamageableSystem : JobComponentSystem
    2. {
    3.     private EndSimulationEntityCommandBufferSystem endSimulationBufferSystem;
    4.     private CollisionWorld colworld;
    5.  
    6.  
    7.     protected override void OnCreate()
    8.     {
    9.         endSimulationBufferSystem = World.GetOrCreateSystem<EndSimulationEntityCommandBufferSystem>();
    10.         colworld = new CollisionWorld();
    11.  
    12.     }
    13.  
    14.    
    15.  
    16.     [RequireComponentTag(typeof(DestroyOnContactWithDamageableComponent))]
    17.     [BurstCompile]
    18.     public struct HitDetectionJob : IJobForEachWithEntity<PhysicsVelocity, Translation>
    19.     {
    20.         [ReadOnly] public float deltaTime;
    21.         [ReadOnly] public CollisionWorld collisionWorld;
    22.         public EntityCommandBuffer.Concurrent buffer;
    23.  
    24.         public void Execute(Entity entityProjectile, int index, ref PhysicsVelocity velocity, ref Translation translation)
    25.         {
    26.             var Hits = new NativeList<Unity.Physics.RaycastHit>(Allocator.Temp);
    27.  
    28.  
    29.  
    30.             var nextFramePosition = translation.Value + velocity.Linear * deltaTime;
    31.  
    32.  
    33.             // RayCast Input
    34.             RaycastInput raycastInput = new RaycastInput()
    35.             {
    36.                 Start = translation.Value,
    37.                 End = nextFramePosition,
    38.                 Filter = CollisionFilter.Default
    39.             };
    40.             // Check if Collided With Something
    41.             if (collisionWorld.CastRay(raycastInput, ref Hits))
    42.             {
    43.                
    44.  
    45.                 // Assigning an initial value
    46.                 Unity.Physics.RaycastHit hit = Hits[0];
    47.  
    48.  
    49.                 Hits.Dispose();
    50.  
    51.                 // Hit Effect should be Instantiated in this Point
    52.                 //var hitPosition = hit.Position;
    53.                 buffer.DestroyEntity(index, entityProjectile);
    54.  
    55.                 // The Physics Entity hit by the Projectile
    56.  
    57.                 //var body = collisionWorld.Bodies[hit.RigidBodyIndex];
    58.  
    59.  
    60.  
    61.             }
    62.         }
    63.  
    64.  
    65.      
    66.  
    67.     }
    68.     protected override JobHandle OnUpdate(JobHandle inputDeps)
    69.     {
    70.  
    71.  
    72.         var job = new HitDetectionJob
    73.         {
    74.  
    75.             deltaTime = Time.deltaTime,
    76.             collisionWorld = colworld,
    77.             buffer = endSimulationBufferSystem.CreateCommandBuffer().ToConcurrent()
    78.  
    79.  
    80.         }.Schedule(this, inputDeps);
    81.        
    82.         return job;
    83.  
    84.     }
    85. }
    Currently I'm getting a "InvalidOperationException: The NativeContainer HitDetectionJob.Data.collisionWorld.m_Bodies has not been assigned or constructed." error at runtime. I'm sure it's just because I do not know how to use a collisionworld.
     
  9. MadboyJames

    MadboyJames

    Joined:
    Oct 28, 2017
    Posts:
    159

    I had never seen stepPhysicsWorldSystem.Simulation, or buildPhysicsWorldSystem.PhysicsWorld used, and I had (well, have) no idea what their roles are or how to use them. Additionally, I did not know why you were deleting and adding components to destroy them in another system (I know now that you linked the article. Thanks! :) ). I only knew to use buffer.concurrent.DestroyEntity(), which has worked pretty well for me. On top of that, I had never seen a trigger event system, nor had any idea how to properly use one. Essentially your whole job infrastructure was brand new and I had no idea how to leverage it. The most I pulled from your code was the realization that ComponentDataFromEntity is a thing that exists. The rest looks the same not because I understood the code, but because you were trying to do what I wanted to do, and this is how you were doing it. I have found that if something works via copy-paste, I can figure out what each piece is doing. But since I've started using ECS I've been flying a bit blind.
     
  10. schaefsky

    schaefsky

    Joined:
    Aug 11, 2013
    Posts:
    27
    Haha, that sounds familiar
    For me the biggest problem is the lack of examples to actually do game stuff, besides the numerous “how to move / rotate 5000 entities”.
    Have not really checked the unity FPS example, but the size alone is pretty daunting to me.
     
    MadboyJames likes this.
  11. MadboyJames

    MadboyJames

    Joined:
    Oct 28, 2017
    Posts:
    159
  12. schaefsky

    schaefsky

    Joined:
    Aug 11, 2013
    Posts:
    27
  13. MadboyJames

    MadboyJames

    Joined:
    Oct 28, 2017
    Posts:
    159
    Gotcha. I downloaded the files, but I'm having trouble identifying the part of the code that would allow me to do an action on collision. I may open a new thread asking explicity for an ECS "OnCollisionEnter" equivalent.
     
  14. Opeth001

    Opeth001

    Joined:
    Jan 28, 2017
    Posts:
    292
    @MadboyJames
    im sorry i wasn't able to check this forum for few days. (working on Flutter XD)

    1) the Collision world is used to do raycasts inside jobs.
    2) the ProjectileData :
    Code (CSharp):
    1.  
    2. public struct ProjectileData : IComponentData
    3.     {
    4.         public byte teamId;                                       // Which Team this Projectile belongs to
    5.         public Entity owner;                                      // To determin which player Entity this Projectile  belongs to
    6.         public float3 cachedVelocity;                        // Projectile Velocity (Cached Direction * Speed to prevent calculating it each frame )
    7.         public float countDown;                                // Time to Live
    8.  
    9.         // Data index Pointer it's a byte cause i have less than 255 weapon types
    10.         public byte WeaponDataId;                           // Prevent Data redundancy referencing a weapon data with an id. (all weapons Data are inside a Global NativeContainer Variable)
    11.     }
    3) FilterToLayerIndex, _MathUtils, and ECSPhysicsLayer are extensions and Static Functions created by me to be used as Utils and to maximise the Code Reuse.

    * _MathUtils.Epsilon is just a public const float Epsilon = 0.0001f; (used to define what's the maximal unit importance in floats (maximise Floats calculations performance and ensures the same results in all devices ))
    eg:
    checkig if (float2 a == float2 b ) will not return the same result in different devices types. (server & Clients)
    in this case it will be more deterministic and even faster to create an Equality function
    Code (CSharp):
    1.  
    2. public static bool IsEqualTo(this float A, float B)
    3.         {
    4.             if ( math.abs(A.x - B.x) < Epsilon )
    5.                 return true;
    6.             else
    7.                 return false;
    8.         }
    * ECSPhysicsLayer is a simple Enum to define my ECS Physics Layers
    Code (CSharp):
    1.  
    2. public enum ECSPhysicsLayer
    3.     {
    4.         Players, //index 0
    5.         Environement, //index 1
    6.         Destructables, //index 2
    7.  
    8.  
    9.         Projectiles,
    10.         None,
    11.     }
    // a function converting the ECS Layers returned from Raycasts to my costum Enum
    Code (CSharp):
    1.  
    2. public static ECSPhysicsLayer FilterToLayerIndex( uint filter)
    3.     {
    4.         switch (filter)
    5.         {
    6.             case 1u << 0:
    7.                 return ECSPhysicsLayer.Players;
    8.  
    9.             case 1u << 1:
    10.                 return ECSPhysicsLayer.Environement;
    11.  
    12.             case 1u << 2:
    13.                 return ECSPhysicsLayer.Destructables;
    14.  
    15.                 // Projectile
    16.             case 1u << 31:
    17.                 return ECSPhysicsLayer.Projectiles;
    18.  
    19.             default:
    20.                 return ECSPhysicsLayer.None;
    21.         }
    22.     }
    23.  
    24. public static uint ToFilter( this ECSPhysicsLayer layer)
    25.     {
    26.         switch (layer)
    27.         {
    28.             case ECSPhysicsLayer.Players:
    29.                 return 1u << 0 ;
    30.  
    31.             case ECSPhysicsLayer.Environement:
    32.                 return 1u << 1;
    33.  
    34.             case ECSPhysicsLayer.Destructables:
    35.                 return 1u << 2;
    36.  
    37.             // Projectile
    38.             case ECSPhysicsLayer.Projectiles :
    39.                 return 1u << 31;
    40.  
    41.             default:
    42.                 return 1u << -1;
    43.         }
    44.     }
    45.  
    46.  
     
    MadboyJames likes this.
  15. MadboyJames

    MadboyJames

    Joined:
    Oct 28, 2017
    Posts:
    159
    @Opeth001 That helps a lot! I don't think I'm instantiating the collision world right, though. I'm just doing colworld= new CollisionWorld(); to enter into the job, but I keep getting a "The NativeContainer HitDetectionJob.Data.collisionWorld.m_Bodies has not been assigned or constructed." error.
     
  16. Opeth001

    Opeth001

    Joined:
    Jan 28, 2017
    Posts:
    292
    the CollisionWorld should not be instantiated!

    Code (CSharp):
    1.  
    2. public class ProjectileHitDetectionSystem :  JobComponentSystem
    3.     {
    4.         private BuildPhysicsWorld builtInPhysicsWorld;
    5.  
    6.  
    7.  
    8.        protected override void OnCreate()
    9.         {
    10.             builtInPhysicsWorld = World.GetOrCreateSystem<BuildPhysicsWorld>();
    11.         }
    12.  
    13.        protected override JobHandle OnUpdate(JobHandle inputDeps)
    14.         {
    15.            // get the CollisionWorld  by this line
    16.            var collisionWorld = builtInPhysicsWorld.PhysicsWorld.CollisionWorld;
    17.  
    18.         }
    19.  
    20. }
    21.  
     
    Last edited: Sep 3, 2019
    MadboyJames likes this.
  17. MadboyJames

    MadboyJames

    Joined:
    Oct 28, 2017
    Posts:
    159
    Oh! I see now. Okay, I got it working! Woo! Thank you so much!
    hrmm... do you have any thoughts on how to apply damage? I poked around in "hit" a bit, but I'm not seeing a way to get the collided entity's data.
     
  18. Opeth001

    Opeth001

    Joined:
    Jan 28, 2017
    Posts:
    292
    in my case im using DynamicBuffers.
    i get the Entity from the Hit

    1)
    //inside system Update Loop
    // the false is for ReadOnly
    // These lookups are used to get ElementBuffers and ComponentData from entities inside Jobs
    Code (CSharp):
    1.  
    2. var hitEventsBufferFromEntity = GetComponentDataFromEntity<anyCD>(false); // for Component Data
    3. or
    4. var lookup = GetBufferFromEntity<HitEventsBuffer>(); // for ElementBuffer
    5.  
    //inside job
    2)
    Code (CSharp):
    1.  
    2. var body = collisionWorld.Bodies[hit.RigidBodyIndex];
    3. var entityCollided = body.Entity;
    4.  
    3)
    Code (CSharp):
    1.  
    2. if(lookup.Exists(entity)) {
    3.    var hitEvents = lookup[entityCollided]
    4. }
    4) Add DMG Data inside the HitEventBuffer

    5) another System should handle All Damage Events for each Damageable Entity ( Containing the HitEvents buffer)

    Good Luck!
     
    Last edited: Sep 3, 2019
    MadboyJames likes this.
  19. MadboyJames

    MadboyJames

    Joined:
    Oct 28, 2017
    Posts:
    159
    Okay, I see. I'll be able to try that out in a bit and I'll post my results. Thank you so much for your help!
     
    Opeth001 likes this.
  20. Opeth001

    Opeth001

    Joined:
    Jan 28, 2017
    Posts:
    292
    @MadboyJames
    you can see in my code im using collisionWorld.CastRay(raycastInput, ref Hits) which returns an array of Hits
    and then calculating by myself the closest hit.
    i did it cause the new version of the ecs Physics is crashing on mobile devices when 2 entities colliders are overlapping and trying to raycast them.

    (it's only happening on mobile devices)
     
    Last edited: Sep 3, 2019
    MadboyJames likes this.
  21. MadboyJames

    MadboyJames

    Joined:
    Oct 28, 2017
    Posts:
    159
    @Opeth001 I see how its supposed to work, but I'm hitting some sytaxical hurdles. How does one create a HitEventsBuffer? Additionally, where did you assign the object reference for GetBufferFromEntity<>() ? Or, a more comprehensive question, how did you implement that code in a job?
     
  22. Opeth001

    Opeth001

    Joined:
    Jan 28, 2017
    Posts:
    292
    Code (CSharp):
    1.  
    2. // In this case, 16 HitEvent will be reserved. (can be resized at Runtime, but will cause performance issues)
    3. [InternalBufferCapacity(16)]
    4. public struct HitEvent : IBufferElementData
    5. {
    6.     public ushort Damage;
    7.     public Entity Owner;
    8. }
    9.  
    10. public struct Health : IComponentData
    11. {
    12.     public int Value;
    13. }
    14.  
    15.  
    16. public class TestLookupsSystem : JobComponentSystem
    17. {
    18.  
    19.     // you can use Any Job type but im not sure if the Elementbuffers can be Write Accessed in parallel.
    20.     // if your game logic can garantee that no buffers will be accessed in parallel you can add this attribute to your lookup [NativeDisableParallelForRestriction].
    21.     struct myJobType : IJob
    22.     {
    23.         public BufferFromEntity<HitEvent> hitsBufferLookup;
    24.         public ComponentDataFromEntity<Health> healthLookup;
    25.  
    26.         public void Execute()
    27.         {
    28.             // get the Entity From the Raycast Hit
    29.             var body = collisionWorld.Bodies[hit.RigidBodyIndex]
    30.             var entityCollided = body.Entity;
    31.  
    32.             // Get a Component Data From Entity
    33.             if (healthLookup.Exists(entityCollided ))
    34.             {
    35.                 var health = healthLookup[entityCollided];
    36.                 // Code here
    37.             }
    38.  
    39.             // Get an Element Buffer From entity
    40.             if (hitsBufferLookup.Exists(entityCollided))
    41.             {
    42.                 var mybuffer = hitsBufferLookup[entityCollided];
    43.                 mybuffer.Add(new HitEvent { Damage = 10, Owner = new Entity() }); // your Game Logic Data
    44.             }
    45.         }
    46.     }
    47.    
    48.  
    49.  
    50.     protected override JobHandle OnUpdate(JobHandle inputDeps)
    51.     {
    52.      
    53.         var myjob = new myJobType
    54.         {
    55.             hitsBufferLookup = GetBufferFromEntity<HitEvent>(false),
    56.             healthLookup = GetComponentDataFromEntity<Health>(false),
    57.     };
    58.        
    59.         return myjob.Schedule(inputDeps);
    60.     }
    61. }
    // check if the Element Buffer Can be Accessed in Parallel or not, in case it's not possible.
    you can still create entities to hold the Hit Events and then Write them to Entities buffers in a single Job


    More Details about the Element Buffers: Here
     
  23. MadboyJames

    MadboyJames

    Joined:
    Oct 28, 2017
    Posts:
    159
    Okay. Currently I have
    Code (CSharp):
    1. if (hitsBufferLookup.Exists(entityCollided))
    2.                 {
    3.                     var myBuffer = hitsBufferLookup[entityCollided];
    4.                     myBuffer.Add(new HitEvent { Damage = 10 });
    5.                 }
    adding the buffer, and
    Code (CSharp):
    1. public struct checkHealth : IJobForEachWithEntity<HealthComponent>
    2.     {
    3.         public EntityCommandBuffer.Concurrent buffer;
    4.         [NativeDisableParallelForRestriction] public BufferFromEntity<HitEvent> hitsBufferLookup;
    5.  
    6.  
    7.         public void Execute(Entity entity, int index, ref HealthComponent health)
    8.         {
    9.  
    10.             if (hitsBufferLookup.Exists(entity))
    11.             {
    12.                 var hitsBuffer = hitsBufferLookup[entity];
    13.                 for (int i = 0; i < hitsBuffer.Length; i++)
    14.                 {
    15.                     health.currentHealth -= hitsBuffer[i].Damage;
    16.                  
    17.                 }
    18.                 hitsBuffer.Clear();
    19.             }
    20.            
    21.  
    22.             if(health.currentHealth<=0)
    23.             { buffer.DestroyEntity(index, entity); }
    24.         }
    25.     }
    Performing the health calculation. The calculation appears to be performing, but it is rather slow. In addition to that, I'm getting a "
    ArgumentException: The entity does not exist
    EntityCommandBuffer was recorded in DestroyOnContactWithDamageableSystem and played back in Unity.Entities.EndSimulationEntityCommandBufferSystem." error. Any thoughts?
     
  24. Opeth001

    Opeth001

    Joined:
    Jan 28, 2017
    Posts:
    292
    can you give more details about your systems ?
    DestroyOnContactWithDamageableSystem*

    cause i dont see any error in your code.
     
  25. MadboyJames

    MadboyJames

    Joined:
    Oct 28, 2017
    Posts:
    159
    Sure. It's the system I'm using to detect collisions, with most of the code discussed on this thread. It is named DestroyOnContactWithDamageable because I was going to use ECS tags to determine if an object would be able to be damaged. Here is the code.
    Code (CSharp):
    1. [UpdateBefore(typeof(HealthControllerSystem))]
    2. public class DestroyOnContactWithDamageableSystem : JobComponentSystem
    3. {
    4.     private EndSimulationEntityCommandBufferSystem endSimulationBufferSystem;
    5.     private BuildPhysicsWorld physicsWorld;
    6.  
    7.  
    8.     protected override void OnCreate()
    9.     {
    10.         endSimulationBufferSystem = World.GetOrCreateSystem<EndSimulationEntityCommandBufferSystem>();
    11.         physicsWorld = World.GetOrCreateSystem<BuildPhysicsWorld>();
    12.  
    13.     }
    14.  
    15.    
    16.  
    17.     //[RequireComponentTag(typeof(DestroyOnContactWithDamageableComponent))] (As far as I was aware, this is how to use an ECS tag, but I have since been corrected, so now I need to figure out what the difference between a component tag and ECS tag is)
    18.     [BurstCompile]
    19.     public unsafe struct HitDetectionJob : IJobForEachWithEntity<ProjectileData, Translation>
    20.     {
    21.         [ReadOnly] public float deltaTime;
    22.         [ReadOnly] public CollisionWorld collisionWorld;
    23.         public EntityCommandBuffer.Concurrent Buffer;
    24.         [NativeDisableParallelForRestriction] public BufferFromEntity<HitEvent> hitsBufferLookup;
    25.  
    26.         public void Execute(Entity enityProjectile, int index, ref ProjectileData projectileData, ref Translation translation)
    27.         {
    28.             var Hits = new NativeList<Unity.Physics.RaycastHit>(Allocator.Temp);
    29.  
    30.  
    31.             var nextFramePosition = translation.Value;
    32.            
    33.  
    34.             RaycastInput raycastInput = new RaycastInput()
    35.             {
    36.                 Start = translation.Value,
    37.                 End = nextFramePosition,
    38.                 Filter = CollisionFilter.Default
    39.             };
    40.             // Check if Collided With Something
    41.             if (collisionWorld.CastRay(raycastInput, ref Hits))
    42.             {
    43.                 float closestHitDistance = 1000;
    44.  
    45.                 // Assigning an initial value
    46.                 Unity.Physics.RaycastHit hit = Hits[0];
    47.  
    48.                 for (var i = 0; i < Hits.Length; i++)
    49.                 {
    50.                     if (Hits[i].Fraction - closestHitDistance <= 0.0001f)
    51.                     {
    52.                         hit = Hits[i];
    53.                         closestHitDistance = Hits[i].Fraction;
    54.  
    55.                        
    56.                     }
    57.                    
    58.                 }
    59.                 var body = collisionWorld.Bodies[hit.RigidBodyIndex];
    60.                 var entityCollided = body.Entity;
    61.  
    62.                 if (hitsBufferLookup.Exists(entityCollided))
    63.                 {
    64.                     var myBuffer = hitsBufferLookup[entityCollided];
    65.                     myBuffer.Add(new HitEvent { Damage = 10 });
    66.                 }
    67.                 else
    68.                 {
    69.                     var myBuffer=Buffer.AddBuffer<HitEvent>(index, entityCollided);
    70.                     myBuffer.Add(new HitEvent { Damage = 10 });
    71.                 }
    72.  
    73.  
    74.                 Hits.Dispose();
    75.  
    76.                 // Hit Effect should be Instantiated in this Point
    77.                 var hitPosition = hit.Position;
    78.                 Buffer.DestroyEntity(index, enityProjectile);
    79.  
    80.  
    81.             }
    82.         }
    83.        
    84.     }
    85.     protected override JobHandle OnUpdate(JobHandle inputDeps)
    86.     {
    87.  
    88.  
    89.         var job = new HitDetectionJob
    90.         {
    91.  
    92.             deltaTime = Time.deltaTime,
    93.             collisionWorld = physicsWorld.PhysicsWorld.CollisionWorld,
    94.             Buffer = endSimulationBufferSystem.CreateCommandBuffer().ToConcurrent(),
    95.             hitsBufferLookup = GetBufferFromEntity<HitEvent>(false),
    96.            
    97.  
    98.  
    99.         }.Schedule(this, inputDeps);
    100.         job.Complete();
    101.         return job;
    102.  
    103.     }
    104. }
    105.  
    106. [InternalBufferCapacity(16)]
    107. public struct HitEvent : IBufferElementData
    108. {
    109.     public ushort Damage;
    110. }
     
  26. MadboyJames

    MadboyJames

    Joined:
    Oct 28, 2017
    Posts:
    159
    So, The problem seems to be with this chunk of code
    Code (CSharp):
    1. if (hitsBufferLookup.Exists(entityCollided))
    2.                 {
    3.                     var myBuffer = hitsBufferLookup[entityCollided];
    4.                     myBuffer.Add(new HitEvent { Damage = 10 });
    5.                 }
    6.                 else
    7.                 {
    8.                     var myBuffer = Buffer.AddBuffer<HitEvent>(index, entityCollided);
    9.                     myBuffer.Add(new HitEvent { Damage = 10 });
    10.                 }
    With this, I get three errors, in this order
    1: error: Cannot find the field `TypeInfos` required for supporting TypeManager intrinsics in burst
    2: error: Unexpected error while processing function `Unity.Entities.TypeManager.GetTypeInfo<HitEvent>()`. Reason: Value cannot be null.
    3: ArgumentException: The entity does not exist

    The first is saying that burst is not supported for this.
    The second is saying that the HitEvent is null? I'm sure I don't understand that..
    The third is saying the entity (associated with the hitbuffer?) does not exist.
     
  27. thelebaron

    thelebaron

    Joined:
    Jun 2, 2013
    Posts:
    299
    Not sure which error pertains to which line but i had a similar issue last night where the else statement didnt jive and I had to use if(!exists) explicitly for adding an element to a buffer.
    Code (CSharp):
    1.    
    2. //else
    3. if (!hitsBufferLookup.Exists(entityCollided))
    4.  
     
  28. MadboyJames

    MadboyJames

    Joined:
    Oct 28, 2017
    Posts:
    159
    Thanks! It eliminated the second error (and commenting out the burst attribute fixed the first), So now I just need to figure out the entity not existing error.

    Update: Actually removing the Burst attribute fixed the first two errors, not the extra if else.
     
    Last edited: Sep 5, 2019
  29. tertle

    tertle

    Joined:
    Jan 25, 2011
    Posts:
    1,720
    Code (CSharp):
    1. Buffer.AddBuffer<HitEvent>(index, entityCollided);
    you haven't specified which entity to add the buffer to.
     
  30. MadboyJames

    MadboyJames

    Joined:
    Oct 28, 2017
    Posts:
    159
     var myBuffer = Buffer.AddBuffer<HitEvent>(index, entityCollided);
    ?
    Is this not specifying it?
     
  31. tertle

    tertle

    Joined:
    Jan 25, 2011
    Posts:
    1,720
    Oh whoops I missread

    Your issue is simply EntityCommandBuffer can not be used in a burst job at the moment (coming in 2019.3 at some point) outside of create/destroy methods.
     
  32. MadboyJames

    MadboyJames

    Joined:
    Oct 28, 2017
    Posts:
    159
    If I take burst out, I still get a "
    ArgumentException: The entity does not exist
    EntityCommandBuffer was recorded in DestroyOnContactWithDamageableSystem and played back in Unity.Entities.EndSimulationEntityCommandBufferSystem.
    at Unity.Entities.EntityComponentStore.AssertCanAddComponent" error when my objects collide. Any ideas on what's causing that?
     
  33. thelebaron

    thelebaron

    Joined:
    Jun 2, 2013
    Posts:
    299
    I'm assuming its because you need to wait for the ecb to playback and actually add the buffer to the entity and then add an element(the hit event) to it
     
  34. tertle

    tertle

    Joined:
    Jan 25, 2011
    Posts:
    1,720
    You can use the buffer straight away and the populate version will be added to entity during playback.

    There's some other type of logic issue with his work causing the issue.
     
  35. MadboyJames

    MadboyJames

    Joined:
    Oct 28, 2017
    Posts:
    159
    Would the issue be because I'm using the same buffer to delete the projectile and add the hitdamage?
    Code (CSharp):
    1.  if (hitsBufferLookup.Exists(entityCollided))
    2.                 {
    3.                     var myBuffer = hitsBufferLookup[entityCollided];
    4.                     myBuffer.Add(new HitEvent { Damage = 10 });
    5.                 }
    6.                 else
    7.                 {
    8.                     var myBuffer = Buffer.AddBuffer<HitEvent>(index, entityCollided);
    9.                     //Buffer.AddBuffer<HitEvent>(index, entityCollided);
    10.                     myBuffer.Add(new HitEvent { Damage = 10 });
    11.                  
    12.                 }
    13.  
    14.  
    15.                 Hits.Dispose();
    16.  
    17.                 // Hit Effect should be Instantiated in this Point
    18.                 var hitPosition = hit.Position;
    19.                 Buffer.DestroyEntity(index, enityProjectile);
    If I remove
    Buffer.DestroyEntity(index, enityProjectile);
    , I do not get the error. But it is crazy slow (2000+ ms per frame with 1000 entities to do a hitsbufferlookup) (It was crazy slow with
    Buffer.DestroyEntity(index, enityProjectile);
    , but I'd like to figure out why it's so unperformant.)

    UPDATE: It's so unperformant because it lacks Burst. So... is there a burst-compliant way to use hitsbuffer, or does anyone have any suggestions about how to detect/ apply damage to a collided object?
     
    Last edited: Sep 6, 2019
  36. Vicotin

    Vicotin

    Joined:
    Mar 18, 2014
    Posts:
    7
    why the RaycastInput() {Start,End Both =translation.Value?
     
  37. MadboyJames

    MadboyJames

    Joined:
    Oct 28, 2017
    Posts:
    159
    @Vicotin I was just detecting if the object is currently inside another collision object, rather than looking to see where it would end up next frame. I have since implemented a solution using collision events, and have only used the raycasting collision detection for things like my selection box.
     
  38. schaefsky

    schaefsky

    Joined:
    Aug 11, 2013
    Posts:
    27
    Could you post yout solution with collision events (I assume it's for projectiles)?
    I can not seem to get to to work properly.
     
  39. MadboyJames

    MadboyJames

    Joined:
    Oct 28, 2017
    Posts:
    159
    I wont have my computer for a few hours. I used the onCollisionEvent loop. Make sure your collision entities have "raises collision events" checked in the physics body component.
     
    Last edited: Oct 10, 2019
  40. schaefsky

    schaefsky

    Joined:
    Aug 11, 2013
    Posts:
    27
    I got that, but I'm still having the issue that it gets raised multiple times. So if I apply damage and want to delete the projectile it does not work.
     
  41. MadboyJames

    MadboyJames

    Joined:
    Oct 28, 2017
    Posts:
    159
    Hrm, I did not have that issue. I think I used a transformlookup
    var lookup= componentDataFromEntity<Translation>();
    to see
    if(lookup.Exists(CollisionEvent.EntityA)){//apply damage}
    . I cannot remember the exact collision event syntax off of the top of my head. It may just be Event.Entities.EntityA or something. At any rate I looked to see if it had not been destroyed, then I apply damage and destroy it. Alternatively, you could put an invincibility frame when an object takes damage. If collision, if not invincible, then apply damage and set invincible for time.deltaTime (or whatever time you want).
     
  42. MadboyJames

    MadboyJames

    Joined:
    Oct 28, 2017
    Posts:
    159
    @schaefsky Here is the code I use to detect collisions and apply damage.
    Code (CSharp):
    1.     [BurstCompile]
    2.     public struct DamageOnCollision : ICollisionEventsJob
    3.     {
    4.         //Lookup to get the projectile data (damage) from a collision
    5.         [ReadOnly] public ComponentDataFromEntity<ProjectileComponent> projectileData;
    6.         //Lookup to get health data from a collision
    7.        public ComponentDataFromEntity<HealthComponent> healthData;
    8.         //Lookup to get selection data (Owner of bullet and collision object)
    9.         [ReadOnly] public ComponentDataFromEntity<SelectableComponent> selectableLookup;
    10.         //the team of the current player
    11.         public int myTeam;
    12.         //buffer which will execute all add/remove/instanciate/destroy code
    13.         public EntityCommandBuffer buffer;
    14.         public void Execute(CollisionEvent collisionEvent)
    15.         {
    16.             //getting entityA and B to reduce the amount of collisionEvent.Entites calls necessary
    17.             Entity entityA = collisionEvent.Entities.EntityA;
    18.             Entity entityB = collisionEvent.Entities.EntityB;
    19.  
    20.             //checks to see if entityA and B are projectiles (projectiles have projectileData)
    21.             bool aIsProjectile = projectileData.Exists(entityA);
    22.             bool bIsProjectile = projectileData.Exists(entityB);
    23.  
    24.             //checks to see if entityA and B are units (units are able to be selected). It is possible to be both a unit and a projectile
    25.             bool aIsUnit=selectableLookup.Exists(entityA);
    26.             bool bIsUnit=selectableLookup.Exists(entityB);
    27.  
    28.             //if entityA is a projectile and B is a unit...
    29.             if(aIsProjectile&& bIsUnit)
    30.             {
    31.                 //...AND the projectile and unit are NOT of the same team (no freindly fire)...
    32.                 if (selectableLookup[entityB].teamID == myTeam && projectileData[entityA].team == myTeam)
    33.                     return;
    34.                 //...then appy damage through the ApplyDamage() Method
    35.                 ApplyDamage(entityA, entityB);
    36.  
    37.                 //if entityA (the projectile) does not have health data, then destroy the projectile after damage is applied
    38.                 if (!healthData.Exists(entityA))
    39.                     buffer.DestroyEntity(entityA);
    40.                
    41.             }
    42.             //Then do the same as above, but only if entityB is the projectile and A is the unit (two projectiles might collide, so both are necessary)
    43.             //I should turn both of these into a method and call it twice, but this is quick, and it works for now.
    44.             if(bIsProjectile&&aIsUnit)
    45.             {
    46.                 if (selectableLookup[entityA].teamID == myTeam && projectileData[entityB].team == myTeam)
    47.                     return;
    48.  
    49.                 ApplyDamage(entityB, entityA);
    50.  
    51.                 if (!healthData.Exists(entityB))
    52.                     buffer.DestroyEntity(entityB);
    53.  
    54.             }
    55.  
    56.  
    57.  
    58.  
    59.         }
    60.         public void ApplyDamage(Entity projectile, Entity target)
    61.         {
    62.             //If an object is selectable, then it will have health data, so I do not need to do a if(healthdata.exists(target)) check
    63.                     var health = healthData[target];
    64.                     float damage = projectileData[projectile].damage;
    65.                     health.currentHealth -= damage;
    66.                     healthData[target] = health;
    67.              
    68.            
    69.         }
    70.  
    71.     }
     
    schaefsky likes this.