一天应该写不完,应该是今明两天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是否“断开”。