ボイド、鳥、Unity3D





パート2: Unity入札の最適化



大きな群れをなして飛んでいる鳥が衝突して巨大な羽ばたき羽毛ボールに陥ることがない理由を疑問に思ったことはありませんか? うーん、あなたがそれについて考えるなら、それはクールだろう。 いずれにせよ、1986年のある日、 Craig Reynoldsという名前の男がいて、群れの中の鳥の行動の単純なモデルを作成することを決め、 彼女のBoidと呼ばれました 。 各モデルには、分離、整列、結合という3つの基本的なルールがあります。 1つ目は隣人との衝突を避けること、2つ目は隣人と同じ方向に飛行すること、3つ目は単独で飛行してグループにとどまらないように指示することです。 これらの単純なルールにより、映画、ゲーム業界で使用される鳥、魚、その他の動物の信じられないほどの群れを作成できます。



この記事では、このモデルを実際に実装する方法を説明します。 開発には、 UnityとC#を使用しますが、ほとんどのことは他のエンジンと言語にも当てはまります。 このチュートリアルでは、Unityの操作の基本については触れません。つまり、ステージでのCtrl + Shift + Nの組み合わせの効果、インスペクターの操作、オブジェクトの複製、移動の方法を知っていることを意味します。 そうでない場合は、 この記事から始めることをお勧めします。 または、写真だけを見ることができます。



基本的な準備







Unityで新しいプロジェクトを作成し、マテリアル、プレハブ、シーン、スクリプトなど、将来のためにいくつかのフォルダーをすぐに構築します。

ステージに指向性ライトを投げ、Boidという名前の球を1つ投げます。 球体をプレハブに変えます。 同時に、後で考えないように、すぐにシーンを保存します。 それでは、スクリプティングを始めましょう。



モデルの場合、3つのパラメーターを計算する必要があります:Separation、Alignment、Cohesion。 後者から始めましょう、それは最も簡単です。 思い出してください、これは周囲のボイドの中心に向けられたベクトルです。 それを見つけるには、ボイドの座標を追加し、量をその数で割る必要があります。 ボイドは、隣人がいることをどのように知っていますか? Physics.OverlapSphereはこれに役立ちます 。 この関数は、指定されたcohesionRadius内のすべてのコライダー(ボイドを含む)がスコープ内に収まる場合、それらを返します。

boids = Physics.OverlapSphere(transform.position, cohesionRadius);
      
      





変数を無効にし、プラス、除算し、非常に便利なDebug.DrawLineColor.magentaを 使用して、ボイドの変換から装飾された線を中央に描画します。 入力Debug.DrawLineは、行の始点と終点の座標、およびオプションの線の色パラメーターを受け入れます。 すべてのデモ機能を実行した結果は開発中にのみ表示され、ビルドには入りません。

 Debug.DrawLine(transform.position, cohesion, Color.magenta);
      
      





Boid.csセンター
 using UnityEngine; public class Boid : MonoBehaviour { private Vector3 cohesion; private float cohesionRadius = 10; private Collider[] boids; void Update() { cohesion = Vector3.zero; boids = Physics.OverlapSphere(transform.position, cohesionRadius); foreach (var boid in boids) { cohesion += boid.transform.position; } cohesion = cohesion / boids.Length; Debug.DrawLine(transform.position, cohesion, Color.magenta); } }
      
      





スクリプトをプレハブに投げ、ボイドを数回コピーして、[再生]をクリックします。 ギズモの表示をオンにすることを忘れないでください。オンにしないと、ラインが表示されません。







男の子をまとめる



それでうまくいくようです。 次に、結果のポイントをモーションに変換する必要があります。 すべてを1つのヒープに保持することは適切ではないため、前のコードを別の関数に移動します。 InvokeRepeatingを使用してタイマーで関数を開始します。 最初の引数は関数の名前、2番目は開始時刻、3番目は繰り返し間隔です。 この機能は、さまざまなスクリプトの遅延起動に非常に役立ちます。

 InvokeRepeating("CalculateVelocity", 0, 1);
      
      





ベクトルを計算するには、学校の数学を使用し、中心の座標からボイドの座標を引きます。 スクリプトにパブリック(後で説明します)変数速度を追加し、関数の最初にゼロに設定し、最後に新しいベクトル結合を追加します。 更新で、経過時間を考慮して、結果を変換の座標に適用します。 Time.deltaTimeは 、動きがFPSに依存せず、すべてのプロセッサーで同じ速度になるようにするために必要です。

 transform.position += velocity * Time.deltaTime;
      
      





さらに、センターがベクターになったため、 Debug.DrawLineを別の優れたDebug.DrawRayに変更します。 違いはありません。2番目の引数だけが、相対座標である必要があります。



Boid.csの凝集
 using UnityEngine; public class Boid : MonoBehaviour { public Vector3 velocity; private float cohesionRadius = 10; private Collider[] boids; private Vector3 cohesion; private void Start() { InvokeRepeating("CalculateVelocity", 0, 1); } void CalculateVelocity() { velocity = Vector3.zero; cohesion = Vector3.zero; boids = Physics.OverlapSphere(transform.position, cohesionRadius); foreach (var boid in boids) { cohesion += boid.transform.position; } cohesion = cohesion / boids.Length; cohesion = cohesion - transform.position; velocity += cohesion; } void Update() { transform.position += velocity * Time.deltaTime; Debug.DrawRay(transform.position, cohesion, Color.magenta); } }
      
      









ボイドを共有します



分離の計算はもう少し複雑です。 近隣のヒープから最も有用な出口の方向を計算する必要があります。 これを行うために、各近傍からのベクトルの加重和を見つけることができます。 Vector3.magnitudeを使用して取得されたベクトルと近隣のベクトルとの距離で除算します。 結果の量では、最も近い隣人が最大の重みを持ちます。

 separation += (transform.position - boid.transform.position) / (transform.position - boid.transform.position).magnitude;
      
      





特定の距離で考慮される近傍の数を制限するのは理にかなっています。このため、カウンターと分離半径に1つの変数を追加します。

 if ((transform.position - boid.transform.position).magnitude < separationDistance)
      
      





さらに、ボイド自身のコライダーによる量でゼロベクトルをヒットする必要はありません。 Physics.OverlapSphereは、Boyder Colliderを含むすべてのコライダーをカバーすることを忘れないでください。 したがって、条件をわずかに変更します。

 if (boid != collider && (transform.position - boid.transform.position).magnitude < separationDistance)
      
      





Boid.csの分離
 using UnityEngine; public class Boid : MonoBehaviour { public Vector3 velocity; private float cohesionRadius = 10; private float separationDistance = 5; private Collider[] boids; private Vector3 cohesion; private Vector3 separation; private int separationCount; private void Start() { InvokeRepeating("CalculateVelocity", 0, 1); } void CalculateVelocity() { velocity = Vector3.zero; cohesion = Vector3.zero; separation = Vector3.zero; separationCount = 0; boids = Physics.OverlapSphere(transform.position, cohesionRadius); foreach (var boid in boids) { cohesion += boid.transform.position; if (boid != collider && (transform.position - boid.transform.position).magnitude < separationDistance) { separation += (transform.position - boid.transform.position) / (transform.position - boid.transform.position).magnitude; separationCount++; } } cohesion = cohesion / boids.Length; cohesion = cohesion - transform.position; if (separationCount > 0) { separation = separation / separationCount; } velocity += cohesion + separation; } void Update() { transform.position += velocity * Time.deltaTime; Debug.DrawRay(transform.position, separation, Color.green); Debug.DrawRay(transform.position, cohesion, Color.magenta); } }
      
      









私たちは男の子を整理します



Boyidsが山の中でさえ考えずに集まるだけでなく、隣人の行動を繰り返す必要があります。 アラインメントの計算は非常に簡単です。公開ベロシティ変数を要約します(そうです!)各近隣から、その数で除算します。 添付のスクリプトには、 GameObject.GetComponentを使用してアクセスできます 。 スクリプトだけでなく、通常はすべてのコンポーネントを見つけることができます。 素晴らしいこと。

 alignment += boid.GetComponent<Boid>().velocity;
      
      





Boid.csの配置
 using UnityEngine; public class Boid : MonoBehaviour { public Vector3 velocity; private float cohesionRadius = 10; private float separationDistance = 5; private Collider[] boids; private Vector3 cohesion; private Vector3 separation; private int separationCount; private Vector3 alignment; private void Start() { InvokeRepeating("CalculateVelocity", 0, 1); } void CalculateVelocity() { velocity = Vector3.zero; cohesion = Vector3.zero; separation = Vector3.zero; separationCount = 0; alignment = Vector3.zero; boids = Physics.OverlapSphere(transform.position, cohesionRadius); foreach (var boid in boids) { cohesion += boid.transform.position; alignment += boid.GetComponent<Boid>().velocity; if (boid != collider && (transform.position - boid.transform.position).magnitude < separationDistance) { separation += (transform.position - boid.transform.position) / (transform.position - boid.transform.position).magnitude; separationCount++; } } cohesion = cohesion / boids.Length; cohesion = cohesion - transform.position; if (separationCount > 0) { separation = separation / separationCount; } alignment = alignment / boids.Length; velocity += cohesion + separation + alignment; } void Update() { transform.position += velocity * Time.deltaTime; Debug.DrawRay(transform.position, separation, Color.green); Debug.DrawRay(transform.position, cohesion, Color.magenta); Debug.DrawRay(transform.position, alignment, Color.blue); } }
      
      





私たちが始めて...反応はありません、すべてがまだです。 速度式にトリックを追加します。

 velocity += cohesion + separation + alignment*2;
      
      





そして...







ベクトルを切る



なんで! まあ、かなり予測可能です。 アラインメントベクトルを増加させ、速度ベクトルを増加させ、アラインメントベクトルを増加させました。 最大制限速度を設定する必要があります。 さらに、ベクトルのすべてのコンポーネントにも制限を設定する必要があります。そうしないと、状況によってはボーイフレンドの行動がやや奇妙になります。 自分で試すことができます。



Unityでベクターをトリミングするには、 Vector3.ClampMagnitude関数があります。 各ベクトルを追加した後、次の形式の構造を追加するだけです。

 velocity = Vector3.ClampMagnitude(velocity, maxSpeed);
      
      





Boid.csクランプ
 using UnityEngine; public class Boid : MonoBehaviour { public Vector3 velocity; private float cohesionRadius = 10; private float separationDistance = 5; private Collider[] boids; private Vector3 cohesion; private Vector3 separation; private int separationCount; private Vector3 alignment; private float maxSpeed = 15; private void Start() { InvokeRepeating("CalculateVelocity", 0, 1f); } void CalculateVelocity() { velocity = Vector3.zero; cohesion = Vector3.zero; separation = Vector3.zero; separationCount = 0; alignment = Vector3.zero; boids = Physics.OverlapSphere(transform.position, cohesionRadius); foreach (var boid in boids) { cohesion += boid.transform.position; alignment += boid.GetComponent<Boid>().velocity; if (boid != collider && (transform.position - boid.transform.position).magnitude < separationDistance) { separation += (transform.position - boid.transform.position) / (transform.position - boid.transform.position).magnitude; separationCount++; } } cohesion = cohesion / boids.Length; cohesion = cohesion - transform.position; cohesion = Vector3.ClampMagnitude(cohesion, maxSpeed); if (separationCount > 0) { separation = separation / separationCount; separation = Vector3.ClampMagnitude(separation, maxSpeed); } alignment = alignment / boids.Length; alignment = Vector3.ClampMagnitude(alignment, maxSpeed); velocity += cohesion + separation * 10 + alignment * 1.5f; velocity = Vector3.ClampMagnitude(velocity, maxSpeed); } void Update() { if (transform.position.magnitude > 25) { velocity += -transform.position.normalized; } transform.position += velocity * Time.deltaTime; Debug.DrawRay(transform.position, separation, Color.green); Debug.DrawRay(transform.position, cohesion, Color.magenta); Debug.DrawRay(transform.position, alignment, Color.blue); } }
      
      





抑制されたベクトルの動作を確認します。







自動化



手動でボイドを配置することはまったく面白くない。 ソフトウェアには、 インスタンス化機能があります。 入り口では、コピーするオブジェクトへのリンク、オブジェクトの新しい座標、およびその回転を送信する必要があります。 コピーされるプレハブに対して、個別のパブリック変数を作成し、インスペクターに入力します。 Random.insideUnitSphereからランダムな座標を取得すると便利です。必要な球体の半径を掛けるだけです。 ボイドは必要なだけ回転させることができ、結果は1つになるため、 Quaternion.identityを使用します。これは回転しないことを意味します。

 Instantiate(boidPrefab, Random.insideUnitSphere * 25, Quaternion.identity);
      
      





ループ内で、上記のアクションを繰り返し、任意の数のboidを取得します。 シーンの中央にある空のオブジェクトに新しいスクリプトをスローし、プレハブリンクに入力します。



HeartOfTheSwarm.cs
 using UnityEngine; public class HeartOfTheSwarm : MonoBehaviour { public Transform boidPrefab; public int swarmCount = 100; void Start() { for (var i = 0; i < swarmCount; i++) { Instantiate(boidPrefab, Random.insideUnitSphere * 25, Quaternion.identity); } } }
      
      





急速に飛んでいるボイドの群れを見るのはあまり便利ではありません。チェーンに入れておくといいでしょう。 これを行うには、更新に小さな条件を追加します。

 if (transform.position.magnitude > 25) { velocity += -transform.position.normalized; }
      
      





その助けを借りて、座標が仮想球の外側にあるボイドは中心に向かって回転します。 最後に、ベクトル係数やその他のパラメーターを少し試してみましょう。そうしないと、目的の効果が機能しません。 以下のネタバレの下の最終コードを参照してください。

 velocity += cohesion + separation * 10 + alignment * 1.5f;
      
      





私たちは感心し始めます。



Boid.cs
 using UnityEngine; public class Boid : MonoBehaviour { public Vector3 velocity; private float cohesionRadius = 10; private float separationDistance = 5; private Collider[] boids; private Vector3 cohesion; private Vector3 separation; private int separationCount; private Vector3 alignment; private float maxSpeed = 15; private void Start() { InvokeRepeating("CalculateVelocity", 0, 0.1f); } void CalculateVelocity() { velocity = Vector3.zero; cohesion = Vector3.zero; separation = Vector3.zero; separationCount = 0; alignment = Vector3.zero; boids = Physics.OverlapSphere(transform.position, cohesionRadius); foreach (var boid in boids) { cohesion += boid.transform.position; alignment += boid.GetComponent<Boid>().velocity; if (boid != collider && (transform.position - boid.transform.position).magnitude < separationDistance) { separation += (transform.position - boid.transform.position) / (transform.position - boid.transform.position).magnitude; separationCount++; } } cohesion = cohesion / boids.Length; cohesion = cohesion - transform.position; cohesion = Vector3.ClampMagnitude(cohesion, maxSpeed); if (separationCount > 0) { separation = separation / separationCount; separation = Vector3.ClampMagnitude(separation, maxSpeed); } alignment = alignment / boids.Length; alignment = Vector3.ClampMagnitude(alignment, maxSpeed); velocity += cohesion + separation * 10 + alignment * 1.5f; velocity = Vector3.ClampMagnitude(velocity, maxSpeed); } void Update() { if (transform.position.magnitude > 25) { velocity += -transform.position.normalized; } transform.position += velocity * Time.deltaTime; Debug.DrawRay(transform.position, separation, Color.green); Debug.DrawRay(transform.position, cohesion, Color.magenta); Debug.DrawRay(transform.position, alignment, Color.blue); } }
      
      









それだけです、ボイドは彼らのcageの中で飛びます。 ただし、それらは非常に少数です! 100を超える量では、すべてが著しく遅くなり始めます。 単一の最適化を行っていないため、当然です。 次のパートでは、はるかに多くのboidで60 FPSを保持できるようにコードを最適化する方法について説明します。 それまでの間、コメントでオプションを提案することができます。



パート2: Unity入札の最適化



GitHubのソース | Unity Web Playerの所有者向けのオンラインバージョン



アニメーションの作成方法に興味のある人のためのミニボーナス。

Screenshots.cs
 using UnityEngine; public class Screenshot : MonoBehaviour { private int count; void Update() { if (Input.GetButtonDown("Jump")) { InvokeRepeating("Capture", 0.1f, 0.3f); } } void Capture() { Application.CaptureScreenshot(Application.dataPath + "/Screenshot" + count + ".png"); count++; } }
      
      






All Articles