mattak's blog

人生を1ミリ進める

Boidsを試す

Unite 2018でBoidsの話があったのだけど、面白かったので真似して実装してみる.

Boidsとは

Boids - Wikipedia

群体のシミュレーションをする際に、3つのルールを適用すればObject群がそれっぽく動くということらしい.

wikipediaの画像が分かりやすかった.

ルール1. 分離

  • ローカルの群れを避けるようにステアリングする

ルール2. 整列

  • ローカルの群れたちが向く方向の平均にステアリングする

ルール3. 凝集

  • ローカルの群れの平均位置に向かってステアリングする

実装

雰囲気で実装してみる.

github.com

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