このサンプルは Entities.0.1.1
およびUnity 2019.2f8
を使用しています
子オブジェクトの座標を使いたい
Unityでオブジェクトを構造化していると、よく親子関係を使用したくなります。
例えば戦車砲を持つユニットの砲弾は、砲塔の先から出てほしいです。これは砲の向きを回転させたり、戦車自体が動いた場合でも同様です。これをスクリプトで制御しても良いのですが、オブジェクトで発射位置と向きを設定出来れば非常に楽といえます。
LocalToParentを使用する
LocalToParent
は、子オブジェクトのローカル座標をワールド座標に変換したマトリクスを格納しています。このコンポーネントはTranslation(ローカル座標)
やRotation(ローカル回転)
、`Scale(ローカル拡縮)等の情報を元に自動的に更新されるコンポーネントで、基本的には値を更新する必要はありません。
このLocalToWorldに格納されている情報を使用すれば、面倒くさい計算を行わず子オブジェクトのワールド座標が取得できます。
回転しながら弾を撃つ砲台
トップ絵の動き(回るキューブが指定方向に弾をばらまく)を作ってみます。最初に砲台と回転する物体を作ります。これはConversionWorkflowで作るのでGameObjectで配置していく感じです。線の方向がGameObjectの上方向です。
コンポーネントを設定します。配置は大体こんな感じです。オーサリングのコードは割愛しますが、基本的にComponentという名前を付けています。
/// <summary> 前方に移動する </summary> public struct BulletTag : IComponentData { } /// <summary> 弾を発射する </summary> public struct GunTag : IComponentData { } /// <summary> 回転する </summary> public struct RotationSpeed : IComponentData { /// <summary> キューブの回転速度(rad) </summary> public float Value; }
キューブを回す
とりあえず砲台となるキューブを回します。Gunの親オブジェクトにでもRotationSpeed
を登録してValueを1とかに設定(早すぎると微妙)、後はRotationSystemを記述して勝手に回します。ここはよくある記述です。ただし、親オブジェクトが移動・回転することで子オブジェクトも移動・回転していることが確認できます。
public class RotationSystem : JobComponentSystem { struct RotationJob : IJobForEach<Rotation, RotationSpeed> { public void Execute(ref Rotation c0, [ReadOnly] ref RotationSpeed c1) { c0.Value = math.mul(c0.Value, quaternion.RotateZ(c1.Value)); } } protected override JobHandle OnUpdate(JobHandle inputDeps) { inputDeps = new RotationJob().Schedule(this, inputDeps); return inputDeps; } }
弾を生成
次にGUNの位置に弾を生成します。弾の位置と向きは親Entityによって動くのでLocalToWorldを使用することで最終的な位置と向きを取得する感じです。
位置は LocalToWorld.Position
から、向きは math.quaternion(LocalToWorld.Value)
でマトリクスを回転に変更すれば良いです。このマトリクスにはスケール等の情報も格納されてるので、親オブジェクトのスケールが(1,1,1)じゃないと変な動きをするかもしれません。
using static Unity.Entities.ComponentType; public class GunSystem : ComponentSystem { // 弾の発射間隔 private float interval; // PrefabとBulletを持つEntityのQuery private EntityQuery bulletPrefab; protected override void OnCreate() => bulletPrefab = GetEntityQuery(ReadOnly<Prefab>(), ReadOnly<BulletTag>()); protected override void OnStartRunning() => interval = 0; protected override void OnUpdate() { var bulletEntity = bulletPrefab.GetSingletonEntity(); // 一定間隔で射撃を行う interval += Time.deltaTime; if (interval > 0.1f) { interval -= 0.1f; // GUNを持つEntityから弾を発射する Entities.WithAllReadOnly<GunTag>().ForEach((ref LocalToWorld localToWorld) => Shot(bulletEntity, localToWorld)); } } void Shot(Entity bulletEntity, in LocalToWorld gunTransform) { var instance = PostUpdateCommands.Instantiate(bulletEntity); // 新しく作成したEntityに座標と向きを登録 PostUpdateCommands.SetComponent(instance, new Translation { Value = gunTransform.Position }); PostUpdateCommands.SetComponent(instance, new Rotation() { Value = math.quaternion(gunTransform.Value) }); } }
Entity生成は、EntityCommandBufferがBurstに対応してないのでメインスレッドでやってしまいます。
弾を上方向に飛ばす
最後に、生成した弾を弾の向きに飛ばします。Entityの向きのfloat3は LocalToWorld.Forward
やLocalToWorld.Up
で取得できるので、これを足せば良いです。
この速度もScaleの影響を受けるので注意が必要です。
public class BulletSystem : JobComponentSystem { [RequireComponentTag(typeof(BulletTag))] struct MoveBulletJob : IJobForEach<Translation, LocalToWorld> { public float DeltaTime; public void Execute(ref Translation translation, [ReadOnly] ref LocalToWorld localtoworld) { translation.Value += localtoworld.Up * DeltaTime * 12; } } protected override JobHandle OnUpdate(JobHandle inputDeps) { inputDeps = new MoveBulletJob { DeltaTime = Time.deltaTime }.Schedule(this, inputDeps); return inputDeps; } }
注意点
- ハイブリットECS…つまり
ConvertToEntity.ConvertAndInjectGameObject
を使用している場合、使用できません。普通に子オブジェクトのTransformを使用してください。 - 親子関係を作る場合、親のオブジェクトを消しても子が残る事があります。
LinkedEntityGroup
を使用して親を消したら子も消えるようにします。
// 何故か標準で存在しない、LinkedEntityGroupオーサリングの例 // ※複数マテリアル等、複数のEntityを生成するオブジェクトが含まれている場合は正常に動作しません [DisallowMultipleComponent] [RequiresEntityConversion] public class LinkedEntityGroupComponent : MonoBehaviour, IConvertGameObjectToEntity { public void Convert(Entity entity, EntityManager dstManager, GameObjectConversionSystem conversionSystem) { var buffer = dstManager.AddBuffer<LinkedEntityGroup>(entity); var children = transform.GetComponentsInChildren<Transform>(); foreach(var child in children) { var childEntity = conversionSystem.GetPrimaryEntity(child.gameObject); buffer.Add(childEntity); } } }