Unity-EntitiesSamples-HelloCube-14

发布于 2024-08-04  59 次阅读


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默认不遍历已它。

null
最后更新于 2024-08-24