ã¯ããã«
ã ãã¶åã2018 å¹´ã®çµããããã« Boidsï¼ç¾¤ä½ï¼ã·ãã¥ã¬ã¼ã·ã§ã³ã ECS ã®åå¼·ã®ããã«æ¸ãã¾ããã
ä¸è¨è¨äºã§ã¯å½æã® API ã使ã£ã¦ãããUnity å´ã§ã¯ ECS ã®å®è£ ãã¾ã è²ã ã¨æ¤è¨¼ä¸ã ã£ãããã«æããã¾ããæè¿ã¯å ¨ã追ã£ã¦ãã¾ããã§ããããããã 6 å¹´ã»ã©çµéããAPI ä½ç³»ãåçããã¨æããããããããããã¦ç¾ç¶ã®å®è£ ã®èª¿æ»ãå ¼ãã¦æ¸ãç´ãã¦ã¿ããã¨ã«ãã¾ããã以åã®å®è£ ã¨æ¯è¼ããªããè²ã ã¨èå¯ããã¦ãããã°ã¨æãã¾ãã
Boids èªä½ã®å®è£ 解説ã«ã¤ãã¦ã¯ä»¥åã®è¨äºããåç §ãã ãããæ¬è¨äºã§ã¯ãECS ã®ã»ããã¢ãããã®é 以éã®å 容ã«ã¤ãã¦ãæ°ãã ECS ã®ä»çµã¿ã®æ¦å¿µç²å¾ã®ããã«ãããã試è¡é¯èª¤ãã¦ããå½¢å¼ã§å±éãã¦ããã¾ããååã¯ãã¥ã¼ããªã¢ã«å½¢å¼ã§ããããä»åã¯ä¸ã¤ä¸ã¤æå³ã確èªããªããè¦ã¦ããã¾ãã®ã§ãçµæ§åé·ãªæãã®è¨äºã«ãªã£ã¦ãã¾ãã
ç®æ¬¡
- ã¯ããã«
- ç®æ¬¡
- ç°å¢
- ãã¢
- ã³ã¼ã
- ECS ã®æ£å¼çãªãªã¼ã¹ã«ã¤ãã¦
- ECS ããã±ã¼ã¸
- ã¨ã³ãã£ãã£ã»ã³ã³ãã¼ãã³ãã»ã·ã¹ãã ã®å®è£ ã¨èª¿æ»
- Boids ã®å®è£ ããã¦ãã
- è¿åæ¤ç´¢
- Boids ã®ã¢ã«ã´ãªãºã ã®å®è£
- ãã©ã¡ã¿ã®ãªã¢ã«ã¿ã¤ã æ´æ°
- ã¨ãã£ã¿ã®ãããã°æç»
- ä¸å¿åº§æ¨ã®ç§»å
- ãã®ä»
- è¿åæ¢ç´¢ã®ããã©ã¼ãã³ã¹æ¹å
- Burst å
- Job å
- è¿åæ¢ç´¢ã¨ãããã¡ã¸ã®æ ¼ç´
- 䏿ãããã¡ã®åé¤
- ããã©ã¼ãã³ã¹
- ãããã«
ç°å¢
ãã¢
æçµçã«ã¯ Job + Burst ã§ 20000 åä½ã»ã©åºãã¦ããã¬ã¼ã ã¬ã¼ãã«å½±é¿ããªãå½¢ã«ãªãã¾ããã
ã³ã¼ã
ECS ã®æ£å¼çãªãªã¼ã¹ã«ã¤ãã¦
ECS 㯠Unity 2022.2 ã®ã¿ã¤ãã³ã°ãã ECS ã®æ£å¼çï¼ãã¼ã¸ã§ã³ 1.0ï¼ããªãªã¼ã¹ãã¾ããã
å ¬å¼ã§å°ç¨ã®ãã¼ã¸ãä½ããã¦ãã¾ãã
ãªããæ¥æ¬èªè¨³ãæªããï¼è£½ååã§ããã¯ãã® Unity ã¾ã§èªå翻訳ãã¦ãã¾ã£ã¦ãã...ï¼ã®ã§ãè±èªçãå¥é翻訳ãã¦è¦ãã»ããè¯ãããããã¾ããããã
å ¬å¼ã®ãµã³ãã«ã¯ GitHub ã«ä¸ãã£ã¦ãã¾ãã
æ¥æ¬èªã¨ãã¦ã¯ Keijiro ããã UTJ æ¥æ¬èªãªãã£ã·ã£ã« YouTube ãã£ã³ãã«ã«ã¦ããã¤ã解説ãä¸ãã¦ãã ãã£ã¦ãã¾ãã
ãã¡ãã®åç»ã§ç´¹ä»ããã¦ãã Keijiro ããã®ãµã³ãã«ãããã¾ãã
ãã®ä»å ¬å¼ã§æ§ã ãªãªã½ã¼ã¹ãç¨æããã¦ãã¾ãã
ECS ããã±ã¼ã¸
ECS é¢é£ã®ããã±ã¼ã¸ã Package Manager ããå ¥ããå¿ è¦ãããã¾ãã
詳ããã¯å¾è¿°ãã¾ããã以ä¸ã® 2 ã¤ã®ããã±ã¼ã¸ãå ¥ããå¿ è¦ãããã¾ãã
- Entities
- ECS ã®ã³ã¢é¨åãæä¾
- Entities Graphics
- ECS ã§ãªãã¸ã§ã¯ãæç»ããé¨åãæä¾
ã¨ã³ãã£ãã£ã»ã³ã³ãã¼ãã³ãã»ã·ã¹ãã ã®å®è£ ã¨èª¿æ»
以åã®ã»ããã¢ãã
ãã¦ã以åã®è¨äºã§ã¯ä»¥ä¸ã®ããã«ããã¼ã¸ã£çµç±ã§ã¨ã³ãã£ãã£ãçæããã³ã³ãã¼ãã³ããä»ä¸ããå½¢ã§ã»ããã¢ãããã¦ãã¾ããã
// Entity ããã¼ã¸ã£ã®åå¾ var manager = World.Active.GetOrCreateManager<EntityManager>(); // ã¢ã¼ãã¿ã¤ãã®ä½æ var archetype = manager.CreateArchetype(typeof(Hoge), ...); // ã¨ã³ãã£ãã£ã®çæã¨ã³ã³ãã¼ãã³ãã®åæå for (int i = 0; i < n; ++i) { var entity = manager.CreateEntity(archetype); manager.SetComponentData(entity, new Hoge { Value = random.NextFloat3(1f) }); ... }
ãã ãã·ã³ãã«ãªã»ããã¢ããã§ããã°ããã§è¯ãã§ãããä½ç½®ãå転ããã©ãã©ã ã£ãããåãªãã¸ã§ã¯ããç°ãªããã©ã¡ã¿ã»ãããæã£ã¦ããããã¨ãã£ãã±ã¼ã¹ã«ããã¦ã¯ã»ããã¢ããã大å¤ã§ãã
SubScene
ããã§ Unity 2020 ãããã®ã¿ã¤ãã³ã°ãã SubScene ã¨ããä»çµã¿ãå°å ¥ããã¾ããã
SubScene ã§ã¯é常ã®ã·ã¼ã³ã®ããã« GameObject 㨠MonoBehaviour ã®ã³ã³ãã¼ãã³ãã®ã»ããã¢ããã«ããã·ã¼ã³æ§ç¯ãè¡ãã¾ããããã¨ããããèªåçã« ECS ã®ä¸çã¸ã¨å¤æããããã¨ããä»çµã¿ã«ãªã£ã¦ãã¾ããå ·ä½çã«è¦ã¦ããã¾ãããã
é©å½ãª SubScene ã使ãé ç½®ãããã®ä¸ã«é©å½ãªãããªã¢ã«ãé©ç¨ãããªãã¸ã§ã¯ããé ç½®ãã¦ã¿ã¾ããã¡ã¤ã³ã·ã¼ã³ã«ã¦ SubScene ã®å³ç«¯ã«ãã§ãã¯ããã¯ã¹ãããã®ã§ããã«ãã§ãã¯ãå ¥ãã¦ã¿ãã¨ã次ã®ããã« Inspector ã®ä¸ã§ã©ã®ãããªã³ã³ãã¼ãã³ããèªåã»ããã¢ãããããããåºã¦ãã¾ãã
ECS ã®æ§é ã¯é常㮠Unity ã®ããã¨ã¯ç°ãªãã®ã§ã·ã¼ã³ã¯ç¹æ®ãªæ§é ã§æ±ããã¾ãããã® SubScene ã¯ã¡ã¤ã³ã·ã¼ã³ä¸ã§ã¯ GameObject 㨠SubScene ã¨ãã MonoBehaviour ç¶æ¿ã³ã³ãã¼ãã³ãã¨ãã¦é ç½®ããã¦ããããã§ãã
SubScene ã³ã³ãã¼ãã³ãã¯ã·ã¼ã³ãã¡ã¤ã«ã§ãã .unity ãä¿æãããã¼ãã»ã¢ã³ãã¼ãã®å½¹å²ãè¡ããã®ã®ããã§ããã ãã®ãããã®è©³ããæµãã¯å ã® UTJ の動画ã«è©³ããã¾ã¨ãããã¦ããã®ã§ãã¡ãããåç §ãã ããã
ã¡ãªã¿ã«ãä»çµã¿ã¨ãã¦ã¯ä»¥åã Pure ECS / Hybrid ECS ã¨ããå½¢ã®åºåããããã¦ãããå¾è 㯠GameObject ãã¼ã¹ã§ ECS ãã»ããã¢ãããããã®ã§ãããSubScene ã®å°å ¥ã«ãããããããæ´ç·´ãããããã§ãã
ã¨ã³ãã£ãã£ã¨ã³ã³ãã¼ãã³ã㨠Baker
ãã¦ãã§ã¯ãã® SubScene ã§ã»ããã¢ãããããªãã¸ã§ã¯ãã«ã³ã³ãã¼ãã³ããä»ä¸ãã¦ã¿ã¾ããã³ã³ãã¼ãã³ãã®ä»ä¸ã¯ Baker ã¨ããä»çµã¿ãéãã¦è¡ãã¾ãã
Baker 㯠GameObject ãã¨ã³ãã£ãã£ã¸ã¨å¤æãããã®ã§ããæ¬¡ã®ãããªã³ã¼ããæ¸ãã¦ã¿ã¾ãã
public struct Velocity : IComponentData { public float3 Value; } public struct Acceleration : IComponentData { public float3 Value; } public class FishAuthoring : MonoBehaviour { public float Speed = 1f; } public class FishBaker : Baker<FishAuthoring> { public override void Bake(FishAuthoring src) { var entity = GetEntity(TransformUsageFlags.Dynamic); AddComponent(entity, new Velocity() { Value = UnityEngine.Random.insideUnitSphere * src.Speed }); AddComponent(entity, new Acceleration() { Value = 0f }); } }
IComponentData ç¶æ¿ã® Velocity ããã³ Acceleration ã ECS ã³ã³ãã¼ãã³ãã§ãã
ããã«å¤ãåãæ¸¡ãããã® FishAuthoring ã¯ãªã¼ãµãªã³ã°ã³ã³ãã¼ãã³ãã¨å¼ã°ããå½¹å²ã®ãã®ã§ãã·ãªã¢ã©ã¤ãºãããã¨ãã£ã¿ä¸ã§ä½¿ç¨ã§ãã夿°ãæããECS ã³ã³ãã¼ãã³ãã¸å¤ãåãæ¸¡ãå½¹å²ããã¾ããSubScene ã®ã²ã¼ã ãªãã¸ã§ã¯ãã«ã¯ãã¡ããä»ä¸ããå½¢ã«ãªãã¾ãã
ãã㦠FishBaker 㯠Baker<T> ç¶æ¿ããã¯ã©ã¹ã§ããããã®ä¸ã® Bake 颿°ãèªåå®è¡ããã¾ããFishAuthoring ãªã¼ãµãªã³ã°ã³ã³ãã¼ãã³ããä»ä¸ãã GameObject ã«å¯¾ãã¦ã¯ãã® Baker ãå¦çãããAddComponent ãã ECS ã³ã³ãã¼ãã³ããèªåä»ä¸ããããã¨ã«ãªãã¾ããBake 颿°ã®ä¸ãè¦ãã¦ã¿ãã¨ãGetEntity() ã§ã¾ãã¨ã³ãã£ãã£ãåå¾ãã¦ãã¾ãã弿°ã¨ãã¦ã¯ TransformUpdateFlag ãä¸ãã¦ããããã㯠GameObject ã® Transform ã³ã³ãã¼ãã³ããã©ã®ãã㪠ECS ã³ã³ãã¼ãã³ãã¸ã¨å¤æãããã¹ãããæå®ãããã®ã¨ãªã£ã¦ãã¾ãã
åºå®ãªãã¸ã§ã¯ãã®ãããªå ´åã¯ä¸è¦ãª ECS ã³ã³ãã¼ãã³ãã®ä»ä¸ãé¿ãããã¨ãã§ãã訳ã§ãããDynamic ãæå®ããå ´å㯠LocalTransform 㨠LocalToWorld ãä»ä¸ãããç§»åå¯è½ã«ãªãã¾ãã
ã§ã¯ãã® FishAuthoring ã GameObject ã«ä»ä¸ãã¦ã¤ã³ã¹ãã¯ã¿ãè¦ã¦ã¿ã¾ãï¼
ãã®ããã«ã»ããã¢ãããã ECS ã³ã³ãã¼ãã³ãåã³ GetEntity ã§æå®ãã TransformUpdateFlag ã«å¿ããã³ã³ãã¼ãã³ããä»ä¸ããã¦ããäºããããã¾ãããã¾ãã»ããã¢ããããå¤ãå ECS ã³ã³ãã¼ãã³ãã«è¨å®ããã¦ãã¾ããããªãããã® Bake å¦çã¯æ§ã
ãªã¿ã¤ãã³ã°ï¼ãµãã·ã¼ã³ã®ééãä¿åãªã©ï¼ã§è¡ãããããã§ããã®ããäºæããªããã°ãé¿ãããã Baker ã¯ã¹ãã¼ãã¬ã¹ã«ããªãã¨ãããªãããã§ãã
ã¡ãªã¿ã« MeshRenderer ã®ãã§ãã¯ãå¤ãã¨ããã¤ãã® Unity.Rendering.* 系㮠ECS ã³ã³ãã¼ãã³ããå¤ããã®ããããã¾ããã¾ãã使ãã FishAuthoring ãå¤ãã¨ä½ããªããªãã¾ããã
ããããããGetEntity() ããããå¯¾å¿æ¨æº MonoBehavior ã³ã³ãã¼ãã³ãï¼MeshRenderer ãªã©ï¼ãã¤ããã¨èªåã§è²ã
ã¨ã»ããã¢ããããããã¨ããããã¾ããããããã¨ã³ãã£ãã£ã«ã¤ããè¤æ°ã®ã³ã³ãã¼ãã³ãã®çµã¿åããï¼ã¢ã¼ãã¿ã¤ãï¼ãæé»çã«è¡ããã Bake å¦çã«ãã£ã¦æ±ºå®ããã¦ãã¾ãã
ã¡ãªã¿ã«ãInspector ã®å³ä¸ã«ããâ¯ãã Runtime ã鏿ããã¨ãInspector ä¸ã§ã©ã®ãããªã³ã³ãã¼ãã³ãã¸ã¨å¤æããããããããã¾ããMixed ã«ããã¨å®è¡æã®ã¿è¦ããããã«ãªãã¾ãã
ã·ã¹ãã
ãã¦ãã§ã¯ãã® ECS ã³ã³ãã¼ãã³ããè¦ã¦åããã¦ã¿ã¾ããããECS ã§ã¯ãã¼ã¿ã¨ãã¦ã®ã³ã³ãã¼ãã³ããã·ã¹ãã ã«ãã£ã¦å¦çãã¾ããã·ã¹ãã ã¯ä»¥åã¯ã³ã³ãã¼ãã³ãã®ãã¼ã¿ãåã£ã¦ããã®ã«å°ç¨ã®æ§é ä½ãç¨æãããã¨è²ã ã¨æéãè¦ãã¾ããã以åã®ã³ã¼ãã以ä¸ã«ç¤ºãã¦ã¿ã¾ãã
public class MoveSystem : ComponentSystem { struct Data { public readonly int Length; public ComponentDataArray<Position> positions; [WriteOnly] public ComponentDataArray<Rotation> rotations; public ComponentDataArray<Velocity> velocities; public ComponentDataArray<Acceleration> accelerations; } [Inject] Data data; protected override void OnUpdate() { var dt = Time.deltaTime; for (int i = 0; i < data.Length; ++i) { ... var v = data.velocities[i].Value; var pos = data.positions[i].Value; var a = data.accelerations[i].Value; v += a * dt; pos += v * dt; var dir = math.normalize(velocity); var rot = quaternion.LookRotationSafe(dir, new float3(0, 1, 0)); data.velocities[i] = new Velocity { Value = v }; data.positions[i] = new Position { Value = pos }; data.rotations[i] = new Rotation { Value = rot }; data.accelerations[i] = new Acceleration { Value = float3.zero }; } } }
çµæ§ãªã³ã¼ãéãå¿ è¦ã§ãããããã«å¯¾ãã¦ç¾å¨ã¯ä»¥ä¸ã®ãããªçãã³ã¼ããæ¸ããã¨ã§åæ§ã®ãã¨ãå®ç¾ã§ãã¾ãã
public partial struct MoveSystem : ISystem { public void OnUpdate(ref SystemState state) { var dt = SystemAPI.Time.DeltaTime; foreach (var (v, a, lt) in SystemAPI.Query< RefRW<Velocity>, RefRW<Acceleration>, RefRW<LocalTransform>>()) { v.ValueRW.Value += a.ValueRO.Value * dt; a.ValueRW.Value = 0f; lt.ValueRW.Position += v.ValueRO.Value * dt; var dir = math.normalize(v.ValueRO.Value); var up = math.up(); lt.ValueRW.Rotation = quaternion.LookRotationSafe(dir, up); } } }
ãã®ã·ã¹ãã ã®ã³ã¼ããæ¸ãã¨ããã® OnUpdate() ã¯èªåå®è¡ããå¥ã®ä½å¦ãããå¼ã³åºããªãã¦ãåä½ããããã«ãªãã¾ãã
ã¾ãä¸çªã®æ³¨ç®ãã¤ã³ã㯠SystemAPI.Query<...> ã§ãããã§è¤æ°ã®æå®ãã ECS ã³ã³ãã¼ãã³ããåæãã¦ãããã ãã§ãããããæã¤ã¢ã¼ãã¿ã¤ãã®ã¨ã³ãã£ãã£ãè¦ã¤ãã¦ãã¦ãåã³ã³ãã¼ãã³ãã®ãã¼ã¿ãã¿ãã«ã§åãåãã foreach ã§åããã¨ãåºæ¥ããã¨ãã大å¤ã·ã³ãã«ãªæ§é ã«ãªã£ã¦ãã¾ãã`
å¯ä¸ã®ç
©éãªç¹ã¯ RefRO<>ï¼å¤ã®å©ç¨ã®ã¿ï¼/ RefRW<>ï¼å¤ã®èªã¿æ¸ãï¼ã§ã³ã³ãã¼ãã³ããã©ãããã¦ããç¹ï¼ä»åã¯ãã¹ã¦æ¸ãè¾¼ã¿ãå¿
è¦ãªã®ã§ RefRW ã®ã¿ã§ãããï¼åã³ãããã®ã¢ã¯ã»ã¹ã ValueRW / ValueRO çµç±ã§åå¾ããã¦ããç¹ã§ãããããã¯ãã¤ãã£ãã¡ã¢ãªã¸ã®ãã¤ã³ã¿ãä¿æããæ§é ä½ã«ãªã£ã¦ããããã®ã¡ã¢ãªã¸ã®ã¢ã¯ã»ã¹æ¹æ³ãæå®ãã¦ããå½¢ã§ãããããã㯠unsafe ref ã¨ãªã£ã¦ãããå®éã®ã³ã³ãã¼ãã³ãã®ã¡ã¢ãªã«ãã¤ã³ã¿çµç±ã§ã¢ã¯ã»ã¹ãããããç´æ¥å¤ã®èªã¿æ¸ããå¯è½ã«ãªã£ã¦ãã¾ãã
ã¾ããã¡ãã£ã¨ç´°ãã話ã«å
¥ãã¾ãããå
é¨çã«ã¯ SystemAPI.Query 㯠EntityQueryBuilder ã¨ããåãè¿ããããã«ã¯æ§ã
ãªæ¡ä»¶ã追å ãã¦çµãè¾¼ã¿ãåºæ¥ã¾ãã
ä¾ãã° WithAll<Hoge, Fuga> ãå¾ãã«ã¤ãã¦ãããã¨ãã®ã¯ã¨ãªã®ä¸ã§æ´ã«ãããã³ã³ãã¼ãã³ããæã£ã¦ãããã®ãã®ãããªæå®ãã§ãã訳ã§ããã
åç
§å¼æ°ã® SystemState ã¯ãã®ã·ã¹ãã ãçæãããã¨ãã«ä½ãããã·ã¹ãã ã®ã¹ãã¼ãã§ãå¤ããä¸ããããå½¢ã«ãªã£ã¦ãã¾ããEntityManager ã¸ã®åç
§ãä¿æãã¦ããããåæ
å ±ãã¸ã§ãã®ä¾åãªã©ãæ ¼ç´ããã¾ãããã¡ãã®å©ç¨ã¯å¾ã§å°ãåºã¦ãã¾ãã
æå¾ã®æ³¨ç®ç¹ã¯ partial ãªæ§é ä½ã¨ãã¦çæããã¦ããç¹ã§ããããã¯è£å´ã§ã½ã¼ã¹ã¸ã§ãã¬ã¼ã¿ã¨ããèªåã³ã¼ãçæãèµ°ã£ã¦ããã使ãã OnUpdate() ããã¨ã«å¥ã®é¢æ°ãä½ããã¦ãã¾ãã
æã
ãéçºããã®ã¯ ISystem ã«å®è£
ãããæ½è±¡åããã API ã使ã£ããã¸ãã¯ã§ãå®éã«ã¯ ISystemCompilerGenerated ãç¶æ¿ããå¥ã® partial ãªæ§é ä½ã§ä½ã¬ãã« API ã使ããªããã¡ã¢ãªå¹çè¯ãã³ã³ãã¼ãã³ãã®ãã¼ã¿ã«ã¢ã¯ã»ã¹ããããå¥ã®ã¸ã§ãã®å®äºãå¾
ã£ã¦ãã¼ã¿ã«ã¢ã¯ã»ã¹ãããã¨ãã£ããããªå¦çã追å ããã¾ããå
ç¨ã® RefRW / RefRO ãªã©ã CompleteDependencies() ã¨ããèªåçæé¢æ°ã®ä¸ã§ RW ãªãæ¸ãè¾¼ã¿å¯è½ã¨ãªãã¿ã¤ãã³ã°å¾
ã¡ããRO ãªãèªã¿åãå¯è½ã¨ãªãã¿ã¤ãã³ã°å¾
ã¡ï¼ã»ã¨ãã©ãªãï¼ãè¡ãããã«ãªã£ã¦ãã¾ããSystemAPI ã¨ãªã£ã¦ããã®ãããã®ã½ã¼ã¹ã¸ã§ãã¬ã¼ã·ã§ã³ä¸ã§æãç«ã¤ã·ã¹ãã å
ã§å©ç¨å¯è½ãª API ã§ããã
ãªããã½ã¼ã¹ã¸ã§ãã¬ã¼ã¿ãåä½ãããããã«ã¯ç¹å®ã® IDEï¼Visual Studio / Riderï¼ãå¿ è¦ã¨ãªãã¾ãã
ä½è«ã§ããããµã³ãã«ãªã©ãè¦ã¦ãã㨠ECS ã®å¤æ°ã¯ãã¹ã«ã«ã±ã¼ã¹ã§æã¤ã®ãéä¾ã®ããã§ãã
Enter Play Mode Settings
ä¸è¨ãã¼ã¸ãè¦ã㨠Enter Play Mode Settings ã§ Domain 㨠Scene ã®ãªãã¼ããããªãè¨å®ã«ãããã¨ãæ¨å¥¨ããã¦ãã¾ãããã®ã¾ã¾ã ã¨å®è¡éå§æã« Domain Reload ã Import Assets ãªã©ã®æéããããªãã«æããã¤ãã¬ã¼ã·ã§ã³é度ä¸ä¸å©ã«ãªãã¾ããããªãã«ããã¨ãã¿ã³ãæ¼ãããããå®è¡ããããããªé度ã«ãªãã¾ãã
ãã¡ã¤ã³ãªãã¼ããããªãã¨ãã㨠static 夿°ãåæåãããªãã¨ãã£ãäºæããªãåä½ããããã¨ã«ãã¤ãªããã®ã§æ³¨æãå¿ è¦ã§ãã
é©åãªã¿ã¤ãã³ã°ï¼ECS é¢é£ã®ã³ã¼ãå¶ä½ï¼ã®ã¿ã«ããããstatic 夿°ã®åæåã¯æåã§è¡ããªã©ã使ç¨ã«ã¯å¹¾ã¤ã注æããå¿ è¦ãããã¾ããããã¤ãã¬ã¼ã·ã§ã³ã¯ããªãæ©ããªãã®ã§ããªãã¹ããªãã«åºæ¥ããããªè¨è¨ã«ãããã¨ããããããã¾ãã
å®è¡
ãã¦å®è¡ãã¦ã¿ãã¨æ¬¡ã®ããã«åãã¾ããã¾ã å種 Boids ã®åãããããããã®æåãä½ãå®è£ ãã¦ãªãã®ã§æåã«ã©ã³ãã ã§æ±ºããé度ã§çéç´ç·éåããã ãã§ãã
注æç¹ã¨ãã¦ã¯ãSubScene ãéãã¦ããç¶æ ï¼Hierarchy ä¸ã§ãã§ãã¯ãå ¥ã£ã¦ããç¶æ ï¼ã§ã¯ä½ãè¦ããªããã¨ã§ããã·ã¼ã³ãã¥ã¼å´ã«ã¯ãªã¼ãµãªã³ã°ç¨ã«é ç½®ããã SubScene å ã® GameObject ãããã ãã§ãã²ã¼ã ãã¥ã¼ã®ããã¨ã¯ç°ãªãã¾ããã·ã¼ã³ãã¥ã¼ã§ã¯ GameObject ãè¦ãã¦ãã¦ããã® GameObject ã¯ç·¨éå¯è½ã§ããããã㯠ECS å´ã®ãã®ã§ããã¾ãããGameObject å´ããããã¨å Bake ãããããã§ãã
SubScene ãéããï¼ãã§ãã¯ãå¤ãï¼ã¨ãScene ä¸ã§ãåãã¦è¦ããããã«ãªãã¾ãã
Boids ã®å®è£ ããã¦ãã
ã§ã¯åæãæ´ããã¨ããã§ Boids ã¨ãã¦åãããã«ã·ã¹ãã ã®å®è£ ããã¦ããããã¨æãã¾ãã
大éã®ã¤ã³ã¹ã¿ã³ã¹ã®çæ
Boids ã®ããã«å¤§éã®ãªãã¸ã§ã¯ãçæããããã®ã§ããããé ç½®ãã GameObject ããã®çæã§ããã®ã¯ã¡ãã£ã¨å¤§å¤ããã¾ããããã§çæå¦çãã³ã¼ããããã£ã¦ã¿ã¾ãã
ããæ¹ã¨ãã¦ã¯ UTJ ã®åç»ã§è§£èª¬ããã¦ããæ¹æ³ã§è¡ãã¾ãã
ã¾ãã¯ä½å¹ä½ãããã©ããããã®ç¯å²ã§ä½ãããã¨ãã£ãè¨å®ãè¡ãããã®è¨å®ã³ã³ãã¼ãã³ããä½ãã¾ããã·ã¼ã³ã«1ã¤ã ãåå¨ãããçæã«å¿ è¦ãªãã©ã¡ã¿ãæ ¼ç´ããã³ã³ãã¼ãã³ãã¨ãããã®ã«ãªãã¾ãã
public struct Config : IComponentData
{
public Entity Prefab;
public int SpawnCount;
public uint RandomSeed;
}
public class ConfigAuthoring : MonoBehaviour
{
public GameObject Prefab;
public int SpawnCount = 100;
public uint RandomSeed = 100;
}
public class ConfigBaker : Baker<ConfigAuthoring>
{
public override void Bake(ConfigAuthoring src)
{
var entity = GetEntity(TransformUsageFlags.None);
var prefab = GetEntity(src.Prefab, TransformUsageFlags.Dynamic);
AddComponent(entity, new Config()
{
Prefab = prefab,
SpawnCount = src.SpawnCount,
RandomSeed = src.RandomSeed,
});
}
}
注ç®ç¹ã¯ Bake å¦çã®ã¨ããã§ããã³ã³ãã£ã°èªä½ã®ã¨ã³ãã£ãã£ã¯ç¹ã«ä½ç½®ãªã©ã¯å¿
è¦ãªãã®ã§ãTransformUsageFlags.None ãæå®ãã¦ã¨ã³ãã£ãã£ãåå¾ãã¦ãã¾ããããã«ãã LocalTransform ã¨ãã£ãä½ç½®ã«é¢é£ããã³ã³ãã¼ãã³ãã¯ä»ä¸ããã¾ãããä¸è¦ãªã³ã³ãã¼ãã³ãã¯ã»ããã¢ããããªããã¨ã§ã¡ã¢ãªãããã©ã¼ãã³ã¹ä¸ãæå©ã«ãªãã¾ãã
䏿¹ã第 1 弿°ã§ GameObject ã® Prefab ãæå®ãã 2 ã¤ã®å¼æ°ãåã GetEntity() ã®æ¹ã§ã¯ TransformUsageFlags.Dynamic ãæå®ãã¦ãã¾ããããã¯ä½ç½®æ
å ±ãå¿
è¦ãªã®ã§ LocalTransform ãªã©ãä»ä¸ããããã«ãã®ãã©ã°ã«ãªãã¾ãã第 1 弿°ã§ä¸ãã¦ãã Prefab ã¯ãFishAuthoring ãä»ä¸ããã²ã¼ã ãªãã¸ã§ã¯ãã Prefab åãããã®ãæå®ãã¾ãã
ããã«ããããã® GameObject ã® Prefab ããã¨ã«ããã¨ã³ãã£ãã£ãåå¾ã§ãã¾ãããã®ã¨ã³ãã£ãã£ã¯å¾ã§è¤æ°ã®ã¤ã³ã¹ã¿ã³ã¹ãçæããã·ã¹ãã ã®éå½¢ã¨ãã¦ä½¿ãã¾ãã
ã§ã¯çæã·ã¹ãã ã®æ¹ãè¦ã¦ã¿ã¾ãã
[UpdateInGroup(typeof(InitializationSystemGroup))] public partial struct SpawnSystem : ISystem { public void OnCreate(ref SystemState state) { state.RequireForUpdate<Config>(); } public void OnUpdate(ref SystemState state) { var config = SystemAPI.GetSingleton<Config>(); var manager = state.EntityManager; var entities = manager.Instantiate(config.Prefab, config.SpawnCount, Allocator.Temp); var random = new Random(config.RandomSeed); foreach (var entity in entities) { var lt = SystemAPI.GetComponentRW<LocalTransform>(entity); var pos = random.NextFloat3() - 0.5f; pos *= 5f; // é©å½ãªç¯å² lt.ValueRW.Position = pos; var dir = random.NextFloat3Direction(); var up = math.up(); lt.ValueRW.Rotation = quaternion.LookRotation(dir, up); var v = SystemAPI.GetComponentRW<Velocity>(entity); v.ValueRW.Value = dir * 2f; // é©å½ãªã¹ãã¼ã var a = SystemAPI.GetComponentRW<Acceleration>(entity); a.ValueRW.Value = 0f; } state.Enabled = false; } }
ã¾ããUpdateInGroup ã¢ããªãã¥ã¼ãã« InitializationSystemGroup ãæå®ãããã¨ã§ãä»ã®ã·ã¹ãã ãããå
ã«æ´æ°ãããããã«ãªãã¾ãããã®ããã«ã·ã¹ãã ã«ã¯ã¢ããªãã¥ã¼ããä»ä¸ãããã¨ã«ãã£ã¦é åºå¶å¾¡ãè¡ããããã«ãªã£ã¦ãã¾ãã
ãªããè¨å®ããé åºã¯ Window > Entities > Systems ãã確èªãããã¨ãåºæ¥ã¾ãã
ã§ã¯ãããå®è¡ãã¦ã¿ã¾ãã
100 ã SpawnCount ã¨ãã¦æå®ããã®ã§ 100 åã®ã©ã³ãã ãªåãã«é²ããªãã¸ã§ã¯ããçæããã¾ããã
Prefab ã«ã¤ãã¦
ãã¦ãããã§ä¸ã¤æ°ã«ãªãã¾ãããFish ã® Prefab ã«ã¤ãã Baker ã¯ãã¤ã©ããã¦å®è¡ããã¦ããã®ã ããï¼ã¨ããç¹ã§ããLocalTransform ãªã©ã¯ GetEntity() ã®å¼æ°ã«ãã£ã¦ä»ä¸ããã¦ããã®ã¯ãããã¾ããããã® Prefab 㯠SubScene ã®ä¸ã«ã¯é
ç½®ãã¦ããããã§ã¯ãªããããã¾ã§ ConfigAuthoring ã® Prefab 夿°ãã£ã¼ã«ãã«æå®ãã¦ããã ãã§ããPrefab èªä½ã SubScene ã«ç´æ¥é
ç½®ãã¦ããããã§ã¯ãªãããããã£ããä½ã以ã¦ãã¦ãã®ã¨ã³ãã£ãã£ã« Velocity ã Acceleration ã¨ãã£ãã³ã³ãã¼ãã³ããã¤ãã¦ããã®ã ãããã¨èãã¾ããã
ããã¯ã©ããã SubScene ã® Bake å¦çã®ããã»ã¹ã®ä¸ã«ããªã¼ãµãªã³ã°ã³ã³ãã¼ãã³ãã®ãã£ã¼ã«ããå«ãã è§£æããªããã¦ããããã§ããWindows > Entities > Hierarchy ã鏿ããã¨ãGameObject ã®é層ã ãã§ã¯ãªããSubScene å ã«å¤æãã¦çæãããã¨ã³ãã£ãã£ããã³ããã«ç´ã¥ããã³ã³ãã¼ãã³ãä¸è¦§ãè¦ããããã«ãªãã¾ãã
ããã¨ãã®ä¸ã«éã Prefab ãã¼ã¹ã®ã¨ã³ãã£ãã£ãè¦ãã¾ããããã㯠ConfigAuthoring ã³ã³ãã¼ãã³ãã® Prefab ã®æå®ãè§£é¤ããã¨ã¨ã³ãã£ãã£ä¸è¦§ããè¦ããªããªãã¾ãããªã®ã§ SubScene ãç·¨éãããééãããããã¨ãé½åº¦ Bake ãèµ°ãã¾ãããªãã¨ãªãä»çµã¿ãããã£ã¦ãã¾ãããã
ãã©ã¡ã¿ã®æããæ¹ã®æ¤è¨
ãã¦ãããããè²ã ã¨æåãä½ã£ã¦ããããã®ã§ããããã®éã«æ°æã¡è¯ãåããä½ãããã«ã¯ãã©ã¡ã¿ãéè¦ã«ãªã£ã¦ãã¾ããã¾ãåãå ·åãåçã«å¤ãããããããã®ã§ãã©ã¡ã¿ã¯å¯å¤ã«ãããã§ããããã§ãä½ããã®å½¢ã§ãã©ã¡ã¿ãåã·ã¹ãã ããåç §ã§ããããã«ãããã§ãã
ã¾ãæåã«èããã®ã¯ãSharedComponentData ã§ããã
ããã¯è¤æ°ã®ã¨ã³ãã£ãã£ãå
±éã®ã³ã³ãã¼ãã³ããã¼ã¿ãæã¤ããã®ä»çµã¿ã§ããå®éã¯ã³ã³ãã¼ãã³ããç´æ¥æããã«ãã®åç
§ãä¿æããå½¢ã«ãªãã¾ãããã ãã¨ã³ãã£ãã£ã¯å
é¨çã«ã¯ã¢ã¼ãã¿ã¤ãï¼ã©ããªã³ã³ãã¼ãã³ãçµã¿åãããï¼+ åãå¤ãæã¤ SharedComponentDataãã¨ããæ§é ã«ãã£ã¦å¥ã®ãã£ã³ã¯ã«å²ãæ¯ããã¦ãã¾ãããã®é¢ä¿ã§ãSharedComponentData ã®å¤ãæ¸ãæãã£ãéã¯ãã£ã³ã¯ã®åæ§ç¯ãèµ°ã£ã¦ãã¾ãããã§ããããã«ã¯ãã¡ãããããªãã®ã³ã¹ããæããã®ã§ããã©ã¡ã¿ãé »ç¹ã«ãã·ãã·åçã«åãæ¿ãããã¨ãã«ã¯åãã¦ããªãããã§ããã¾ã SharedComponentData ãåã¨ã³ãã£ãã£ããã¢ã¯ã»ã¹ããã«ã¯ç¾ç¶ GetSharedComponentManaged<T>() ã®ãããªããã¼ã¸ããªå¼ã³åºãã®ã³ã¹ããçºçããå¾ã§è¦ã Burst ã®æ©æµãåãããã¾ãããæè§ã® ECS ã®ã¡ã¢ãªå¹çã®æ©æµãèãã¦ãã¾ãæ¸å¿µãããç¹ã®ã§ãã®ã¢ããã¼ãã¯ããã¾ããã
ããã§ãèµ·ç¹ã¨ãªã£ã¦ãã Config ã®ã¤ããã¨ã³ãã£ãã£ã«ãã©ã¡ã¿ç¨ã®ã³ã³ãã¼ãã³ããä»ä¸ããããã ComponentLookup ã¨ããä»çµã¿ã§åã£ã¦ãããã¨ã«ãã¾ããã
ãã¡ãã¯ãã¨ã³ãã£ãã£ãæ·»ãåã¨ãã¦ã¢ã¯ã»ã¹ã§ããæå®ããã³ã³ãã¼ãã³ããæ ¼ç´ãã¦ããä»çµã¿ã§ããã¢ã¯ã»ã¹ã¯ O(1) ã§è»½ã Burst ã®æ©æµãåããããããã§ããå Fish ããèªèº«ãçæããã¨ã³ãã£ãã£ãçµç±ãã¦ãã©ã¡ã¿ã³ã³ãã¼ãã³ãã«ã¢ã¯ã»ã¹ãããã¨ããã®ãå®è£
ãã¦ã¿ã¾ãã
ãã©ã¡ã¿ãèæ ®ããå®è£
è²ã æ§é ãæ¸ãç´ãã¦ããã¾ããã¾ãã¯ãã©ã¡ã¿ã¯ã¨ãã£ã¿ä¸ã§è¨å®ãããã®ã§ãªã¼ãµãªã³ã°ã³ã³ãã¼ãã³ããä½ãã¾ãã
public struct Parameter : IComponentData { public float MinSpeed; public float MaxSpeed; public float3 AreaScale; public float AreaDistance; public float AreaForce; public float NeighborDistance; public float NeighborAngle; public float SeparationForce; public float AlignmentForce; public float CohesionForce; } public class ParameterAuthoring : MonoBehaviour { [Header("Move")] public float MinSpeed = 2f; public float MaxSpeed = 5f; [Header("Area")] public float3 AreaScale = 5f; public float AreaDistance = 3f; public float AreaForce = 1f; [Header("Neighbors")] public float NeighborDistance = 1f; public float NeighborFov = 90f; [Header("Separation")] public float SeparationForce = 5f; [Header("Alignment")] public float AlignmentForce = 2f; [Header("Cohesion")] public float CohesionForce = 2f; } public class ParameterBaker : Baker<ParameterAuthoring> { public override void Bake(ParameterAuthoring src) { var entity = GetEntity(TransformUsageFlags.None); AddComponent(entity, new Parameter() { MinSpeed = src.MinSpeed, MaxSpeed = src.MaxSpeed, AreaScale = src.AreaScale, AreaDistance = src.AreaDistance, AreaForce = src.AreaForce, NeighborDistance = src.NeighborDistance, NeighborAngle = src.NeighborAngle, SeparationForce = src.SeparationForce, AlignmentForce = src.AlignmentForce, CohesionForce = src.CohesionForce, }); } }
ãããããªãã©ã¡ã¿ãç¨æãã¦ãã¾ããã´ã¼ã«ã¯ãããããåã·ã¹ãã ã®è¨ç®ã®ä¸ã§ä½¿ããããã«ããã¨ããã§ããã¾ããè¤æ°ã® Parameter ãæ··å¨ã§ããããã«ããã¾ãã
ãã㯠SubScene ã®ä¸ã® GameObject ã«ä»ä¸ãã¦ããã¾ãããªããConfig ã¨ãã¦ãããã®ã¯ School ã¨ã¡ãã£ã¨ååãå¤ãã¾ããï¼ãã©ã¡ã¿ã¨ã¡ãã£ã¨æå³ããã¶ããã...ãSchool of Fish = é群ï¼ãããã 2 ã¤ã®ã³ã³ãã¼ãã³ããã¢ã¿ãããããªãã¸ã§ã¯ãã Bootstrap ã¨åä»ãã¦ãã¾ãã
次㫠FishAuthoring å¨ããæ¸ãç´ãã¾ããVelocity ã Acceleration ãåãã¦ãã¾ããããååå°ããªç²åº¦ãªã®ã§åãåããæ¥½ãªããã«ã¾ã¨ãã¦ãã¾ããã¨ã«ãã¾ãã
public struct Fish : IComponentData { public float3 Velocity; public float3 Acceleration; public Entity ParamEntity; // ãããæ°ãã } public class FishAuthoring : MonoBehaviour { } public class FishBaker : Baker<FishAuthoring> { public override void Bake(FishAuthoring src) { var entity = GetEntity(TransformUsageFlags.Dynamic); AddComponent(entity, new Fish() { Velocity = UnityEngine.Random.insideUnitSphere, Acceleration = 0f, ParamEntity = Entity.Null, }); } }
ã³ã³ãã¼ãã³ãã®ã¡ã³ãã¨ã㦠Entity åã®å¤æ°ã追å ããã¾ããããã¦ã次ã¯ããã«ãã©ã¡ã¿ã®æå±ããã¨ã³ãã£ãã£ãã»ãããã¦ããã¾ããSpawnSystem ãæ¹é ãã¦ããã¾ãã
public partial struct SpawnSystem : ISystem { public void OnUpdate(ref SystemState state) { foreach (var (school, param, entity) in SystemAPI.Query< RefRW<School>, RefRO<Parameter>>().WithEntityAccess()) // Entity ãåå¾ { if (school.ValueRO.Initialized) continue; Create(ref state, school.ValueRO, param.ValueRO, entity); school.ValueRW.Initialized = true; } } void Create( ref SystemState state, in School school, in Parameter param, Entity groupEntity) { ... var entities = state.EntityManager.Instantiate( school.Prefab, school.SpawnCount, Allocator.Temp); ... foreach (var entity in entities) { ... var fish = SystemAPI.GetComponentRW<Fish>(entity); ... fish.ValueRW.ParamEntity = groupEntity; // ããã§ã»ãã } } }
ãããªå½¢ã§ School ããã³ Parameter ã³ã³ãã¼ãã³ããã¤ããã¨ã³ãã£ãã£ãæ¢ãã¦ãã¦ãä¸åã ã Create() ãå®è¡ããããã«ãã¾ããCreate() ã®ä¸èº«ã¯å
ç¨ã® SpawnSystem ã¨ã»ã¼åãã§ãããæ°ãã追å ããã¨ã³ãã£ãã£ãã»ããããã¨ãããç°ãªãã¾ããã¡ãªã¿ã«ã¨ã³ãã£ãã£ã¯ã¯ã¨ãªã« WithEntityAccess() ãã¤ãããã¨ã§åå¾å¯è½ã§ãã
æå¾ã«ãããã·ã¹ãã ããå©ç¨ãã¾ããããã§ ComonentLookUp<T> ãå©ç¨ãã¾ãã
public partial struct MoveSystem : ISystem { ComponentLookup<Parameter> _paramLookUp; public void OnCreate(ref SystemState state) { _paramLookUp = state.GetComponentLookup<Parameter>(isReadOnly: true); } public void OnUpdate(ref SystemState state) { _paramLookUp.Update(ref state); ... foreach (var (fish, lt) in SystemAPI.Query< RefRW<Fish>, RefRW<LocalTransform>>()) { var param = _paramLookUp[fish.ValueRO.ParamEntity]; fish.ValueRW.Velocity += fish.ValueRO.Acceleration * dt; var speed = math.length(fish.ValueRO.Velocity); speed = math.clamp(speed, param.MinSpeed, param.MaxSpeed); var dir = math.normalize(fish.ValueRO.Velocity); fish.ValueRW.Velocity = dir * speed; ... } } }
SystemState ãã GetComponentLookup<T>() ã§ ComponentLookUp ãåå¾ããOnUpdate() ã®æåã§ãã® Update() ãå¼ã³ã¾ããããã¨ãæ·»åã§ä¸ããã¨ã³ãã£ãã£ã®ã³ã³ãã¼ãã³ããåã£ã¦ãããã¨ããä»çµã¿ã§ããå
ã»ã©ãã©ã㨠SharedComponentData ã®ã¨ããã§è§¦ãã¾ããããã¨ã³ãã£ãã£ã¯ã¢ã¼ãã¿ã¤ãã®å¤æ´ã SharedComponentData ã®å¤ã®å¤æ´ãªã©ã§ãã£ã³ã¯ãåæ§ç¯ãããããããããããã«è¿½å¾ãã¦é©åãªã³ã³ãã¼ãã³ãã®åç
§ãåã£ã¦ãããããã«æ¯å Update() ãå¼ã¶å¿
è¦ãããã¾ãã
ããã§æå®ãããã©ã¡ã¿ãåã¤ã³ã¹ã¿ã³ã¹ã«ä¿æããããã¨ãåºæ¥ã¾ããï¼
å£è·³ãè¿ã
ã§ã¯ãã®å¢ãã§å£ã®è·³ãè¿ããå®è£
ãã¦ã¿ã¾ããããAreaSystem ã¨ãã¾ãããã®ã³ã¼ãã¯ã»ã¨ãã©ä»¥åã¨åãã§ããISystem ã ComponentLookUp ãªã©ã®æ¸ãæ¹ã ãä¿®æ£ããã³ã¼ãã以ä¸ã«ãªãã¾ãã
public partial struct AreaSystem : ISystem { ComponentLookup<Parameter> _paramLookUp; public void OnCreate(ref SystemState state) { _paramLookUp = state.GetComponentLookup<Parameter>(isReadOnly: true); } public void OnUpdate(ref SystemState state) { _paramLookUp.Update(ref state); foreach (var (fish, lt) in SystemAPI.Query< RefRW<Fish>, RefRO<LocalTransform>>()) { var param = _paramLookUp[fish.ValueRW.ParamEntity]; var scale = param.AreaScale * 0.5f; var thresh = param.AreaDistance; var weight = param.AreaForce; float3 pos = lt.ValueRO.Position; fish.ValueRW.Acceleration += GetAccelAgainstWall(pos.x - -scale.x, math.right(), thresh, weight) + GetAccelAgainstWall(pos.y - -scale.y, math.up(), thresh, weight) + GetAccelAgainstWall(pos.z - -scale.z, math.forward(), thresh, weight) + GetAccelAgainstWall(+scale.x - pos.x, math.left(), thresh, weight) + GetAccelAgainstWall(+scale.y - pos.y, math.down(), thresh, weight) + GetAccelAgainstWall(+scale.z - pos.z, math.back(), thresh, weight); } } float3 GetAccelAgainstWall(float dist, float3 dir, float thresh, float weight) { if (dist < thresh) { dist = math.max(dist, 0.01f); var a = dist / math.max(thresh, 0.01f); return dir * (weight / a); } return float3.zero; } }
ããã§å®è¡ããã¨æ¬¡ã®ããã«ãªãã¾ãã
è¤æ°ã®ãã©ã¡ã¿ãæã¦ãããã«ããã®ã§ãããä¸ã¤çæãªãã¸ã§ã¯ãï¼Bootstrapï¼ãå¢ããã¦å¥ã® Prefab ãå²ãå½ã¦ã¦ã¿ã¾ããã
ããã§ãã©ã¡ã¿è¨å®ãåºæ¥ã¾ããï¼
è¿åæ¤ç´¢
DynamicBuffer ã®ä»ä¸
ã§ã¯ Boids ã®ã¢ã«ã´ãªãºã ãå®è£ ãã¦ããã¾ããããæãæéããããããã©ã¼ãã³ã¹ã³ã¹ãã®å¤§ãããã®ãå¨è¾ºã®ã¤ã³ã¹ã¿ã³ã¹ã®æ¢ç´¢ã§ããããã¯å¤åã«æé©åã®ä½å°ãããã®ã§ãããã²ã¨ã¾ãæç´ã«å ¨æ¢ç´¢ã¨ãã¦ã³ã¼ããæ¸ãã¦ã¿ããã¨ã«ãã¾ããã¤ã¾ãããã¹ã¦ã®ã¤ã³ã¹ã¿ã³ã¹ã«å¯¾ãã¦ä»ã®ãã¹ã¦ã®ã¤ã³ã¹ã¿ã³ã¹ã¨ã®è·é¢ã測ã£ã¦è¿ããã®ããªã¹ãåããäºéã«ã¼ãã«ãªãã¾ãã
ä»ã®ã·ã¹ãã ã§ã¯ SystemAPI.Query ã§åãæå®ãã¦ã³ã³ãã¼ãã³ããããéãã¦ãã¦ãã¾ããããäºéã«ã¼ããããã®ã§å°ãæ
å ±ãåã£ã¦ããæ¹æ³ãã«ã¼ãã®ããæ¹ãç°ãªãã¾ãã
ã¾ããå¯å¤é·ã®ãã¼ã¿ã§ãã DynamicBuffer<T> ãä»ä¸ãã¾ãã
[InternalBufferCapacity(8)] public struct NeighborsEntityBufferElement : IBufferElementData { public Entity Entity; } ... public class FishBaker : Baker<FishAuthoring> { public override void Bake(FishAuthoring src) { var entity = GetEntity(TransformUsageFlags.Dynamic); ... AddBuffer<NeighborsEntityBufferElement>(entity); } }
IBufferElementData ã¯å¯å¤é·ãã¼ã¿ã®ä¸è¦ç´ ã¨ãªãã¾ãããã㦠AddBuffer<T>() ããã¨æå®ããã¨ã³ãã£ãã£ã¸ DynamicBuffer<T> ãä»ä¸ããã¾ãããã®ãããã¡ã¯äºã決ããããè¦ç´ æ°ï¼Capacityï¼ã®ç¯å²å
ã§ã¨ã³ãã£ãã£ã®ãã£ã³ã¯ã®ä¸ã«ã¤ã³ã©ã¤ã³å±éããã¾ããããã«ãã£ã¦ãã£ãã·ã¥å¹çã®è¯ãã¢ã¯ã»ã¹ãå¯è½ã«ãªã£ã¦ãã¾ãããã ããã®ãµã¤ãºãè¶
ããã¨å¯å¤é·ãã¼ã¿ã¯ãã¼ãå´ã¸ã¨ç§»ããã£ã³ã¯ã®ä¸ã§ã¯ããã¸ã®åç
§ãæã¤å½¢ã«ãªãããã§ãï¼ããã©ã¼ãã³ã¹ãè½ã¡ã¾ãï¼ããããã£ãä»çµã¿ã§å¯å¤é·ãå®ç¾ããã¦ããããã§ãã
ã¡ãªã¿ã«ããã©ã«ãã® Capacity 㯠InternalBufferCapacity ã§æå®ã§ãã¾ããæå®ããªãå ´åã¯ãå±éãããã¼ã¿ã 128 byte 以å
ã«ãªãè¦ç´ æ°ãæå®ãããããã§ããä¾ãã°ä¸è¨ãã¼ã¿ã¯ä¸è¦ç´ 8 byteï¼Entity ã®ãµã¤ãºï¼ãªã®ã§ã128/8 = 16 ãããã©ã«ãã¨ãªãã¾ãã
ä»åã¯ãããªã«ããããã®å¨å²ã®å¨å²ã®ã¤ã³ã¹ã¿ã³ã¹ã®åå¾ã¯è¦ããªãã®ã§ã8 åç¨åº¦ã«ãã¦ãã¾ãã
è¿åæ¢ç´¢
ã§ã¯ããã«åã¤ã³ã¹ã¿ã³ã¹ã®è¦çåæ¹ã«ããå¥ã®ã¤ã³ã¹ã¿ã³ã¹ãè¦ã¤ããã·ã¹ãã ãæ¸ãã¦ã¿ã¾ããå°ãé·ãã®ã§ã³ã¼ãä¸ã«ã³ã¡ã³ããã¾ãã
public partial struct NeighborsDetectionSystem : ISystem { // ãã©ã¡ã¿ç¨ã® LookUp ComponentLookup<Parameter> _paramLookUp; // DynamicBuffer ãã¨ã³ãã£ãã£å¼ãããããã® LookUp BufferLookup<NeighborsEntityBufferElement> _neighborsLookUp; // æ¯å SystemAPI.Query ã§ã¯ãªãäºãã¯ã¨ãªããã£ãã·ã¥ãã¦ãã EntityQuery _query; public void OnCreate(ref SystemState state) { // LookUp çæ _paramLookUp = state.GetComponentLookup<Parameter>(isReadOnly: true); _neighborsLookUp = state.GetBufferLookup<NeighborsEntityBufferElement>(isReadOnly: false); // ã¯ã¨ãªã¯ãã®ãããªå½¢ã§ä½æå¯è½ _query = SystemAPI.QueryBuilder().WithAll<Fish, LocalTransform>().Build(); } public void OnUpdate(ref SystemState state) { // LookUp æ´æ° _paramLookUp.Update(ref state); _neighborsLookUp.Update(ref state); // ãã¹ã¦ Dispose ãã¹ã³ã¼ãããæãããè¡ãããã« using ãã¤ãã¦ãã // äºéã«ã¼ããåãããã«ãã¯ã¨ãªããã¨ã³ãã£ãã£ãã³ã³ãã¼ãã³ãã®é åãåå¾ using var entities = _query.ToEntityArray(Allocator.Temp); using var localTransforms = _query.ToComponentDataArray<LocalTransform>(Allocator.Temp); using var fishes = _query.ToComponentDataArray<Fish>(Allocator.Temp); // ã«ã¼ã㯠for æã§åãã¦ã¤ã³ããã¯ã¹ã§ã¢ã¯ã»ã¹ for (int i = 0; i < entities.Length; ++i) { var entity0 = entities[i]; // DynamicBuffer ã¯ã¨ã³ãã£ãã£æ·»åã¢ã¯ã»ã¹ã§åå¾å¯è½ // ãããã¡ã¯ã¯ãªã¢ãã¦ãã var neighbors0 = _neighborsLookUp[entity0]; neighbors0.Clear(); var fish0 = fishes[i]; var param = _paramLookUp[fish0.ParamEntity]; var neighborAngle = math.radians(param.NeighborAngle); var neighborDist = param.NeighborDistance; var prodThresh = math.cos(neighborAngle); var lt0 = localTransforms[i]; var pos0 = lt0.Position; var fwd0 = math.normalizesafe(fish0.Velocity); // äºéã«ã¼ãã§ç·å½ã for (int j = 0; j < entities.Length; ++j) { if (i == j) continue; // ç¸æã¨ã®è·é¢ãè¦ã var lt1 = localTransforms[j]; var pos1 = lt1.Position; var to = pos1 - pos0; var dist = math.length(to); if (dist > neighborDist) continue; // è¦çã«å ¥ã£ã¦ãããããã§ã㯠var dir = to / math.max(dist, 1e-3f); var prod = math.dot(dir, fwd0); if (prod < prodThresh) continue; // ãããã¡ã«è¿½å var entity1 = entities[j]; var elem = new NeighborsEntityBufferElement() { Entity = entity1 }; neighbors0.Add(elem); // ã¤ã³ã©ã¤ã³å±éããå¤ããªãããã«ãã£ãã«å°éãããçµãã if (neighbors0.Length == neighbors0.Capacity) break; } } } }
äºéã«ã¼ããè¡ãããã« SystemAPI.Query ããã¹ãããå ´åã¯ã«ã¼ãã®è¦ç´ ãã¿ãã«ã¨ãã¦è¿ã£ã¦ãã¦ãã¾ãä¸ã«ãã«ã¼ãå
ã§å度ã¯ã¨ãªãçµã¿ç«ã¦ãªãã¨ãªãã¾ãããã¯ã¨ãªä½æã®ã³ã¹ãããã ã§ã¯ãªãã®ã§ã代ããã« EntityQuery ãæåã«ä½ã£ã¦ãã£ãã·ã¥ãã¦ããã¦ä½¿ãã¾ãããã¨ã«ãã¾ããã¾ãããããã ToEntityArray() ã ToComponentDataArray<T> ã«ãã£ã¦é
åã¨ãã¦è¦ç´ ãå¾ã¦ããã¾ããé
åãããã°äºéã«ã¼ããæ¸ãã®ã¯ç°¡åã§ããã
ãããã¡ã¯ BufferLookUp<T> ãç¨ãã¦ã¢ã¯ã»ã¹ãã¦ãã¾ããæ®å¿µãªããå
ã®ä»æ§ã®ãããªé¢ä¿ãããã¯ã¨ãªããã¯ã¢ã¯ã»ã¹åºæ¥ãªãããã§ãããComponentLookUp<T> ã¨åãæä½ã§ DynamicBuffer ãåå¾ã§ãã¾ãã
ã¯ã¼ã¹ãã±ã¼ã¹ã§ã¯å ¨ã¦ã®ã¤ã³ã¹ã¿ã³ã¹ãè¦ç´ ã¨ãã¦å ¥ã£ã¦ãã¾ãã®ã§ããããããªã«å¤ãã®ã¤ã³ã¹ã¿ã³ã¹ãå¾ã¦ã Boids ã®è¦ãç®ã«å¤§ããªå½±é¿ã¯ä¸ããªãã®ã§ãã¤ã³ã©ã¤ã³åãè§£é¤ãããã¼ã¿ããã¼ãã¸ç§»ã£ã¦ãã¾ãã®ãé²ãããã«ããé åã®æå¤§æ°ã¯ Capacity ã§å¶éããããã«ãã¾ããã
Boids ã®ã¢ã«ã´ãªãºã ã®å®è£
ããã§ã¯åã¢ã«ã´ãªãºã ãå®è£ ãã¦ããã¾ããããã¾ãã¯åé¢ï¼Separationï¼ããã§ãããã¡ããã³ã¡ã³ããä»è¨ãã¾ãã
public partial struct SeparationSystem : ISystem { ComponentLookup<Parameter> _paramLookUp; // Neighbors ã®ãããã¡ã®ã¨ã³ãã£ãã£ãã LocalTransform å¼ãããããã®ã«ãã¯ã¢ãã ComponentLookup<LocalTransform> _transformLookUp; public void OnCreate(ref SystemState state) { _paramLookUp = state.GetComponentLookup<Parameter>(isReadOnly: true); _transformLookUp = state.GetComponentLookup<LocalTransform>(isReadOnly: true); } public void OnUpdate(ref SystemState state) { _paramLookUp.Update(ref state); _transformLookUp.Update(ref state); // DynamicBuffer ãã¯ã¨ãªã«æå®ãã¦ã¢ã¯ã»ã¹å¯è½ foreach (var (fish, lt, neighbors) in SystemAPI.Query< RefRW<Fish>, RefRO<LocalTransform>, DynamicBuffer<NeighborsEntityBufferElement>>()) { var n = neighbors.Length; if (n == 0) continue; var param = _paramLookUp[fish.ValueRW.ParamEntity]; var pos = lt.ValueRO.Position; // å¹³åã®é¢ããæ¹åãã¯ãã«ãè¨ç® var forceDir = float3.zero; for (int i = 0; i < n; ++i) { var neighborEntity = neighbors[i].Entity; var neighborPos = _transformLookUp[neighborEntity].Position; var to = neighborPos - pos; forceDir += -math.normalizesafe(to); } forceDir /= n; forceDir = math.normalizesafe(forceDir); // å é度ã«è¶³ã fish.ValueRW.Acceleration += forceDir * param.SeparationForce; } } }
è㯠DynamicBuffer<T> ãã¯ã¨ãªã«æå®ãããã¨ã§ã¿ãã«ã§ç°¡åã«é
åãåå¾ã§ããã¨ããã§ãããããã¦æ ¼ç´ããã¨ã³ãã£ãã£ãéãã¦ãä»åº¦ã¯ LocalTransform ã³ã³ãã¼ãã³ããåã£ã¦ãã¦ãã¾ããããã«ã¯ ComponentLookUp ã使ã£ã¦ãã¾ãã
ãã¦ãããã§åé¢ãå®è£ ã§ãã¾ãããGIF ã ã¨åããã¥ããã§ããâ¦è¿ã¥ããããã¨é¢ããããã«åãã¾ãã
æ´å
æ¬¡ã¯æ´åï¼Alignmentï¼ã§ããè¦çå ã®åä½ã®é度平åã«è¿ã¥ããã·ã¹ãã ã«ãªãã¾ãããã¡ãã¯ç¹å¥ãªæ°ããã³ã¼ãã¯å¿ è¦ã¨ãã¾ããã
public partial struct AlignmentSystem : ISystem { ComponentLookup<Parameter> _paramLookUp; ComponentLookup<Fish> _fishLookUp; public void OnCreate(ref SystemState state) { _paramLookUp = state.GetComponentLookup<Parameter>(isReadOnly: true); _fishLookUp = state.GetComponentLookup<Fish>(isReadOnly: true); } public void OnUpdate(ref SystemState state) { _paramLookUp.Update(ref state); _fishLookUp.Update(ref state); foreach (var (fish, neighbors) in SystemAPI.Query< RefRW<Fish>, DynamicBuffer<NeighborsEntityBufferElement>>()) { var n = neighbors.Length; if (n == 0) continue; var averageV = float3.zero; for (int i = 0; i < n; ++i) { var neighborEntity = neighbors[i].Entity; var neighborV = _fishLookUp[neighborEntity].Velocity; averageV += neighborV; } averageV /= n; var param = _paramLookUp[fish.ValueRW.ParamEntity]; var v = fish.ValueRO.Velocity; fish.ValueRW.Acceleration += (averageV - v) * param.AlignmentForce; } } }
çµå
æå¾ã«çµåï¼Cohesionï¼ã§ããããã¯è¿é£ã®åä½ã®ä¸å¿ã¸ã¨ç§»åããåãä¸ããã·ã¹ãã ã«ãªãã¾ãããã¡ããæ°ããææ³ãªã©ã¯åºã¦ãã¾ããã
public partial struct CohesionSystem : ISystem { ComponentLookup<Parameter> _paramLookUp; ComponentLookup<LocalTransform> _transformLookUp; public void OnCreate(ref SystemState state) { _paramLookUp = state.GetComponentLookup<Parameter>(isReadOnly: true); _transformLookUp = state.GetComponentLookup<LocalTransform>(isReadOnly: true); } public void OnUpdate(ref SystemState state) { _paramLookUp.Update(ref state); _transformLookUp.Update(ref state); foreach (var (fish, lt, neighbors) in SystemAPI.Query< RefRW<Fish>, RefRW<LocalTransform>, DynamicBuffer<NeighborsEntityBufferElement>>()) { var n = neighbors.Length; if (n == 0) continue; var averagePos = float3.zero; for (int i = 0; i < n; ++i) { var neighborEntity = neighbors[i].Entity; var neighborPos = _transformLookUp[neighborEntity].Position; averagePos += neighborPos; } averagePos /= n; var pos = lt.ValueRO.Position; var param = _paramLookUp[fish.ValueRW.ParamEntity]; fish.ValueRW.Acceleration += (averagePos - pos) * param.CohesionForce; } } }
å®è¡é ã®èª¿æ´
ãã¦ããã¾ãåãã¦ããããã«è¦ãã¾ããå®ã¯ãã®ã¾ã¾ã§ã¯ã·ã¹ãã ã®é çªãã°ãã°ãã«ãªã£ã¦ãã¾ããæ¬æ¥ã¯ MoveSystem ããä»ã®å
¨ã¦ã®ã·ã¹ãã ã®å éåº¦ã®æ´æ°çµæããã¨ã«éåº¦ã®æ´æ°ãããããã LocalTransform ã®ä½ç½®ã»å転ã¨ãã¦åæ ãã¦ã»ãããããæå¾ã«å®è¡ããã®ãæã¾ããã§ãããã®é çªå¶å¾¡ã«ä¾¿å©ãªæ©è½ã¨ã㦠UpdateBefore ã UpdateAfter ã¨ãã£ãé åºå¶å¾¡ã¢ããªãã¥ã¼ããç¨æããã¦ãã¾ãã
ãããæ¬¡ã®ããã«å種ã·ã¹ãã ã¸çµã¿è¾¼ã¿ã¾ãã
[UpdateBefore(typeof(MoveSystem))] public partial struct AlignmentSystem : ISystem { ... }
ããã«ãããMoveSystem ãç¨æããã·ã¹ãã ã®ä¸ã§æå¾ã«å®è¡ãããããã«ãªãã¾ãã
å ãã¦ãæ´ã« UpdateAfter() ã使ã£ã¦ãNeighborsDetectionSystem ãããå¾ã«ããå¿
è¦ãããã¾ãã
[UpdateBefore(typeof(MoveSystem))] [UpdateAfter(typeof(NeighborsDetectionSystem))] public partial struct AlignmentSystem : ISystem { ... }
ãã¦ãããã§ä¸éã Boids ã®æåãåºæ¥ã¾ããããããã©ã¼ãã³ã¹å¨ãï¼Job ã Burstï¼ã®è©±ã«å ¥ãåã«ãããã¤ãå®ç¨ä¸æ°ã«ãªãç¹ã«å¯¾å¿ãã¦ããããã¨æãã¾ãã
ãã©ã¡ã¿ã®ãªã¢ã«ã¿ã¤ã æ´æ°
Boids ã®åãã調æ´ããã«ã¯ãã©ã¡ã¿ã®èª¿æ´ãã¨ã¦ã大äºãªã®ã§ãããç¾ç¶ãã©ã¡ã¿ã¯ Bake ããã¦ãã¾ã£ã¦ãããã©ã³ã¿ã¤ã ã§ã®å¤æ´ãã§ãã¾ãããã¾ãå®éã®ã¦ã¼ã¹ã±ã¼ã¹ã§ã¯å¨å²ã®ç¶æ³ã«å¿ãã¦åçã«ãã©ã¡ã¿ã夿´ãããã±ã¼ã¹ãåºã¦ããå¯è½æ§ãããã¾ãããããã®è¦ä»¶ãæºãããæ§é ãèãã¦ã¿ã¾ãããããããªæ¹æ³ãããã¨æãã¾ãã®ã§ããã§ã¯ä¸ä¾ãã¨ããæãã§ã覧ãã ããã
æ´æ°ã¯ Managed ãªä¸çããè¡ããã¨ã«ãªãã¾ãããã©ã¡ã¿ã¯è¤æ°æã¦ãããã«æ´æ°ãããããããããèå¥ããæ å ±ãå¿ è¦ã¨ãªãã¾ããããã§ãã©ã¡ã¿æ§é ã Managed ãªä¸ç㨠Unmanaged ã§ããåãã§ããããã«ãã©ã¡ã¿æ§é ã«å°ãæãå ¥ãã¾ããæ´ã«ããã«å¯¾å¿ã§ãããã©ã¡ã¿ã® Set / Get ã³ã¼ããå®è£ ãã¦ã¿ã¾ãã
// MonoBehaviour ä¸ã«é²åºã§ããããã·ãªã¢ã©ã¤ãºå±æ§ãä»ä¸ [System.Serializable] public struct Parameter : IComponentData { // ãã® Type ã ID ã¨ãã¦ä½¿ããã¨ã«ãã // åãã¿ã¤ããè¤æ°ã® Boids ã«å²ãå½ã¦ããã¦ããã°å ¨é¨æ´æ° public int Type; public float MinSpeed; public float MaxSpeed; public float3 AreaScale; public float AreaDistance; public float AreaForce; public float NeighborDistance; public float NeighborAngle; public float SeparationForce; public float AlignmentForce; public float CohesionForce; // ããã©ã«ããã©ã¡ã¿ public static Parameter Default { get => new Parameter() { Type = 0, MinSpeed = 2f, MaxSpeed = 5f, AreaScale = 5f, AreaDistance = 3f, AreaForce = 1f, NeighborDistance = 1f, NeighborAngle = 90f, SeparationForce = 5f, AlignmentForce = 5f, CohesionForce = 5f, }; } // å ¨ã¦ã®åè´ãã Type ã®ãã¼ã¿ã䏿¸ã // å®è£ ã®è§£èª¬ã¯å¾è¿° public static bool Set(in Parameter newParam) { var manager = World.DefaultGameObjectInjectionWorld.EntityManager; var query = manager.CreateEntityQuery(ComponentType.ReadWrite<Parameter>()); var entities = query.ToEntityArray(Allocator.Temp); if (entities.Length == 0) return false; bool set = false; foreach (var entity in entities) { var param = manager.GetComponentData<Parameter>(entity); if (param.Type != newParam.Type) continue; manager.SetComponentData(entity, newParam); set = true; } return set; } // Type ã®åè´ãããã©ã¡ã¿ãåå¾ public static bool Get(ref Parameter outParam) { var manager = World.DefaultGameObjectInjectionWorld.EntityManager; var query = manager.CreateEntityQuery(ComponentType.ReadOnly<Parameter>()); var parameters = query.ToComponentDataArray<Parameter>(Allocator.Temp); if (parameters.Length == 0) return false; foreach (var param in parameters) { if (param.Type != outParam.Type) continue; outParam = param; return true; } return false; } } public class ParameterAuthoring : MonoBehaviour { // ç´æ¥ãªã¼ãµãªã³ã°ã³ã³ãã¼ãã³ããå¤ãæã¤ä»£ããã« // ã·ãªã¢ã©ã¤ãºãã IComponentData ã使ãããã«ãã public Parameter param = Parameter.Default; } public class ParameterBaker : Baker<ParameterAuthoring> { public override void Bake(ParameterAuthoring src) { var entity = GetEntity(TransformUsageFlags.None); // ç´æ¥ã»ãããã AddComponent(entity, src.param); } }
å°ãé·ãã§ããããããªæãã§ããã¾ãããã©ã¡ã¿ã®ãã¼ã¿ããªã¼ãµãªã³ã°ã³ã³ãã¼ãã³ãããå¤ããã·ãªã¢ã©ã¤ãºå¯è½ãª IComponentData ã¨ãã¾ãããããã¦è¤æ°ãã®ãã©ã¡ã¿ãåãæ¿ããããããã«ããã©ã¡ã¿ãèå¥ã§ãããããªå¤æ°ã¨ã㦠Type ã追å ãã¾ãããã§ã¯ããã使ã£ã¦ããã©ã¡ã¿ã«ãã¼ã¿ãã»ãããããèªã¿åºããããããParameter.Set() 㨠Parameter.Get() ãè¦ã¦ããã¾ãããã
ã¾ã ECS ã®å¤ã®ä¸çããã¨ã³ãã£ãã£ã®ããã¼ã¸ã£ã«ã¢ã¯ã»ã¹ããã«ã¯ World.DefaultGameObjectInjectionWorld.EntityManager ãå©ç¨ãã¾ãã以å㯠World.Active ã§ããã©ã«ãã®ã¯ã¼ã«ãã«ã¢ã¯ã»ã¹ã§ãã¾ããããã¡ãã£ã¨é·ããªãã¾ãããã
ãã®ããã¼ã¸ã£ãçµç±ã㦠CreateEntityQuery() ã§ã¯ã¨ãªãçæããå¿
è¦ãªã³ã³ãã¼ãã³ãã«ã¢ã¯ã»ã¹ã§ãã¾ããã¡ãªã¿ã« EntityQueryBuilder ã§ãã¯ã¨ãªã¯ä½ãã¾ãããCreateEntityQuery() ã®ã»ããå°ãã·ã³ãã«ã«æ¸ãã¾ãã
// var query = manager.CreateEntityQuery(ComponentType.ReadWrite<Parameter>()); var query = new EntityQueryBuilder(Allocator.Temp).WithAll<Parameter>().Build(manager);
ãããã¦ã¯ã¨ãªãä½ãã¦ãã¾ãã°å¾ã¯æ¢åºã® ToComponentDataArray<T>()
ã§ Parameter ã®ã³ã³ãã¼ãã³ãã®é
åãå¾ããã¾ãããã¨ã¯å¾ãããã³ã³ãã¼ãã³ãã® Type ãè¦ã¦ãã£ã¦åè´ãããã®ã䏿¸ããã¦ããæãã§ããã¡ãªã¿ã« ToComponentDataArray ã§è¿ã£ã¦ããé
åã¯å®ä½ã®ã³ã³ãã¼ãã³ãã§ã¯ãªãã³ãã¼ããã䏿ãªãã¸ã§ã¯ãã¨ãªãã¾ãããã®ãããå®ä½ãæ¸ãæããããã«ã¯ SetComponentData() ãå¼ãã§ãããå¿
è¦ãããã¾ããåæ§ã®ããã»ã¹ã Get å´ã§ãè¡ãã°æºåå®äºã§ãã
ãã¨ã¯ãããå©ç¨ããé©å½ãª MonoBehaviour ã³ã³ãã¼ãã³ããç¨æããã°æ¸ãæããåºæ¥ã¾ããä»åã¯ãã¨ãã£ã¿ä¸ã§å¤ãç·¨éããã¨æå®ããã¿ã¤ãã®ãã©ã¡ã¿ãæ¸ãæãã§ããæ¬¡ã®ãããªãã®ãä½ã£ã¦ã¿ã¾ãããå©ç¨ããªãã¨ã㯠Disable ã«ãã¦ããã使ãããã¨ã㯠On ã«ããã¨æ¯ãã¬ã¼ã æ¸ãæãããã¨ãããã®ã§ããæ¬çªã«ãã®ã¾ã¾ä½¿ãã¨ãããããã©ã¡ã¿ã®åçæ¸ãæããã§ãã¯ç¨ã¿ãããªä½ç½®ã¥ãã®ãã®ã§ãã
public class ParameterUpdater : MonoBehaviour { // ã¨ãã£ã¿ä¸ã§è¦ãããã¼ã¿ public Parameter param; // æå®ããã¿ã¤ãã®ãã¼ã¿ãå徿¸ã¿ã bool _isGot = false; // ã¨ãã£ã¿ä¸ã®ã¿ã¤ãã®å¤æ´ãç£è¦ int _type = 0; void OnEnable() { _type = param.Type; _isGot = Parameter.Get(ref param); } void OnDisable() { _isGot = false; } void Update() { if (param.Type != _type) { _isGot = false; } // ã¿ã¤ã夿´å¾ã¯ãã©ã¡ã¿ãåå¾ if (!_isGot) { _isGot = Parameter.Get(ref param); if (_isGot) { _type = param.Type; } } // ãã©ã¡ã¿å徿¸ã¿ã®å ´åã¯ã¨ãã£ã¿ã§æå®ãããã®ã§æ¸ãæã else { Parameter.Set(param); } } }
ããã§æ¬¡ã®ããã«åä½ãã¾ãã
ã¨ãã£ã¿ã®ãããã°æç»
以åã¯ã¨ãªã¢ãã·ã¼ã³ä¸ã§ MonoBehaviour.OnDrawGizmos() ã使ã£ã¦ãããã°è¡¨ç¤ºãã¦ããã®ã§ãããä»ã®æ§æã ã¨ã¨ã³ããªãã¤ã³ãã¨ãªã MonoBehaviour ã³ã³ãã¼ãã³ããããªãã®ã§æç»ã§ãã¾ãããããããããã°è¡¨ç¤ºã¨ãã¦ã©ã®ã¨ãªã¢ã Boids ãåãã®ãã¯è¦ããã¨ããã§ãã
æ¹æ³ã¨ãã¦ã¯ä¾ãã°ä»¥ä¸ã® 2 ã¤ãèãããã¾ãã
- å°ç¨ã® MonoBehaviour ã³ã³ãã¼ãã³ããç¨æãã¦æç»
- å ç¨ã®ãã©ã¡ã¿æ´æ°ã¨åãããã«æ¯ãã¬ã¼ã åå¾
OnDrawGizmos()ã§æç»
- ãããã°æç»ç¨ ISystem ã使
- ã¡ã¤ã³ã¹ã¬ããã§åãã·ã¹ãã ã§ããã°ãããã°æç»ãå¯è½
Debug.DrawLine()ãªã©ã§æç»
ä»åã¯å®è¡ãã¦ããªãã¨ãã§ãç¯å²ã確èªãããã®ã§ãåè ãè¯ãããã§ããã§ã¯ã³ã¼ããæ¸ãã¦ã¿ã¾ãã
using UnityEngine; using Unity.Entities; using Unity.Collections; using Unity.Transforms; #if UNITY_EDITOR using UnityEditor; #endif namespace Boids.Runtime { public class DebugViewer : MonoBehaviour { public bool area = false; void OnDrawGizmos() { if (area) DrawAreas(); } void DrawAreas() { var manager = World.DefaultGameObjectInjectionWorld.EntityManager; var query = manager.CreateEntityQuery( ComponentType.ReadOnly<Parameter>(), ComponentType.ReadOnly<LocalTransform>()); var entities = query.ToEntityArray(Allocator.Temp); foreach (var entity in entities) { var param = manager.GetComponentData<Parameter>(entity); var lt = manager.GetComponentData<LocalTransform>(entity); Gizmos.color = (Vector4)(new float4(param.DebugAreaColor, 1f)); Gizmos.matrix = Matrix4x4.TRS(lt.Position, lt.Rotation, param.AreaScale); Gizmos.DrawWireCube(Vector3.zero, Vector3.one); Gizmos.matrix = Matrix4x4.identity; #if UNITY_EDITOR Handles.Label(lt.Position, "Boids"); #endif } } } }
ã®ãºã¢ã¯ãã®ã¾ã¾ã® API ã ã¨ä½ç½®ããæå®ã§ããªãã®ã§ãããGizmos.matrix ã«ãã©ã³ã¹ãã©ã¼ã è¡åãå
¥ãã¦ãããã¨å転ãã¹ã±ã¼ã«ããããã¾ãããªããParameter ã«ã¯è²åãã§ããããã« DebugAreaColor ã追å ãã¦ããã¾ãã
ããã§å®è¡ãã¦ãªãã¨ãã¯æ¬¡ã®ããã«ãªãã¾ãã
ã¡ãªã¿ã«ãããã°æç»ãä¼´ãã·ã¹ãã ã¯ãã¸ã§ããªã©ã使ããªãã±ã¼ã¹ã§ã¯ã次ã®ããã«æ®éã« Debug.DrawLine() ã§è¦ããåºæ¥ã¾ãããµã³ãã«ã¨ãã¦å°ç¨ã®ã·ã¹ãã ãä½ãã®ã¯é¢åãªã®ã§ãã²ã¨ã¾ã MoveSystem ã«å
¥ãã¦ã¿ã¾ãããªãããããã°æç»ãè¦ãã«ã¯ã®ãºã¢è¡¨ç¤ºã ON ã«ããå¿
è¦ãããã¾ãã
public partial struct MoveSystem : ISystem { .... public void OnUpdate(ref SystemState state) { ... foreach (var (fish, lt) in SystemAPI.Query<...>()) { ... Debug.DrawRay(pos, v * 0.3f, Color.green, 0f, true); } } }
ãããªæãã«ãªãã¾ãã
ä¸å¿åº§æ¨ã®ç§»å
ãã¦ããããã°æç»ã¯ä½ã£ãã®ã§ãããããããç¾ç¶ãã·ãã¥ã¬ã¼ã·ã§ã³ã¯åç¹ä¸å¿ã¨ãªã£ã¦ãã¾ãããããã·ãã¥ã¬ã¼ã·ã§ã³ä¸å¿ãåãããããã«ãã¦ã¿ã¾ããã¾ãããããã°æç»ãä½ã£ã¦æãã¾ãããã¹ã±ã¼ã«å¤æ´ã¯ Bootstrap ç¨ã®ãªãã¸ã§ã¯ãã®ã¹ã±ã¼ã«ããã®ã¾ã¾ä½¿ã£ãã»ããæ¥½ãããªã®ã§ãã®ããã«æ¹å¤ãã¦ã¿ã¾ãã
ã¹ã±ã¼ã«ã®åãæ±ã
ããã§å°ãçè§£ããªãã¨ãããªãç¹ãããã¾ããããã¾ã§ãã©ã³ã¹ãã©ã¼ã ã使ãã¨ã³ãã£ãã£ãçæããéã¯ãTransformUsageFlags.Dynamic ã使ã£ã¦ãã¾ãããããã«ãã LocalTransform ã³ã³ãã¼ãã³ããä»ä¸ããã¾ãããã¡ãã®ããã¥ã¡ã³ããè¦ã¦ã¿ã¾ãã
ããã¨ã¹ã±ã¼ã«è¦ç´ 㯠XYZ ãåãå¤ã¨ã㦠float åã¨ãªã£ã¦ãã¾ããããã¯ããã©ã¼ãã³ã¹ï¼è¡åè¨ç®ã³ã¹ããªã©ï¼ãã¡ã¢ãªéçã«æå©ã§ã¯ããã¾ãããä»åã®ãããªã±ã¼ã¹ã§ã¯ XYZ ãå¥ã
ã«åãæ±ãããã¨ããã§ãããã®ãããªã±ã¼ã¹ã®ããã« TransformUsageFlags.NonUniformScale ã¨ãããã®ãç¨æããã¦ãã¾ãããããä¸ããã¨ãLocalTransform ã®ä»ã« PostTransformMatrix ã¨ããã³ã³ãã¼ãã³ããã»ããã¢ããããã¾ãã
public class ParameterBaker : Baker<ParameterAuthoring> { public override void Bake(ParameterAuthoring src) { var entity = GetEntity(TransformUsageFlags.NonUniformScale); AddComponent(entity, src.param); } }
ã¹ã±ã¼ã«è¡åã¨ãã¦å¾ãããããã§ããï¼ã¡ãªã¿ã«ã·ã¢ã¼ã表ç¾ã§ããããã§ãï¼ãã§ã¯ãããã¦å¾ã PostTransformMatrix ãã©ã®ããã«ä½¿ãããè¦ã¦ã¿ã¾ããã¾ã㯠SpawnSystem ã§ããã®ä¸ã§ã¹ã±ã¼ã«ãèæ
®ããã¨ãªã¢ã®ä¸ã«ã©ã³ãã ã«åå¸ããããã«ãã¦ã¿ã¾ãã
public partial struct SpawnSystem : ISystem { public void OnUpdate(ref SystemState state) { foreach (var (school, param, lt, ptm, entity) in SystemAPI.Query< ... RefRO<PostTransformMatrix>>().WithEntityAccess()) { ... var localTransform = lt.ValueRO.ToMatrix(); var scaleTransform = ptm.ValueRO.Value; var transform = math.mul(localTransform, scaleTransform); Create(..., transform, ...); ... } } void Create( ... in float4x4 areaTransform, ...) { ... foreach (var entity in entities) { var lt = SystemAPI.GetComponentRW<LocalTransform>(entity); var localPos = random.NextFloat3() - 0.5f; lt.ValueRW.Position = math.transform(areaTransform, localPos); ... } } }
PostTransformMatrix ã«ã¯ã¹ã±ã¼ã«è¡åãå
¥ã£ã¦ãã¾ããããã math.mul() ã§ LocalTransform ã®ãã©ã³ã¹ãã©ã¼ã è¡åã«ããããã¨ã§ãXYZ ãèæ
®ããã¹ã±ã¼ã«è¾¼ã¿ã®è¡åãå¾ããã¨ãåºæ¥ã¾ãããªããLocalTransform ããå¾ããããã©ã³ã¹ãã©ã¼ã è¡åã®ã¹ã±ã¼ã«æåã¯ãã¹ã±ã¼ã«ãã¦ããã©ã¼ã ã§ã¯ãªãå ´å㯠1.0 ã¨ãªãããã§ãã
ã¡ãªã¿ã«æå math.mul() ãããªãã§ localTransform * scaleTransform ã¨ãã¦ãã¦ãã°ãã¾ããããåãªã * operator ã«ããæãç®ã¯å
ç©ãåã£ã¦ãã¾ãã®ã§ä½ç½®ãå転æåãæ¶ãã¦ãã¾ãããã§ãããã
å¾ãããè¡åãä½ç½®ã«å¯¾ãã¦é©ç¨ããå ´å㯠math.transform() ã使ç¨ãã¾ããããã§ç®±ã«ã©ã³ãã ã«åºããä½ç½®ãå¾ãããããã«ãªãã¾ããã
ã·ã¹ãã ã®æ¹å¤
ããã«ä¼´ã AreaSystem ã®ã·ã¹ãã ãæ¹å¤ãã¾ãã
... public partial struct AreaSystem : ISystem { ... ComponentLookup<PostTransformMatrix> _postTransformMatrixLookUp; public void OnCreate(ref SystemState state) { ... _postTransformMatrixLookUp = state.GetComponentLookup<PostTransformMatrix>(isReadOnly: true); } public void OnUpdate(ref SystemState state) { ... _postTransformMatrixLookUp.Update(ref state); foreach (var (fish, lt) in SystemAPI.Query< RefRW<Fish>, RefRO<LocalTransform>>()) { ... // ã°ã«ã¼ãã® PostTarnsformMatrix ãå¾ã var areaPtm = _postTransformMatrixLookUp[paramEntity]; // float3 ã¹ã±ã¼ã«ã¯ PostTransformMatrix ããå¾ããã var scale = areaPtm.Value.Scale() * 0.5f; // ç®±ã®ãã¼ã«ã«åº§æ¨ç³»ã«å¤æãã // ãã ããã¹ã±ã¼ã«æåã ãã¯ãã®ã¾ã¾ = ã¯ã¼ã«ã座æ¨ç³»ã§ã®å¤§ããã使ã var pos = lt.ValueRO.Position; var transformRt = float4x4.TRS(areaLt.Position, areaLt.Rotation, 1f); pos = math.transform(math.inverse(transformRt), pos); // 座æ¨ç³»ã¨ãã¦ã¯ãã¼ã«ã«åº§æ¨ç³»ï¼å¤§ããã¯ãã®ã¾ã¾ã«ä½ç½®ã¯åç¹ãå転ãªãï¼ var addAccel = GetAccelAgainstWall(pos.x - -scale.x, math.right(), thresh, weight) + GetAccelAgainstWall(pos.y - -scale.y, math.up(), thresh, weight) + GetAccelAgainstWall(pos.z - -scale.z, math.forward(), thresh, weight) + GetAccelAgainstWall(+scale.x - pos.x, math.left(), thresh, weight) + GetAccelAgainstWall(+scale.y - pos.y, math.down(), thresh, weight) + GetAccelAgainstWall(+scale.z - pos.z, math.back(), thresh, weight); // åã®ãããæ¹åãã¯ã¼ã«ã座æ¨ç³» addAccel = math.rotate(areaLt.Rotation, addAccel); fish.ValueRW.Acceleration += addAccel; } } ... }
å®è¡
ããã§å®è¡ããã¨æ¬¡ã®ããã«ã¨ãªã¢ã®ä¸ã«ããã¾ãé群ãè¦ãããããã«ãªãã¾ãï¼ããããããããã«å£ä»¥å¤ã®åã¯å¼±ããã¦ãã¾ãï¼ã
ã¡ãªã¿ã«åçãªä½ç½®ç§»åãã¹ã±ã¼ã«ãè¡ãã¨æ¬¡ã®ããã«ãªãã¾ãï¼ãµãã·ã¼ã³ãéãã¦ãããããã®ã¾ã¾ã ã¨åä½ãè¦ããªãã®ã§ããããã°è¡¨ç¤ºã®æ¡å¼µã§è¦ããããã«ãã¦ãã¾ãï¼ã
ãã©ã¡ã¿ã調æ´ããã¨ãããªæãã§äº¤ããã¨ãªã¢å ã§ã¯ãã£ã¤ãã¦ã¨ãªã¢ãé¢ããã¿ã¤ãã³ã°ã§å¥ãã¦ãããã¿ãããªãã¨ãåºæ¥ã¾ããã
ãã®ä»
ããã©ã¼ãã³ã¹ã®è©±ã«è¡ãåã«ãããã¾ã§ç´¹ä»ãã¦ããªãã£ãã¨ãããæå¾ã«è¦ã¦ããã¾ãã
ISystem 㨠SystemBase
ããã¾ã§ã¯ ISystem ç¶æ¿ã®æ§é ä½ãã¼ã¹ã®ã·ã¹ãã ã§ä½æãã¦ãã¾ããã䏿¹ãSystemBase ç¶æ¿ã®ã¯ã©ã¹ãã¼ã¹ã®ã·ã¹ãã ã使å¯è½ã§ãã
SystemBase ã®å ´å㯠Burst ãå¹ããªãã¨ãã£ãããã©ã¼ãã³ã¹ä¸ä¸å©ãªç¹ãããã¾ãããManaged ãªã¡ã³ããæã¦ããã¨ãã£ãæè»æ§ã®é¢ã§ã®å©ç¹ãããã¾ãã
Aspect
IAspect ã¨ããè¤æ°ã®ã³ã³ãã¼ãã³ããã¾ã¨ãä¸ããæ½è±¡åã®ä»çµã¿ãåå¨ãã¾ãã
ããã¯è¤æ°ã®ã³ã³ãã¼ãã³ããã¾ã¨ãä¸ããããã«ä¼´ãå夿°ã®ã¢ã¯ã»ã¹ãç°¡ç¥åããããå¦çãè¨è¿°ããããããã¨ãã§ãããã®ã§ããä¾ãã°æ¬¡ã®ãã㪠FishAspect ãä½ã£ã¦ã¿ã¾ãã
public readonly partial struct FishAspect : IAspect { readonly RefRW<Fish> _fish; readonly RefRW<LocalTransform> _localTransform; readonly DynamicBuffer<NeighborsEntityBufferElement> _neighborsEntityBuffer; public Fish Fish { get => _fish.ValueRO; set => _fish.ValueRW = value; } public float3 Velocity { get => _fish.ValueRO.Velocity; set => _fish.ValueRW.Velocity = value; } public float3 Acceleration { get => _fish.ValueRO.Acceleration; set => _fish.ValueRW.Acceleration = value; } public Entity ParamEntity { get => _fish.ValueRO.ParamEntity; } public LocalTransform LocalTransform { get => _localTransform.ValueRO; set => _localTransform.ValueRW = value; } public DynamicBuffer<NeighborsEntityBuffeElementr> Neighbors => _neighborsEntityBuffer; }
ããã SeparationSystem ã§ä½¿ã£ã¦ã¿ã¾ããããããã¨ä»¥ä¸ã®ããã«åãã©ã¡ã¿ã¸ã®ã¢ã¯ã»ã¹ã®ååãç´æçã«ãªãã¾ãã
... public partial struct SeparationSystem : ISystem { public void OnUpdate(ref SystemState state) { ... foreach (var fish in SystemAPI.Query<FishAspect>()) { var n = fish.Neighbors.Length; if (n == 0) continue; var pos = fish.LocalTransform.Position; var forceDir = float3.zero; for (int i = 0; i < n; ++i) { var neighborEntity = fish.Neighbors[i].Entity; var neighborPos = _transformLookUp[neighborEntity].Position; var to = neighborPos - pos; forceDir += -math.normalizesafe(to); } forceDir /= n; var param = _paramLookUp[fish.ParamEntity]; fish.Acceleration += forceDir * param.SeparationForce; } } }
ã¾ãããã®å¦çãã¾ã¨ã㦠Aspect å´ã«è¨è¿°ãããã¨ãåºæ¥ã¾ãã
public readonly partial struct FishAspect : IAspect { ... public void UpdateSeparation( in Parameter param, in ComponentLookup<LocalTransform> transformLookUp) { var n = Neighbors.Length; if (n == 0) return; var pos = LocalTransform.Position; var forceDir = float3.zero; for (int i = 0; i < n; ++i) { var neighborEntity = Neighbors[i].Entity; var neighborPos = transformLookUp[neighborEntity].Position; var to = neighborPos - pos; forceDir += -math.normalizesafe(to); } forceDir /= n; Acceleration += forceDir * param.SeparationForce; } }
... public partial struct SeparationSystem : ISystem { public void OnUpdate(ref SystemState state) { ... foreach (var fish in SystemAPI.Query<FishAspect>()) { var param = _paramLookUp[fish.ParamEntity]; fish.UpdateSeparation(param, _transformLookUp); } } }
Unmanaged ãªä¸çã§åãããã«ãªã£ã¦ãããBurst ã®æ©æµãåãããã¨ãåºæ¥ã¾ããå ´åã«ãã£ã¦ã¯ãã¾ãåãã¨æãã¾ããããã è¤æ°ã®ã·ã¹ãã ã«ã¾ããã£ã¦åä½ããããã¨ããã¨ãä¾ãã° LocalTransform ããã®ã«ãã£ã¦ã¯ RefRO ã§è¯ãã£ããããã®ã«ãã£ã¦ã¯ RefRW ãå¿
è¦ã ã£ãããå ´åã«ãã£ã¦ã¯è¦ããªãã£ããâ¦ãã¨ãã£ãã±ã¼ã¹ãæããã·ã¹ãã ä¾å度ãé«ãã§ããä½è¨ãªæ
å ±ãåã£ã¦ãããã¨ãããããã¨ããã®åã®ãªã¼ãã¼ãããããããããããã¿ã«å
¨ã¦ã Aspect åãããã¨ããã¨ã³ã¼ãã®æ½è±¡åº¦ãä¸ãã£ã¦ãã¾ã£ã¦èªã¿ã¥ãããªããã¡ãªãããããããã§ããä»åã®ã±ã¼ã¹ã§ã¯ä½¿ããªãã»ããè¯ããããªã®ã§ Aspect åã¯ããã«é²ãã¦ãããã¨æãã¾ãã
ã¡ãªã¿ã« Aspect ã使ããã¨ãInspector ãããã®ã¨ã³ãã£ãã£ãã©ã® Aspect ã«å¯¾å¿ãã¦ãããã確èªã§ãã¾ãã
è¿åæ¢ç´¢ã®ããã©ã¼ãã³ã¹æ¹å
ç¾ç¶ã®ããã©ã¼ãã³ã¹
Job å㨠Burst ã®é©ç¨ã®åã«ä¸æ¦ç¾ç¶ã®ããã©ã¼ãã³ã¹ã確èªãã¦ããã¾ããã²ã¨ã¾ã 300 ä½ã»ã©ã® Boid ãçæãã¦ã¿ã¦ã¿ã¾ãã
ä»ã®ã·ã¹ãã ã 1 ms æªæºãªã®ã«å¯¾ããNeighborsDetectionSystem ã¯ããªãé·ãæéãæãã£ã¦ãã¾ããããã¯å
é¨ã§äºéã«ã¼ããè¡ã£ã¦ããããã§ãè¨ç®éã O(n2) ã§ã¨ã¦ãéãå¦çã«ãªã£ã¦ãããã¨ã«ããã¾ãã
é«éåææ³æ¤è¨
Boids ã®è¨ç®éæ¹åã«ã¯ãããããªææ³ãèããããã¨æãã¾ãããããã§ã¯ããã·ã¥é¢æ°ã使ã£ãã°ãªããã«ããé«éåã試ãã¦ã¿ã¾ãã
æ¦å¿µã¨ãã¦ã¯ã3 次å
空éãé©å½ãªå¤§ããã§ã°ãªããåå²ããããããã®åä½ãæå±ãã空éã®ã¤ã³ããã¯ã¹ã ã§æ±ãã¾ããããã¦ãã® 3 次å
ã® int ãå
¥åã¨ããããã·ã¥é¢æ°ï¼ãã® 3 ã¤ã®æ°åã使ã£ã¦é©å½ãª 1 次å
ã®æ°åãè¿ã颿°ï¼ãéãã¦å¾ãå¤ãã¤ã³ããã¯ã¹ã¨ãããã¨ã³ãã£ãã£ãªã¹ããä¿ç®¡ãããã¼ãã«ã使ãã¾ããããããã¨ãåãã°ãªããã«æå±ããã¨ã³ãã£ãã£ãé
åã¨ãã¦å¾ãããããã«ãªãã¾ããããã·ã¥é¢æ°ã¯ã¤ã³ããã¯ã¹ã®ã¿ã§å¾ãããã®ã§ãèªèº«ããã³é£ãåãã»ã« 27 åã«æå±ããã¨ã³ãã£ãã£ãããéãã¦ããã®ãé«éã§ãããããã¦å¾ãã¨ã³ãã£ãã£ã®é
åã使ã£ã¦
NeighborsDetectionSystem ã®å¦çãå®è¡ãã¦ãããã°ãç·å½ããã§è¦ã¦ããäºéã«ã¼ãã®ã¨ãããéããã¦ãè»½ãæ¸ãã¨ããããã§ãã
å®è£
ãã¼ãã«ã®æ§é ã¨ãã¦ã¯ãNativeParallelMultiHashMap ã¨ããæ§é ä½ãå©ç¨ã§ãã¾ãããã㯠1 ã¤ã®ãã¼ã«å¯¾ãã¦è¤æ°ã®å¤ãæ ¼ç´ã§ããã¨ãããã®ã§ãã
ã§ã¯ããã使ã£ã¦ NeighborsDetectionSystem ãæ¸ãç´ãã¦ã¿ã¾ããããå°ã大ããªã¯ã©ã¹ã«ãªãã¾ãã
public partial struct NeighborsDetectionSystem : ISystem { // LookUp ã¯åã¨åããå©ç¨ ComponentLookup<Parameter> _paramLookUp; BufferLookup<NeighborsEntityBufferElement> _neighborsLookUp; // ããã·ã¥ãããã®ãµã¤ãºã決ãããã®ã§ã¯ã¨ãªã§å ¨ä½æ°ãç¢ºèª EntityQuery _query; // ã°ãªããã®ã»ã«ã®è¨å® float _cellsize; NativeArray<int3> _cellOffsets; // int3 ã§ã»ã«ã®ã¤ã³ããã¯ã¹ãåãåãé©å½ãª int ãè¿ã int GetHash(int3 cell) { return cell.x * 73856093 ^ cell.y * 19349663 ^ cell.z * 83492791; } public void OnCreate(ref SystemState state) { // 諸ã åæå _paramLookUp = state.GetComponentLookup<Parameter>(isReadOnly: true); _neighborsLookUp = state.GetBufferLookup<NeighborsEntityBufferElement>(isReadOnly: false); _query = SystemAPI.QueryBuilder().WithAll<Fish, LocalTransform>().Build(); _cellsize = 0.5f; // 3 次å ã°ãªããã®å¨å² 27 ã»ã«ã®ãªãã»ãã _cellOffsets = new NativeArray<int3>(27, Allocator.Persistent); { var i = 0; for (int x = -1; x <= 1; ++x) { for (int y = -1; y <= 1; ++y) { for (int z = -1; z <= 1; ++z) { _cellOffsets[i++] = new int3(x, y, z); } } } } } public void OnDestroy(ref SystemState state) { // Persistent ã§ç¢ºä¿ãããã¤ãã£ãã³ã¬ã¯ã·ã§ã³ã¯ã¡ã¢ãªãªã¼ã¯ããªãããã«ç ´æ£ if (_cellOffsets.IsCreated) _cellOffsets.Dispose(); } public void OnUpdate(ref SystemState state) { _paramLookUp.Update(ref state); _neighborsLookUp.Update(ref state); using var entities = _query.ToEntityArray(Allocator.Temp); using var fishes = _query.ToComponentDataArray<Fish>(Allocator.Temp); using var localTransforms = _query.ToComponentDataArray<LocalTransform>(Allocator.Temp); // ããã·ã¥ãããã®ãµã¤ãºã決ãã int n = fishes.Length; // ããã·ã¥ãããã¯ãã¼ãããã·ã¥å¤ã // ããªã¥ã¼ãããã«å«ã¾ãã¦ããã¤ã³ã¹ã¿ã³ã¹ã®ã¤ã³ããã¯ã¹ using var hashMap = new NativeParallelMultiHashMap<int, int>(n, Allocator.Temp); // ã»ã«ã®ãµã¤ãºã¯è¨å®ããã NeighborsDistance ãã©ã¡ã¿ã®ä¸ã§ // ãã£ã¨ã大ãããã®ã®ååãµã¤ãºã¨ãã // 大ããããã¨ã¨ã³ãã£ãã£ã®æ°ãå¤ããªã£ã¦ãã¾ãã // éã«å°ããããã¨ååãªç¯å²ãè¦ããªããã float nextCellSize = 0.1f; // å ¨ã¦ã®ã¤ã³ã¹ã¿ã³ã¹ã®ä½ç½®ãããã·ã¥å¤ã«å¤æãã¦ããã·ã¥ãããã«æ ¼ç´ // å ãã¦ä¸è¨ã®ã°ãªãããµã¤ãºæ±ºããããã§è¡ãï¼æ¬¡ãã¬ã¼ã ã§ä½¿ãï¼ for (int i = 0; i < n; ++i) { var pos = localTransforms[i].Position; var cell = (int3)(pos / _cellsize); var hash = GetHash(cell); hashMap.Add(hash, i); var fish = fishes[i]; var paramEntity = fish.ParamEntity; var param = _paramLookUp[paramEntity]; nextCellSize = math.max(nextCellSize, param.NeighborDistance * 0.5f); } // å ¨ã¦ã®ã¤ã³ã¹ã¿ã³ã¹ãè¦ã for (int i = 0; i < n; ++i) { // 種ã ã®èªèº«ã®ãã©ã¡ã¿ãè¨ç® var pos0 = localTransforms[i].Position; var cell0 = (int3)(pos0 / _cellsize); var fish0 = fishes[i]; var fwd0 = math.normalizesafe(fish0.Velocity); var param = _paramLookUp[fish0.ParamEntity]; var neighborAngle = math.radians(param.NeighborAngle); var neighborDist = param.NeighborDistance; var prodThresh = math.cos(neighborAngle); var entity0 = entities[i]; var neighbors0 = _neighborsLookUp[entity0]; neighbors0.Clear(); // é£ãåãã»ã«ãè¦ã¦ãã for (int offsetIndex = 0; offsetIndex < _cellOffsets.Length; ++offsetIndex) { // é£ãåãã»ã«ã®ããã·ã¥ãè¨ç® var hash0 = GetHash(cell0 + _cellOffsets[offsetIndex]); // ããã·ã¥å¤ãããã®ã»ã«ã«å«ã¾ãã¦ããåä½ã®ã¤ã³ããã¯ã¹ãåå¾ // TryGetFirstValue -> TryGetNextValue ã§ã¤ãã¬ã¼ã·ã§ã³ // å«ã¾ãã¦ããªãå ´åã¯ã¹ããã if (!hashMap.TryGetFirstValue(hash0, out var j, out var it)) continue; // å«ã¾ãã¦ããåã ã TriyGetNextValue ã§è¦ã¦ãã do { // å é¨ã®å¤å®ã¯ä»¥åã¨å ¨ãåã var entity1 = entities[j]; if (entity0 == entity1) continue; var lt1 = localTransforms[j]; var pos1 = lt1.Position; var to = pos1 - pos0; var dist = math.length(to); if (dist > neighborDist) continue; var dir = to / math.max(dist, 1e-3f); var prod = math.dot(dir, fwd0); if (prod < prodThresh) continue; var elem = new NeighborsEntityBufferElement() { Entity = entity1 }; neighbors0.Add(elem); if (neighbors0.Length >= neighbors0.Capacity) break; } while (hashMap.TryGetNextValue(out j, ref it)); } } // 次ãã¬ã¼ã ã§ã¯èªåè¨ç®ãããã»ã«ãµã¤ãºã使ã _cellsize = nextCellSize; } }
NativeParallelMultiHashMap ã®ä½¿ãæ¹ã¯ã³ã¼ãã®ã¨ããã§ããTryGetFirstValue() ã§å¤ã®ä»ã«ã¤ãã¬ã¼ã¿ãå¾ããã¾ããããã使ã£ã¦ TryGetNextValue() ããã¦ãããã¨ã§ãåä¸ãã¼ã«å¯¾ããããªã¥ã¼å
¨ã¦ãå¾ããã寸æ³ã§ããã¡ãã£ã¨ã¤ãã¬ã¼ã·ã§ã³ãé¢åã§ãããæ¯è¼çã·ã³ãã«ã«ä½¿ãã¾ããã
ããã·ã¥ã®è¨ç®ã®ã¨ããã¯è¬ãªæ°å¤ã並ãã§ãã¾ããâ¦ããã㯠XYZ ã®ããããã®å¤ã使ã£ã¦å¤§ããªç´ æ°ã¨æãç®ããããããã XOR ã§çµã¿åããã¦ãã¾ããããã«ãããè¨ç®ã軽éã§åãã®å°ãªãããã·ã¥å¤ãè¨ç®ãããã¨ãåºæ¥ã¦ãã¾ãï¼ã»ã«ã«ãã£ã¦ã¯ããã·ã¥ã被ãããã±ã¼ã¹ãããã¾ãããçµæçã«ã¯ããã¯è·é¢å¤å®ã§å¼¾ããã¾ãï¼ã
æµããã¾ã¨ããã¨ã
- ä½ç½®
=> ã°ãªãã
=> ããã·ã¥å¤ã¸ã¨å¤æ
- ããã·ã¥å¤ããã¼ã¨ãã¦ãè¤æ°è¦ç´ æ ¼ç´åºæ¥ãã³ã³ããã¸ãã®ã°ãªããã«ããåä½ããªã¹ãã¨ãã¦ç»é²
- ããããã®åä½ã®è¿é£åä½ã¯å¨å²ã®ã»ã« 27 ãã¹ã«ããåä½ãå©ç¨
ã¨ããæãã§ãããªããã¯ã¨ãªã§ã¤ãã¬ã¼ã·ã§ã³ããã®ã§ã¯ãªãäºåã«é åãåå¾ãã¦åãå½¢ã«ãªã£ã¦ãã¾ãããããã·ã¥ãããåã«ãã 2 éã«ã¼ãã§ãªããªã£ãããä»ã®ã·ã¹ãã åæ§ã¯ã¨ãªã«ãã£ã¦ãå¦çã§ããã¨æãã¾ãããã ãåå¼·ã®ããã«ãã¡ãã®å½¢å¼ããã¼ããããã¨ã«ãã¾ãã
ããã©ã¼ãã³ã¹æ¹å
ããã§ããã©ã¼ãã³ã¹ãè¦ã¦ã¿ãã¨æ¬¡ã®ããã«ãªã£ã¦ãã¾ãã
O(n2) ã§ã¯ãªããªã£ãã®ã§ãããªãéããªã£ã¦ãã¾ããã
ä½è«
æåã¯ãã®ããã·ã¥ãããã School ã«æããã¦ãããã·ã¥ãããã®æ§ç¯ã¨ DynamicBuffer ã¸ã¤ã³ã¹ã¿ã³ã¹ãè©°ãã¦ããã¨ãããå¥ã®ã·ã¹ãã ã¨ãã¦ä½ããã¨ããã®ã§ãããIComponentData ã«ãã¤ãã£ãã³ã¬ã¯ã·ã§ã³ã Persistent ãªã¢ãã±ã¼ã·ã§ã³ã§æãããã«ã¯ã
ICleanupComponentDataã®ä½æ- ãã®ã³ã³ãã¼ãã³ããã¼ã¿ãä»ä¸ããã·ã¹ãã
ICleanupComponentDataãããã¤ãã£ãã³ã¬ã¯ã·ã§ã³ãç ´æ£ããã·ã¹ãã
ã¨ãã£ã追å è¦ç´ ãå¿ è¦ã¨ãªããã³ã¼ãã¨èª¬æãç ©éã«ãªãã¯ãããã®ã§æ¢ããä»åã¯ä¸ã¤ã®ã·ã¹ãã ã®ä¸ã«ç´ãã鏿ããã¾ããã
ä»ã«ãè¯ãæ¹æ³ãããããã§ãããæãã¦ä¸ããã
Burst å
ã§ã¯ä½æããã·ã¹ãã ã Burst åãã¦ã¿ã¾ããã¨ã¯ãã£ã¦ããããã¨ã¯ç°¡åã§ãå
¨ã¦ã®ã·ã¹ãã ã® OnUpdate() ã« BurstCompile 屿§ãã¤ããã ãã§ãã
... public partial struct SeparationSystem : ISystem { ... [BurstCompile] // ãããã¤ããã ã public void OnUpdate(ref SystemState state) { ... } }
ãã¾ãè¡ãã° Jobs > Burst > Open Inspector... ãã Burst ã³ã³ãã¤ã«çµæã確èªã§ãã¾ãã
ããã§å®è¡ããã¨æ¬¡ã®ããã«åçã«ããã©ã¼ãã³ã¹ãæ¹åããã¾ãã
AlignmentSystem ãªã©ã«è³ã£ã¦ã¯ 0.03 ms ã»ã©ã«ãªãã¾ããã3000 å¹ãããã«å¢ããã¦ãä¸å¿ 60 fps ã§åãã¾ãã
ãã ãã©ããã¦ã屿çã«åå¸ãéä¸ãã¦ãã¾ããããªã±ã¼ã¹ï¼ã¿ããªãè¿ãã°ãªããã»ã«ã«ããï¼ã§ã¯éããªã£ã¦ãã¾ãã¾ãã
Job å
Boids ã®ã¢ã«ã´ãªãºã ã¯è¿åæ¢ç´¢ä»¥å¤ã¯ç¹ã«é åºä¾åæ§ããªãã§ãããããããã®åä½å¥ã«ä¸¦åè¨ç®ããã®ã«åãã¦ãã¾ããã§ã¯æå¾ã« Job åã«ã¤ãã¦è¦ã¦ããã¾ãããã
IJobEntity
ã³ã³ãã¼ãã³ãã®ãã¼ã¿ã使ã£ã¦ä¸¦åå¦çãè¡ãã®ã¯ãIJobEntity ãç¨ãããã¨ã§ç°¡åã«å®ç¾ã§ãã¾ãã
IJobEntity 㯠Job ã® Execute 颿°ã®ä¸ã§ foreach ã§ãã£ãã¨ãã®ãããªå½¢ã§å¼æ°ã¨ãã¦ã³ã³ãã¼ãã³ãã®ãã¼ã¿ãªã©ãåãåããã¨ãåºæ¥ã便å©ãª Job ã®å½¢æ
ã§ãã
ä¾ãã° IJobParallelFor ã§ã¯æ¬¡ã®ããã«èªåã§å
¥ååºåãå®ç¾©ãåã£ã¦ãã¦ãã¾ããã
public struct CustomJob : IJobParallelFor { // å ¥åãã©ã¡ã¿ [ReadOnly] public ComponentDataArray<Position> positions; ... // åºåãã©ã¡ã¿ public ComponentDataArray<Acceleration> accelerations; ... // å ¥åã使ã£ã¦åºå public void Execute(int index) { float3 pos = positions[index].Value; float3 accel = accelerations[index].Value; accel += ...; } ... } protected override JobHandle OnUpdate(JobHandle inputDeps) { var job = new Job { positions = data.positions, accelerations = data.accelerations, ... }; return job.Schedule(data.Length, 32, inputDeps); }
ããã«å¯¾ããIJobEntity ã§ã¯æ¬¡ã®ããã«ã¯ã¨ãªããã³ã³ãã¼ãã³ãã®ãã¼ã¿ãªã©ãåãåãã¾ãã
public partial struct CustomJob : IJobEntity { // åãããã«ãã©ã¡ã¿ [ReadOnly] public Something something; ... void Execute(ref LocalTransform lt, ref MoveComponent move) { // ... ãªã«ããã } } public partial struct CustomSystem : ISystem { ... public void OnUpdate(ref SystemState state) { ... var job = new CustomJob() { something = ..., }; var query = SystemAPI .QueryBuilder() .WithAll<LocalTransform, MoveComponent>() .Build(); state.Dependency = job.ScheduleParallel(query, state.Dependency); } }
以åã IJobProcessComponentData ã¨ãããã®ãããã¾ããããæ´ã«ã·ã³ãã«ã«ãªã£ã¦ãã¾ãã
Job åã®è©¦ã¿
ããã§ããã®ã使³ã«å¾ãæ¢åã® AlignmentSytem ã Job åãã¦ã¿ã¾ããOnUpdate() ã®ä¸ã«ãã£ã SystemAPI.Query<...> ã«ãã£ã¦åãã¦ãã foreach æã®ä¸èº«ã Job ã® Execute ã«ç§»ãããã®å¤å´ã§ã»ããã¢ãããã¦ãã夿°ï¼ComponentLookUp ãªã©ï¼ã¯ Job ã®å
¥å夿°ã¨ãã¦æ¸¡ãå½¢ã§ãã
[BurstCompile] public partial struct AlignmentJob : IJobEntity { [ReadOnly] public ComponentLookup<Parameter> ParamLookUp; [ReadOnly] public ComponentLookup<Fish> FishLookUp; void Execute( ref Fish fish, in DynamicBuffer<NeighborsEntityBufferElement> neighbors) { var n = neighbors.Length; if (n == 0) return; var averageV = float3.zero; for (int i = 0; i < n; ++i) { var neighborEntity = neighbors[i].Entity; var neighborV = FishLookUp[neighborEntity].Velocity; averageV += neighborV; } averageV /= n; var param = ParamLookUp[fish.ParamEntity]; var v = fish.Velocity; fish.Acceleration += (averageV - v) * param.AlignmentForce; } } [UpdateBefore(typeof(MoveSystem))] [UpdateAfter(typeof(NeighborsDetectionSystem))] public partial struct AlignmentSystem : ISystem { ComponentLookup<Parameter> _paramLookUp; ComponentLookup<Fish> _fishLookUp; public void OnCreate(ref SystemState state) { _paramLookUp = state.GetComponentLookup<Parameter>(isReadOnly: true); _fishLookUp = state.GetComponentLookup<Fish>(isReadOnly: true); } public void OnUpdate(ref SystemState state) { _paramLookUp.Update(ref state); _fishLookUp.Update(ref state); var job = new AlignmentJob() { FishLookUp = _fishLookUp, ParamLookUp = _paramLookUp, }; var query = SystemAPI .QueryBuilder() .WithAll<Fish, NeighborsEntityBufferElement>() .Build(); state.Dependency = job.ScheduleParallel(query, state.Dependency); } }
ããããããã§å®è¡ããã¨æ¬¡ã®ããã«ã¨ã©ã¼ã¨ãªã£ã¦ãã¾ãã¾ãã
InvalidOperationException: The writeable ComponentTypeHandle<Boids.Sample03.Runtime.Fish> AlignmentJob.JobData.__TypeHandle.__Boids_Sample03_Runtime_Fish_RW_ComponentTypeHandle is the same ComponentLookup<Boids.Sample03.Runtime.Fish> as AlignmentJob.JobData.FishLookUp, two containers may not be the same (aliasing).
ããã¯ãåã Fish ã«å¯¾ã㦠Execute 颿°ã®å¼æ°ã® ref ã«ãã£ã¦ã ComponentLookUp<Fish> ã«ãã£ã¦ãã¢ã¯ã»ã¹ã§ãã¦ãã¾ããã¨ã«ããã¨ã©ã¼ã®ããã§ããåä¸ã®ã³ã³ãã¼ãã³ãã¸è¤æ°ã®ã¢ã¯ã»ã¹æ¹æ³ããã¤ãã¨ã¯ç«¶åã®å¯è½æ§ãããããã許å¯ãããªãããã§ãã
ããã§ãä»ã®åä½ã®æ å ±ãåç §ãããã¨ãã¯ããããåãã¬ã¼ã ã«æ ¼ç´ãã¦ãããå¥ã®ã³ã³ãã¼ãã³ããä½ãããã¡ããåç §ããããã«ãã¾ãããããªæãã§æ°ãã«å¥ã³ã³ãã¼ãã³ããè¶³ãã¾ãã
public struct FishJobData : IComponentData { public float3 Position; public float3 Velocity; } public class FishBaker : Baker<FishAuthoring> { public override void Bake(FishAuthoring src) { ... AddComponent(entity, new FishJobData() { Position = src.transform.position, Velocity = v, }); ... } } public partial struct SpawnSystem : ISystem { ... void Create(...) { ... foreach (var entity in entities) { ... var jobData = SystemAPI.GetComponentRW<FishJobData>(entity); jobData.ValueRW.Position = lt.ValueRO.Position; jobData.ValueRW.Velocity = fish.ValueRO.Velocity; } } } public partial struct MoveSystem : ISystem { ... public void OnUpdate(ref SystemState state) { ... foreach (var (fish, jobData, lt) in SystemAPI.Query< RefRW<Fish>, RefRW<FishJobData>, RefRW<LocalTransform>>()) { ... var pos = ...; var v = ...; jobData.Position = pos; jobData.Velocity = v; } } }
åãã¬ã¼ã ã®æçµæ
å ±ãä¿æãã¦ãããã®ã§ããããã¦ãã®ä¸ã§ AlignmentSystem ã«æãå
¥ãã¾ãã
[BurstCompile] public partial struct AlignmentJob : IJobEntity { [ReadOnly] public ComponentLookup<Parameter> ParamLookUp; [ReadOnly] public ComponentLookup<FishJobData> FishJobDataLookUp; void Execute( // èªèº«ã¸ã®ã¢ã¯ã»ã¹ããã³æ¸ãè¾¼ã¿ã¯ Fish ã³ã³ãã¼ãã³ã ref Fish fish, in DynamicBuffer<NeighborsEntityBufferElement> neighbors) { var n = neighbors.Length; if (n == 0) return; var averageV = float3.zero; for (int i = 0; i < n; ++i) { var neighborEntity = neighbors[i].Entity; // ä»ã®åä½ã¸ã®ã¢ã¯ã»ã¹ã¯ FishJobData ã³ã³ãã¼ãã³ããéãã¦è¡ã var neighborV = FishJobDataLookUp[neighborEntity].Velocity; averageV += neighborV; } averageV /= n; var param = ParamLookUp[fish.ParamEntity]; var v = fish.Velocity; fish.Acceleration += (averageV - v) * param.AlignmentForce; } } [UpdateBefore(typeof(MoveSystem))] [UpdateAfter(typeof(NeighborsDetectionSystem))] public partial struct AlignmentSystem : ISystem { ComponentLookup<Parameter> _paramLookUp; ComponentLookup<FishJobData> _fishJobDataLookUp; public void OnCreate(ref SystemState state) { _paramLookUp = state.GetComponentLookup<Parameter>(isReadOnly: true); _fishJobDataLookUp = state.GetComponentLookup<FishJobData>(isReadOnly: true); } public void OnUpdate(ref SystemState state) { _paramLookUp.Update(ref state); _fishJobDataLookUp.Update(ref state); var job = new AlignmentJob() { FishJobDataLookUp = _fishJobDataLookUp, ParamLookUp = _paramLookUp, }; var query = SystemAPI .QueryBuilder() .WithAll<Fish, NeighborsEntityBufferElement>() .Build(); state.Dependency = job.ScheduleParallel(query, state.Dependency); } }
ããã§æ¸ãè¾¼ã¿ãè¡ãããã³ã³ãã¼ãã³ãï¼Fishï¼ã¸ã®ã¢ã¯ã»ã¹æ¹æ³ãéå®ãããå®å
¨ã«æ¸ãè¾¼ã¿ãå¯è½ãªãã¨ãä¿è¨¼ããã¾ããã
åãç¹ã«æ³¨æããAreaSystemãSeparationSystemãCohesionSystemãMoveSystem ã¯å¾ã¯åæ§ã«æ©æ¢°çã« Job åãããã¨ãå¯è½ã§ãã
è¿åæ¢ç´¢ã® Job å
ãã¦ãä¸çç¸ã§ã¯ãããªãã®ãè¿åæ¢ç´¢ï¼NeighborsDetectionSystemï¼ã®ã¨ããã§ããã¡ã¤ã³ã¹ã¬ããç·¨ã§ãã«ã¼ãã®ããæ¹ãéã£ããã¨ä¸çããã¾ãããããã Job åãã¦ã¿ã¾ãã
è¿åæ¢ç´¢ã®ä¸ã§ã¯ 2 ã¤ã®ãã¨ããã£ã¦ãã¾ãããä¸ã¤ã¯ããããã® 3 次å
ã°ãªããã®ã»ã«ã®ã©ãã«æå±ãã¦ããããè¦ãããã·ã¥ãããã®æ§ç¯ãããä¸ã¤ã¯ãã®ããã·ã¥ãããã使ã£ã¦ããããã®åä½ã®å¨è¾ºã«ãã©ã®åä½ããããã® DynamicBufferï¼NeighborsEntityBufferElementï¼ã®æ§ç¯ã§ããããããå¥ã
ã® Job ã§å¦çãããã¨ã«ãã¾ãã
ããã·ã¥ãããæ§ç¯å¦ç
次ã®ãããªã³ã¼ãã«ãªãã¾ãã
// 2 ã¤ã® Job ãã使ãã®ã§å¥ã¯ã©ã¹ã¸åé¢ [BurstCompile] internal static class NeighborsDetectionUtil { [BurstCompile] public static int GetHash(in int3 cell) { return cell.x * 73856093 ^ cell.y * 19349663 ^ cell.z * 83492791; } } [BurstCompile] public struct NeighborsHashMapBuildJob : IJobParallelFor { // ããã·ã¥ããã㯠ParallelWriter ã§åãåã [WriteOnly] public NativeParallelMultiHashMap<int, int>.ParallelWriter HashMap; [ReadOnly] public float CellSize; [ReadOnly] public NativeArray<LocalTransform> LocalTransforms; public void Execute(int index) { var pos = LocalTransforms[index].Position; var cell = (int3)(pos / CellSize); var hash = NeighborsDetectionUtil.GetHash(cell); HashMap.Add(hash, index); } } public partial struct NeighborsDetectionSystem : ISystem { ComponentLookup<Parameter> _paramLookUp; BufferLookup<NeighborsEntityBufferElement> _neighborsLookUp; NativeParallelMultiHashMap<int, int> _hashMap; EntityQuery _fishQuery; EntityQuery _paramQuery; NativeArray<int3> _cellOffsets; public void OnCreate(ref SystemState state) { _paramLookUp = state.GetComponentLookup<Parameter>(isReadOnly: true); _neighborsLookUp = state.GetBufferLookup<NeighborsEntityBufferElement>(isReadOnly: false); _hashMap = new NativeParallelMultiHashMap<int, int>(100, Allocator.Persistent); _fishQuery = SystemAPI.QueryBuilder().WithAll<Fish, LocalTransform>().Build(); _paramQuery = SystemAPI.QueryBuilder().WithAll<Parameter>().Build(); _cellOffsets = new NativeArray<int3>(27, Allocator.Persistent); { var i = 0; for (int x = -1; x <= 1; ++x) { for (int y = -1; y <= 1; ++y) { for (int z = -1; z <= 1; ++z) { _cellOffsets[i++] = new int3(x, y, z); } } } } } public void OnDestroy(ref SystemState state) { if (_cellOffsets.IsCreated) _cellOffsets.Dispose(); if (_hashMap.IsCreated) _hashMap.Dispose(); } [BurstCompile] public void OnUpdate(ref SystemState state) { _paramLookUp.Update(ref state); _neighborsLookUp.Update(ref state); // ãããã¡ã¯ Temp ã ã¨ããè§£æ¾ããã¦ãã¾ãã®ã§ TempJob ã§ã¢ãã±ã¼ã·ã§ã³ // è§£æ¾ã¯å¾ã§è¦ã¾ãããã¹ã³ã¼ãå¤ã§è§£æ¾ãè¡ãã®ã§ using ã¯ã¤ããªã var entities = _fishQuery.ToEntityArray(Allocator.TempJob); var fishes = _fishQuery.ToComponentDataArray<Fish>(Allocator.TempJob); var localTransforms = _fishQuery.ToComponentDataArray<LocalTransform>(Allocator.TempJob); // æå¤§å¤ãè¦ããã»ã«ãµã¤ãºæ±ºå®ã¯ä¸¦åã«ãã¥ãã & // ãã©ã¡ã¿æ°ãå°ãªããã°æ¨å³ããªãã®ã§ãããã§è¨ç®ãã¦ãã¾ã using var parameters = _paramQuery.ToComponentDataArray<Parameter>(Allocator.Temp); float cellSize = 0.1f; for (int i = 0; i < parameters.Length; ++i) { var param = parameters[i]; cellSize = math.max(cellSize, param.NeighborDistance * 0.5f); } // ããã·ã¥ãããã®ã¯ãªã¢ã¨ãµã¤ãºå¤æ´ _hashMap.Clear(); int n = fishes.Length; if (_hashMap.Capacity < n) _hashMap.Capacity = n; // ããã·ã¥ãããæ§ç¯ã¸ã§ãã®å®è¡ var hashMapBuildJob = new NeighborsHashMapBuildJob() { HashMap = _hashMap.AsParallelWriter(), // ãããå¤§äº CellSize = cellSize, LocalTransforms = localTransforms, }; state.Dependency = hashMapBuildJob.Schedule(n, 32, state.Dependency); // ...ãã®å¾ã«è¿åã®åä½ãåéããã¸ã§ããä½ã } }
ããã§ã¯ãäºåã«é
åãåå¾ããæ¹å¼ããã¦ããã®ã§ãIJobEntity ã使ã代ããã« IJobParallelFor ã§ã¤ã³ããã¯ã¹ãã¼ã¹ã®ã¸ã§ãã§åããã¨ã«ãã¦ãã¾ãããã®éãã¯ã¨ãªããåå¾ããã³ã³ãã¼ãã³ããã¨ã³ãã£ãã£ã® NativeArray 㯠Allocator.Temp ã§åå¾ãã¦ãã¾ãã¨ã¹ã³ã¼ãåä½ã®å¯¿å½ãããªããããAllocator.TempJob ã¨ãã¦ç¢ºä¿ãã¾ãï¼è§£æ¾ã¯æ¬¡ã®ç¯ã§è¦ã¾ãï¼ã
ããã·ã¥ãããã並å㪠Job ã§åãæ±ãéã¯ããã®ã¾ã¾ã§ã¯æ¬¡ã®ãããªã¨ã©ã¼ãåºã¦ãã¾ãã¾ãã
InvalidOperationException: NeighborsHashMapBuildJob.HashMap is not declared [ReadOnly] in a IJobParallelFor job. The container does not support parallel writing. Please use a more suitable container
å¹¾ã¤ãã®ãã¤ãã£ãã³ã¬ã¯ã·ã§ã³ã§ã¯ã並åãªæ¸ãè¾¼ã¿ãè¡ãããã®å°ç¨ã®ã¤ã³ã¿ã¼ãã§ã¼ã¹ãç¨æããã¦ãããããã ParallelWriter ã§ãã
åå¾ããããã«ã¯ AsParallelWriter() ãããã·ã¥ãããã«å¯¾ãã¦å¼ã³ã¾ããããã«ãã並åæä½ã«ããããã·ã¥ãããæ§ç¯ãå¯è½ã«ãªãã¾ããã
è¿åæ¢ç´¢ã¨ãããã¡ã¸ã®æ ¼ç´
ãã¦ããã®ããã«æ§ç¯ããããã·ã¥ãããã使ããè¿åæ¢ç´¢ããããã
DynamicBuffer ã¸ã¨è©°ããå¦çããããã¨æãã¾ããããããªãããæ¬¡ã®ããã« DynamicBuffer åç
§ç¨ã®ã«ãã¯ã¢ããã IJobParallelFor ã«ããããã¨ã¨ã©ã¼ãåºã¦ãã¾ãã¾ãã
public struct NeighborsDetectionJob : IJobParallelFor { ... public BufferLookup<NeighborsEntityBufferElement> BufferLookUp; ... }
InvalidOperationException: NeighborsDetectionJob.BufferLookUp is not declared [ReadOnly] in a IJobParallelFor job. The container does not support parallel writing. Please use a more suitable container type.
ããã§ã¯ BufferLookUp ã¯ä¸¦åæ¸ãè¾¼ã¿ããµãã¼ããã¦ããªãç¹ãã¾ã [ReadOnly] ãã¤ãã¦ããªããã¨ã«ããå®å
¨æ§ãä¿è¨¼ã§ããªãç¹ãã¨ã©ã¼ã¨ãã¦å ±åããã¦ãã¾ããã¡ãªã¿ã« [ReadOnly] ãã¤ããã¨ããã¡ããæ¸ãè¾¼ã¿ãè¡ãéã«ã¨ã©ã¼ãå ±åããã¾ããä¾ãã°ã³ã¼ãã¯æ¬¡ã®ããã«ãªãã¾ãã
[BurstCompile] public struct NeighborsDetectionJob : IJobParallelFor { ... [ReadOnly] public NativeArray<Entity> Entities; ... [ReadOnly] public BufferLookup<NeighborsEntityBufferElement> BufferLookUp; ... public void Execute(int index) { ... var entitySelf = Entities[index]; ... var buf = BufferLookUp[entitySelf]; buf.Clear(); ... for (...) { ... buf.Add(...); ... } } }
ãã®ããã«ãªæ¸ãè¾¼ã¿å¦çã試ã¿ã¦ã¿ã¾ããããããªãããClear() ããã¨ããã Add() ããã¨ããã§æ¸ãè¾¼ã¿å¦çãå
¥ããããããæ¬¡ã®ãããªã¨ã©ã¼ã¨ãªãã¾ãã
The BufferLookup<Boids.Sample03.Runtime.NeighborsEntityBufferElement> has been declared as [ReadOnly] in the job, but you are writing to it.
ããã§ããã䏿®µéå¦çããã¾ãããã¨ã«ãã¾ããå
·ä½çã«ã¯ããã£ãããã®è¿åæ¢ç´¢ã®çµæã䏦忏ãè¾¼ã¿ããµãã¼ãããã³ã³ããã«æ ¼ç´ãããããå¥ã®ã¸ã§ãã§ DynamicBuffer ã¸ã¨æ¸ãè¾¼ããã¨ããæ¹éãåã£ã¦ã¿ã¾ããã ãã¶é·ããªãã¾ããã以ä¸ã®ãããªå½¢ã«ãªãã¾ãã
[BurstCompile] public struct NeighborsDetectionJob : IJobParallelFor { // 䏿¦æ ¼ç´ãã¦ããæ å ± // åã¨ã³ãã£ãã£ããã¼ã¨ãã¦é£æ¥ããåä½ãè¤æ°åæ ¼ç´ // å¾ã«ããã DynamicBuffer ã¸ã¨ç§»ã [WriteOnly] public NativeParallelMultiHashMap< Entity, NeighborsEntityBufferElement>.ParallelWriter Neighbors; // ãã®ä»ã¸ã§ãã®å¦çã«å¿ è¦ãªã·ã¹ãã ããæ¸¡ãæ å ± [ReadOnly] public NativeArray<Entity> Entities; [ReadOnly] public NativeArray<Fish> Fishes; [ReadOnly] public NativeArray<LocalTransform> LocalTransforms; [ReadOnly] public ComponentLookup<Parameter> ParamLookUp; [ReadOnly] public NativeParallelMultiHashMap<int, int> HashMap; [ReadOnly] public float CellSize; [ReadOnly] public NativeArray<int3> CellOffsets; public void Execute(int index) { // è¿åæ¢ç´¢ã®å¦çã¯ããã¾ã§ã®è§£èª¬ã¨ã ãããåã var posSelf = LocalTransforms[index].Position; var cellSelf = (int3)(posSelf / CellSize); var fishSelf = Fishes[index]; var fowardSelf = math.normalizesafe(fishSelf.Velocity); var param = ParamLookUp[fishSelf.ParamEntity]; var neighborAngle = math.radians(param.NeighborAngle); var neighborDist = param.NeighborDistance; var prodThresh = math.cos(neighborAngle); var entitySelf = Entities[index]; int neighborsCount = 0; bool isNeighborCountFull = false; for (int offsetIndex = 0; offsetIndex < CellOffsets.Length; ++offsetIndex) { var cell = cellSelf + CellOffsets[offsetIndex]; var hashSelf = NeighborsDetectionUtil.GetHash(cell); if (!HashMap.TryGetFirstValue(hashSelf, out var j, out var it)) continue; do { var entityTarget = Entities[j]; if (entitySelf == entityTarget) continue; var ltTarget = LocalTransforms[j]; var posTarget = ltTarget.Position; var to = posTarget - posSelf; var dist = math.length(to); if (dist > neighborDist) continue; var dir = to / math.max(dist, 1e-3f); var prod = math.dot(dir, fowardSelf); if (prod < prodThresh) continue; // è¿åæ¢ç´¢ã®çµæè¦ã¤ãã£ãåä½ã¯ä¸æçã« HashMap ã«å ¥ãã¦ãã var elem = new NeighborsEntityBufferElement() { Entity = entityTarget }; Neighbors.Add(entitySelf, elem); // å¯éãã¦ããå ´åã¯æ°ãå¤ãã®ã§æã¡åããã ++neighborsCount; isNeighborCountFull = neighborsCount >= FishConfig.NeighborsEntityBufferMaxSize; if (isNeighborCountFull) break; } while (HashMap.TryGetNextValue(out j, ref it)); if (isNeighborCountFull) break; } } } [BurstCompile] public partial struct NeighborsWriteJob : IJobEntity { // NeighborsDetectionJob ã§éãããããã¡ãåãåã [ReadOnly] public NativeParallelMultiHashMap< Entity, NeighborsEntityBufferElement> Neighbors; // IJobEntity çµç±ã§ DynamicBuffer ãåãåã public void Execute( Entity entity, ref DynamicBuffer<NeighborsEntityBufferElement> buffer) { // ãã® DynamicBuffer 㸠DetectionJob ã®çµæãç§»ã buffer.Clear(); if (!Neighbors.TryGetFirstValue(entity, out var elem, out var it)) return; do { buffer.Add(elem); } while (Neighbors.TryGetNextValue(out elem, ref it)); } } [BurstCompile] public partial struct NeighborsDetectionSystem : ISystem { ... // DetectionJob ã®çµæã䏿çã«æ ¼ç´ããããã·ã¥ããã NativeParallelMultiHashMap<Entity, NeighborsEntityBufferElement> _neighborMap; ... public void OnCreate(ref SystemState state) { ... _neighborMap = new NativeParallelMultiHashMap<Entity, NeighborsEntityBufferElement>(100, Allocator.Persistent); } public void OnDestroy(ref SystemState state) { ... if (_neighborMap.IsCreated) _neighborMap.Dispose(); } [BurstCompile] public void OnUpdate(ref SystemState state) { ... ããã¾ã§ã¯æå±ã»ã«ã®ããã·ã¥ãããã®ãã«ã // ä¸ææ ¼ç´ãããã¡ã®ãµã¤ãºã¯ååä½ãè¦ãæå¤§å使° * Entity ã®æ° _neighborMap.Clear(); var maxBufferSize = n * FishConfig.NeighborsEntityBufferMaxSize; if (_neighborMap.Capacity < maxBufferSize) _neighborMap.Capacity = maxBufferSize; // è¿åæ¢ç´¢ã¸ã§ãã®äºç´ var detectionJob = new NeighborsDetectionJob() { Entities = entities, Fishes = fishes, LocalTransforms = localTransforms, ParamLookUp = _paramLookUp, HashMap = _hashMap, Neighbors = _neighborMap.AsParallelWriter(), CellSize = cellSize, CellOffsets = _cellOffsets, BufferLookUp = _neighborsLookUp, }; state.Dependency = detectionJob.Schedule(n, 16, state.Dependency); // æ¸ãè¾¼ã¿ã¸ã§ãã®äºç´ var writeJob = new NeighborsWriteJob() { Neighbors = _neighborMap, }; state.Dependency = writeJob.ScheduleParallel(_fishQuery, state.Dependency); ... (ãããã¡ãåé¤ããå¦çï¼ } }
ã·ã¹ãã å´ã§ããããã®ã¨ã³ãã£ãã£ã¨ãã®è¿ãã«ããè¤æ°ã®ã¨ã³ãã£ãã£ã«å¯¾å¿ãã䏦忏ãè¾¼ã¿å¯è½ãªããã·ã¥ãããï¼NativeParallelMultiHashMap<Entity, NeighborsEntityBufferElement>ï¼ã確ä¿ãã¦ããã¾ãããã®éãããã·ã¥ãããã¯ä¸¦åå¦çã®åã«ãµã¤ãºãäºãæå®ãã¦ããå¿
è¦ãããã®ã§ãè¿åã«ããã¨å¤æããåä½ã®æå¤§æ°ã決ãã¦ããããã®æ° â å
¨ã¨ã³ãã£ãã£ã®æ°ããããã¡ã® Capacity ã¨ãã¦ç¢ºä¿ãã¦ããã¾ããå¾ã¯ããã®ãããã¡ã« IJobParallelFor ç¶æ¿ã® NeighborsDetectionJob ã®ä¸¦åå¦çã§ãå
ç¨æ§ç¯ããæå±ã»ã«ã®ããã·ã¥ããããåèã«ã確ä¿ããããã·ã¥ãããã«è¿åæ
å ±ãæ§ç¯ãã¦ããã¾ãã
ããã¦ãã®ããã«æ§ç¯ããåä¸ã®ããã·ã¥ãããããåã¨ã³ãã£ãã£ã«ç´ã¥ãã DynamicBuffer ã¸ã¨åé
ãã¦ããã¾ãããããè¡ãã¸ã§ãã NeighborsWriteJob ã§ããIJobEntity ç¶æ¿ã®ã¸ã§ãã§ã¯ Entity ãåã³ã³ãã¼ãã³ããDynamicBuffer ãåãåããã®ã§ãã®ä¸ã§å¦çããã¦ããå½¢ã§ããå¾ãããæ
å ±ãç§»ãã¦ããã ãã®å¦çã§å¦çãã·ã³ãã«ã§ãããå®éå¦çã軽ãã§ãã
ãªããä»åã¯ããã¾ã§ã®ä»çµã¿ã夿ããããã« 2 ã¤ã®ã¸ã§ãã«åå²ãã¾ããããæå±ã»ã«ã®ããã·ã¥ãããã®ãã¼ã Entity ã¨ãªã£ã¦ããã°ãåä¸ã® IJobEntity ç¶æ¿ã®ã¸ã§ãã§ä¸¡æ¹ã®å¦çãè¡ããã¨æããã¾ãã
䏿ãããã¡ã®åé¤
æå¾ã«ãã·ã¹ãã ã§ç¢ºä¿ãã䏿ãããã¡ã®è§£æ¾ã§ããTempJob ã§ç¢ºä¿ãããã®ã¯æåã§ Dispose() ãå¼ã³ãè§£æ¾ããå¿
è¦ãããã¾ãããã ã·ã¹ãã ã§ã¯ã¸ã§ããæããï¼ã¹ã±ã¸ã¥ã¼ã«ããï¼ã ããªã®ã§ãå®éã¯è§£æ¾ã¯ OnUpdate() ã®ä¸ã§ã¯è¡ãã¾ãããããã§ã確ä¿ãã䏿ãããã¡ãéæ¾ããããã®ã¸ã§ããç¨æããstate.Dependency ã®ä¾åé¢ä¿ãç¨ãã¦ãå
¨ã¦ã®ã¸ã§ããçµãã£ãæå¾ã«è§£æ¾ãããã¨ãããã¨ãããå¿
è¦ãããã¾ããããã¯ä»¥ä¸ã®ãããªã³ã¼ãã«ãªãã¾ãã
[BurstCompile] public struct NeighborsCleanUpJob : IJob { // 空ã®ã¸ã§ãã§ã¢ããªãã¥ã¼ãã«ããè§£æ¾ãè¡ã [DeallocateOnJobCompletion][ReadOnly] public NativeArray<Entity> Entities; [DeallocateOnJobCompletion][ReadOnly] public NativeArray<Fish> Fishes; [DeallocateOnJobCompletion][ReadOnly] public NativeArray<LocalTransform> LocalTransforms; public void Execute() {} } ... public partial struct NeighborsDetectionSystem : ISystem { ... public void OnUpdate(ref SystemState state) { ... // æ§ã ãªä¸æãããã¡ã®ç¢ºä¿ var entities = _fishQuery.ToEntityArray(Allocator.TempJob); var fishes = _fishQuery.ToComponentDataArray<Fish>(Allocator.TempJob); var localTransforms = _fishQuery.ToComponentDataArray<LocalTransform>(Allocator.TempJob); ... // ãããããªã¸ã§ãã®å¦ç state.Dependency = ...Schedule(state.Dependency); ... var cleanUpJob = new NeighborsCleanUpJob() { Entities = entities, Fishes = fishes, LocalTransforms = localTransforms, }; state.Dependency = cleanUpJob.Schedule(state.Dependency); } }
[DeallocateOnJobCompletion] ã¯ãã®åã®éãã¸ã§ããå®äºããã¨è§£æ¾ãã¦ãããã¢ããªãã¥ã¼ãã§ãããããå®è¡å
容ã¯ç©ºã®ã¸ã§ãã¨ãã¦å®è¡ãã¦ãããã° TempJob 確ä¿ã®ãããã¡ã®è§£æ¾ãè¡ã£ã¦ããã¾ãã
ããã©ã¼ãã³ã¹
ããã§å ¨ã¦ã®ã¸ã§ããã¯ã¼ã«ã¹ã¬ããã§ä¸¦åã«åä½ããããã«ãªãã¾ããã
3000 å¹åºãã¦ããã¼ã¿ã«ã§ 1.5 msec ç¨ã§ãã¡ã¤ã³ã¹ã¬ããã«ã¯å½±é¿ããªããªãã¾ããã[UpdateBefore()] ã state.Dependency ã§è¨è¨ããåã¸ã§ãã®åå¾é¢ä¿ããã£ããã¨åä½ãã¦ãã¾ãã
ããã§ 20000 å¹ãããåãããããã«ãªãã¾ããã
ãããã«
ãã¼ã¿æåã¯ãã¹ã¦ã®ã¦ã¼ã¹ã±ã¼ã¹ã§åãã¦ãããã®ã§ã¯ãªãã¨æãã¾ãããã«ã¼ã«ã«åã£ã¦è¨è¿°ãããã¨ã§å¤§å¹ ãªããã©ã¼ãã³ã¹æ¹åã®æ©æµãåããããã¨ããã§ã¯ã¨ã¦ãå¼·åã ã¨æãã®ã§ããã®ããã«ä»¥åããç°¡åã«ã»ããã¢ããã§ããããã«ãªã£ãã®ã¯å¬ããã§ããã
è²ã ã¨ä¾¿å©ã«ã¯ãªãã¾ããããããã§ãå é¨çã«çè§£ããªãã¨ãã¾ãæ¸ããªãã¨ãããããããããªæ°ããæ¦å¿µãåæã¨ããä»çµã¿ã®çè§£ã®ç©ã¿éããå¿ è¦ã ã¨æãã¾ãã®ã§ããããããã¨ãè²ã åºæ¥ãããã«ãªãã«ã¯ããªãåå¼·ãå¿ è¦ãªå°è±¡ãè¦ãã¾ããã







































