前回はECSとはどういうものなのか?やシンプルな使い方で使ってみました。
前回の場合はCubeをインスタンス化し下に移動させるという機能を作成し、全てのエンティティに対して(Cubeタグを使って実行する対象を絞っていますが)処理を実行していたので、今回はチャンク毎に処理を実行して同じようなことをしてみようと思います。
Cubeが落下する機能の作成
やる事は異なっていますが、機能自体は前回作成した機能と同じです。
コンポーネントの作成
前回と同じようにCubeの移動スピードと回転スピードのコンポーネントを作成します。
まずはMoveSpeedです。
1 2 3 4 5 6 7 8 9 10 11 | using System; using Unity.Entities; namespace MyChunkJob { [Serializable] public struct MoveSpeed : IComponentData { public float Value; } } |
個人的な理由で名前空間MyChunkJobに作成しています。
MoveSpeedはIComponentDataを実装し、構造体(struct)で作成します。
前回は[GenerateAuthoringComponent]を構造体の前に付けてゲームオブジェクトのインスペクタで値を設定出来るようにしましたが、今回は別のスクリプトで設定出来るようにするので[Serializable]アトリビュートだけを取り付けます。
RotationSpeedも同様です。
1 2 3 4 5 6 7 8 9 10 11 | using System; using Unity.Entities; namespace MyChunkJob { [Serializable] public struct RotationSpeed : IComponentData { public float Value; } } |
インスペクタで設定出来るスクリプトの作成
ゲームオブジェクトに取り付けてCubeの移動スピードや回転スピードのデータを取り付けたり、Convertメソッドでエンティティに変換するChunkDataAuthoringスクリプトを作成します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | using Unity.Entities; using UnityEngine; namespace MyChunkJob { public class ChunkDataAuthoring : MonoBehaviour, IConvertGameObjectToEntity { // コンポーネントに変換 public void Convert(Entity entity, EntityManager dstManager, GameObjectConversionSystem conversionSystem) { var moveSpeed = new MyChunkJob.MoveSpeed { Value = 2f }; var rotationSpeed = new MyChunkJob.RotationSpeed { Value = 2f }; dstManager.AddComponentData(entity, moveSpeed); dstManager.AddComponentData(entity, rotationSpeed); } } } |
ChunkDataAuthoringスクリプトはMonoBehaviourとIConvertGameObjectToEntityインタフェースを継承して作成します。
MonoBehaviourクラスを継承して作成したクラスなのでゲームオブジェクトに取り付けることが出来ます。
IConvertGameObjectToEntityインタフェースを継承するとConvertメソッドを実装する必要があります。
Convertメソッドはゲームオブジェクトに取り付けているコンポーネントをエンティティに変換する処理を記述します。
MoveSpeedとRotationSpeedをインスタンス化し、EntityManagerであるdstManagerのAddComponentDataメソッドを使ってエンティティに作成したインスタンス(データ)を取り付けています。
Cubeを動かすシステムの作成
次に生成したCubeエンティティの移動と回転をさせるシステムを作成します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 | using Unity.Burst; using Unity.Collections; using Unity.Entities; using Unity.Jobs; using Unity.Mathematics; using Unity.Transforms; using UnityEngine; namespace MyChunkJob { public class SystemToOperateChunks : SystemBase { // エンティティクエリ private EntityQuery entityQuery; protected override void OnCreate() { // エンティティクエリの作成 entityQuery = GetEntityQuery(ComponentType.ReadOnly<MyChunkJob.MoveSpeed>() , ComponentType.ReadOnly<MyChunkJob.RotationSpeed>() , ComponentType.ReadOnly<CubeTag>() ); } [BurstCompile] struct ChunkJob : IJobChunk { public float DeltaTime; [ReadOnly] public ComponentTypeHandle<MyChunkJob.MoveSpeed> moveSpeedHandle; [ReadOnly] public ComponentTypeHandle<MyChunkJob.RotationSpeed> rotationSpeedHandle; public ComponentTypeHandle<Translation> positionHandle; public ComponentTypeHandle<Rotation> rotationHandle; public void Execute(ArchetypeChunk chunk, int chunkIndex, int firstEntityIndex) { // チャンクから指定したハンドルのコンポーネントを取得 var moveSpeeds = chunk.GetNativeArray(moveSpeedHandle); var rotationSpeeds = chunk.GetNativeArray(rotationSpeedHandle); var positions = chunk.GetNativeArray(positionHandle); var rotations = chunk.GetNativeArray(rotationHandle); // チャンク分繰り返し for (var i = 0; i < chunk.Count; i++) { var moveSpeed = moveSpeeds[i]; var rotationSpeed = rotationSpeeds[i]; var pos = positions[i]; var rot = rotations[i]; // 移動と回転の計算 pos.Value += math.down() * moveSpeed.Value * DeltaTime; rot.Value = math.mul(rot.Value, quaternion.AxisAngle(math.up(), rotationSpeed.Value * DeltaTime)); // 位置と回転の更新 if (pos.Value.y > 0f) { positions[i] = new Translation { Value = pos.Value }; rotations[i] = new Rotation { Value = rot.Value }; } else { positions[i] = new Translation { Value = new float3(positions[i].Value.x, 50f, positions[i].Value.z) }; rotations[i] = new Rotation { Value = quaternion.identity }; } } } } protected override void OnUpdate() { // ハンドルタイプの取得 var moveSpeedType = GetComponentTypeHandle<MyChunkJob.MoveSpeed>(true); var rotationSpeedType = GetComponentTypeHandle<MyChunkJob.RotationSpeed>(true); var posType = GetComponentTypeHandle<Translation>(); var rotType = GetComponentTypeHandle<Rotation>(); // ジョブの作成 var job = new ChunkJob() { DeltaTime = Time.DeltaTime, moveSpeedHandle = moveSpeedType, rotationSpeedHandle = rotationSpeedType, positionHandle = posType, rotationHandle = rotType }; Dependency = job.Schedule(entityQuery, Dependency); Dependency.Complete(); } } } |
CubeとCapsuleを作成して動かす
動かすシステムが出来たのでCubeとCapsuleを作成して動くか確認してみます。
ヒエラルキー上にCubeを作成し、名前をECSCubeとします。
TransformのScaleのYを2にします。
ECSCubeには前回作成したCubeTagを取り付けます。
1 2 3 4 5 6 7 8 9 10 | using Unity.Entities; namespace ECSCube { [GenerateAuthoringComponent] public struct CubeTag : IComponentData { } } |
また、先ほど作成したChunkDataAuthoringスクリプトもECSCubeに取り付けます。
さらに実行時にゲームオブジェクトからエンティティに変換する為、インスペクタのAdd ComponentからDOTS→Convert To Entityコンポーネントも取り付けます。
ECSCubeは以下のようになります。
ヒエラルキー上で右クリックから3D Object→Capsuleを作成し、ChunkDataAuthoringとConvert To Entityコンポーネントを取り付けます。
Capsuleは先ほどのシステムでは動かさないのでCubeTagは取り付けません。
Capsuleは以下のようになります。
Unityを実行してみるとECSCubeだけが回転しながら落下をします。
スクリプトでCubeのYの位置を50としているので実行開始とともにCapsuleより上から出現します。
Cubeプレハブの作成
Cubeは量産する必要があるのでCubeプレハブを作成します。
ヒエラルキー上のECSCubeのConvert To Entityコンポーネントを削除します。
Assetsフォルダ内にドラッグ&ドロップしてプレハブにします。
これでECSCubeのプレハブが出来ました。
ヒエラルキー上のECSCubeは削除します。
ECSCubeプレハブは以下のようになっています。
ECSCubeプレハブをインスタンス化するスクリプトの作成
次はECSCubeプレハブをインスタンス化するスクリプトInstantiateCubeScriptを作成します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 | using Unity.Entities; using Unity.Mathematics; using Unity.Transforms; using UnityEngine; using UnityEngine.UI; namespace MyChunkJob { public class InstantiateCubeScript : MonoBehaviour { [SerializeField] private GameObject prefab; // 一度にインスタンス化する数 [SerializeField] private int numberToInstantiate = 500; // トータルでインスタンス化した数 private int total; // インスタンス化した数の表示テキスト [SerializeField] private Text totalText; // エンティティにしたプレハブ private Entity entityPrefab; // デフォルトのワールド private World defaultWorld; // エンティティマネージャー private EntityManager entityManager; // Start is called before the first frame update void Start() { // デフォルトのワールドの取得 defaultWorld = World.DefaultGameObjectInjectionWorld; // デフォルトのワールドのエンティティマネージャーの取得 entityManager = defaultWorld.EntityManager; // GameObjectConversionSettings settings = GameObjectConversionSettings.FromWorld(defaultWorld, null); entityPrefab = GameObjectConversionUtility.ConvertGameObjectHierarchy(prefab, settings); } void Update() { if (Input.GetKeyDown(KeyCode.Space)) { for (int i = 0; i < numberToInstantiate; i++) { InstantiateEntity(); } // 数の表示 total += numberToInstantiate; totalText.text = total.ToString(); } } private void InstantiateEntity() { Entity myEntity = entityManager.Instantiate(entityPrefab); //// プレハブに既にデータをセットしていればこちら entityManager.SetComponentData(myEntity, new Translation { Value = new float3(UnityEngine.Random.Range(-20f, 20f), 50f, 0f) }); } } } |
このスクリプトは前回の記事とほとんど同じなので説明は割愛させて頂きます。
InstantiateEntityメソッドで行っていることはプレハブからエンティティを生成し、Translationの設定をしています。
InstantiateCubeScriptを何らかのゲームオブジェクトに設定します。
インスペクタでprefabにECSCubeプレハブを設定し、totalTextにはインスタンス化した数を表示するUIのTextを設定してください。
ここまででスペースキーを押すとECSCubeプレハブをエンティティに変換したものがnumberToInstantiateの数分生成されるようになります。
終わりに
この記事を途中まで書いていたのですが、公開するのを忘れていた為、本日公開しました(記事自体は昨年末に作ったやつ)。(^_^;)
内容はすっかり忘れているので、もしかしたら内容に抜けている所があるかもしれません・・・・。