【Unity】ゲームに使える数学/追尾編
お久しぶりです。去年入社致しました、ウエムラです。
1月に開催された、世界で開かれているグローバルゲームジャムに参加してきました。
初めての参加なのでびくびくしてましたが、とても楽しかったです!
できた作品:ゲームジャム作品
(私の担当した部分は[結果画面]で、あまり役に立っていませんが…)
さて、今回はゲームに使える数学についてです。
ゲームを作る上で数学は避けては通れぬ道…
私は数学が全然得意ではありませんが、そんな私も調べながらであれば少しは出来るので、記事にしてみようと思いました!
最近、本格的にUnityを始めたので、Unityで書いていこうと思います
素材はこちらを使っていきます。(見た目を変えるだけなので、なくてもOKです!)
アセット素材
追尾
どんな用途に使うか?(参考)
・敵の移動
・敵の攻撃
・カーソルのエフェクト
追尾は色々なゲームで使いますが、主に上記であげた内容で使うと思います。
なくてもゲームは作れますが、あればとても面白い動きを実現できます!
では、早速やっていきましょう!
下準備としてこんな感じに配置してみました。
(ひよこは気にしないでください…メモ帳です…)
エネミーを配置してみる
まずは敵のオブジェクトを作りましょう。
1. 2D Object -> Sprite でオブジェクトを作ります
2. 名前をわかりやすい名前に変更(私はEnemyにしました。以下EnemyObjで説明)
3. EnemyObj の Sprite Renderer に 画像をくっつける
4. EnemyObj に Rigidbody2D を追加する
5. Rigidbody2D の isKinematic にチェックを入れる
私の場合、こんな感じになりました。
エネミーにスクリプトをつけてみる
では、追尾させるスクリプトを組んでみましょう!(C#で進めていきます)
スクリプト名は Enemy.cs としました。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 |
[sourcecode lang="c"] using UnityEngine; using System.Collections; public class Ememy : MonoBehaviour { Vector3 mouseToWorldPos; // マウスポジション Vector3 prev; // 1フレーム前の座標保持 float speed = 0.1f; // 移動量 float moveForce = 3.0f; // 移動量(Rigidbody用) void Start() { prev = this.gameObject.transform.localPosition; } void Update() { MousePositionUpdate(); NormalMove(); RigidbodyMove(); AngleUpdate(); } /// マウスのポジション void MousePositionUpdate() { // マウスの座標取得 Vector2 mousePos = Input.mousePosition; // マウスの座標を元にワールド座標を取得 mouseToWorldPos = Camera.main.ScreenToWorldPoint(mousePos); } /// RigidBody無しの移動 void NormalMove() { } // RigidBody有りの移動 void RigidbodyMove() { } /// エネミー角度 void AngleUpdate() { } } [/sourcecode] |
一旦ここまで作ります。
UnityEditor側に戻り、EnemyObjにこのスクリプトをアタッチしてみましょう!
(ここまで、作ってもまだ動きません)
追尾移動(RigidBody無し)
それでは早速、追尾を実装してみますが、いくつか方法があるので試してみたいと思います。
では、Enemy.cs の void NormalMove() に処理を追加してみましょう!
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 |
[sourcecode lang="c"] // RigidBody無しの移動 void NormalMove() { Vector2 enemyPos = this.gameObject.transform.localPosition; float vx = mouseToWorldPos.x - enemyPos.x; float vy = mouseToWorldPos.y - enemyPos.y; float dx, dy, radian, value; /** * ターゲットとのラジアン/ベクトル */ // Mathf.Atan2を使う場合 radian = Mathf.Atan2(vy, vx); // 2つの座標から角度を計算 dx = Mathf.Cos(radian) * speed; // Sin,Cosを使用してその方向へ移動 dy = Mathf.Sin(radian) * speed; // Mathf.Atan2を使わない場合 //value = Mathf.Sqrt( (vx * vx) + (vy * vy) ); // 2つの座標からベクトル計算 //dx = (vx / value) * speed; // valueで割って正規化 //dy = (vy / value) * speed; // 移動制御 if(Mathf.Abs(vx) < 0.1f){ dx = 0f; } if(Mathf.Abs(vy) < 0.1f){ dy = 0f; } // 移動を反映 enemyPos.x += dx; enemyPos.y += dy; this.gameObject.transform.localPosition = enemyPos; } [/sourcecode] |
2つのパターンを書きましたので、お好みで。
Mathf.Atan2を使う方法は、角度からターゲットの方向に移動させています。
Mathf.Atan2を使わない方法は、ベクトルを計算、正規化をして移動させています。
追尾移動(RigidBody有り)
void Update() にある NormalMove() はコメントアウトしてください。
1 2 3 4 5 6 7 8 9 |
[sourcecode lang="c"] void Update() { MousePositionUpdate(); //NormalMove(); RigidbodyMove(); AngleUpdate(); } [/sourcecode] |
RigidBodyをつけるともっと簡単に実装することができます。
では、早速処理を追加してみます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
[sourcecode lang="c"] /// Rigidbody有りの移動 void RigidbodyMove() { Vector2 enemyPos = this.gameObject.transform.localPosition; float vx = mouseToWorldPos.x - enemyPos.x; float vy = mouseToWorldPos.y - enemyPos.y; // 追跡方向の決定 Vector2 direction = new Vector2( vx, vy ).normalized; // ターゲット方向に力を加える GetComponent ().velocity = (direction * moveForce); if(Mathf.Abs(vx) < 0.1f || Mathf.Abs(vy) < 0.1f){ GetComponent ().velocity = new Vector2 (0, 0); } } [/sourcecode] |
RigidBodyを使うことで、結構楽に実装できたりします。
.normalized を使うことで簡単に正規化することが可能です。
おまけ
おまけで角度もつけてみたいと思います。
今だと同じ方向を向いたままですが、角度をつけることで動きがそれっぽくなります。
(記事2がとてもわかりやすかったので使わさせていただきました!)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
[sourcecode lang="c"] void AngleUpdate() { // 向き調整 Vector3 diff = transform.localPosition - prev; if (diff.magnitude < 0.01f) { // Atan2で2つの角度を求めて計算して、Mathf.Rad2Deg で 弧度法 に変換 float angle = Mathf.Atan2(diff.y, diff.x) * Mathf.Rad2Deg + 90; // Quaternion.AngleAxis で 軸を中心にした回転をさせる transform.rotation = Quaternion.AngleAxis (angle, Vector3.forward); } // 1フレーム前の座標 prev = transform.localPosition; } [/sourcecode] |
Mathfクラスを使うと、自分で計算を書く必要がないのでとても楽ですね!
Quaternionクラスに便利なものが結構あるので、調べてみてもいいかもしれません。
画像をミサイル等にすると、カーソルに追尾している感じが出て味が出ると思います。
おまけはここまでです。
まとめ
今回はそれなりに簡単な追尾の仕方を書かさせていただきました。
次回は、シューティングゲームの弾で使う初歩的な数学等?書けたらと思います。(未定です)
数学が苦手な方も調べながらであれば、できるはずですので、一緒に挑戦して克服していきましょう!
記事をご閲覧頂き、ありがとうございました!
参考サイト/素材サイト