Unity-EntitiesSamples-Tornado

发布于 2024-08-14  14 次阅读


一天应该写不完,应该是今明两天2024.8.13)

运行效果

代码解读

ParticleAuthoring

using UnityEngine;
using Unity.Entities;

namespace Tutorials.Tornado
{
    public class ParticleAuthoring : MonoBehaviour
    {
        class Baker : Baker<ParticleAuthoring>
        {
            public override void Bake(ParticleAuthoring authoring)
            {
                var entity = GetEntity(TransformUsageFlags.Dynamic);
                AddComponent<Particle>(entity);
            }
        }
    }

    public struct Particle : IComponentData
    {
        public float radiusMult;
    }
}

附在龙卷风粒子的预制体上,radiusMult决定受到力的大小。

BarAuthoring

using UnityEngine;
using Unity.Entities;

namespace Tutorials.Tornado
{
    public class BarAuthoring : MonoBehaviour
    {
        class Baker : Baker<BarAuthoring>
        {
            public override void Bake(BarAuthoring authoring)
            {
                var entity = GetEntity(TransformUsageFlags.Dynamic);
                AddComponent<Bar>(entity);
                AddComponent<BarThickness>(entity);
            }
        }
    }

    public struct Bar : IComponentData
    {
        public int pointA;
        public int pointB;
        public float length;
    }

    public struct BarThickness : IComponentData
    {
        public float Value;
    }
}

附在bar预制体上,记录bar两端的坐标和自身的长度,还有厚度。

ConfigAuthoring

using Unity.Entities;
using UnityEngine;
using UnityEngine.Serialization;

namespace Tutorials.Tornado
{
    public class ConfigAuthoring : MonoBehaviour
    {
        public GameObject BarPrefab;
        [Range(0f, 1f)] public float BarDamping;
        [Range(0f, 1f)] public float BarFriction;
        public float BarBreakResistance;

        [Range(0f, 1f)] public float TornadoForce;
        public float TornadoMaxForceDist;
        public float TornadoHeight;
        public float TornadoUpForce;
        public float TornadoInwardForce;

        public GameObject ParticlePrefab;
        public float ParticleSpinRate;
        public float ParticleUpwardSpeed;

        class Baker : Baker<ConfigAuthoring>
        {
            public override void Bake(ConfigAuthoring authoring)
            {
                var entity = GetEntity(TransformUsageFlags.Dynamic);
                AddComponent(entity, new Config
                {
                    BarPrefab = GetEntity(authoring.BarPrefab, TransformUsageFlags.Dynamic),
                    BarDamping = authoring.BarDamping,
                    BarFriction = authoring.BarFriction,
                    BarBreakResistance = authoring.BarBreakResistance,

                    TornadoForce = authoring.TornadoForce,
                    TornadoMaxForceDist = authoring.TornadoMaxForceDist,
                    TornadoHeight = authoring.TornadoHeight,
                    TornadoUpForce = authoring.TornadoUpForce,
                    TornadoInwardForce = authoring.TornadoInwardForce,

                    ParticlePrefab = GetEntity(authoring.ParticlePrefab, TransformUsageFlags.Dynamic),
                    ParticleSpinRate = authoring.ParticleSpinRate,
                    ParticleUpwardSpeed = authoring.ParticleUpwardSpeed
                });
            }
        }
    }

    public struct Config : IComponentData
    {
        public Entity BarPrefab;
        public float BarDamping;
        public float BarFriction;
        public float BarBreakResistance;

        public float TornadoForce;
        public float TornadoMaxForceDist;
        public float TornadoHeight;
        public float TornadoUpForce;
        public float TornadoInwardForce;

        public Entity ParticlePrefab;
        public float ParticleSpinRate;
        public float ParticleUpwardSpeed;
    }
}

记录设置,附在subScene的空物体内,将其转换成entity并附上data。

BuildingSpawnSystem

using Unity.Burst;
using Unity.Collections;
using Unity.Entities;
using Unity.Mathematics;
using Unity.Rendering;
using Unity.Scenes;

namespace Tutorials.Tornado
{
    [UpdateInGroup(typeof(InitializationSystemGroup))]
    [UpdateAfter(typeof(SceneSystemGroup))]
    public partial struct BuildingSpawnSystem : ISystem
    {
        [BurstCompile]
        public void OnCreate(ref SystemState state)
        {
            state.RequireForUpdate<Config>();
        }

        [BurstCompile]
        public void OnUpdate(ref SystemState state)
        {
            var config = SystemAPI.GetSingleton<Config>();
            var random = Random.CreateFromIndex(1234);

            var points = new NativeList<float3>(Allocator.Temp);
            var connectivity = new NativeList<byte>(Allocator.Temp);

            // buildings
            for (int i = 0; i < 35; i++)
            {
                int height = random.NextInt(4, 12);
                var pos = new float3(random.NextFloat(-45f, 45f), 0f, random.NextFloat(-45f, 45f));
                float spacing = 2f;
                var anchor = byte.MaxValue;
                for (int j = 0; j < height; j++)
                {
                    points.Add(new float3(pos.x + spacing, j * spacing, pos.z - spacing));
                    connectivity.Add(anchor);

                    points.Add(new float3(pos.x - spacing, j * spacing, pos.z - spacing));
                    connectivity.Add(anchor);

                    points.Add(new float3(pos.x, j * spacing, pos.z + spacing));
                    connectivity.Add(anchor);

                    anchor = 0;
                }
            }

            // ground details
            for (int i = 0; i < 600; i++)
            {
                var posA = new float3(random.NextFloat(-55f, 55f), 0f, random.NextFloat(-55f, 55f));
                var posB = posA;

                posA.x += random.NextFloat(-.2f, -.1f);
                posA.y += random.NextFloat(0f, 3f);
                posA.z += random.NextFloat(.1f, .2f);
                points.Add(posA);

                connectivity.Add(0);

                posB.x += random.NextFloat(.2f, .1f);
                posB.y += random.NextFloat(0f, .2f);
                posB.z += random.NextFloat(-.1f, -.2f);
                points.Add(posB);

                if (random.NextFloat() < .1f)
                    connectivity.Add(byte.MaxValue);
                else
                    connectivity.Add(0);
            }

            var pointCount = points.Length;

            void IncreaseConnectivity(int index)
            {
                var value = connectivity[index];
                if (value < byte.MaxValue)
                {
                    connectivity[index] = (byte)(value + 1);
                }
            }

            var bars = new NativeList<Bar>(Allocator.Temp);
            var colors = new NativeList<float4>(Allocator.Temp);

            for (int i = 0; i < points.Length; i++)
            {
                for (int j = i + 1; j < points.Length; j++)
                {
                    var delta = points[j] - points[i];
                    var lengthsq = math.lengthsq(delta);
                    if (lengthsq < 5f * 5f && lengthsq > .2f * .2f)
                    {
                        IncreaseConnectivity(i);
                        IncreaseConnectivity(j);

                        var length = math.sqrt(lengthsq);
                        bars.Add(new Bar { pointA = i, pointB = j, length = length });

                        float upDot = math.acos(math.abs(math.dot(new float3(0, 1, 0), delta / length))) / math.PI;
                        colors.Add(new float4(new float3(upDot * random.NextFloat(.7f, 1f)), 1f));
                    }
                }
            }

            var dsParent = new NativeArray<int>(points.Length, Allocator.Temp);
            var dsSize = new NativeArray<int>(points.Length, Allocator.Temp);

            for (int i = 0; i < dsParent.Length; i++)
            {
                dsParent[i] = i;
                dsSize[i] = 1;
            }

            int FindRoot(int i)
            {
                if (dsParent[i] == i) return i;
                dsParent[i] = FindRoot(dsParent[i]);
                return dsParent[i];
            }

            for (int i = 0; i < bars.Length; i++)
            {
                var a = FindRoot(bars[i].pointA);
                var b = FindRoot(bars[i].pointB);

                if (a == b) continue;

                if (dsSize[a] < dsSize[b])
                {
                    (a, b) = (b, a);
                }

                dsParent[b] = a;
                dsSize[a] += dsSize[b];
            }

            var pointData = new PointArrays
            {
                current = new NativeArray<float3>(bars.Length * 2, Allocator.Persistent),
                previous = new NativeArray<float3>(bars.Length * 2, Allocator.Persistent),
                connectivity = new NativeArray<byte>(bars.Length * 2, Allocator.Persistent),
                count = new NativeReference<int>(pointCount, Allocator.Persistent)
            };

            for (int i = 0; i < points.Length; i++)
            {
                pointData.current[i] = points[i];
                pointData.previous[i] = points[i];
                pointData.connectivity[i] = connectivity[i];
            }

            state.EntityManager.Instantiate(config.BarPrefab, bars.Length, Allocator.Temp);
            var query = SystemAPI.QueryBuilder().WithAll<Bar, URPMaterialPropertyBaseColor>().Build();
            query.CopyFromComponentDataArray(bars.AsArray());
            query.CopyFromComponentDataArray(colors.AsArray().Reinterpret<URPMaterialPropertyBaseColor>());

            foreach (var thickness in SystemAPI.Query<RefRW<BarThickness>>())
            {
                thickness.ValueRW.Value = random.NextFloat(.25f, .35f);
            }

            var barEntities = query.ToEntityArray(Allocator.Temp);

            for (int i = 0; i < bars.Length; i++)
            {
                var cluster = new BarCluster { Value = FindRoot(bars[i].pointA) };
                state.EntityManager.AddSharedComponent(barEntities[i], cluster);
            }

            var singletonEntity = SystemAPI.GetSingletonEntity<Config>();
            state.EntityManager.AddComponentData(singletonEntity, pointData);

            state.Enabled = false;
        }

        [BurstCompile]
        public void OnDestroy(ref SystemState state)
        {
            if (SystemAPI.HasSingleton<PointArrays>())
            {
                var points = SystemAPI.GetSingleton<PointArrays>();
                points.current.Dispose();
                points.previous.Dispose();
                points.connectivity.Dispose();
                points.count.Dispose();
            }
        }
    }

    public struct PointArrays : IComponentData
    {
        public NativeArray<float3> current;
        public NativeArray<float3> previous;
        public NativeArray<byte> connectivity;
        public NativeReference<int> count;
    }

    public struct BarCluster : ISharedComponentData
    {
        public int Value;
    }
}

该代码负责生成点集,根据点集生成bar,还有anchor锚点。

拆分Update:

            var config = SystemAPI.GetSingleton<Config>();
            var random = Random.CreateFromIndex(1234);

            var points = new NativeList<float3>(Allocator.Temp);
            var connectivity = new NativeList<byte>(Allocator.Temp);

            // buildings
            for (int i = 0; i < 35; i++)
            {
                int height = random.NextInt(4, 12);
                var pos = new float3(random.NextFloat(-45f, 45f), 0f, random.NextFloat(-45f, 45f));
                float spacing = 2f;
                var anchor = byte.MaxValue;
                for (int j = 0; j < height; j++)
                {
                    points.Add(new float3(pos.x + spacing, j * spacing, pos.z - spacing));
                    connectivity.Add(anchor);

                    points.Add(new float3(pos.x - spacing, j * spacing, pos.z - spacing));
                    connectivity.Add(anchor);

                    points.Add(new float3(pos.x, j * spacing, pos.z + spacing));
                    connectivity.Add(anchor);

                    anchor = 0;
                }
            }

            // ground details
            for (int i = 0; i < 600; i++)
            {
                var posA = new float3(random.NextFloat(-55f, 55f), 0f, random.NextFloat(-55f, 55f));
                var posB = posA;

                posA.x += random.NextFloat(-.2f, -.1f);
                posA.y += random.NextFloat(0f, 3f);
                posA.z += random.NextFloat(.1f, .2f);
                points.Add(posA);

                connectivity.Add(0);

                posB.x += random.NextFloat(.2f, .1f);
                posB.y += random.NextFloat(0f, .2f);
                posB.z += random.NextFloat(-.1f, -.2f);
                points.Add(posB);

                if (random.NextFloat() < .1f)
                    connectivity.Add(byte.MaxValue);
                else
                    connectivity.Add(0);
            }

获取单例Config,获得配置。创建名为points和connectivity的NativeList,用于储存点集和锚点。两个容器一一对应。

points储存点的坐标信息,connectivity储存锚点耐久度(?)

第一层锚点的数值均为byte.MaxValue,后续均为0.

第一个for循环用于生成三角柱的点集,第二个for循环生成零散建筑的点集。若将第二个for循环注释,结果如下:

干净
            var pointCount = points.Length;

            void IncreaseConnectivity(int index)
            {
                var value = connectivity[index];
                if (value < byte.MaxValue)
                {
                    connectivity[index] = (byte)(value + 1);
                }
            }

            var bars = new NativeList<Bar>(Allocator.Temp);
            var colors = new NativeList<float4>(Allocator.Temp);

            for (int i = 0; i < points.Length; i++)
            {
                for (int j = i + 1; j < points.Length; j++)
                {
                    var delta = points[j] - points[i];
                    var lengthsq = math.lengthsq(delta);
                    if (lengthsq < 5f * 5f && lengthsq > .2f * .2f)
                    {
                        IncreaseConnectivity(i);
                        IncreaseConnectivity(j);

                        var length = math.sqrt(lengthsq);
                        bars.Add(new Bar { pointA = i, pointB = j, length = length });

                        float upDot = math.acos(math.abs(math.dot(new float3(0, 1, 0), delta / length))) / math.PI;
                        colors.Add(new float4(new float3(upDot * random.NextFloat(.7f, 1f)), 1f));
                    }
                }
            }

bars用于存储bar的数据,后续正式生成bar的entity时将该数据覆盖到entity内。colors同理。

两层for循环用于遍历所有点对(两个点相互配对),若两点距离小于一定值则根据两点生成bar。将两点两端的坐标在points对应的index记录到bar数据内,bar长度记录到bar数据内,存在bars内。点若为bar的一部分,则使用IncreaseConnectivity(int index)增加对应的connectivity。

根据bar的垂直程度改变bar颜色。越垂直越黑,即越接近(0,0,0,1)。

            var dsParent = new NativeArray<int>(points.Length, Allocator.Temp);
            var dsSize = new NativeArray<int>(points.Length, Allocator.Temp);

            for (int i = 0; i < dsParent.Length; i++)
            {
                dsParent[i] = i;
                dsSize[i] = 1;
            }

            int FindRoot(int i)
            {
                if (dsParent[i] == i) return i;
                dsParent[i] = FindRoot(dsParent[i]);
                return dsParent[i];
            }

            for (int i = 0; i < bars.Length; i++)
            {
                var a = FindRoot(bars[i].pointA);
                var b = FindRoot(bars[i].pointB);

                if (a == b) continue;

                if (dsSize[a] < dsSize[b])
                {
                    (a, b) = (b, a);
                }

                dsParent[b] = a;
                dsSize[a] += dsSize[b];
            }

创建NativeArray类型的dsParent和dsSize,用于后续创建disJoinitSet(记录相连的bar)。其中dsParent归类所有disJointSet,用相同的数字(同类中index最小的数字)表示同类。dsSize表示disJointSet中各类的大小,记录在index为该类在dsParent内的数字。

后续根据分类创建ISharedComponentData

            var pointData = new PointArrays
            {
                current = new NativeArray<float3>(bars.Length * 2, Allocator.Persistent),
                previous = new NativeArray<float3>(bars.Length * 2, Allocator.Persistent),
                connectivity = new NativeArray<byte>(bars.Length * 2, Allocator.Persistent),
                count = new NativeReference<int>(pointCount, Allocator.Persistent)
            };

            for (int i = 0; i < points.Length; i++)
            {
                pointData.current[i] = points[i];
                pointData.previous[i] = points[i];
                pointData.connectivity[i] = connectivity[i];
            }

            state.EntityManager.Instantiate(config.BarPrefab, bars.Length, Allocator.Temp);
            var query = SystemAPI.QueryBuilder().WithAll<Bar, URPMaterialPropertyBaseColor>().Build();
            query.CopyFromComponentDataArray(bars.AsArray());
            query.CopyFromComponentDataArray(colors.AsArray().Reinterpret<URPMaterialPropertyBaseColor>());

            foreach (var thickness in SystemAPI.Query<RefRW<BarThickness>>())
            {
                thickness.ValueRW.Value = random.NextFloat(.25f, .35f);
            }

            var barEntities = query.ToEntityArray(Allocator.Temp);

            for (int i = 0; i < bars.Length; i++)
            {
                var cluster = new BarCluster { Value = FindRoot(bars[i].pointA) };
                state.EntityManager.AddSharedComponent(barEntities[i], cluster);
            }

            var singletonEntity = SystemAPI.GetSingletonEntity<Config>();
            state.EntityManager.AddComponentData(singletonEntity, pointData);

            state.Enabled = false;

将前面生成的bar数据和点数记录在pointData内,并附在config的entity上。

pointData前三项数据长度为bars.Length * 2有些不解,可能考虑只有buildings部分生成的点需要保证,后面ground details部分生成的点随缘也行。实际上bars.length远大于pointCount。

正式生成bar预制体,并将bars和colors的数据覆盖到生成的bar内。修改bar的厚度,根据disJointSet设置ISharedComponentData。

关闭该system。

TornadoSpawnSystem

using Unity.Burst;
using Unity.Collections;
using Unity.Entities;
using Unity.Mathematics;
using Unity.Rendering;
using Unity.Scenes;
using Unity.Transforms;

namespace Tutorials.Tornado
{
    [UpdateInGroup(typeof(InitializationSystemGroup))]
    [UpdateAfter(typeof(SceneSystemGroup))]
    public partial struct TornadoSpawnSystem : ISystem
    {
        [BurstCompile]
        public void OnCreate(ref SystemState state)
        {
            state.RequireForUpdate<Config>();
        }

        [BurstCompile]
        public void OnUpdate(ref SystemState state)
        {
            var config = SystemAPI.GetSingleton<Config>();
            var entities = state.EntityManager.Instantiate(config.ParticlePrefab, 1000, Allocator.Temp);

            var random = Random.CreateFromIndex(1234);

            foreach (var entity in entities)
            {
                var particle = SystemAPI.GetComponentRW<Particle>(entity);
                var transform = SystemAPI.GetComponentRW<LocalTransform>(entity);
                var color = SystemAPI.GetComponentRW<URPMaterialPropertyBaseColor>(entity);

                transform.ValueRW.Position = new float3(random.NextFloat(-50f, 50f), random.NextFloat(0f, 50f),
                    random.NextFloat(-50f, 50f));
                transform.ValueRW.Scale = random.NextFloat(.2f, .7f);
                particle.ValueRW.radiusMult = random.NextFloat();
                color.ValueRW.Value = new float4(new float3(random.NextFloat(.3f, .7f)), 1f);
            }

            state.Enabled = false;
        }
    }
}

在一定区域内随机生成1000个particle来形成龙卷风主体。随后关闭改system。##

TornadoSystem

using Unity.Burst;
using Unity.Entities;
using Unity.Mathematics;
using Unity.Transforms;

namespace Tutorials.Tornado
{
    /*
     * Updates the swirling boxes that form the tornado.
     */
    [UpdateInGroup(typeof(FixedStepSimulationSystemGroup))]
    public partial struct TornadoSystem : ISystem
    {
        [BurstCompile]
        public void OnCreate(ref SystemState state)
        {
            state.RequireForUpdate<Config>();
        }

        [BurstCompile]
        public void OnUpdate(ref SystemState state)
        {
            var elapsedTime = (float)SystemAPI.Time.ElapsedTime;
            var config = SystemAPI.GetSingleton<Config>();
            new TornadoParticleJob
            {
                ParticleSpinRate = config.ParticleSpinRate,
                ParticleUpwardSpeed = config.ParticleUpwardSpeed,
                ElapsedTime = elapsedTime,
                Tornado = BuildingSystem.Position(elapsedTime),
                DeltaTime = SystemAPI.Time.DeltaTime
            }.ScheduleParallel();
        }
    }

    [BurstCompile]
    public partial struct TornadoParticleJob : IJobEntity
    {
        public float ElapsedTime;
        public float2 Tornado;
        public float DeltaTime;
        public float ParticleSpinRate;
        public float ParticleUpwardSpeed;

        public void Execute(ref LocalTransform transform, in Particle particle)
        {
            var tornadoPos = new float3(Tornado.x + BuildingSystem.TornadoSway(transform.Position.y, ElapsedTime),
                transform.Position.y, Tornado.y);
            var delta = tornadoPos - transform.Position;
            float dist = math.length(delta);
            delta /= dist;
            float inForce = dist - math.saturate(tornadoPos.y / 50f) * 30f * particle.radiusMult + 2f;
            transform.Position += new float3(-delta.z * ParticleSpinRate + delta.x * inForce, ParticleUpwardSpeed,
                delta.x * ParticleSpinRate + delta.z * inForce) * DeltaTime;
            if (transform.Position.y > 50f)
            {
                transform.Position.y = 0f;
            }
        }
    }
}

system有[UpdateInGroup(typeof(FixedStepSimulationSystemGroup))]的attribute,则在这个group内执行。该group内所有system都以固定速度(每秒50次)运行。

将自游戏开始的时长,config的数据,每帧间隔数据传入名为TornadoParticleJob的Job内。计算粒子的位置。

CameraSystem

using Unity.Burst;
using Unity.Entities;
using Unity.Mathematics;
using UnityEngine;

namespace Tutorials.Tornado
{
    //[UpdateInGroup(typeof(FixedStepSimulationSystemGroup), OrderFirst = true)]
    public partial struct CameraSystem : ISystem
    {
        [BurstCompile]
        public void OnCreate(ref SystemState state)
        {
            state.RequireForUpdate<Config>();
        }

        public void OnUpdate(ref SystemState state)
        {
            var tornadoPosition = BuildingSystem.Position((float)SystemAPI.Time.ElapsedTime);
            var cam = Camera.main.transform;
            cam.position = new Vector3(tornadoPosition.x, 10f, tornadoPosition.y) - cam.forward * 60f;
        }
    }
}

根据龙卷风的位置计算并更新camera的位置。

BuildingRenderSystem

using Unity.Burst;
using Unity.Collections;
using Unity.Entities;
using Unity.Mathematics;
using Unity.Transforms;

namespace Tutorials.Tornado
{
    /*
     * Updates the transforms of the bars.
     */
    public partial struct BuildingRenderSystem : ISystem
    {
        [BurstCompile]
        public void OnCreate(ref SystemState state)
        {
            state.RequireForUpdate<Config>();
        }

        [BurstCompile]
        public void OnUpdate(ref SystemState state)
        {
            new PointRenderJob
            {
                CurrentPoints = SystemAPI.GetSingleton<PointArrays>().current
            }.ScheduleParallel();
        }

        [BurstCompile]
        public partial struct PointRenderJob : IJobEntity
        {
            [ReadOnly] public NativeArray<float3> CurrentPoints;

            public void Execute(ref LocalToWorld ltw, in Bar bar, in BarThickness thickness)
            {
                var a = CurrentPoints[bar.pointA];
                var b = CurrentPoints[bar.pointB];

                var d = math.distance(a, b);

                var norm = (a - b) / d;

                var t = (a + b) / 2;
                var r = quaternion.LookRotationSafe(norm, norm.yzx);
                var s = new float3(new float2(thickness.Value), d);

                ltw.Value = float4x4.TRS(t, r, s);
            }
        }
    }
}

将先前生成的点集数据传入PointRenderJob 内。该Job遍历所有含LocalToWorld,Bar,BarThickness类型的entity,并执行execute的内容。更改bar的位置,缩放,方向。

BuildingSystem

using Unity.Burst;
using Unity.Burst.Intrinsics;
using Unity.Collections;
using Unity.Collections.LowLevel.Unsafe;
using Unity.Entities;
using Unity.Jobs;
using Unity.Mathematics;

namespace Tutorials.Tornado
{
    /*
     * Updates the bars and joints of the buildings.
     * The force of the tornado breaks the joints.
     */
    [UpdateInGroup(typeof(FixedStepSimulationSystemGroup))]
    public partial struct BuildingSystem : ISystem
    {
        [BurstCompile]
        public void OnCreate(ref SystemState state)
        {
            state.RequireForUpdate<Config>();
            state.RequireForUpdate<PointArrays>();
        }

        [BurstCompile]
        public void OnUpdate(ref SystemState state)
        {
            var config = SystemAPI.GetSingleton<Config>();
            var pointData = SystemAPI.GetSingleton<PointArrays>();
            var time = (float)SystemAPI.Time.ElapsedTime;

            state.Dependency = new PointUpdateJob
            {
                config = config,
                currentPoints = pointData.current,
                previousPoints = pointData.previous,
                connectivity = pointData.connectivity,
                time = time,
                randomSeed = Random.CreateFromIndex(state.GlobalSystemVersion).NextUInt(),
                tornadoFader = math.saturate(time / 10),
                tornadoPosition = Position(time),
            }.Schedule(pointData.count.Value, 64, state.Dependency);

            state.EntityManager.GetAllUniqueSharedComponents<BarCluster>(out var clusters, Allocator.Temp);

            var barQuery = SystemAPI.QueryBuilder().WithAll<Bar, BarCluster>().Build();
            var barDataHandle = SystemAPI.GetComponentTypeHandle<Bar>();
            var dependencies = new NativeArray<JobHandle>(clusters.Length, Allocator.Temp);

            var barJob = new BarUpdateJob
            {
                config = config,
                barDataTypeHandle = barDataHandle,
                current = pointData.current,
                previous = pointData.previous,
                connectivity = pointData.connectivity,
                counter = pointData.count
            };

            for (int i = 0; i < clusters.Length; i++)
            {
                barQuery.SetSharedComponentFilter(clusters[i]);
                dependencies[i] = barJob.Schedule(barQuery, state.Dependency);
            }

            state.Dependency = JobHandle.CombineDependencies(dependencies);
        }

        public static float TornadoSway(float y, float time)
        {
            return math.sin(y / 5f + time / 4f) * 3f;
        }


        public static float2 Position(float time)
        {
            return new float2(math.cos(time / 6f), math.sin(time / 6f * 1.618f)) * 30f;
        }
    }

    [BurstCompile]
    struct PointUpdateJob : IJobParallelFor
    {
        public Config config;
        public NativeArray<float3> currentPoints;
        public NativeArray<float3> previousPoints;
        public NativeArray<byte> connectivity;
        public float time;
        public uint randomSeed;
        public float tornadoFader;
        public float2 tornadoPosition;

        public void Execute(int i)
        {
            if (connectivity[i] == byte.MaxValue) return;

            var point = currentPoints[i];
            var start = point;

            var previous = previousPoints[i];
            previous.y += .01f;

            // tornado force
            float tdx = tornadoPosition.x + BuildingSystem.TornadoSway(point.y, time) - point.x;
            float tdz = tornadoPosition.y - point.z;
            float tornadoDist = math.sqrt(tdx * tdx + tdz * tdz);
            tdx /= tornadoDist;
            tdz /= tornadoDist;
            if (tornadoDist < config.TornadoMaxForceDist)
            {
                float force = 1f - tornadoDist / config.TornadoMaxForceDist;
                float yFader = math.saturate(1f - point.y / config.TornadoHeight);
                force *= tornadoFader * config.TornadoForce *
                         Random.CreateFromIndex(randomSeed ^ (uint)i).NextFloat(-.3f, 1.3f);

                var forceVec = new float3
                {
                    x = -tdz + tdx * config.TornadoInwardForce * yFader,
                    y = config.TornadoUpForce,
                    z = tdx + tdz * config.TornadoInwardForce * yFader,
                };

                previous -= forceVec * force;
            }

            point += (point - previous) * (1 - config.BarDamping);

            previous = start;
            if (point.y < 0f)
            {
                point.y = 0f;
                previous.y = -previous.y;
                previous.x += (point.x - previous.x) * config.BarFriction;
                previous.z += (point.z - previous.z) * config.BarFriction;
            }

            previousPoints[i] = previous;
            currentPoints[i] = point;
        }
    }

    [BurstCompile]
    struct BarUpdateJob : IJobChunk
    {
        public Config config;

        [NativeDisableContainerSafetyRestriction]
        public ComponentTypeHandle<Bar> barDataTypeHandle;

        [NativeDisableContainerSafetyRestriction]
        public NativeArray<float3> current;

        [NativeDisableContainerSafetyRestriction]
        public NativeArray<float3> previous;

        [NativeDisableContainerSafetyRestriction]
        public NativeArray<byte> connectivity;

        [NativeDisableContainerSafetyRestriction]
        public NativeReference<int> counter;

        public void Execute(in ArchetypeChunk chunk, int unfilteredChunkIndex, bool useEnabledMask,
            in v128 chunkEnabledMask)
        {
            var barData = chunk.GetNativeArray(ref barDataTypeHandle);

            for (int i = 0; i < chunk.Count; i++)
            {
                var bar = barData[i];
                var iA = bar.pointA;
                var iB = bar.pointB;
                var currentA = current[iA];
                var currentB = current[iB];
                var anchorA = connectivity[iA] == byte.MaxValue;
                var anchorB = connectivity[iB] == byte.MaxValue;

                var d = currentB - currentA;

                float dist = math.length(d);
                float extraDist = dist - bar.length;

                var push = d / dist * extraDist;

                if (!anchorA && !anchorB)
                {
                    currentA += push / 2;
                    currentB -= push / 2;
                }
                else if (anchorA)
                {
                    currentB -= push;
                }
                else if (anchorB)
                {
                    currentA += push;
                }

                current[bar.pointA] = currentA;
                current[bar.pointB] = currentB;

                if (math.abs(extraDist) > config.BarBreakResistance)
                {
                    if (connectivity[iB] > 1 && !anchorB)
                    {
                        bar.pointB = DuplicatePoint(iB);
                    }
                    else if (connectivity[iA] > 1 && !anchorA)
                    {
                        bar.pointA = DuplicatePoint(iA);
                    }
                }

                barData[i] = bar;
            }
        }

        int DuplicatePoint(int index)
        {
            var newIdx = counter.AtomicAdd(1);
            connectivity[index] = (byte)(connectivity[index] - 1);

            connectivity[newIdx] = 1;
            current[newIdx] = current[index];
            previous[newIdx] = previous[index];
            return newIdx;
        }
    }
}

更新点集的位置,消耗bar上锚点anchor的耐久。

拆分Update和Job

            var config = SystemAPI.GetSingleton<Config>();
            var pointData = SystemAPI.GetSingleton<PointArrays>();
            var time = (float)SystemAPI.Time.ElapsedTime;

            state.Dependency = new PointUpdateJob
            {
                config = config,
                currentPoints = pointData.current,
                previousPoints = pointData.previous,
                connectivity = pointData.connectivity,
                time = time,
                randomSeed = Random.CreateFromIndex(state.GlobalSystemVersion).NextUInt(),
                tornadoFader = math.saturate(time / 10),
                tornadoPosition = Position(time),
            }.Schedule(pointData.count.Value, 64, state.Dependency);
//====================================================================================
    [BurstCompile]
    struct PointUpdateJob : IJobParallelFor
    {
        public Config config;
        public NativeArray<float3> currentPoints;
        public NativeArray<float3> previousPoints;
        public NativeArray<byte> connectivity;
        public float time;
        public uint randomSeed;
        public float tornadoFader;
        public float2 tornadoPosition;

        public void Execute(int i)
        {
            if (connectivity[i] == byte.MaxValue) return;

            var point = currentPoints[i];
            var start = point;

            var previous = previousPoints[i];
            previous.y += .01f;

            // tornado force
            float tdx = tornadoPosition.x + BuildingSystem.TornadoSway(point.y, time) - point.x;
            float tdz = tornadoPosition.y - point.z;
            float tornadoDist = math.sqrt(tdx * tdx + tdz * tdz);
            tdx /= tornadoDist;
            tdz /= tornadoDist;
            if (tornadoDist < config.TornadoMaxForceDist)
            {
                float force = 1f - tornadoDist / config.TornadoMaxForceDist;
                float yFader = math.saturate(1f - point.y / config.TornadoHeight);
                force *= tornadoFader * config.TornadoForce *
                         Random.CreateFromIndex(randomSeed ^ (uint)i).NextFloat(-.3f, 1.3f);

                var forceVec = new float3
                {
                    x = -tdz + tdx * config.TornadoInwardForce * yFader,
                    y = config.TornadoUpForce,
                    z = tdx + tdz * config.TornadoInwardForce * yFader,
                };

                previous -= forceVec * force;
            }

            point += (point - previous) * (1 - config.BarDamping);

            previous = start;
            if (point.y < 0f)
            {
                point.y = 0f;
                previous.y = -previous.y;
                previous.x += (point.x - previous.x) * config.BarFriction;
                previous.z += (point.z - previous.z) * config.BarFriction;
            }

            previousPoints[i] = previous;
            currentPoints[i] = point;
        }
    }

将必要的数据传入PointUpdateJob内。该Job继承IJobParallelFor,能根据分配的长度单独修改nativeContainer的每一个元素。之后在Job内仿真能被龙卷风影响的point的位置信息。

            var barQuery = SystemAPI.QueryBuilder().WithAll<Bar, BarCluster>().Build();
            var barDataHandle = SystemAPI.GetComponentTypeHandle<Bar>();
            var dependencies = new NativeArray<JobHandle>(clusters.Length, Allocator.Temp);

            var barJob = new BarUpdateJob
            {
                config = config,
                barDataTypeHandle = barDataHandle,
                current = pointData.current,
                previous = pointData.previous,
                connectivity = pointData.connectivity,
                counter = pointData.count
            };

            for (int i = 0; i < clusters.Length; i++)
            {
                barQuery.SetSharedComponentFilter(clusters[i]);
                dependencies[i] = barJob.Schedule(barQuery, state.Dependency);
            }
//=====================================================================================

    [BurstCompile]
    struct BarUpdateJob : IJobChunk
    {
        public Config config;

        [NativeDisableContainerSafetyRestriction]
        public ComponentTypeHandle<Bar> barDataTypeHandle;

        [NativeDisableContainerSafetyRestriction]
        public NativeArray<float3> current;

        [NativeDisableContainerSafetyRestriction]
        public NativeArray<float3> previous;

        [NativeDisableContainerSafetyRestriction]
        public NativeArray<byte> connectivity;

        [NativeDisableContainerSafetyRestriction]
        public NativeReference<int> counter;

        public void Execute(in ArchetypeChunk chunk, int unfilteredChunkIndex, bool useEnabledMask,
            in v128 chunkEnabledMask)
        {
            var barData = chunk.GetNativeArray(ref barDataTypeHandle);

            for (int i = 0; i < chunk.Count; i++)
            {
                var bar = barData[i];
                var iA = bar.pointA;
                var iB = bar.pointB;
                var currentA = current[iA];
                var currentB = current[iB];
                var anchorA = connectivity[iA] == byte.MaxValue;
                var anchorB = connectivity[iB] == byte.MaxValue;

                var d = currentB - currentA;

                float dist = math.length(d);
                float extraDist = dist - bar.length;

                var push = d / dist * extraDist;

                if (!anchorA && !anchorB)
                {
                    currentA += push / 2;
                    currentB -= push / 2;
                }
                else if (anchorA)
                {
                    currentB -= push;
                }
                else if (anchorB)
                {
                    currentA += push;
                }

                current[bar.pointA] = currentA;
                current[bar.pointB] = currentB;

                if (math.abs(extraDist) > config.BarBreakResistance)
                {
                    if (connectivity[iB] > 1 && !anchorB)
                    {
                        bar.pointB = DuplicatePoint(iB);
                    }
                    else if (connectivity[iA] > 1 && !anchorA)
                    {
                        bar.pointA = DuplicatePoint(iA);
                    }
                }

                barData[i] = bar;
            }
        }

        int DuplicatePoint(int index)
        {
            var newIdx = counter.AtomicAdd(1);
            connectivity[index] = (byte)(connectivity[index] - 1);

            connectivity[newIdx] = 1;
            current[newIdx] = current[index];
            previous[newIdx] = previous[index];
            return newIdx;
        }
    }

生成job所需的componentHandle,和JobHandle类型的NativeArray。将所需的数据传入BarUpdateJob内。获取所有包含Bar和BarCluster(ISharedComponentData)的query,根据BarCluster的值分出子集query,传给Job让该job只对该子集query执行。通过BarCluster分出不同的BarUpdateJob,将对应的JobHandle存入dependencies内。

该Job继承自IJobChunk,遍历的是Archetypes内的Chunk。Archetype存放所有components组合和components本身的数据。components只有一个Archetype与其对应,没有Archetype包含另一个Archetype的关系。Chunk为Archetype存放components的单位,与Archetype的关系犹如char[]和char[][]。

该Job内,根据bar两端点的距离和bar本身长度的差计算“力”,根据“力”计算新位置,消耗锚点耐久,决定该bar是否“断开”。

null
最后更新于 2024-08-24