How do I detect collisions?

How do I detect collisions between entities so I can destroy them on impact?
I'm very new to DOTS, but I managed to get a scene running with about 500 entities floating about and bumping into each other in dots with the Unity Physics package. I also managed to get projectiles to shoot from the camera and the projectiles bump into the entities.

1 Like

The easiest way is to run or schedule a job implementing ICollisionEventsJob or ITriggerEventsJob

Firstly, I need to add a "PhysicsShape" component to objects you want to interact with.

Let's say Entity A and entity B.

Next, search for Collision Response (dropdown field) and select the "Raise Trigger Events" option for entity A&B.

Now, in the same component check, you have in Collision Filter > Belongs To & Collides With to Everything.

At this point, you will need to make 2 different tags using IComponentData and attach one to each entity. (Entity A could be PlayerTag, and Entity B PickupTag)
Example given:

using Unity.Entities;

[GenerateAuthoringComponent]
public struct PlayerTag : IComponentData
{
}
using Unity.Entities;

[GenerateAuthoringComponent]
public struct PickupTag : IComponentData
{
}

Last but not least, a TriggerSystem will be needed, see code:

using Unity.Burst;
using Unity.Collections;
using Unity.Entities;
using Unity.Physics;
using Unity.Physics.Systems;

public partial class TriggerSystem : SystemBase
{
    private EndSimulationEntityCommandBufferSystem endECBSystem;
    private StepPhysicsWorld stepPhysicsWorld;

    protected override void OnCreate()
    {
        endECBSystem = World.GetOrCreateSystem<EndSimulationEntityCommandBufferSystem>();
        stepPhysicsWorld = World.GetOrCreateSystem<StepPhysicsWorld>();
    }

    protected override void OnUpdate()
    {
        var triggerJob = new TriggerJob
        {
            allPickups = GetComponentDataFromEntity<PickupTag>(true),
            allPlayers = GetComponentDataFromEntity<PlayerTag>(),
            ecb = endECBSystem.CreateCommandBuffer()
        };

        Dependency = triggerJob.Schedule(stepPhysicsWorld.Simulation, Dependency);
        endECBSystem.AddJobHandleForProducer(Dependency);
    }
}


[BurstCompile]
struct TriggerJob : ITriggerEventsJob
{
    [ReadOnly] public ComponentDataFromEntity<PickupTag> allPickups;
    public ComponentDataFromEntity<PlayerTag> allPlayers;
    public EntityCommandBuffer ecb;

    public void Execute(TriggerEvent triggerEvent)
    {
        Entity entityA = triggerEvent.EntityA;
        Entity entityB = triggerEvent.EntityB;

        if (allPickups.HasComponent(entityA) && allPickups.HasComponent(entityB)) return;

        if (allPickups.HasComponent(entityA) && allPlayers.HasComponent(entityB))
        {
            ecb.DestroyEntity(entityA);
        }
        else if(allPickups.HasComponent(entityB) && allPlayers.HasComponent(entityA))
        {
            ecb.DestroyEntity(entityB);
        }
    }
}

That should destroy ALL the entities that have the PickupTag script attached to them.

14 Likes

This is how components look like in the editor.


6 Likes

Thank you so much. That was pretty straightforward. I got it working right away!

Why that code isn't part of the documentation among the other examples is beyond me. Thank you for showing us, saved me some time fiddling around.

I still had to troubleshoot why errors are thrown when entities are destroyed in other systems, even though care is being taken to destroy in later entity command buffer systems. Add the following to the example above to fix it:

    [UpdateInGroup(typeof(FixedStepSimulationSystemGroup))]
    [UpdateAfter(typeof(StepPhysicsWorld))]
    [UpdateBefore(typeof(ExportPhysicsWorld))]
    public partial class TriggerSystem : SystemBase

and

        protected override void OnStartRunning()
        {
            this.RegisterPhysicsRuntimeSystemReadOnly();
        }
5 Likes

TL;DR; How to do this correctly (with max performance/parallel) in a real game? and as a nice to have without "huge 'if statement' script"?

This seems to be the most recent tread on the topic. I use the physics system for all interactions, collisions, within fire range, AI (should I move to/away from the target), area of effect damage... and the code just becomes a complete mess because you need to filter all these interactions to determine what type of interaction this is. Followed by either creating a 'processEntity' what will trigger a different system to take care of the logic, or processing it in this already bulky piece of code.
And fair enough determining the interaction will always take some CPU cycles, so doing it yourself is not a big problem. But it seems to schedule on a single thread??

  1. At the bottom I included my current ITriggerEventsJob splitter/processor code. With ifElse structure to determine the interaction that occurred. If I look at this above example of this forum thread... "this is the way" it should be.(? true ?)

  2. To process in Parrallel, I schedule the ITriggerEventsJob and procces nothing only put entity A and B in a NativeList. Followed by a ": IJobParallelFor" to do the splitting/processing and it uses only this NativeList, but I cannot schedule the list in parallel because at the time of scheduling the NativeList length is not known from the ITriggerEventsJob (since its not completed yet). What I do now is just use a big number for the scheduling and Return if the index is bigger than the NativeList length (once the Job starts the list will be filled and the length will be known). Is there a better way to schedule this so it can run in parallel? (This methode works as long as the value you schedule is bigger than the List length. I look at the list length of the previous frame to increase this value)

public EntityCommandBuffer.ParallelWriter ecb;
        [ReadOnly] public NativeList<TriggerObjectsComponent> tempTriggerBufferForJob2;

        public void Execute(int index)
        {
            if (index >= tempTriggerBufferForJob2.Length)
            {              
                return;
            }

            var A = tempTriggerBufferForJob2[index].A;
            var B = tempTriggerBufferForJob2[index].B;

splitting/processing here...
var A =   triggerEvent.EntityA;
var B =  triggerEvent.EntityB;

if (lht.HasComponent(A) || lht.HasComponent(B) || rht.HasComponent(A) || rht.HasComponent(B))
            {
                var newInteraction = ecb.CreateEntity(index);

                var handTrigger = lht.HasComponent(A) || rht.HasComponent(A) ? A : B;
                var objectGrabbed = lht.HasComponent(A) || rht.HasComponent(A) ? B : A;
                var isLefthand = lht.HasComponent(handTrigger);

                if (shopItems.HasComponent(objectGrabbed))
                {
                    ecb.AddComponent(index, newInteraction, new HandShopInteractionTriggerPairComponent() { HandTrigger = handTrigger, ObjectGrabbed = objectGrabbed, isLeftHand = isLefthand });
                }
                else
                {
                    ecb.AddComponent(index, newInteraction, new HandInteractionTriggerPairComponent() { HandTrigger = handTrigger, ObjectGrabbed = objectGrabbed, isLeftHand = isLefthand });
                }
            }
            else if (enemyWithinRangePair.HasComponent(A) || enemyWithinRangePair.HasComponent(B))
            {

                var withinRangeTrigger = enemyWithinRangePair.HasComponent(A) ? A : B;
                var enemy = enemyWithinRangePair.HasComponent(A) ? B : A;

                var unitWithBrain = enemyWithinRangePair[withinRangeTrigger].OwnerOfTrigger;

                if (otherBrainLinks.HasComponent(unitWithBrain))
                {
                    var brain = otherBrainLinks[unitWithBrain].brainEntity;
                    var preferedAction = otherPreferredFireActionComponent[unitWithBrain];                   
                    ecb.AppendToBuffer(index, brain, new BrainActionInputData() { myName = preferedAction.preferredFireAction, target = enemy });
                }
            }   
            else if (hitEffects.HasComponent(A) || hitEffects.HasComponent(B))
            {
                if (hitEffects.HasComponent(A) && hitEffects.HasComponent(B))
                {
                    Debug.Log("Beide Objecten hebben hit effects, PhysicsTriggerSystem");
                }
                else
                {
                    var hitEffectsTrigger = hitEffects.HasComponent(A) ? A : B;
                    var hitObject = hitEffects.HasComponent(A) ? B : A;

                    var bufferLength = hitEffects[hitEffectsTrigger].Length;

                    for (int i = 0; i < bufferLength; i++)
                    {
                        var instanceHitEffect = ecb.Instantiate(index, hitEffects[hitEffectsTrigger][i].hitEffectPrefab);

                        ecb.SetComponent(index, instanceHitEffect, new Translation() { Value = translation[hitEffectsTrigger].Value });
                        ecb.SetComponent(index, instanceHitEffect, new TargetHitComponent() { Target = hitObject });
                        ecb.SetComponent(index, instanceHitEffect, new OwnerComponent() { Owner = hitEffectsTrigger });

                        if (otherRootOwners.HasComponent(hitEffectsTrigger) && otherRootOwners.HasComponent(hitEffects[hitEffectsTrigger][i].hitEffectPrefab))
                        {

                            ecb.SetComponent(index, instanceHitEffect, new RootOwnerComponent() { RootOwner = otherRootOwners[hitEffectsTrigger].RootOwner });
                        }

                        ecb.SetComponent(index, instanceHitEffect, statProperties[hitEffectsTrigger]);
                    }

                    if (!otherDestroyTag.HasComponent(hitEffectsTrigger))
                    {                       
                        ecb.AddComponent(index, hitEffectsTrigger, new DestroyComponentTag());
                    }
                }
            }
            else if (towerTag.HasComponent(A) && towerTag.HasComponent(B))
            {
                if (towercollisionbufferup.HasComponent(A) && towercollisionbufferup.HasComponent(B))
                {
                    var minimmalHeightDifferenceForTowerInteraction = 0.5f;
                    if (math.abs(translation[A].Value.y - translation[B].Value.y) > minimmalHeightDifferenceForTowerInteraction)
                    {
                        if (translation[A].Value.y < translation[B].Value.y)
                        {
                            ecb.AppendToBuffer(index, A, new ConnectedTowerBufferUpElement() { connectedTowerUp = B });
                            ecb.AppendToBuffer(index, B, new ConnectedTowerBufferDownElement() { connectedTowerDown = A });
                        }
                        else
                        {
                            ecb.AppendToBuffer(index, B, new ConnectedTowerBufferUpElement() { connectedTowerUp = A });
                            ecb.AppendToBuffer(index, A, new ConnectedTowerBufferDownElement() { connectedTowerDown = B });
                        }
                    }
                }
            }
            else if (resourceField.HasComponent(A) || resourceField.HasComponent(B))
            {
                var resource = resourceField.HasComponent(A) ? A : B;
                var turret = resourceField.HasComponent(A) ? B : A;

                var newInteraction = ecb.CreateEntity(index);

                ecb.AddComponent(index, newInteraction, new PoolMinerTriggerPairComponent() { ResourcePool = resource, OwnerOfTrigger = turret, Trigger = turret });

            }

Hey, sorry to bother, but I've been looking everywhere for usage example on DOTS 1.0 for how to run an
ITriggerEventsJob

These no longer exist:

  • [UpdateInGroup(typeof(FixedStepSimulationSystemGroup))]
  • [UpdateAfter(typeof(StepPhysicsWorld))]
  • [UpdateBefore(typeof(ExportPhysicsWorld))]

Is this the correct usage instead? Is this when physics updates should run?
[UpdateAfter(typeof(Unity.Physics.Systems.PhysicsSimulationGroup))]

Then, how do you actually schedule a ITriggerEventsJob in ECS 1.0? Here:

       public void OnUpdate(ref SystemState state)
        {
            var ecbSingleton = SystemAPI.GetSingleton<EndSimulationEntityCommandBufferSystem.Singleton>();
            var ecb = ecbSingleton.CreateCommandBuffer(state.WorldUnmanaged);
            var jhandle = new PositionalWarpingJob
            {
                ecb = ecb,
            };
            // Dependency = triggerJob.Schedule(stepPhysicsWorld.Simulation, Dependency);
            // endECBSystem.AddJobHandleForProducer(Dependency);
            state.Dependency = jhandle.Schedule(Unity.Physics.SimulationSingleton ??, state.Dependency);

        }

I have no idea where to search for this stuff (https://docs.unity3d.com/Packages/com.unity.entities@1.0/manual/index.html) and even if I found something, it doesn't have usage examples. So please help :)


[UPDATE]
Last resort idea / the new way to search the "unity docs":

Step 1: download the EntityComponentSystemSamples-master github project
Step 2: open notepad++, Find In Files: ITriggerEventsJob, extension: *.cs, path: EntityComponentSystemSamples-master\PhysicsSamples\Assets
Step 3:

state.Dependency = new TriggerGravityFactorJob
        {
            TriggerGravityFactorGroup = m_Handles.TriggerGravityFactorGroup,
            PhysicsGravityFactorGroup = m_Handles.PhysicsGravityFactorGroup,
            PhysicsVelocityGroup = m_Handles.PhysicsVelocityGroup,
        }.Schedule(SystemAPI.GetSingleton<SimulationSingleton>(), state.Dependency);

That project is set up with the weirdest folder structure and scenes, and I completely missed ITriggerEventsJob. Also it doesn't come up when just searching within unity or in the folders. You need notepad++ ;)

PS:

[UpdateInGroup(typeof(FixedStepSimulationSystemGroup))]
[UpdateAfter(typeof(PhysicsSystemGroup))]

@Tudor : Please have a look at the PhysicsSamples project. Under demos, there is a specific section about events:

https://github.com/Unity-Technologies/EntityComponentSystemSamples/tree/master/PhysicsSamples/Assets/6. Events

In these demos, an ITriggerEventsJob is triggered here as an example.
Note that CollectTriggerEventsJob derives from ITriggerEventsJob.

Please also take a look at the trigger events section in the Unity Physics documentation:
https://docs.unity3d.com/Packages/com.unity.physics@1.0/manual/simulation-results.html#trigger-events

1 Like

Note stuff like the PhysicsShape has been effectively deprecated by moving it into part of the samples, rather than in the API. I believe the expectation is to add rigidbodies as "usual" as part of the authoring process.

2 Likes

It makes me a bit moany that all the links above are now broken.

1 Like

I updated the links and description. The samples project was changed recently.
Thanks for pointing out the broken links.

2 Likes

Great with the corrections but could you also provide an update as to the state of PhysicsBody and PhysicsShape are they deprecated or soon to be deprecated?. Should one then go with RigidBodys and Colliders and more importantly if so how would you go about handling collisions in burst compiled ECS code?

(PS We would really like to avoid importing a “Sample” in our production code base :wink:

Ping ping :-)

Question is really:
How can I leverage collision filters in ECS on an entity that has only Ridigbody and Collider and not PhysicsBody and PhysicsShape? (I still really want to avoid importing Unity Physics Samples project code in our production code ;-).

Can it be done in a similar way to how one currently have to handle e.g. InverseInertia and InverseMass by making a custom system to bake the values from the Rigidbody to the PhysicsMass component?

I simply cannot find a way to set the BelongsTo and CollidesWith collision filters (are they also only part of the Samples code) and using classic Layers Overrides does not work at all.

Any one?

Based on a reply from tangell here in Unity forum I managed to get everything working.

I ended up creating a CollisionFilterAuthoring component with two custom (flagged enum) properties and called them BelongsTo and CollidesWith which when put on a Prefab will set a TemporaryBakingType on the entity and then a CustomCollisionFilterSystem that picks this up and sets the collision filters on the PhysicsCollider (I set everything is set up to run only when baking systems run)

Of course this bypasses the built-in layers functionality but if you wish to use that, the same setup can easily be adapted to tap into Include Layers, Exclude Layers and GetLayer() of the Prefab and set Collision Filters accordingly.

Link to post: https://forum.unity.com/threads/entities-1-1-experimental-release-now-available.1494323/#post-9404102

1 Like

Hi @te_headfirst ! First off, sorry for the delay.

I am glad you found a solution in your case.

We are acutely aware of the fact that some important features in built-in physics are not yet baked into Unity Physics (e.g., the collider layer overrides), and analogously that some features in the custom authoring components are not available in the built-in physics component interfaces (e.g., the physics shape's "Force Unique" property). Rest assured that we are working on multiple fronts right now and this is one of them.

In the meantime, many thanks for sharing your solution as it will help others that are in a similar solution until we have remedied this situation fully.

1 Like

Another thing.
In accordance to what I said above about us chipping away at this literally "half baked" situation (pardon the pun :)) continuously, I am happy to let you know that the mass overrides from the RigidBody authoring component are now correctly baked into the PhysicsMass ECS component since 1.1.
I believe you are referring to the properties below, correct?

9407462--1317209--upload_2023-10-13_9-58-54.png

1 Like

Yup, it was the Inertia properties :slight_smile:

The plan is to go for 1.1 when it is “fully baked” :wink: and stay on 1.0 until then.

1 Like