Search Unity

Question How to use triggers?

Discussion in 'Physics for ECS' started by Cell-i-Zenit, Nov 9, 2020.

  1. Cell-i-Zenit

    Cell-i-Zenit

    Joined:
    Mar 11, 2016
    Posts:
    290
    Hi,

    I would like to do something when something triggers a trigger event, but as far as i see i only get entity A & B and they will trigger as long as they overlap. This just feels a little bit to basic and low level. Also since i only get the entities, i somehow need to collect them and in another job assign some values to it.

    Is there a "simple" way of checking if something hits the first time a trigger? Or do i need to program this by myself?

    I looked into the samples, but the code is just way to complicated for my simple usecase. Also iam spawning everything by code and iam not using the authoring components.

    I thought something like this would be exactly what i am trying to do:

    Code (CSharp):
    1. Entites.ForEach((TriggerEvent event, ref CheckpointData data)=>{
    2. ...
    3. EntityManager.RemoveComponent<CheckpointData>() }).Schedule();
    Any advice? Basically iam building a racing game and the player should go into each checkpoint before the finish line, so i need to check if every checkpoint entity got hit. I thought i just mark a checkpoint as reached and then remove it from the "distance calculation" list.

    Currently trying to figure out how to solve this. Thanks

    EDIT: i tried something out, but iam not happy with it as i cannot see how i can make this "fast"

    Have a job like this:

    Code (CSharp):
    1. struct CollisionEventJob : ITriggerEventsJob
    2.   {
    3.     public NativeList<Entity> _hitEntities;
    4.  
    5.     public void Execute(TriggerEvent triggerEvent)
    6.     {
    7.       _hitEntities.Add(triggerEvent.EntityA);
    8.       _hitEntities.Add(triggerEvent.EntityB);
    9.     }
    10.   }
    Then schedule it like this:

    Code (CSharp):
    1. Dependency = collisionEventJob.Schedule(m_StepPhysicsWorldSystem.Simulation,
    2.         ref physicsWorldSystem.PhysicsWorld, Dependency);
    3.       EndFramePhysicsSystem.AddInputDependency(Dependency);
    And now i need iterate over the list and use the entitymanager to lookup if an entity contains a specific component, so i can write to it? This is not scalable right?

    So for me it looks like i should just do the whole thing by hand: calculate the distance between the player and all checkpoint flags by hand..
     
    Last edited: Nov 9, 2020
  2. petarmHavok

    petarmHavok

    Joined:
    Nov 20, 2018
    Posts:
    461
    You can also have the list of trigger events and iterate through that in a foreach loop on your own. Look at Simulation.TriggerEvents and do a simple

    foreach (TriggerEvent triggerEvent in Simulation.TriggerEvents)

    The problem is that we can't correlate component data with a trigger event other than the Entity, because physics runtime data is not in ECS and therefore can't access individual component data. However, we provided the Entity which gives you the window into the ECS world.
     
  3. Cell-i-Zenit

    Cell-i-Zenit

    Joined:
    Mar 11, 2016
    Posts:
    290
    You need to be abit more specific here :)

    i cannot iterate over Simulation.TriggerEvents, since this is not a static variable, how do i get the simulation?
     
  4. steveeHavok

    steveeHavok

    Joined:
    Mar 19, 2019
    Posts:
    481
    Event handling is definitely a different mindset from a polling the Data.
    It generally adds extra information across frames that the core Unity Physics solution is trying to avoid.

    That said, lets see if we can make things easier.
    First you can get the simulation from the StepPhysicsWorld system and loop through the TriggerEvents as @petarmHavok mentioned e.g.

    var simulation = World.GetExistingSystem<StepPhysicsWorld>().Simulation;

    Alternatively, the more basic 2d1. Event - Triggers script could be changed to met your needs. The
    TriggerGravityFactor
    component could become a CheckpointData component, that is added to your checkpoint trigger entity and could contain an
    int Id
    . Then the current
    TriggerGravityFactorJob.Execute
    function could be tweaked to update some VehicleCheckpointData component that is on the
    dynamicEntity
    which is overlapping with the
    triggerEntity
    .

    It might be worth looking at the TinyRacing project use-case for Boost Pads as well.

    These examples are the simplest form of processing the events. You would be updating the vehicle checkpoint data every frame there is an overlap with the trigger. This is a little redundant, but shouldn't be particularly demanding.

    For multiple frame data e.g. Enter, Stay and Exit state, you need to process the raw event data and store extra information. If you copy the DynamicBufferTriggerEventAuthoring script from the samples into your own project and add a
    StatefulTriggerEvent
    dynamic buffer to your Checkpoints and/or your Vehicles you should start seeing those dynamic buffers being populated on overlap. Then your own Checkpoint system can just loop over the appropriate entities that have the dynamic buffer and your VehicleCheckpointData. This should be similar to the ChangeMaterial demo system.

    Hope this helps. I must go back and take another look at the Events demos if they are still causing confusion.
     
    Cell-i-Zenit likes this.
  5. Cell-i-Zenit

    Cell-i-Zenit

    Joined:
    Mar 11, 2016
    Posts:
    290
    Iam on 0.5.1 prev2 and there is no iterator on stepphyiscsworld.Simulation.

    I got the code on the bottom done after checking out the 2.1 repo, thanks for that! :)

    I have some questions about the dependency code. Can you talk a little bit why we need to set the dependencies like that? (or if i did this correctly with the command buffer?)

    Code (CSharp):
    1.  Dependency = collisionEventJob.Schedule(_stepPhysicsWorldSystem.Simulation,
    2.         ref _physicsWorldSystem.PhysicsWorld, Dependency);
    3.       _endFramePhysicsSystem.AddInputDependency(Dependency);
    4.       Dependency.Complete();
    5. _commandBufferSystem.AddJobHandleForProducer(Dependency);
    6.  
    Thanks for your answer

    Code (CSharp):
    1.   [UpdateInGroup(typeof(FixedStepSimulationSystemGroup))]
    2.   [UpdateAfter(typeof(StepPhysicsWorld))]
    3.   [UpdateBefore(typeof(EndFramePhysicsSystem))]
    4.   public class RaceModeSystem : SystemBase
    5.   {
    6.     private BuildPhysicsWorld _physicsWorldSystem;
    7.     private StepPhysicsWorld _stepPhysicsWorldSystem;
    8.     private EndFramePhysicsSystem _endFramePhysicsSystem;
    9.     private EndSimulationEntityCommandBufferSystem _commandBufferSystem;
    10.  
    11.     protected override void OnCreate()
    12.     {
    13.       var entityQueryDesc = new EntityQueryDesc()
    14.         {Any = new ComponentType[] {typeof(CheckpointFlag), typeof(FinishFlag)}};
    15.       RequireForUpdate(GetEntityQuery(entityQueryDesc));
    16.       _physicsWorldSystem = World.GetOrCreateSystem<BuildPhysicsWorld>();
    17.       _stepPhysicsWorldSystem = World.GetOrCreateSystem<StepPhysicsWorld>();
    18.       _endFramePhysicsSystem = World.GetOrCreateSystem<EndFramePhysicsSystem>();
    19.       _commandBufferSystem = World.GetOrCreateSystem<EndSimulationEntityCommandBufferSystem>();
    20.     }
    21.  
    22.     protected override void OnUpdate()
    23.     {
    24.       var collisionEventJob = new CollisionEventJob()
    25.       {
    26.         Finish = GetComponentDataFromEntity<FinishFlag>(),
    27.         Checkpoints = GetComponentDataFromEntity<CheckpointFlag>(),
    28.         CommandBuffer = _commandBufferSystem.CreateCommandBuffer(),
    29.         CheckpointLength = GetEntityQuery(typeof(CheckpointFlag)).CalculateEntityCount()
    30.       };
    31.  
    32.       Dependency = collisionEventJob.Schedule(_stepPhysicsWorldSystem.Simulation,
    33.         ref _physicsWorldSystem.PhysicsWorld, Dependency);
    34.       _endFramePhysicsSystem.AddInputDependency(Dependency);
    35.       Dependency.Complete();
    36.  
    37.       _commandBufferSystem.AddJobHandleForProducer(Dependency);
    38.     }
    39.   }
    40.  
    41.   struct CollisionEventJob : ITriggerEventsJob
    42.   {
    43.     public int CheckpointLength;
    44.     public EntityCommandBuffer CommandBuffer;
    45.     public ComponentDataFromEntity<FinishFlag> Finish;
    46.     public ComponentDataFromEntity<CheckpointFlag> Checkpoints;
    47.  
    48.     public void Execute(TriggerEvent triggerEvent)
    49.     {
    50.       ProcessEntity(triggerEvent.EntityA);
    51.       ProcessEntity(triggerEvent.EntityB);
    52.     }
    53.  
    54.     private void ProcessEntity(Entity entity)
    55.     {
    56.       if (Checkpoints.HasComponent(entity))
    57.       {
    58.         CommandBuffer.RemoveComponent<CheckpointFlag>(entity);
    59.         CommandBuffer.SetComponent(entity, new NonUniformScale() {Value = new float3(0.1f, 0.1f, 0.1f)});
    60.       }
    61.  
    62.       if (Finish.HasComponent(entity) && CheckpointLength == 0)
    63.       {
    64.         CommandBuffer.SetComponent(entity, new NonUniformScale() {Value = new float3(0.1f, 0.1f, 0.1f)});
    65.       }
    66.     }
    67.   }
     
  6. petarmHavok

    petarmHavok

    Joined:
    Nov 20, 2018
    Posts:
    461
    StepPhysicsWorld.Simulation.TriggerEvents can definitely be iterated using the foreach loop in the version you are using. In fact, the ITriggerEventsJob internally relies on this. :)

    Regarding you code, you most certainly don't need DependencyComplete(). You've linked it in all the necessary places, so completing it will just create a sync point for no reason. Does the code work for you? With/without the Complete() call?

    I'd also make this system run after ExportPhysicsWorld, not StepPhysicsWorld. The reason is that Export also writes component data, so you might run into problems.