Search Unity

Trigger fires immediately in setup only if Entity has a parent entity

Discussion in 'Physics for ECS' started by hellaeon, Sep 27, 2020.

  1. hellaeon

    hellaeon

    Joined:
    Jul 20, 2010
    Posts:
    90
    Hi All,

    First off, I realised I had posted this in the main forum, and it really now seems to be specifically with the physics system firing immediately.

    Original post: https://forum.unity.com/threads/component-behaves-oddly-in-setup-if-it-has-a-parent-entity.973413/

    The problem I am having:
    • The objects I care about can have 1 of 2 states. Infected/Not infected.
    • Each object is spawned in a non infected state.
    • Some numbers for an example - Out of 100 spawned objects, 1 is infected, all the others are not infected.
    • Because at least one object is infected, a trigger system detects when the objects physics triggers touch and I spread the infection this way.
    • This works fine, until I put a parent entity on each child.
    • Now in the first frame, the trigger system fires as if every object is touching.
    • If I leave the parent component but turn off the move system I do not get the same problem.
    • From this I assume something about the move system must be causing the issue, but it leans towards something around the timing of transforms being set or something similar.
    My ECS manager, this runs at the start to set up the scene. This is where I attach the parent object


    Code (CSharp):
    1. [UpdateAfter(typeof(LocationEntity))]
    2.     public class HumansECSManager : MonoBehaviour
    3.     {
    4.         EntityManager manager;
    5.         public GameObject HumanPrefab;
    6.         public int EntitiesPerLocation = 100;
    7.  
    8.         BlobAssetStore store;
    9.         // Start is called before the first frame update
    10.         void Start()
    11.         {
    12.             store = new BlobAssetStore();
    13.             manager = World.DefaultGameObjectInjectionWorld.EntityManager;
    14.             var settings = GameObjectConversionSettings.FromWorld(World.DefaultGameObjectInjectionWorld, store);
    15.             var human = GameObjectConversionUtility.ConvertGameObjectHierarchy(HumanPrefab, settings);
    16.  
    17.             // lets grab all our locations now
    18.             var query = new EntityQueryDesc()
    19.             {
    20.                 All = new ComponentType[]
    21.                 {
    22.                     ComponentType.ReadOnly<LocationIndex>(),
    23.                 }
    24.             };
    25.  
    26.             EntityQuery m_Group = manager.CreateEntityQuery(query);
    27.             var locations = m_Group.ToComponentDataArray<LocationIndex>(Allocator.TempJob);
    28.             var locationEntities = m_Group.ToEntityArray(Allocator.TempJob);
    29.  
    30.             // now we can set up our entites
    31.             int totalLocations = locations.Length;
    32.             int patientZero = UnityEngine.Random.Range(0, totalLocations * EntitiesPerLocation);
    33.  
    34.             for (int x = 0; x < totalLocations; x++)
    35.             {
    36.                 // each location could be smaller or bigger
    37.                 float locationSize = (100 - 1) / 2.0f;
    38.  
    39.                 for (int y = 0; y < EntitiesPerLocation; y++)
    40.                 {
    41.                     var instance = manager.Instantiate(human);
    42.                     float startX = UnityEngine.Random.Range(-1 * locationSize, locationSize);
    43.                     float startZ = UnityEngine.Random.Range(-1 * locationSize, locationSize);
    44.                     float endX = UnityEngine.Random.Range(-1 * locationSize, locationSize);
    45.                     float endZ = UnityEngine.Random.Range(-1 * locationSize, locationSize);
    46.                     float speed = UnityEngine.Random.Range(2.0f, 4.0f);
    47.                     float3 startPos = new Vector3(startX, 0, startZ);
    48.                     float3 endPos = new Vector3(endX, 0, endZ);
    49.                     Vector3 travelVector = endPos - startPos;
    50.  
    51.                     manager.SetComponentData(instance, new HumanMovement
    52.                     {
    53.                         Speed = speed,
    54.                         TravelVector = math.normalize(travelVector) * speed
    55.                     });
    56.  
    57.                     manager.SetComponentData(instance, new HumanLocationData
    58.                     {
    59.                         Location = locations[x].index
    60.                     });
    61.  
    62.                     manager.SetComponentData(instance, new HumanPositioning
    63.                     {
    64.                         StartPos = startPos,
    65.                         EndPos = endPos,
    66.                         distance = math.distance(endPos, startPos)
    67.                     });
    68.  
    69.                     bool infected = ((x * EntitiesPerLocation) + y == patientZero);
    70.                     manager.SetComponentData(instance, new HumanInfection
    71.                     {
    72.                         isAsymptomatic = false,
    73.                         isInfected = infected,
    74.                         infectionAmount = infected ? 100.0f : 0.0f
    75.                     });
    76.  
    77.                     manager.AddComponentData(instance, new Parent
    78.                     {
    79.                         Value = locationEntities[x]
    80.                     });
    81.  
    82.                     float4x4 f4x4 = float4x4.identity;
    83.                     manager.AddComponentData(instance, new LocalToParent() { Value = f4x4 });
    84.                     manager.SetComponentData(instance, new Translation { Value = startPos });
    85.  
    86.                 }
    87.             }
    88.             locations.Dispose();
    89.             locationEntities.Dispose();
    90.         }
    91.  
    92.         private void OnDestroy()
    93.         {
    94.             store.Dispose();
    95.         }
    96.     }


    My move system
    Code (CSharp):
    1. [UpdateInGroup(typeof(MainLocationDirectorGroup))]
    2.     public class HumanMoveSystem : SystemBase
    3.     {
    4.         protected override void OnUpdate()
    5.         {
    6.             float deltaTime = Time.DeltaTime;
    7.             var randomArray = World.GetExistingSystem<RandomSystem>().RandomArray;
    8.             float locationSize = 98 / 2.0f;
    9.  
    10.             Entities
    11.                 .WithName("HumanGetNewLocationSystem")
    12.                 .WithAny<HumanTag>()
    13.                 .WithNativeDisableParallelForRestriction(randomArray)
    14.                 .WithBurst()
    15.                 .ForEach((int nativeThreadIndex,
    16.                     ref Translation position,
    17.                     ref HumanMovement humanMovementData,
    18.                     ref HumanLocationData humanLocation,
    19.                     ref HumanPositioning humanPositioning) =>
    20.                 {
    21.  
    22.                     position.Value += humanMovementData.TravelVector * deltaTime;
    23.                     float distance = math.distance(humanPositioning.EndPos, position.Value);
    24.                     humanPositioning.distance = distance;
    25.                     var random = randomArray[nativeThreadIndex];
    26.                     if (distance < 1f)
    27.                     {
    28.  
    29.                         float change = random.NextFloat(0, 100);
    30.                         if (change > 90.0f)
    31.                             humanLocation.changeDestination = true;
    32.                         else
    33.                         {
    34.                             humanPositioning.StartPos = humanPositioning.EndPos;
    35.                             float distanceCheck;
    36.  
    37.                             do
    38.                             {
    39.                                 float x = random.NextFloat(-1 * locationSize, locationSize);
    40.                                 float z = random.NextFloat(-1 * locationSize, locationSize);
    41.                                 humanPositioning.EndPos = new float3(x, 0, z);
    42.                                 distanceCheck = math.distance(humanPositioning.StartPos, humanPositioning.EndPos);
    43.                                 float3 travelVector = humanPositioning.EndPos - humanPositioning.StartPos;
    44.                                 humanMovementData.TravelVector = math.normalize(travelVector) * humanMovementData.Speed;
    45.                             }
    46.                             while (distanceCheck < locationSize / 2.0f);
    47.                         }
    48.                     }
    49.  
    50.                     randomArray[nativeThreadIndex] = random;
    51.  
    52.                 })
    53.                 .ScheduleParallel();
    54.         }
    55.     }


    Finally the trigger system

    Code (CSharp):
    1. [UpdateInGroup(typeof(FixedStepSimulationSystemGroup))]
    2.     [UpdateAfter(typeof(EndFramePhysicsSystem))]
    3.  
    4.     public class InfectionTriggerDetectionSystem : SystemBase
    5.     {
    6.         // we need a couple of extra worlds that have finished simulating
    7.         BuildPhysicsWorld physicsWorld;
    8.         StepPhysicsWorld stepWorld;
    9.  
    10.         protected override void OnCreate()
    11.         {
    12.             physicsWorld = World.GetOrCreateSystem<BuildPhysicsWorld>();
    13.             stepWorld = World.GetOrCreateSystem<StepPhysicsWorld>();
    14.         }
    15.  
    16.         [BurstCompile]
    17.         struct BoxTriggerEventJob : ITriggerEventsJob
    18.         {
    19.             public ComponentDataFromEntity<HumanInfection> InfectionGroup;
    20.             public float SpreadPercentageLevel;
    21.  
    22.             public void Execute(TriggerEvent triggerEvent)
    23.             {
    24.                 Entity entityA = triggerEvent.EntityA;
    25.                 Entity entityB = triggerEvent.EntityB;
    26.  
    27.                 var componentA = InfectionGroup[entityA];
    28.                 var componentB = InfectionGroup[entityB];
    29.  
    30.                 bool infectedA = componentA.isInfected && componentA.infectionAmount > 0;
    31.                 bool infectedB = componentB.isInfected && componentB.infectionAmount > 0;
    32.                 if (infectedA || infectedB)
    33.                 {
    34.                     componentA.isInfected = true;
    35.                     if (componentB.infectionAmount > 0)
    36.                         componentA.infectionAmount += componentB.infectionAmount * SpreadPercentageLevel;
    37.                     componentA.infectionAmount = math.min(100.0f, componentA.infectionAmount);
    38.  
    39.                     componentB.isInfected = true;
    40.                     if (componentA.infectionAmount > 0)
    41.                         componentB.infectionAmount += componentA.infectionAmount * SpreadPercentageLevel;
    42.                     componentB.infectionAmount = math.min(100.0f, componentB.infectionAmount);
    43.                    
    44.                     InfectionGroup[entityA] = componentA;
    45.                     InfectionGroup[entityB] = componentB;
    46.                 }
    47.             }
    48.         }
    49.  
    50.         protected override void OnUpdate()
    51.         {
    52.             Dependency = new BoxTriggerEventJob
    53.             {
    54.                 InfectionGroup = GetComponentDataFromEntity<HumanInfection>(),
    55.                 SpreadPercentageLevel = Config.INFECTION_PERCENTAGE
    56.             }.Schedule(stepWorld.Simulation,
    57.             ref physicsWorld.PhysicsWorld, Dependency);
    58.         }
    59.     }


    For sanity - here is my player loop
    upload_2020-9-27_23-8-20.png

    I feel its to do with when systems run and I am unsure what to do next. Is there some extra thing I need to wait for because it has a parent object?

    I have tried a number of things including ordering the trigger system before or after move, and it made no difference. I am stuck with the physics wanting to be updated in the Simulation group, yet the parent entity seems to create havoc with the physics.

    Can anyone point me in the right direction or perhaps do you have any suggestions?
     
  2. petarmHavok

    petarmHavok

    Joined:
    Nov 20, 2018
    Posts:
    461
    The player loop doesn't represent what I'm seeing in code, your trigger system is not in the correct group. Is that just another try or did this end up being wrong for some reason?
     
  3. hellaeon

    hellaeon

    Joined:
    Jul 20, 2010
    Posts:
    90
    @petarmHavok Thanks for replying.

    Sorry about that, that was me stuffing about. This is the correct latest version, according to the code there and other small changes.
    upload_2020-9-28_20-35-41.png


    To explain a few other items as well:

    'LocationEntity' is a class that has some game objects in my scene that I run so it can set up a bunch of stuff at start time. These entities are the ones I use in the ECS manager query - (they have a 'LocationIndex').
    These locations are the parents of the entities used in the human move system as you can see in that initial setup loop.
    • HumanMoveSystem moves the entities as you can see,
    • SetNewLocation will test whether we want to move this specific entity to a new location. That actually reparents the entity to the new location
    • ShowInfectionMaterialSystem checks to see if the entity is infected, if so, it updates the material
    • Finally, InformationSystem queries the entities to see how many are in locations and infected.
    I have actually had this running before. I also had this issue and somehow fixed it (obviously did not) and now I want to understand what I should do.

    In particular I am not great with the order of when things should happen. For instance I assume physics should happen at that time, and I think I might need to have some job dependency set up.

    • Does UpdateAfter guarantee that it will wait for that job to finish?
    But just to re-iterate, I don't have the same problem if I remove the Parent and LocalToParent components. An alternative is I get that parent Matrix and use that instead but I don't know the maths of getting the parents matrix and taking that into account with my own....

    cheers
     
    Last edited: Sep 28, 2020
  4. petarmHavok

    petarmHavok

    Joined:
    Nov 20, 2018
    Posts:
    461
    UpdateAfter only guarantees systems will run in the order you want them. For jobs to be linked, you need to use the Dependency property (any class extending SystemBase has it). Apart from that, all physics systems have AddInputDependency() and GetOutputDependency() so you can link to them as well.