13意义不明,只能通过Hierarchy面板确认确实有Entity在动,但Game场景内什么都没有。
运行效果
该示例展示如何通过data的数值,EnableComponent和通过Archetypes的分类,来表明状态。
代码解读
ConfigAuthoring
using Unity.Entities;
using Unity.Mathematics;
using UnityEngine;
namespace HelloCube.StateChange
{
public class ConfigAuthoring : MonoBehaviour
{
public GameObject Prefab;
public uint Size;
public float Radius;
public Mode Mode;
class Baker : Baker<ConfigAuthoring>
{
public override void Bake(ConfigAuthoring authoring)
{
var entity = GetEntity(TransformUsageFlags.None);
AddComponent(entity, new Config
{
Prefab = GetEntity(authoring.Prefab, TransformUsageFlags.Dynamic),
Size = authoring.Size,
Radius = authoring.Radius,
Mode = authoring.Mode,
});
AddComponent<Hit>(entity);
#if UNITY_EDITOR
AddComponent<StateChangeProfilerModule.FrameData>(entity);
#endif
}
}
}
public struct Config : IComponentData
{
public Entity Prefab;
public uint Size;
public float Radius;
public Mode Mode;
}
public struct Hit : IComponentData
{
public float3 Value;
public bool HitChanged;
}
public struct Spin : IComponentData, IEnableableComponent
{
public bool IsSpinning;
}
public enum Mode
{
VALUE = 1,
STRUCTURAL_CHANGE = 2,
ENABLEABLE_COMPONENT = 3
}
}
创建了Config ,Hit,Spin的Data和枚举类型Mode。Config记录方块集的设置,Hit记录鼠标点击的位置和鼠标点击情况,Spin决定方块是否要旋转。Mode决定该场景用什么方式改变方块的状态,所有mode效果均相同。
该代码附在Subscene的Config内,该Entity为Singleton,Mode和Config同为Singleton。
CubeSpawnSystem
using Unity.Burst;
using Unity.Collections;
using Unity.Entities;
using Unity.Rendering;
using Unity.Transforms;
namespace HelloCube.StateChange
{
public partial struct CubeSpawnSystem : ISystem
{
Config priorConfig;
[BurstCompile]
public void OnCreate(ref SystemState state)
{
state.RequireForUpdate<Config>();
state.RequireForUpdate<ExecuteStateChange>();
}
[BurstCompile]
public void OnUpdate(ref SystemState state)
{
var config = SystemAPI.GetSingleton<Config>();
if (ConfigEquals(priorConfig, config))
{
return;
}
priorConfig = config;
var query = SystemAPI.QueryBuilder().WithAll<URPMaterialPropertyBaseColor>().Build();
state.EntityManager.DestroyEntity(query);
var entities = state.EntityManager.Instantiate(
config.Prefab,
(int)(config.Size * config.Size),
Allocator.Temp);
var center = (config.Size - 1) / 2f;
int i = 0;
foreach (var transform in
SystemAPI.Query<RefRW<LocalTransform>>())
{
transform.ValueRW.Scale = 1;
transform.ValueRW.Position.x = (i % config.Size - center) * 1.5f;
transform.ValueRW.Position.z = (i / config.Size - center) * 1.5f;
i++;
}
var spinQuery = SystemAPI.QueryBuilder().WithAll<Spin>().Build();
if (config.Mode == Mode.VALUE)
{
state.EntityManager.AddComponent<Spin>(query);
}
else if (config.Mode == Mode.ENABLEABLE_COMPONENT)
{
state.EntityManager.AddComponent<Spin>(query);
state.EntityManager.SetComponentEnabled<Spin>(spinQuery, false);
}
}
bool ConfigEquals(Config c1, Config c2)
{
return c1.Size == c2.Size && c1.Radius == c2.Radius && c1.Mode == c2.Mode;
}
}
}
OnCreate常规
OnUpdate前两段就不大理解,若是要让这个system只执行一次大可以用state.Enabled = false。
创建一个Query来获取拥有对应component的query,用来遍历对应的Entity。
用EntityManager生成方块,并摆放成方阵。因方块自带URPMaterialPropertyBaseColor组件,故可被query遍历。
当Mode为VALUE的时候只添加Spin组件。当Mode为ENABLEABLE_COMPONENT时同样添加Spin,又由于方块都有Spin,可被spinQuery遍历,Spin组件被设置为关闭FALSE。
InputSystem
using Unity.Burst;
using Unity.Entities;
using UnityEngine;
namespace HelloCube.StateChange
{
public partial struct InputSystem : ISystem
{
[BurstCompile]
public void OnCreate(ref SystemState state)
{
state.RequireForUpdate<Hit>();
state.RequireForUpdate<Config>();
state.RequireForUpdate<ExecuteStateChange>();
}
public void OnUpdate(ref SystemState state)
{
var hit = SystemAPI.GetSingletonRW<Hit>();
hit.ValueRW.HitChanged = false;
if (Camera.main == null || !Input.GetMouseButton(0))
{
return;
}
var ray = Camera.main.ScreenPointToRay(Input.mousePosition);
if (new Plane(Vector3.up, 0f).Raycast(ray, out var dist))
{
hit.ValueRW.HitChanged = true;
hit.ValueRW.Value = ray.GetPoint(dist);
}
}
}
}
创建一个平面,模拟鼠标点击生成的射线,获取点击平面获取的点并记录到Singleton Hit内,并声明发生点击。
因为获取Camera的数值,且因为Camera为managedComponent,所以OnUpdate不能使用[BurstCompile]。
SetStateSystem
因该代码过长,将IJobEntity单独分开
using Unity.Burst;
using Unity.Entities;
using Unity.Mathematics;
using Unity.Rendering;
using Unity.Transforms;
using UnityEngine;
using Unity.Profiling.LowLevel.Unsafe;
namespace HelloCube.StateChange
{
public partial struct SetStateSystem : ISystem
{
[BurstCompile]
public void OnCreate(ref SystemState state)
{
state.RequireForUpdate<Hit>();
state.RequireForUpdate<Config>();
state.RequireForUpdate<ExecuteStateChange>();
}
[BurstCompile]
public void OnUpdate(ref SystemState state)
{
var config = SystemAPI.GetSingleton<Config>();
var hit = SystemAPI.GetSingleton<Hit>();
if (!hit.HitChanged)
{
#if UNITY_EDITOR
SystemAPI.GetSingletonRW<StateChangeProfilerModule.FrameData>().ValueRW.SetStatePerf = 0;
#endif
return;
}
var radiusSq = config.Radius * config.Radius;
var ecbSystem = SystemAPI.GetSingleton<BeginSimulationEntityCommandBufferSystem.Singleton>();
state.Dependency.Complete();
var before = ProfilerUnsafeUtility.Timestamp;
if (config.Mode == Mode.VALUE)
{
new SetValueJob
{
RadiusSq = radiusSq,
Hit = hit.Value
}.ScheduleParallel();
}
else if (config.Mode == Mode.STRUCTURAL_CHANGE)
{
new AddSpinJob
{
RadiusSq = radiusSq,
Hit = hit.Value,
ECB = ecbSystem.CreateCommandBuffer(state.WorldUnmanaged).AsParallelWriter()
}.ScheduleParallel();
new RemoveSpinJob
{
RadiusSq = radiusSq,
Hit = hit.Value,
ECB = ecbSystem.CreateCommandBuffer(state.WorldUnmanaged).AsParallelWriter()
}.ScheduleParallel();
}
else if (config.Mode == Mode.ENABLEABLE_COMPONENT)
{
new EnableSpinJob
{
RadiusSq = radiusSq,
Hit = hit.Value,
ECB = ecbSystem.CreateCommandBuffer(state.WorldUnmanaged).AsParallelWriter()
}.ScheduleParallel();
new DisableSpinJob
{
RadiusSq = radiusSq,
Hit = hit.Value,
}.ScheduleParallel();
}
state.Dependency.Complete();
var after = ProfilerUnsafeUtility.Timestamp;
#if UNITY_EDITOR
// profiling
var conversionRatio = ProfilerUnsafeUtility.TimestampToNanosecondsConversionRatio;
var elapsed = (after - before) * conversionRatio.Numerator / conversionRatio.Denominator;
SystemAPI.GetSingletonRW<StateChangeProfilerModule.FrameData>().ValueRW.SetStatePerf = elapsed;
#endif
}
}
}
OnCreate常规。
OnUpdate内判断是否发生点击,若是则继续执行。
通过Mode执行不同组合的IJobEntity。
[BurstCompile]
partial struct SetValueJob : IJobEntity
{
public float RadiusSq;
public float3 Hit;
void Execute(ref URPMaterialPropertyBaseColor color, ref Spin spin, in LocalTransform transform)
{
if (math.distancesq(transform.Position, Hit) <= RadiusSq)
{
color.Value = (Vector4)Color.red;
spin.IsSpinning = true;
}
else
{
color.Value = (Vector4)Color.white;
spin.IsSpinning = false;
}
}
}
当Mode为VALUE时执行SetValueJob 。
该Job遍历所有含有URPMaterialPropertyBaseColor,Spin 和LocalTransform类型的Entity,根据位置信息更改颜色,和spin内的数值。
[WithNone(typeof(Spin))]
[BurstCompile]
partial struct AddSpinJob : IJobEntity
{
public float RadiusSq;
public float3 Hit;
public EntityCommandBuffer.ParallelWriter ECB;
void Execute(Entity entity, ref URPMaterialPropertyBaseColor color, in LocalTransform transform,
[ChunkIndexInQuery] int chunkIndex)
{
// If cube is inside the hit radius.
if (math.distancesq(transform.Position, Hit) <= RadiusSq)
{
color.Value = (Vector4)Color.red;
ECB.AddComponent<Spin>(chunkIndex, entity);
}
}
}
[WithAll(typeof(Spin))]
[BurstCompile]
partial struct RemoveSpinJob : IJobEntity
{
public float RadiusSq;
public float3 Hit;
public EntityCommandBuffer.ParallelWriter ECB;
void Execute(Entity entity, ref URPMaterialPropertyBaseColor color, in LocalTransform transform,
[ChunkIndexInQuery] int chunkIndex)
{
// If cube is NOT inside the hit radius.
if (math.distancesq(transform.Position, Hit) > RadiusSq)
{
color.Value = (Vector4)Color.white;
ECB.RemoveComponent<Spin>(chunkIndex, entity);
}
}
}
当Mode类型为STRUCTURAL_CHANGE时,分别执行AddSpinJob和RemoveSpinJob。该job遍历所有含有URPMaterialPropertyBaseColor,LocalTransform且没有Spin(该Job含有attribute[WithNone(typeof(Spin))])的Entity。判断并更改data内容。
RemoveSpinJob 同。
[WithNone(typeof(Spin))]
[BurstCompile]
public partial struct EnableSpinJob : IJobEntity
{
public float RadiusSq;
public float3 Hit;
public EntityCommandBuffer.ParallelWriter ECB;
void Execute(Entity entity, ref URPMaterialPropertyBaseColor color, in LocalTransform transform,
[ChunkIndexInQuery] int chunkIndex)
{
// If cube is inside the hit radius.
if (math.distancesq(transform.Position, Hit) <= RadiusSq)
{
color.Value = (Vector4)Color.red;
ECB.SetComponentEnabled<Spin>(chunkIndex, entity, true);
}
}
}
[BurstCompile]
public partial struct DisableSpinJob : IJobEntity
{
public float RadiusSq;
public float3 Hit;
void Execute(Entity entity, ref URPMaterialPropertyBaseColor color, in LocalTransform transform,
EnabledRefRW<Spin> spinnerEnabled)
{
// If cube is NOT inside the hit radius.
if (math.distancesq(transform.Position, Hit) > RadiusSq)
{
color.Value = (Vector4)Color.white;
spinnerEnabled.ValueRW = false;
}
}
}
当Mode类型为ENABLEABLE_COMPONENT时,分别执行EnableSpinJob和DisableSpinJob。
因为Spin继承了IEnableableComponent,所以可通过关闭该data让query和IJob默认不遍历已它。