示例#1
0
        protected override void OnUpdate()
        {
            var enemyCount  = m_EnemyQuery.CalculateEntityCount();
            var targetCount = m_TargetQuery.CalculateEntityCount();

            EntityManager.GetAllUniqueSharedComponentData(m_UniqueTypes);

            // Each variant of the Boid represents a different value of the SharedComponentData and is self-contained,
            // meaning Boids of the same variant only interact with one another. Thus, this loop processes each
            // variant type individually.
            for (int boidVariantIndex = 0; boidVariantIndex < m_UniqueTypes.Count; boidVariantIndex++)
            {
                var settings = m_UniqueTypes[boidVariantIndex];
                m_BoidQuery.AddSharedComponentFilter(settings);

                var boidCount = m_BoidQuery.CalculateEntityCount();

                if (boidCount == 0)
                {
                    // Early out. If the given variant includes no Boids, move on to the next loop.
                    // For example, variant 0 will always exit early bc it's it represents a default, uninitialized
                    // Boid struct, which does not appear in this sample.
                    m_BoidQuery.ResetFilter();
                    continue;
                }

                // The following calculates spatial cells of neighboring Boids
                // note: working with a sparse grid and not a dense bounded grid so there
                // are no predefined borders of the space.

                var hashMap                 = new NativeMultiHashMap <int, int>(boidCount, Allocator.TempJob);
                var cellIndices             = new NativeArray <int>(boidCount, Allocator.TempJob, NativeArrayOptions.UninitializedMemory);
                var cellEnemyPositionIndex  = new NativeArray <int>(boidCount, Allocator.TempJob, NativeArrayOptions.UninitializedMemory);
                var cellTargetPositionIndex = new NativeArray <int>(boidCount, Allocator.TempJob, NativeArrayOptions.UninitializedMemory);
                var cellCount               = new NativeArray <int>(boidCount, Allocator.TempJob, NativeArrayOptions.UninitializedMemory);
                var cellEnemyDistance       = new NativeArray <float>(boidCount, Allocator.TempJob, NativeArrayOptions.UninitializedMemory);
                var cellAlignment           = new NativeArray <float3>(boidCount, Allocator.TempJob, NativeArrayOptions.UninitializedMemory);
                var cellSeparation          = new NativeArray <float3>(boidCount, Allocator.TempJob, NativeArrayOptions.UninitializedMemory);
                var raycastInputs           = new NativeArray <RaycastInput>(boidCount, Allocator.TempJob, NativeArrayOptions.UninitializedMemory);
                var initialRaycastResults   = new NativeArray <RaycastHit>(boidCount, Allocator.TempJob, NativeArrayOptions.UninitializedMemory);
                var unobstructedDirections  = new NativeArray <float3>(boidCount, Allocator.TempJob, NativeArrayOptions.UninitializedMemory);
                var copyTargetPositions     = new NativeArray <float3>(targetCount, Allocator.TempJob, NativeArrayOptions.UninitializedMemory);
                var copyEnemyPositions      = new NativeArray <float3>(enemyCount, Allocator.TempJob, NativeArrayOptions.UninitializedMemory);
                // The following jobs all run in parallel because the same JobHandle is passed for their
                // input dependencies when the jobs are scheduled; thus, they can run in any order (or concurrently).
                // The concurrency is property of how they're scheduled, not of the job structs themselves.

                // These jobs extract the relevant position, heading component
                // to NativeArrays so that they can be randomly accessed by the `MergeCells` and `Steer` jobs.
                // These jobs are defined inline using the Entities.ForEach lambda syntax.
                var initialCellAlignmentJobHandle = Entities
                                                    .WithSharedComponentFilter(settings)
                                                    .WithName("InitialCellAlignmentJob")
                                                    .ForEach((int entityInQueryIndex, in LocalToWorld localToWorld) =>
                {
                    cellAlignment[entityInQueryIndex] = localToWorld.Forward;
                })
                                                    .ScheduleParallel(Dependency);

                var initialCellSeparationJobHandle = Entities
                                                     .WithSharedComponentFilter(settings)
                                                     .WithName("InitialCellSeparationJob")
                                                     .ForEach((int entityInQueryIndex, in LocalToWorld localToWorld) =>
                {
                    cellSeparation[entityInQueryIndex] = localToWorld.Position;
                })
                                                     .ScheduleParallel(Dependency);

                var initialRaycastInputsJobHandle = Entities
                                                    .WithSharedComponentFilter(settings)
                                                    .WithName("InitialRaycastInputsJob")
                                                    .ForEach((int entityInQueryIndex, in LocalToWorld localToWorld) =>
                {
                    raycastInputs[entityInQueryIndex] = new RaycastInput
                    {
                        Start  = localToWorld.Position,
                        End    = localToWorld.Position + (localToWorld.Forward * settings.OuterDetectionRadius),
                        Filter = new CollisionFilter
                        {
                            BelongsTo    = ~0u,
                            CollidesWith = 1,     // Environment layer
                            GroupIndex   = 0
                        },
                    };
                })
                                                    .ScheduleParallel(Dependency);

                var world = World.DefaultGameObjectInjectionWorld.GetExistingSystem <Unity.Physics.Systems.BuildPhysicsWorld>().PhysicsWorld.CollisionWorld;
                var batchRaycastJobHandle = ScheduleBatchRayCast(world, raycastInputs, initialRaycastResults, initialRaycastInputsJobHandle);

                var findUnobstructedDirectionsJobHandle = Entities
                                                          .WithName("FindUnobstructedDirectionsJob")
                                                          .WithSharedComponentFilter(settings)
                                                          .ForEach((int entityInQueryIndex, in LocalToWorld localToWorld, in DynamicBuffer <Float3BufferElement> buffer) =>
                {
                    JobLogger.Log("In find unobstructed job");
                    float3 bestDir    = float3.zero;
                    float furthestHit = 0f;
                    RaycastHit hit;
                    DynamicBuffer <float3> float3buffer = buffer.Reinterpret <float3>();
                    for (int i = 0; i < float3buffer.Length; i++)
                    {
                        float3 end         = localToWorld.Position + ((math.mul(localToWorld.Value, new float4(float3buffer[i], 1)) * settings.OuterDetectionRadius)).xyz;
                        RaycastInput input = new RaycastInput()
                        {
                            Start  = localToWorld.Position,
                            End    = end,
                            Filter = new CollisionFilter
                            {
                                BelongsTo    = ~0u,
                                CollidesWith = 1,     // Environment layer
                                GroupIndex   = 0
                            },
                        };
                        if (world.CastRay(input, out hit))
                        {
                            var dist = math.distance(hit.Position, localToWorld.Position);
                            if (dist > furthestHit)
                            {
                                bestDir     = hit.Position - localToWorld.Position;
                                furthestHit = dist;
                                JobLogger.Log("Found a better way");
                            }
                        }
                        else     // this direction is unobstructed, return
                        {
                            unobstructedDirections[entityInQueryIndex] = hit.Position - localToWorld.Position;
                            JobLogger.Log("Found a way");
                            return;
                        }
                    }
                    unobstructedDirections[entityInQueryIndex] = bestDir;
                }).ScheduleParallel(batchRaycastJobHandle);
示例#2
0
        protected override void OnUpdate()
        {
            var obstacleCount = m_ObstacleQuery.CalculateEntityCount();
            var targetCount   = m_TargetQuery.CalculateEntityCount();

            EntityManager.GetAllUniqueSharedComponentData(m_UniqueTypes);

            // Each variant of the Boid represents a different value of the SharedComponentData and is self-contained,
            // meaning Boids of the same variant only interact with one another. Thus, this loop processes each
            // variant type individually.
            for (int boidVariantIndex = 0; boidVariantIndex < m_UniqueTypes.Count; boidVariantIndex++)
            {
                var settings = m_UniqueTypes[boidVariantIndex];
                m_BoidQuery.AddSharedComponentFilter(settings);

                var boidCount = m_BoidQuery.CalculateEntityCount();

                if (boidCount == 0)
                {
                    // Early out. If the given variant includes no Boids, move on to the next loop.
                    // For example, variant 0 will always exit early bc it's it represents a default, uninitialized
                    // Boid struct, which does not appear in this sample.
                    m_BoidQuery.ResetFilter();
                    continue;
                }

                // The following calculates spatial cells of neighboring Boids
                // note: working with a sparse grid and not a dense bounded grid so there
                // are no predefined borders of the space.

                var hashMap     = new NativeMultiHashMap <int, int>(boidCount, Allocator.TempJob);
                var cellIndices = new NativeArray <int>(boidCount, Allocator.TempJob, NativeArrayOptions.UninitializedMemory);
                var cellObstaclePositionIndex = new NativeArray <int>(boidCount, Allocator.TempJob, NativeArrayOptions.UninitializedMemory);
                var cellTargetPositionIndex   = new NativeArray <int>(boidCount, Allocator.TempJob, NativeArrayOptions.UninitializedMemory);
                var cellCount            = new NativeArray <int>(boidCount, Allocator.TempJob, NativeArrayOptions.UninitializedMemory);
                var cellObstacleDistance = new NativeArray <float>(boidCount, Allocator.TempJob, NativeArrayOptions.UninitializedMemory);
                var cellAlignment        = new NativeArray <float3>(boidCount, Allocator.TempJob, NativeArrayOptions.UninitializedMemory);
                var cellSeparation       = new NativeArray <float3>(boidCount, Allocator.TempJob, NativeArrayOptions.UninitializedMemory);

                var copyTargetPositions   = new NativeArray <float3>(targetCount, Allocator.TempJob, NativeArrayOptions.UninitializedMemory);
                var copyObstaclePositions = new NativeArray <float3>(obstacleCount, Allocator.TempJob, NativeArrayOptions.UninitializedMemory);

                // The following jobs all run in parallel because the same JobHandle is passed for their
                // input dependencies when the jobs are scheduled; thus, they can run in any order (or concurrently).
                // The concurrency is property of how they're scheduled, not of the job structs themselves.

                // These jobs extract the relevant position, heading component
                // to NativeArrays so that they can be randomly accessed by the `MergeCells` and `Steer` jobs.
                // These jobs are defined inline using the Entities.ForEach lambda syntax.
                var initialCellAlignmentJobHandle = Entities
                                                    .WithSharedComponentFilter(settings)
                                                    .WithName("InitialCellAlignmentJob")
                                                    .ForEach((int entityInQueryIndex, in LocalToWorld localToWorld) =>
                {
                    cellAlignment[entityInQueryIndex] = localToWorld.Forward;
                })
                                                    .ScheduleParallel(Dependency);

                var initialCellSeparationJobHandle = Entities
                                                     .WithSharedComponentFilter(settings)
                                                     .WithName("InitialCellSeparationJob")
                                                     .ForEach((int entityInQueryIndex, in LocalToWorld localToWorld) =>
                {
                    cellSeparation[entityInQueryIndex] = localToWorld.Position;
                })
                                                     .ScheduleParallel(Dependency);

                var copyTargetPositionsJobHandle = Entities
                                                   .WithName("CopyTargetPositionsJob")
                                                   .WithAll <BoidTarget>()
                                                   .WithStoreEntityQueryInField(ref m_TargetQuery)
                                                   .ForEach((int entityInQueryIndex, in LocalToWorld localToWorld) =>
                {
                    copyTargetPositions[entityInQueryIndex] = localToWorld.Position;
                })
                                                   .ScheduleParallel(Dependency);

                var copyObstaclePositionsJobHandle = Entities
                                                     .WithName("CopyObstaclePositionsJob")
                                                     .WithAll <BoidObstacle>()
                                                     .WithStoreEntityQueryInField(ref m_ObstacleQuery)
                                                     .ForEach((int entityInQueryIndex, in LocalToWorld localToWorld) =>
                {
                    copyObstaclePositions[entityInQueryIndex] = localToWorld.Position;
                })
                                                     .ScheduleParallel(Dependency);

                // Populates a hash map, where each bucket contains the indices of all Boids whose positions quantize
                // to the same value for a given cell radius so that the information can be randomly accessed by
                // the `MergeCells` and `Steer` jobs.
                // This is useful in terms of the algorithm because it limits the number of comparisons that will
                // actually occur between the different boids. Instead of for each boid, searching through all
                // boids for those within a certain radius, this limits those by the hash-to-bucket simplification.
                var parallelHashMap        = hashMap.AsParallelWriter();
                var hashPositionsJobHandle = Entities
                                             .WithName("HashPositionsJob")
                                             .WithAll <Boid>()
                                             .ForEach((int entityInQueryIndex, in LocalToWorld localToWorld) =>
                {
                    var hash = (int)math.hash(new int3(math.floor(localToWorld.Position / settings.CellRadius)));
                    parallelHashMap.Add(hash, entityInQueryIndex);
                })
                                             .ScheduleParallel(Dependency);

                var initialCellCountJob = new MemsetNativeArray <int>
                {
                    Source = cellCount,
                    Value  = 1
                };
                var initialCellCountJobHandle = initialCellCountJob.Schedule(boidCount, 64, Dependency);

                var initialCellBarrierJobHandle        = JobHandle.CombineDependencies(initialCellAlignmentJobHandle, initialCellSeparationJobHandle, initialCellCountJobHandle);
                var copyTargetObstacleBarrierJobHandle = JobHandle.CombineDependencies(copyTargetPositionsJobHandle, copyObstaclePositionsJobHandle);
                var mergeCellsBarrierJobHandle         = JobHandle.CombineDependencies(hashPositionsJobHandle, initialCellBarrierJobHandle, copyTargetObstacleBarrierJobHandle);

                var mergeCellsJob = new MergeCells
                {
                    cellIndices               = cellIndices,
                    cellAlignment             = cellAlignment,
                    cellSeparation            = cellSeparation,
                    cellObstacleDistance      = cellObstacleDistance,
                    cellObstaclePositionIndex = cellObstaclePositionIndex,
                    cellTargetPositionIndex   = cellTargetPositionIndex,
                    cellCount         = cellCount,
                    targetPositions   = copyTargetPositions,
                    obstaclePositions = copyObstaclePositions
                };
                var mergeCellsJobHandle = mergeCellsJob.Schedule(hashMap, 64, mergeCellsBarrierJobHandle);

                // This reads the previously calculated boid information for all the boids of each cell to update
                // the `localToWorld` of each of the boids based on their newly calculated headings using
                // the standard boid flocking algorithm.
                float deltaTime      = math.min(0.05f, Time.DeltaTime);
                var   steerJobHandle = Entities
                                       .WithName("Steer")
                                       .WithSharedComponentFilter(settings) // implies .WithAll<Boid>()
                                       .WithReadOnly(cellIndices)
                                       .WithReadOnly(cellCount)
                                       .WithReadOnly(cellAlignment)
                                       .WithReadOnly(cellSeparation)
                                       .WithReadOnly(cellObstacleDistance)
                                       .WithReadOnly(cellObstaclePositionIndex)
                                       .WithReadOnly(cellTargetPositionIndex)
                                       .WithReadOnly(copyObstaclePositions)
                                       .WithReadOnly(copyTargetPositions)
                                       .ForEach((int entityInQueryIndex, ref LocalToWorld localToWorld) =>
                {
                    // temporarily storing the values for code readability
                    var forward                      = localToWorld.Forward;
                    var currentPosition              = localToWorld.Position;
                    var cellIndex                    = cellIndices[entityInQueryIndex];
                    var neighborCount                = cellCount[cellIndex];
                    var alignment                    = cellAlignment[cellIndex];
                    var separation                   = cellSeparation[cellIndex];
                    var nearestObstacleDistance      = cellObstacleDistance[cellIndex];
                    var nearestObstaclePositionIndex = cellObstaclePositionIndex[cellIndex];
                    var nearestTargetPositionIndex   = cellTargetPositionIndex[cellIndex];
                    var nearestObstaclePosition      = copyObstaclePositions[nearestObstaclePositionIndex];
                    var nearestTargetPosition        = copyTargetPositions[nearestTargetPositionIndex];

                    // Setting up the directions for the three main biocrowds influencing directions adjusted based
                    // on the predefined weights:
                    // 1) alignment - how much should it move in a direction similar to those around it?
                    // note: we use `alignment/neighborCount`, because we need the average alignment in this case; however
                    // alignment is currently the summation of all those of the boids within the cellIndex being considered.
                    var alignmentResult = settings.AlignmentWeight
                                          * math.normalizesafe((alignment / neighborCount) - forward);
                    // 2) separation - how close is it to other boids and are there too many or too few for comfort?
                    // note: here separation represents the summed possible center of the cell. We perform the multiplication
                    // so that both `currentPosition` and `separation` are weighted to represent the cell as a whole and not
                    // the current individual boid.
                    var separationResult = settings.SeparationWeight
                                           * math.normalizesafe((currentPosition * neighborCount) - separation);
                    // 3) target - is it still towards its destination?
                    var targetHeading = settings.TargetWeight
                                        * math.normalizesafe(nearestTargetPosition - currentPosition);

                    // creating the obstacle avoidant vector s.t. it's pointing towards the nearest obstacle
                    // but at the specified 'ObstacleAversionDistance'. If this distance is greater than the
                    // current distance to the obstacle, the direction becomes inverted. This simulates the
                    // idea that if `currentPosition` is too close to an obstacle, the weight of this pushes
                    // the current boid to escape in the fastest direction; however, if the obstacle isn't
                    // too close, the weighting denotes that the boid doesnt need to escape but will move
                    // slower if still moving in that direction (note: we end up not using this move-slower
                    // case, because of `targetForward`'s decision to not use obstacle avoidance if an obstacle
                    // isn't close enough).
                    var obstacleSteering     = currentPosition - nearestObstaclePosition;
                    var avoidObstacleHeading = (nearestObstaclePosition + math.normalizesafe(obstacleSteering)
                                                * settings.ObstacleAversionDistance) - currentPosition;

                    // the updated heading direction. If not needing to be avoidant (ie obstacle is not within
                    // predefined radius) then go with the usual defined heading that uses the amalgamation of
                    // the weighted alignment, separation, and target direction vectors.
                    var nearestObstacleDistanceFromRadius = nearestObstacleDistance - settings.ObstacleAversionDistance;
                    var normalHeading = math.normalizesafe(alignmentResult + separationResult + targetHeading);
                    var targetForward = math.select(normalHeading, avoidObstacleHeading, nearestObstacleDistanceFromRadius < 0);

                    // updates using the newly calculated heading direction
                    var nextHeading = math.normalizesafe(forward + deltaTime * (targetForward - forward));
                    localToWorld    = new LocalToWorld
                    {
                        Value = float4x4.TRS(
                            new float3(localToWorld.Position + (nextHeading * settings.MoveSpeed * deltaTime)),
                            quaternion.LookRotationSafe(nextHeading, math.up()),
                            new float3(1.0f, 1.0f, 1.0f))
                    };
                }).ScheduleParallel(mergeCellsJobHandle);

                // Dispose allocated containers with dispose jobs.
                Dependency = steerJobHandle;
                var disposeJobHandle = hashMap.Dispose(Dependency);
                disposeJobHandle = JobHandle.CombineDependencies(disposeJobHandle, cellIndices.Dispose(Dependency));
                disposeJobHandle = JobHandle.CombineDependencies(disposeJobHandle, cellObstaclePositionIndex.Dispose(Dependency));
                disposeJobHandle = JobHandle.CombineDependencies(disposeJobHandle, cellTargetPositionIndex.Dispose(Dependency));
                disposeJobHandle = JobHandle.CombineDependencies(disposeJobHandle, cellCount.Dispose(Dependency));
                disposeJobHandle = JobHandle.CombineDependencies(disposeJobHandle, cellObstacleDistance.Dispose(Dependency));
                disposeJobHandle = JobHandle.CombineDependencies(disposeJobHandle, cellAlignment.Dispose(Dependency));
                disposeJobHandle = JobHandle.CombineDependencies(disposeJobHandle, cellSeparation.Dispose(Dependency));
                disposeJobHandle = JobHandle.CombineDependencies(disposeJobHandle, copyObstaclePositions.Dispose(Dependency));
                disposeJobHandle = JobHandle.CombineDependencies(disposeJobHandle, copyTargetPositions.Dispose(Dependency));
                Dependency       = disposeJobHandle;

                // We pass the job handle and add the dependency so that we keep the proper ordering between the jobs
                // as the looping iterates. For our purposes of execution, this ordering isn't necessary; however, without
                // the add dependency call here, the safety system will throw an error, because we're accessing multiple
                // pieces of boid data and it would think there could possibly be a race condition.

                m_BoidQuery.AddDependency(Dependency);
                m_BoidQuery.ResetFilter();
            }
            m_UniqueTypes.Clear();
        }
示例#3
0
    protected override void OnCreate()
    {
        _endSimulationEcbSystem = World.GetOrCreateSystem <EndSimulationEntityCommandBufferSystem>();
        _unitSelectSystem       = World.GetOrCreateSystem <UnitSelectionPrepSystem>();

        _walkableLayer = 1u << 0;

        _unitQuery = GetEntityQuery(
            ComponentType.ReadOnly(typeof(UnitTag)),
            ComponentType.ReadWrite(typeof(TargetPosition)),
            ComponentType.ReadOnly(typeof(FormationIndex)),
            ComponentType.ReadOnly(typeof(Translation))
            );

        _selectedUnitQuery = GetEntityQuery(
            ComponentType.ReadOnly(typeof(UnitTag)),
            ComponentType.ReadOnly(typeof(TargetPosition)),
            ComponentType.ReadOnly(typeof(Translation)),
            ComponentType.ReadOnly(typeof(FormationIndex)),
            ComponentType.ReadOnly(typeof(SelectedFormationSharedComponent)),
            ComponentType.ReadOnly(typeof(FormationGroup))
            );
        _selectedUnitQuery.AddSharedComponentFilter(new SelectedFormationSharedComponent {
            IsSelected = true
        });


        _selectedFormationUnitQuery = GetEntityQuery(
            ComponentType.ReadOnly(typeof(UnitTag)),
            ComponentType.ReadOnly(typeof(FormationIndex)),
            ComponentType.ReadOnly(typeof(SelectedFormationSharedComponent)),
            ComponentType.ReadOnly(typeof(Translation))
            );
        _selectedFormationUnitQuery.AddSharedComponentFilter(new SelectedFormationSharedComponent {
            IsSelected = true
        });


        _unitDragIndicatorQuery = GetEntityQuery(
            ComponentType.ReadOnly(typeof(DragIndicatorTag))
            );

        _selectedDragIndicatorQuery = GetEntityQuery(
            ComponentType.ReadOnly(typeof(DragIndicatorTag)),
            ComponentType.ReadWrite(typeof(SelectedFormationSharedComponent))
            );
        _selectedDragIndicatorQuery.AddSharedComponentFilter(new SelectedFormationSharedComponent {
            IsSelected = true
        });

        var enabledDragIndicatorQueryDesc = new EntityQueryDesc {
            None = new ComponentType[] { typeof(DisableRendering) },
            All  = new ComponentType[] { typeof(DragIndicatorTag) }
        };

        _enabledDragIndicatorQuery = GetEntityQuery(enabledDragIndicatorQueryDesc);

        _disabledDragIndicatorQuery = GetEntityQuery(
            ComponentType.ReadOnly(typeof(DragIndicatorTag)),
            ComponentType.ReadWrite(typeof(DisableRendering))
            );
    }