第6章 爆弾、コイン、障害物の開発

マップに登場するキャラを設計して、実装します。開発の流れは、プレイヤーと同じです。1つのゲームオブジェクトに統合したプレイヤーに対して、分割したクラスを組み合わせて、バリエーションが作れるようにします。

6.1 キャラの機能

企画構想で挙げた各キャラの機能は次のとおりです。

  • 爆弾
    • 触れると爆発する危険物
    • 停止している普通の爆弾、決まった軌道で移動する黄色い爆弾、跳ね回っている赤い爆弾の3種類ある
  • コイン
    • 停止している銅貨、決まった軌道で移動する銀貨、跳ね回っている金貨がある
    • ステージ内にあるすべてのコインを取ったらクリア
  • 障害物
    • 行く手をさえぎる障害物
    • 触れてもミスにはならない
    • 岩や、決まった軌道を移動する木箱がある

これらから、キャラの機能を列挙することから始めます。

6.1.1 状態の管理機能

すべてのキャラで共通なのが、ゲームの進行によって、動いたり、停止することです。

  • ステージ開始時からカウントダウンまで停止
  • カウントダウンが終わったら、移動開始
  • ゲームオーバーやクリアしたら、停止

プレイヤーで使ったIGameStateListenerが使えます。ゲームの通知を受け取って、移動の開始や停止させるクラスを用意します。

6.1.2 プレイヤーと触れたときの動作

プレイヤーと接触したときの動作は、次のとおりです。

  • 爆弾
    • 爆発して、ゲームオーバーを要求する
  • コイン
    • 消えて、基本点と残り秒数に応じた得点が加算される
    • すべてのコインを取り終えたら、クリアを要求する
  • 障害物
    • プレイヤーに触れても、スクリプト的な動作はなし

爆発を検知するのを、プレイヤーにするか、爆弾にするかの問題がありました。今回は、プレイヤーにやることがありません。爆弾側で完結させるのがよさそうです。この方法の懸案は、爆弾をすべて検索して、通知を渡すのが重い可能性です。この点を、実装時に確認します。

爆弾に、OnTriggerEnterを実装して、相手がプレイヤーかどうかを確認します。プレイヤーなら、爆発を発生させます。また、第4章で検討したゲームオーバーを要求するIGameOverEmitterを実装して、ゲームオーバーを要求します。接触相手がプレイヤー以外なら、何もしません。

爆弾が問題なければ、コインの方も、コインにまとめて実装する方法でよいでしょう。コインには、IGetCoinEmitterを実装して、プレイヤーと接触したら、コインの取得を報告させます。

6.1.3 動き方

動き方は、次のとおりです。

  • 通常爆弾、銅のコイン
    • 動かない
  • 黄爆弾、銀のコイン、木箱
    • 決まったルートを、一定速度で巡回する
  • 赤爆弾、金のコイン
    • 等速直線運動をして、壁や障害物にぶつかると、跳ね返る

動き方は、プレイヤーに触れたときの動作とは別のグループでまとめられます。これらを別クラスに分けて実装することで、ゲームオブジェクトにアタッチするスクリプトの組み合わせで、上記のキャラが用意できます。

動かないものは、ゲームオブジェクトにコライダーと、必要に応じてタグ、レイヤーを設定すればよさそうです。岩などの衝突があるものは、staticを有効にしておくとよいでしょう。スクリプトは、不要です。

決まったルートを一定速度で巡回するのは、黄爆弾、銀のコイン、木箱です。通過する座標と、ループ方法をインスペクターで設定したいので、MonoBehaiourで実装するのが良さそうです。木箱は、プレイヤーで押しても動かせません。RigidbodyのIs Kinematicを有効にして、MovePositionで移動させることになります。プレイヤーとは、違う動きになる可能性があるので、CharacterMoverとは別のクラスを用意します。

等速直線運動をして、壁や障害物にぶつかると跳ね返るのは、赤爆弾と金のコインです。これらは、移動開始時にRigidbodyのvelocityに初速を設定して、あとは慣性移動に任せます。これも、CharacterMoverとは違うので、別のクラスを用意します。

巡回と跳ね返りは、どちらもゲームの開始と停止の通知を受け取って、動作をはじめたり、停止する機能を持ちます。IStartStopインターフェースを実装すればよいでしょう。通知を受け取ったときの処理は、両者で異なります。処理的な共通点はほとんどないので、独立したスクリプトで実装することにします。

6.2 状態の管理

状態の切り替えは、CharacterBehaviourクラスで管理することにします。IGameStateListenerを実装すれば、ゲーム進行の通知を受け取れます。

動かし方を検討した結果、プレイヤーのように、受け取った移動量に応じて動かすような処理はありませんでした。設定されているパラメータに基づいて、自動的に移動します。状態に応じて、移動の開始と停止を指示できるようにすればよいでしょう。

爆発やコインの取得も、ゲーム中のみ有効です。移動と同様に、開始と停止が受け取れれば制御できます。

以上から、リスト6.1のようなインターフェースを定義します。

リスト6.1: IStartStopインターフェース

 1: /// <summary>
 2: /// ゲームが開始したり、停止したときに呼び出されるメソッドを定義するインターフェース。
 3: /// </summary>
 4: public interface IStartStop
 5: {
 6:     /// <summary>
 7:     /// ゲームが開始したときに呼び出されるメソッド。
 8:     /// </summary>
 9:     public void OnGameStarted();
10: 
11:     /// <summary>
12:     /// ゲームが停止したときに呼び出されるメソッド。
13:     /// </summary>
14:     public void OnGameStopped();
15: }

CharacterBehaviourクラスの開始時に、IStartStopインターフェースのインスタンスをGetComponentsで取得して、変数に代入してキャッシュします。CharacterBehaviourクラスは実装済みです。リスト6.2が、該当するコードです。

リスト6.2: CharacterBehaviourのIStartStopのキャッシュ処理

    IStartStop[] startStops;

    void Awake()
    {
        startStops = GetComponents<IStartStop>();
    }

ゲームの開始や停止の通知が届いたら、キャッシュしたインスタンスをループで取り出して、対応するメソッドを呼び出します。InitStateに、リスト6.3のとおり、実装しています。

リスト6.3: CharacterBehaviourのIStartStopの呼び出し処理

    void InitState()
    {
        if (!state.ChangeState())
        {
            return;
        }

        switch(state.CurrentState)
        {
            case State.Play:
                for (int i=0;i<startStops.Length;i++)
                {
                    startStops[i].OnGameStarted();
                }
                break;

            case State.End:
                for (int i = 0; i < startStops.Length; i++)
                {
                    startStops[i].OnGameStopped();
                }
                break;
        }
    }

GetComponentsと、ちょっとしたループで、オブジェクト内の通知システムを構築しました。

6.3 プレイヤーに触れたときの処理

残りの作業は、次のとおりです。

  • 爆弾がプレイヤーに接触した処理
  • コインがプレイヤーに接触した処理
  • 跳ね返り移動
  • 決まったルートの移動

プレイヤーの開発と同様に、状態管理も含めて、こららも別々に開発できます。

6.3.1 爆弾がプレイヤーに接触した処理

爆弾は、プレイヤーと接触したことを検知したら、爆発用のゲームオブジェクトをInstantiateして、自分をDestroyします。ゲームオーバーを要求するには、IGameOverEmitterで定義したGameOverRequestをInvokeします。

爆発処理は、Attackerクラスに実装します。スクリプトのひな形を、/Assets/Yoketoru/Scripts/Game/Bombフォルダーに用意してあります。指示を読んで、実装してください。

  • GitHub Desktopなどで、変更点をコミットして、プッシュする
  • Current branchをクリックして、dev-game-overというブランチを作成する。ブランチ元は、masterを選ぶ
  • UnityのProjectビューで、Attackerスクリプトをダブルクリックして開く
  • クラス定義に、IStartStopインターフェースを加える
  • 黄色アイコンで、クイック操作が表示されるので、クリックして、インターフェースを実装しますを選択する
  • bool型のインスタンス変数isStartedを定義する。falseを代入しておくとよい
  • OnGameStartedメソッド内のthrow new System...の行を削除して、isStartedにtrueを代入する
  • OnGameStoppedメソッド内のthrow new System...の行を削除して、isStartedにfalseを代入する

ここまでで、ゲームの開始と停止を状態管理から受け取って、isStartedで確認できる仕組みが実装できました。続いて、OnTriggerEnterに、次の処理を実装して、爆発の処理を実装します。

  • 確認用に実装してあったDebug.Logを消す
  • 相手のタグがPlayer以外なら、処理は不要なので、returnする
  • isStartedがfalseなら、爆発は不要なので、returnする
  • explosionPrefabを、Instantiateで発生させる。positionは、transform.position。rotationは、Quaternion.identityを指定する
  • GameOverRequest.Invoke();を実行して、ゲームオーバーを要求する
  • Destroy(gameObject);で、爆弾を消す

以上で完了です。プレイヤーの操作が完成していたら、プレイヤーを爆弾にぶつけて、爆発して、ゲームオーバーへ遷移するのを確認してください。

できたら、コミットして、プッシュします。

6.3.2 コインがプレイヤーに接触した処理

コインを拾う処理を実装します。

Coinスクリプトの雛形は、実装済みです。UnityのProjectビューから、Assets/Yoketoru/Scripts/Game/Itemフォルダーを開いて、Coinスクリプトを開きます。

爆発の接触処理と同様の手順で、次の作業をしてください。

  • masterブランチから、dev-get-coinブランチを作成する
  • IStartStopインターフェースを追加して、インターフェースを実装する
  • isStarted変数を定義する
  • ゲームの開始と停止の通知メソッドに、isStartedの設定を実装する

次に、OnTriggerEnterの内容を、次の手順で実装します。

  • 確認用に実装してあったDebug.Logを消す
  • 相手のタグがPlayer以外なら、処理は不要なので、returnする
  • isStartedがfalseなら、爆発は不要なので、returnする
  • CoinGot.Invoke(point);と書く。これで、ゲームの進行管理にコインを取ったことを知らせる
  • Destroy(gameObject);で、コインを消す

以上で完了です。プレイヤーの操作が完成していたら、プレイヤーでコインを取ってください。コインが消えて、得点が入ります。また、すべてのコインを取ったら、クリアします。

できたら、コミットして、プッシュします。

6.4 移動の実装

移動には、次の3種類がありました。

  • 動かない
  • 決まったルートを、一定速度で巡回する
  • 等速直線運動で移動して、何かに衝突したら、跳ね返る

検討したことをもとにして、実装します。

6.4.1 動かないもの

masterブランチから、dev-static-objectブランチを作成して、作業するとよいでしょう。

動かないものには、スクリプトは不要でした。ProjectビューのAssets/Yoketoru/Prefabsフォルダーを開いて、該当するオブジェクトの設定を、インスペクターで確認してください。

  • Bombプレハブをダブルクリックして開いて、次の項目を確認する
    • Sphere ColliderのIs Triggerをチェックして、トリガーモードにする
    • 上書き保存する
  • Coinプレハブをダブルクリックして開いて、次の項目を確認する
    • CoinスクリプトのPointに100を入力して、基本点を100点にする
    • Sphere ColliderのIs Triggerをチェックして、トリガーモードにする
    • 上書き保存する
  • LargeRockプレハブをダブルクリックして開いて、次の項目を確認する
    • Staticにチェックを入れて、固定オブジェクトにする
    • Layerを、Wallにする
    • Box ColliderのIs Triggerのチェックを外して、接触を有効にする
    • 上書き保存する
  • Rockプレハブをダブルクリックして開いて、次の項目を確認する
    • Staticにチェックを入れて、固定オブジェクトにする
    • Layerを、Wallにする
    • Shere ColliderのIs Triggerのチェックを外して、接触を有効にする
    • 上書き保存する

以上で、動かないオブジェクトの設定は完了です。Playして、岩にプレイヤーで突撃して、動かせないことを確認してください。

完成したら、コミットとプッシュします。

6.4.2 跳ね返り移動

登場する順番的には、ルートの巡回が先なのですが、実装が難しいため、こちらを先に解説します。作業順は、簡単なものを優先するのが効率的です。

masterブランチから、dev-patrolブランチを作成して、作業するとよいでしょう。

ステージ2からはじまるように設定する

跳ね返りは、Stage1では出てこないので、このままでは確認しにくいです。Stage2からはじまるように設定します。

  • Projectウィンドウから、Assets/Yoketoru/Scenesフォルダーを開いて、GameSystemシーンをダブルクリックして開く
  • HierachyウィンドウのGameSystemオブジェクトをクリックして選択する
  • Inspectorウィンドウで、Start Stage欄を2に設定する

Playすると、ゲームがStage2からはじまるようになります。完成するまでのPlay回数が多くなると、タイトルから起動して、カウントダウンを待つのが面倒です。手間は増えますが、開発用のシーンを作成したり、テストランナーを利用する選択肢もあります。このあたりは、開発する内容に応じて、楽そうな方法を選びます。

ひな形の確認

跳ね返り移動は、MonoBehaviourを継承して、IStartStopインターフェースを実装します。ルートの巡回移動とは共通点が少ないので、新規スクリプトで作成します。ひな形を、Assets/Yoketoru/Scripts/Game/AutoMoverフォルダーに用意しています。Projectウィンドウから、該当フォルダーを開いて、ReflectionMoverスクリプトをダブルクリックして開いてください。リスト6.4が、コードです。

リスト6.4: ReflectionMoverのひな形

 1: using UnityEngine;
 2: 
 3: /// <summary>
 4: /// 反射移動を制御するクラス。
 5: /// </summary>
 6: public class ReflectionMover : MonoBehaviour, IStartStop
 7: {
 8:     [Tooltip("移動方向"), SerializeField]
 9:     Vector3 firstDirection = Vector3.right;
10:     [Tooltip("移動速度"), SerializeField]
11:     float speed = 2;
12: 
13:     Rigidbody rb;
14: 
15:     void Awake()
16:     {
17:         rb = GetComponent<Rigidbody>();
18:         rb.velocity = Vector3.zero;
19:     }
20: 
21:     private void FixedUpdate()
22:     {
23:         // TODO: 速度を維持する
24:     }
25: 
26:     public void OnGameStarted()
27:     {
28:         Debug.Log($"{name} 移動開始");
29:     }
30: 
31:     public void OnGameStopped()
32:     {
33:         Debug.Log($"{name} 移動停止");
34:     }
35: }

ReflectionMoverクラスは、MonoBehaviourを継承して、IStartStopインターフェースを実装しています。

最初の移動方向をfirstDistance、速度をspeedで定義しています。これらは、Inspectorウィンドウで設定できます。方向と速度を分けているのは、設定を簡単にするためです。

Rigidbodyのキャッシュ用の変数としてrbを定義して、Awakeで取得と、停止する設定をしています。

あとは、実装予定のメソッドを定義しています。

実装方法と手順の検討

跳ね返り移動は、とても簡単です。ゲームが開始したときに初速を設定して、ゲームが停止したときに移動を止めます。

理屈的にはこれだけでよいのですが、ゲーム用の物理エンジンは、精度を犠牲にして、処理速度を優先するものが多くあります。Unityの物理エンジンも同様です。誤差から、衝突するごとに徐々に速くなったり、逆に、遅くなったりします。これを防ぐために、方向はそのままで、速さをspeedに保つ処理を、FixedUpdateに実装します。

また、一定の速度より遅い場合、跳ね返らないようにする設定があります。この設定がないと、別のオブジェクトに載っているオブジェクトが、数値の誤差によって、いつまでも跳ね続ける不具合が起きます。それをなくすための工夫です。しかし、今回のように、跳ね返って欲しいゲームの場合、この設定が悪い方向に作用します。薄い角度で壁にぶつかると、跳ね返らずに、壁にくっついて動くようになります。ブロック崩しなどを作っていると、この現象に遭遇します。この設定を、無効にする必要があります。

ここまでの話しを踏まえて、実装と設定の手順を考えます。

  • OnGameStartedと、OnGameStoppedを実装する
    • 動かさないと、他の設定の確認ができないので、まずはOnGameStartedを実装する
    • 停止はすぐに実装できるので、同時に実装する
  • 動作確認
    • しばらく実行して、爆弾やコインが加速したり、壁に張り付く症状を確認する
  • 跳ね返り設定
    • 設定で対策できる
  • 動作確認
    • 壁に張り付かなくなるのを確認する
  • 速度の維持の実装
    • 最後に、FixedUpdateに、速度を維持する処理を実装する
  • 動作確認
    • すべてが、期待どおりに動くことを確認する

OnGameStartedとOnGameStoppedの実装

OnGameStartedの実装は、次のとおりです。

  • Debug.Logを削除する
  • rb.velocityに、speed * firstDirection.normalizedを代入する

firstDirectionは、長さが1とは限りません。normalizedを参照することで、単位ベクトルを求めて、それにspeedをかけます。

OnGameStoppedは、次のとおりです。

  • Debug.Logを削除する
  • rb.velocityに、Vector3.zeroを代入する

Playして、動作を確認してください。Inspectorで設定されているスピードと方向にしたがって、動き始めます。ただし、壁に衝突しても、期待する方向には跳ね返らず、動きが遅くなります。また、角に達すると、止まってしまいます。これを直していきます。

跳ね返り設定

跳ね返りの設定は、Project SettingsのPhysicsで設定します。

  • Editメニューから、Project Settingsを選ぶ
  • Physicsを選ぶ
  • Bounce Thresholdを0にする

Bounce Thresholdが、跳ね返るかを判定する閾値(しきいち)です。デフォルトの2だと、速度が2以下では跳ね返らなくなります。0にすると、遅くても跳ね返るようになります。

Playして、動作を確認してください。壁で跳ね返るようになります。ただ、角度が期待と違います。これは、衝突したときの摩擦と跳ね返り係数が未設定なためです。

摩擦と跳ね返り係数は、PhysicMaterialで設定します。Projectウィンドウの+から作成できますが、今回は用意してあります(図6.1)。

跳ね返り用のPhysicMaterial

図6.1: 跳ね返り用のPhysicMaterial

Dynamic Friction(動摩擦係数)と、Static Friction(静摩擦係数)は、0に設定します。これは、接触したときに、オブジェクトが回転しないようにするためです。回転すると、回転に力を使ってしまい、跳ね返りが弱くなります。

Bounciness(跳ね返り係数)は、1に設定します。これは、完全に跳ね返すための設定です。0だと、跳ね返りません。

物理の世界では、衝突した相手のPhysic Materialとの組み合わせで、跳ね返り方が決まります。今回のようなゲームの世界では、こちらの設定を優先したい場合があります。そのために、Friction CombineをMinimumにして、抵抗は小さい方を採用するようにします。また、Bounce CombineをMaximumに設定して、跳ね返り係数は大きい方を採用するようにします。これで、抵抗を0、跳ね返りを1に固定します。

Reflectマテリアルを、BombReflectionプレハブと、CoinReflectionプレハブのColliderにアタッチします。

  • Projectウィンドウで、Assets/Yoketoru/Prefabsフォルダーを開く
  • Projectウィンドウで、Assets/Yoketoru/PhysicMaterialsフォルダーを開く
  • Reflectマテリアルをドラッグして、BombReflectionプレハブのSphere ColliderのMateiral欄にドロップして(図6.2)、アタッチする
Reflectマテリアルをアタッチする

図6.2: Reflectマテリアルをアタッチする

  • 同様に、Reflectマテリアルをドラッグして、CoinReflectionプレハブのSphere ColliderのMateiral欄にドロップして、アタッチする

Playして、動作を確認してください。納得のできる跳ね返り方になります。しばらく見ていると、速度が遅くなったり、速くなったりします。これを修正します。

速度の維持の実装

速度が変わるのは、Physicsの誤差によるものです。物理エンジンにすべての制御を任せて作れるゲームは、意外とありません。多かれ少なかれ、スクリプトでの調整が必要になります。今回は、FixedUpdateで、速度を再設定するようにします。

  • floatの変数currentSpeedを定義して、rb.velocity.magnitudeを代入する。これで、現在の速さが分かる
  • currentSpeedがほぼ0なら、returnして何もしない。ほぼ0かどうかは、Mathf.Approximatelyで判定する
  • rb.velocityに、speedをrb.velocityの単位ベクトルにかけた値を代入する。これで、現在の移動方向に、speedの速さで動くように、速度を再設定する

Playして、様子を確認してください。速さが変わらなければ、実装完了です。コミットして、プッシュします。

6.4.3 決まったルートの巡回

決まったルートの巡回移動を実装します。まずは、機能を書き出します。

  • Inspectorウィンドウで、次の項目を設定できるようにする
    • 移動速度
    • 巡回する座標を並べたウェイポイント
    • 最後のウェイポイントに達したら、往復するように動くPingPongと、先頭のウェイポイントに戻るLoopの設定をもつ
    • スタート直後に目指すウェイポイントのインデックス。通常は1だが、調整しやすくするための設定
  • ゲームオブジェクトを選択したときに、ウェイポイントをギズモで表示する
  • ゲームが開始するまでは動かない
  • ゲームが停止したら止める
  • プレイヤーからは押せないので、Is Kinematicを有効にして、MovePositionで移動させる
  • 移動の流れは、ひな形にコメントで記載

これらを実装する手順を検討します。

  1. ゲームオブジェクトの設定の確認と修正
  2. 変数定義と、ギズモの描画(実装済み)
  3. ゲームの開始と停止の実装。移動をデバッグ表示
  4. 次のウェイポイントへの移動を実装
  5. 目指すウェイポイントの更新を実装
  6. 端に達したら、PingPongで戻す処理の実装
  7. 端に達したら、Loopさせる処理の実装

今回の処理は、やや複雑なので、段階を経て実装します。これまでと同じく、masterブランチから、dev-patrolブランチを作成してから作業をするとよいでしょう。

ゲームオブジェクトの設定の確認と修正

巡回移動は、黄色爆弾、銀のコイン、木箱です。これらのゲームオブジェクトの設定を確認します。

  • Projectウィンドウで、Assets/Yoketoru/Prefabsフォルダーを開く
  • BombPatrolプレハブを選択する
  • Inspectorウィンドウで、次の項目を確認する
    • RigidbodyのIs Kinematicをチェックする。これで、物理的な動作から、機械的な動作になって、他のオブジェクトから押せなくなる
    • Sphere ColliderのIs Triggerにチェックする。これで、プレイヤーの移動を妨げずに触れるようになる
  • CoinPatrolプレハブを選択して、BombPatrolと同じ項目を確認する
  • Crateプレハブを、ダブルクリックして開く
  • Inspectorウィンドウで、次の項目を確認する
    • LayerをWallにする。これで、プレイヤーと接触対象になる
    • RigidbodyのIs Kinematicをチェックする
    • Hierarchyウィンドウで、子オブジェクトのCubeを選択する
    • LayerをWallにする
    • Box ColliderのIs Triggerのチェックを外す。これで、プレイヤーに衝突するようになる
  • 設定を保存する

以上で、オブジェクトの設定は完了です。

ここで設定した爆弾とコインは、コライダーをIs Triggerにしたので、跳ね回る爆弾やコインと接触しません。接触させたい場合は、跳ね回るオブジェクトの設定を参考に、設定してください。

ウェイポイントの場所をギズモで表示

これは、すでにひな型として実装済みです。Projectウィンドウで、Assets/Yoketoru/Scenes/Stagesフォルダーを開いて、Stage2を、Hierarchyウィンドウにドラッグ&ドロップして開きます。

Stage2シーンにあるCrate (1)などをクリックして選択してください。Scenesビューを見ると、右の木箱の場所と、少し下に、赤い丸が表示されます(図6.3)。これが、ウェイポイントの場所を示すギズモです。

ウェイポイントのギズモ

図6.3: ウェイポイントのギズモ

他のCrateや、ItemPatrolを選択すると、それぞれのウェイポイントが確認できます。また、Inspectorウィンドウで、Way Pointsを変更すると、ギズモの位置が変わります。

OnDrawGizmosSelectedメソッドが、このギズモを描画するコードです。便利なので、ご活用ください。

ゲームの開始と停止の実装

ゲームの開始と停止の処理を検討します。ルートの巡回は、現在の座標から、目的のウェイポイントへ移動します。FixedUpdateごとに、MovePositionで移動させるので、その処理をしなければ移動しません。

そこで、bool型の変数isStartedを定義します。ゲームが開始したら、trueにして、移動を開始するようにします。FixedUpdateの最初で、isStartedを確認して、移動するか判断します。ゲームが停止したら、isStartedをfalseにします。

移動中は、Debug.Logで何か表示するようにします。移動の実装は、手間がかかりそうなので、切り分けて開発する工夫をします。

  • Projectウィンドウで、Assets/Yoketoru/Scripts/Game/AutoMoverフォルダーを開く
  • 開いたフォルダーにあるWayPointMoverスクリプトをダブルクリックして、開く
  • インスタンス変数として、bool型のisStartedを定義する。初期値にfalseを代入しておくと安全
  • OnGameStartedメソッドのDebug.Logを消して、isStartedをtrueにする処理を書く
  • OnGameStoppedメソッドのDebug.Logを消して、isStartedをfalseにする処理を書く
  • FixedUpdateメソッドの最初に、isStartedがfalseなら、returnする処理を書く
  • 次の行に、Debug.Logで、「移動」と出力する

できたら、Playして動作を確認してください。カウントダウンが終わってから「移動」が表示されて、ゲームオーバーやクリアしたら、ログ表示が停止すれば成功です。

次のウェイポイントへの移動を実装

移動の開始と停止ができたら、動きを実装します。これも、段階を踏んで進めましょう。

  • FixedUpdateメソッドの「移動ベクトルを求める」の下に、Vector3型の変数toTargetを定義して、次のウェイポイントの座標から、現在の座標を引く。次のウェイポイントは、wayPoints[nextIndex]で得られる。現在の座標は、transform.positionで得られる
  • 「目的地までの残りの距離を求める」の下に、float型の変数distanceを定義して、toTargetの長さを代入する。toTargetの長さは、toTarget.magnitudeで求められる
  • 「1回分の移動距離を求める」の下に、float型の変数stepを定義して、speed * Time.fixedDeltaTimeを代入する。1秒あたりの移動距離であるspeedに、経過秒数Time.fixedDeltaTiimeをかけると、1回分の移動距離が求まる

到着の確認と、目的地の切り替えは、次のステップで実装します。今回は、このまま移動させます。

  • 「到着しないなら、1回分の距離を移動」の下に、Vector3型の変数moveを定義して、stepをtoTargetの単位ベクトルにかけた値を代入する。toTargetの単位ベクトルは、toTarget.normalizedで求められる
  • 最後の行で、rb.MovePositionメソッドを呼び出す。引数には、rb.positionに、moveを足した値を渡す

Playして、カウントダウンが終わると、木箱や銀のコインが動きます。最初の目的地に到着して、停止したら成功です。

目指すウェイポイントの更新を実装

端に達したら、PingPongで戻す処理の実装

端に達したら、Loopさせる処理の実装

決まったルートの巡回のまとめ

ルートの巡回は、やや難しい処理でした。このような処理は、いきなりすべてを実装しようとせずに、分割するのが開発のコツです。Debug.Logを活用すると、実際に処理を実装していなくても、プログラムが正しく動作していることが確認できます。

6.5 キャラ実装のまとめ