ECS(Entity Component System) 基礎中の基礎

ECS(Entity Component System) 基礎中の基礎

原点回りにCubeが回転するだけの単純なサンプルです。

Script

 CubeDataで使用するデータを定義します。

using UnityEngine;
using Unity.Entities;
using Random = Unity.Mathematics.Random;

namespace EmptyCan
{
    public struct CubeData : IComponentData
    {
        public float Speed;
        public Vector3 Direction;
        public Vector3 Axis;
        public Quaternion Rotaiton;
        public Quaternion InitRotation;
        public float MaxRadius;

        public static CubeData Set(uint seed, float max_radius)
        {
            float min_speed = 100f;
            float max_speed = 10000f;
            Random random = new Random(seed);
            Quaternion rotation = Quaternion.Euler(random.NextFloat(0f, 360f), random.NextFloat(0f, 360f), random.NextFloat(0f, 360f));

            float min_value = 1f / max_radius;
            float t = random.NextFloat(min_value, 1f);
            t = random.NextFloat(t, 1f);  //原点付近で生成される数を減らすため
            float radius = t * max_radius;
            Vector3 direction = rotation * Vector3.up * radius;
            Vector3 axis = rotation * Vector3.forward;

            Quaternion init_rotation = Quaternion.FromToRotation(Quaternion.FromToRotation(Vector3.forward, axis) * Vector3.up, -direction);
            return new CubeData()
            {
                Speed = random.NextFloat(min_speed, max_speed),
                Direction = direction,
                Axis = axis,
                Rotaiton = rotation,
                InitRotation = init_rotation,
                MaxRadius = max_radius
            };
        }
    }
}

CubeAuthoringではEntityを取得し、そのEntityにCubeData Componentを追加しています。よって、CubeAuthoringにはCubeを動かすための処理はありません。また、GetEntityでTransformUsageFlags.Dynamicを指定した場合、LocalToWorldとLocalTransform Componentが追加されます。

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

namespace EmptyCan
{
    public class CubeAuthoring : MonoBehaviour
    {
        public uint Seed;
        public float MaxRadius;

        class Baker : Baker<CubeAuthoring>
        {
            public override void Bake(CubeAuthoring authoring)
            {
                CubeData data = CubeData.Set(math.max(1, authoring.Seed), math.max(1f, authoring.MaxRadius));
                Entity entity = GetEntity(TransformUsageFlags.Dynamic); //Entityを取得
                AddComponent(entity, data);
            }
        }
    }
}

Cubeを動かすための処理は以下のScriptで行います。このように、ECSではデータと処理を分けて記述する必要があります。また、このSystemは動かすために何かする必要はなく、自動的に実行されます。Cubeを動かすためにはCubeDataとLocalTransformの取得を行い、CubeDataをもとに計算を行いLocalTransformを変更する必要があります。よって、SystemAPI.QueryでこれらのComponentを取得します。SystemAPI.Query内のRefROは読み取り専用、RefRWは読み込みだけではなく書き込みができることを示します。そして、Componentから値を取得する場合はValueRO、書き込みを行う場合はValueRWを使用します。

using Unity.Burst;
using Unity.Entities;
using Unity.Transforms;
using UnityEngine;

namespace EmptyCan
{
    public partial struct CubeSystem : ISystem
    {
        [BurstCompile]
        public void OnUpdate(ref SystemState state)
        {
            //CubeDataとLocalTransformをもつ場合にComponentを取得
            foreach(var (cube, xform) in SystemAPI.Query<RefRO<CubeData>, RefRW<LocalTransform>>())
            {
                float radius = cube.ValueRO.MaxRadius;
                float angle = cube.ValueRO.Speed / radius * (float)SystemAPI.Time.ElapsedTime;
                Vector3 direction = cube.ValueRO.Direction;
                Vector3 axis = cube.ValueRO.Axis;
                Quaternion init_rotation = cube.ValueRO.InitRotation;

                Vector3 position = Quaternion.AngleAxis(angle, axis) * direction;
                xform.ValueRW.Position = position;

                Quaternion quaternion = init_rotation * Quaternion.FromToRotation(Vector3.forward, axis);
                quaternion = Quaternion.AngleAxis(angle, axis) * quaternion;
                xform.ValueRW.Rotation = quaternion;
            }
        }
    }
}

SystemAPI.Queryについて

 SystemAPI.Queryでは指定したComponentを有するEntityのComponentを取得できます。複数のComponentを指定した場合、それらを有するEntityのComponentのみを取得します。

 例としてEntityを作成する際にTransformUsageFlags.Dynamicを指定したCubeAとTransformUsageFlags.Noneを指定したCubeBを使用します。(Burstを利用する場合はstringが使用できないのでFixedStringを使っています。)

//CubeA
CubeData data = CubeData.Set(
    math.max(1, authoring.Seed),
    math.max(1f, authoring.MaxRadius),
    new FixedString32Bytes(authoring.name));
Entity entity = GetEntity(TransformUsageFlags.Dynamic);
//CubeB
CubeData data = CubeData.Set(
    math.max(1, authoring.Seed), 
    math.max(1f, authoring.MaxRadius), 
    new FixedString32Bytes(authoring.name));
Entity entity = GetEntity(TransformUsageFlags.None);

TransformUsageFlags.DynamicはLocalTransformが追加されますが、TransformUsageFlags.Noneは追加されません(Colliderがある場合は自動的にLocalTransformが追加されます)。この条件でSystemAPI.QueryへCubeDataのみ指定するとConsoleにはCubeAとCubeBが表示されます。

foreach (var cube in SystemAPI.Query<RefRO<CubeData>>())
{
    Debug.Log(cube.ValueRO.Name);
}

次に、SystemAPI.QueryにLocalTransformを追加します。

foreach (var (cube, xform) in SystemAPI.Query<RefRO<CubeData>, RefRW<LocalTransform>>())
{
    Debug.Log(cube.ValueRO.Name);
}

これを実行すると、LocalTransformを持たないCubeBは除外されるため、ConsoleにはCubeAのみ表示されます。

設定と実行結果

 以上のScriptを利用してCubeを回転させます。始めに、SubSceneを作成します。作成したSubSceneの横に表示されているチェックボックスにチェックを入れます。

次に、Cubeを作成し、SubSceneへ移動させます。

最後に、作成したCubeへCubeAuthoringを追加します。

実行すると以下のように、原点回りにCubeが回転します。

参考ページ

Youtube:はじめての Unity ECS – Entity Component System を使ってみよう!