Unite 2018でBoidsの話があったのだけど、面白かったので真似して実装してみる.
Boidsとは
群体のシミュレーションをする際に、3つのルールを適用すればObject群がそれっぽく動くということらしい.
wikipediaの画像が分かりやすかった.
ルール1. 分離
- ローカルの群れを避けるようにステアリングする
ルール2. 整列
- ローカルの群れたちが向く方向の平均にステアリングする
ルール3. 凝集
- ローカルの群れの平均位置に向かってステアリングする
実装
雰囲気で実装してみる.
public class Boid : MonoBehaviour { public bool ApplySeparation = true; public bool ApplyAlignment = true; public bool ApplyCohesion = true; public bool ApplyRandom = true; public float Radius = 10f; private Transform[] allmates; private void OnEnable() { this.allmates = this.transform.parent.GetComponentsInChildren<Boid>() .Where(it => it.gameObject.GetInstanceID() != this.gameObject.GetInstanceID()) .Select(it => it.transform) .ToArray(); } private void FixedUpdate() { var randomStrength = 0.0001f; var flockmates = TransformUtils.SelectLocalFlockmates(this.transform, this.allmates, this.Radius); var v1 = this.ApplySeparation ? TransformUtils.CalcSeparationRule(this.transform, flockmates) * 10f : Vector3.zero; var v2 = this.ApplyAlignment ? TransformUtils.CalcAlignmentRule(this.transform, flockmates) : Vector3.zero; var v3 = this.ApplyCohesion ? TransformUtils.CalcCohesionRule(this.transform, flockmates) : Vector3.zero; var vr = this.ApplyRandom ? new Vector3( Random.Range(-randomStrength, randomStrength), Random.Range(-randomStrength, randomStrength), Random.Range(-randomStrength, randomStrength)) : Vector3.zero; var diff = v1 + v2 + v3 + vr; this.transform.LookAt(this.transform.position + diff); } }
各ロジック
public static class TransformUtils { /// <summary> /// Calc boids rule1. steering position by separation. /// </summary> /// <param name="target">target object</param> /// <param name="flockmates">local flockmates without the target object</param> /// <returns>stearing vector by separation rule</returns> public static Vector3 CalcSeparationRule(Transform target, Transform[] flockmates) { if (flockmates.Length < 1) return Vector3.zero; var v = flockmates.Select(it => target.position - it.position).Aggregate((sum, it) => it); return v / (0.00001f + v.sqrMagnitude); } /// <summary> /// Calc boids rule2. steering position by alignment. /// </summary> /// <param name="target">target object</param> /// <param name="flockmates">local flockmates without the target object</param> /// <returns></returns> /// <returns>stearing vector by alignment rule</returns> public static Vector3 CalcAlignmentRule(Transform target, Transform[] flockmates) { if (flockmates.Length < 1) return Vector3.zero; return flockmates.Select(it => it.forward).Aggregate((sum, it) => sum + it) / flockmates.Length; } /// <summary> /// Calc boids rule3. steering position by cohesion. /// </summary> /// <param name="target">target object</param> /// <param name="flockmates">local flockmates without the target object</param> /// <returns>stearing vector by cohesion rule</returns> public static Vector3 CalcCohesionRule(Transform target, Transform[] flockmates) { if (flockmates.Length < 1) return Vector3.zero; var average = flockmates.Select(it => it.position).Aggregate((sum, it) => sum + it) / flockmates.Length; return average - target.position; } /// <summary> /// Select local flockmates within the circle radius /// </summary> /// <param name="target">target object</param> /// <param name="mates">flockmates object without me</param> /// <param name="radius">raidus of target flockmates</param> /// <returns></returns> public static Transform[] SelectLocalFlockmates(Transform target, Transform[] mates, float radius) { if (mates.Length < 1) return new Transform[0]; var radius2 = radius * radius; return mates.Where(it => (it.position - target.position).sqrMagnitude <= radius2).ToArray(); } }
結果
なんかたのしい! せっかくなのでちょっと整理したらライブラリとして体裁整える。
試行数4