JobSystem使用範例二:動態新增移除Transform單位,尋找最近目標

Flamesky發表於2024-09-04
如何試用 TransformAccessArray | 新增/移除 Transform 到 TransformAccessArray中執行
24gevbpq93uREUwegh9u32ibqae9uyshdfbwep23ugo99832ewuTW$Ewwt
以下是尋找最近目標的示例。
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Jobs;
using Unity.Mathematics;
using Unity.Jobs;
using Unity.Collections;
using Unity.Profiling;

public class FindNearestTargets : MonoBehaviour
{
    public List<Transform>
        troopsToAdd = new List<Transform>() ,
        troopsToRemove = new List<Transform>() ,
        targetsToAdd = new List<Transform>() ,
        targetsToRemove = new List<Transform>();
    TransformAccessArray _troopsDataAccess, _targetsDataAccess;
    NativeList<float3> _troopPositions, _targetPositions;
    NativeList<int> _nearestTargetIndices;
    public JobHandle Dependency;
    ProfilerMarker
        ___proc_results = new ProfilerMarker("process job results") ,
        ___maintain_data_access = new ProfilerMarker("maintan data access") ,
        ___schedule_new = new ProfilerMarker("schedule jobs") ,
        ___schedule = new ProfilerMarker("schedule calls");
    const float k_tick_rate = 0.4f;// tick every 0.4s
    void OnEnable ()
    {
        InvokeRepeating( nameof(Tick) , k_tick_rate , k_tick_rate );
        _troopsDataAccess = new TransformAccessArray( 32 );
        _targetsDataAccess = new TransformAccessArray( 32 );
        _troopPositions = new NativeList<float3>( 32 , Allocator.Persistent );
        _targetPositions = new NativeList<float3>( 32 , Allocator.Persistent );
        _nearestTargetIndices = new NativeList<int>( 32 , Allocator.Persistent );
    }
    void OnDisable ()
    {
        CancelInvoke();
        Dependency.Complete();
        if( _troopsDataAccess.isCreated ) _troopsDataAccess.Dispose();
        if( _targetsDataAccess.isCreated ) _targetsDataAccess.Dispose();
        if( _troopPositions.IsCreated ) _troopPositions.Dispose();
        if( _targetPositions.IsCreated ) _targetPositions.Dispose();
        if( _nearestTargetIndices.IsCreated ) _nearestTargetIndices.Dispose();
    }
    #if UNITY_EDITOR
    void OnDrawGizmos ()
    {
        if( !Application.isPlaying ) return;
        Gizmos.color = Color.blue;
        for( int i=_troopsDataAccess.length-1 ; i!=-1 ; i-- )
            Gizmos.DrawWireCube( _troopsDataAccess[i].position , Vector3.one );
        Gizmos.color = Color.red;
        for( int i=_targetsDataAccess.length-1 ; i!=-1 ; i-- )
            Gizmos.DrawWireSphere( _targetsDataAccess[i].position , 1f );
    }
    #endif
    void Tick ()
    {
        Dependency.Complete();

        // process results
        ___proc_results.Begin();
        {
            // don't do this like I'm doing here, it's just for quick & dirty viz
            // i.e. avoid reading/writing from native collection outside jobs, it very slow (especially in big loops)

            int numResults = _nearestTargetIndices.Length;
            for( int i=0 ; i<numResults ; i++ )
            {
                int nearestTargetIndex = _nearestTargetIndices[i];
                Debug.DrawLine( _troopPositions[i]  , _targetPositions[nearestTargetIndex] , Color.yellow , k_tick_rate );
                // Transform troop = _troopsDataAccess[i];
                // Transform target = _targetsDataAccess[nearestTargetIndex];
            }
        }
        ___proc_results.End();

        // TransformAccessArray maintainance
        ___maintain_data_access.Begin();
        {
            int numTroopsToRemove = troopsToRemove.Count;
            if( numTroopsToRemove!=0 )
            {
                for( int i=0 ; i<numTroopsToRemove ; i++ )
                {
                    Transform next = troopsToRemove[i];
                    if( next!=null )
                    for( int k=_troopsDataAccess.length-1 ; k!=-1 ; k-- )
                    {
                        if( _troopsDataAccess[k]==next )
                        {
                            _troopsDataAccess.RemoveAtSwapBack( k );
                            break;
                        }
                    }
                }
                troopsToRemove.Clear();
            }
            
            int numTroopsToAdd = troopsToAdd.Count;
            if( numTroopsToAdd!=0 )
            {
                for( int i=0 ; i<numTroopsToAdd ; i++ )
                    if( troopsToAdd[i]!=null )
                        _troopsDataAccess.Add( troopsToAdd[i] );
                troopsToAdd.Clear();
            }

            int numTargetsToRemove = targetsToRemove.Count;
            if( numTargetsToRemove!=0 )
            {
                for( int i=0 ; i<numTargetsToRemove ; i++ )
                {
                    Transform next = targetsToRemove[i];
                    if( next!=null )
                    for( int k=_targetsDataAccess.length-1 ; k!=-1 ; k-- )
                    {
                        if( _targetsDataAccess[k]==next )
                        {
                            _targetsDataAccess.RemoveAtSwapBack( k );
                            break;
                        }
                    }
                }
                targetsToRemove.Clear();
            }

            int numTargetsToAdd = targetsToAdd.Count;
            if( numTargetsToAdd!=0 )
            {
                for( int i=0 ; i<numTargetsToAdd ; i++ )
                    if( targetsToAdd[i]!=null )
                        _targetsDataAccess.Add( targetsToAdd[i] );
                targetsToAdd.Clear();
            }
        }
        ___maintain_data_access.End();

        // schedule new jobs:
        ___schedule_new.Begin();
        int numTroops = _troopsDataAccess.length;
        int numTargets = _targetsDataAccess.length;
        if( numTroops!=0 && numTargets!=0 )
        {
            ___schedule.Begin();
            _troopPositions.Length = numTroops;
            _nearestTargetIndices.Length = numTroops;
            _targetPositions.Length = numTargets;
            
            JobHandle troopPositionsJobHandle = new ReadPositionsJob{ Results = _troopPositions }
                .Schedule( _troopsDataAccess );
            JobHandle targetPositionsJobHandle = new ReadPositionsJob{ Results = _targetPositions }
                .Schedule( _targetsDataAccess );
            
            var findNearestTargetJob = new FindNearestTargetJob
            {
                Origins = _troopPositions ,
                Targets = _targetPositions ,
                Results = _nearestTargetIndices ,
            };
            Dependency = JobHandle.CombineDependencies( troopPositionsJobHandle , targetPositionsJobHandle , Dependency );
            Dependency = findNearestTargetJob.Schedule(
                arrayLength:            numTroops ,
                innerloopBatchCount:    math.max( numTroops/SystemInfo.processorCount/4 , 1 ) ,
                dependsOn:                Dependency
            );
            ___schedule.End();
        }
        ___schedule_new.End();
    }
}

[Unity.Burst.BurstCompile]
public struct ReadPositionsJob : IJobParallelForTransform
{
    [WriteOnly] public NativeArray<float3> Results;
    void IJobParallelForTransform.Execute ( int index , TransformAccess transform )
        => Results[index] = transform.position;
}

[Unity.Burst.BurstCompile]
public struct FindNearestTargetJob : IJobParallelFor
{
    [ReadOnly] public NativeList<float3> Origins;// reads one per jobIndex
    [ReadOnly][NativeDisableParallelForRestriction] public NativeList<float3> Targets;// reads all per jobIndex
    [WriteOnly] public NativeArray<int> Results;
    void IJobParallelFor.Execute ( int jobIndex )
    {
        float3 origin = Origins[jobIndex];
        int candidate = -1;
        float candidateDistSq = float.PositiveInfinity;
        for( int i=0 ; i<Targets.Length ; i++ )
        {
            float distSq = math.distancesq( origin , Targets[i] );
            if( distSq<candidateDistSq )
            {
                candidateDistSq = distSq;
                candidate = i;
            }
        }
        Results[jobIndex] = candidate;
    }
}

相關文章