【Unity実践】#12 ゲームクリア時の演出(プレイヤー関連)【ランゲーム】
今回の内容
今回からはゲームクリア時の実装を進めていきます。
まずはプレイヤー関連の演出を実装します。
やや作業量が多いですが、頑張って進めましょう。
※初めての方はこちらから
⇒ 【第1回記事】導入とサンプルの紹介
ゲームの状態を管理する
クリア時だけでなく、今後ゲームオーバー時やプレイ中などのゲーム状態を管理する必要がでてきます。
そこで1つスクリプトを作成し、ゲーム状態を管理するための Enum を作成します。
新規スクリプトを作成し、名前を GameManagerScript とします。
以下を記述しましょう。
using System.Collections; using System.Collections.Generic; using UnityEngine; public class GameManagerScript : MonoBehaviour { public enum GAME_STATUS { Play, Clear, Pause, GameOver }; public static GAME_STATUS status; void Start() { // ステータスをPlayに status = GAME_STATUS.Play; } void Update() { } }
Play(プレイ中)、Clear(クリア時)、Pause(一時停止)、GameOver(ゲームオーバー時)の4つの状態を用意しました。
ゲーム開始時に Play に設定しておきます。
シーンに空のオブジェクトを作成し、名前を GameManager とします。
GameManager に GameManagerScript をアタッチしておきましょう。
ここでは GameManagerScript はこれ以上触りませんが、今後変更を加えて行くことにはなるので、
存在は覚えておきましょう。
(関連記事)
Enumを使ってゲーム中の状態や条件をわかりやすく管理しよう
クリア時のプレイヤーのアニメーション設定
クリアした際のプレイヤーのアニメーションを設定します。
Player を選択して Animatorビューを開き、Trigger型のパラメータ Clear を追加します。
さらに、Projectビューから Unitychan > Animations > unitychan_WIN00 > WIN00 を追加します。
AnyState から WIN00 へ遷移を作成してください。
アニメーションの準備はこれで完了です。
(関連記事)
アニメーションの遷移を検証①【遷移条件】
クリア時のプレイヤーの演出
クリア時(クリア地点到達時)にプレイヤーには以下の行動をさせます。
・ゴール地点(最終地点)に向かって移動
・ゴール地点に到達後、180度回転してアニメ再生
なお、ここでいうクリア地点、ゴール地点は以下のイメージです。
クリア地点に到達したら、最終地点であるゴール地点へと強制的に移動させて演出を実行します。
実装の第1段階として、クリア地点に触れた時に呼び出されるメソッドを作成し、
そのタイミングで目的地としてゴール地点を設定し、ゲーム状態を Clear に変更します。
PlayerScript を以下に変更してください。
using System.Collections; using System.Collections.Generic; using UnityEngine; public class PlayerScript : MonoBehaviour { Animator animator; public bool isRunning; public float sensitivity = 1f; const float LOAD_WIDTH = 6f; const float MOVE_MAX = 2.5f; Vector3 previousPos, currentPos; Vector3 dest; // 次の目的地。クリア時に使用 void Start() { animator = GetComponent<Animator>(); } void Update() { // スワイプによる移動処理 if (Input.GetMouseButtonDown(0)) { previousPos = Input.mousePosition; } if (Input.GetMouseButton(0)) { // スワイプによる移動距離を取得 currentPos = Input.mousePosition; float diffDistance = (currentPos.x - previousPos.x) / Screen.width * LOAD_WIDTH; diffDistance *= sensitivity; // 次のローカルx座標を設定 ※道の外にでないように float newX = Mathf.Clamp(transform.localPosition.x + diffDistance, -MOVE_MAX, MOVE_MAX); transform.localPosition = new Vector3(newX, 0, 0); // タップ位置を更新 previousPos = currentPos; } // isRunning = true; ※削除してください animator.SetBool("IsRunning", isRunning); } public void Clear(Vector3 pos) { GameManagerScript.status = GameManagerScript.GAME_STATUS.Clear; dest = pos; } }
クリア後の目的地を設定するためのプロパティ dest と、クリア時に呼び出される Clearメソッドを追加しました。
※あえて「ゴール地点」では無く「目的地」の意味でプロパティを作成しているのは、
クリア時以外の強制的な移動のケースでも利用できることを想定しています。
とはいえ、この連載内ではクリア時以外は触れないので、読者様側での改造用となります。
続けて、ゲーム状態が Clear になったときの処理を Update 内に記述します。
using System.Collections; using System.Collections.Generic; using UnityEngine; public class PlayerScript : MonoBehaviour { Animator animator; public bool isRunning; public float sensitivity = 1f; const float LOAD_WIDTH = 6f; const float MOVE_MAX = 2.5f; Vector3 previousPos, currentPos; Vector3 dest; // 次の目的地。クリア時に使用 float speed = 6f; void Start() { animator = GetComponent<Animator>(); } void Update() { // クリア時の処理 if (GameManagerScript.status == GameManagerScript.GAME_STATUS.Clear) { // 目的地の方向を向く transform.LookAt(dest); // 目的地の方向に移動させる Vector3 dir = (dest - transform.position).normalized; transform.position += dir * speed * Time.deltaTime; // 目的地に十分近づいたら、最終演出 if ((dest - transform.position).magnitude < 0.5f) { transform.position = dest; transform.rotation = Quaternion.Euler(0, 180, 0); animator.SetBool("IsRunning", false); animator.SetTrigger("Clear"); enabled = false; } return; } // スワイプによる移動処理 if (Input.GetMouseButtonDown(0)) { previousPos = Input.mousePosition; } if (Input.GetMouseButton(0)) { // スワイプによる移動距離を取得 currentPos = Input.mousePosition; float diffDistance = (currentPos.x - previousPos.x) / Screen.width * LOAD_WIDTH; diffDistance *= sensitivity; // 次のローカルx座標を設定 ※道の外にでないように float newX = Mathf.Clamp(transform.localPosition.x + diffDistance, -MOVE_MAX, MOVE_MAX); transform.localPosition = new Vector3(newX, 0, 0); // タップ位置を更新 previousPos = currentPos; } // isRunning = true; ※削除してください animator.SetBool("IsRunning", isRunning); } public void Clear(Vector3 pos) { GameManagerScript.status = GameManagerScript.GAME_STATUS.Clear; dest = pos; } }
一旦PlayerScript側の準備はこれでOKです。
ゴール床にトリガーを設定
Goalオブジェクトにプレイヤーが触れたら、
先ほど作成した PlayerScript の Clearメソッドを発火するように Goalオブジェクトを変更します。
Goalオブジェクトに BoxCollider を追加して、以下の設定とします。
新規スクリプトを作成して名前を GoalScript とし、以下を記述してください。
using System.Collections; using System.Collections.Generic; using UnityEngine; public class GoalScript : MonoBehaviour { Vector3 centerPos; void Start() { centerPos = new Vector3(transform.position.x, 0, transform.position.z); } private void OnTriggerEnter(Collider other) { if (other.CompareTag("Player")) { other.GetComponent<PlayerScript>().Clear(centerPos); } } }
これを Goalオブジェクトにアタッチすれば、Goalオブジェクト関連の準備は完了です。
(関連記事)
当たり判定の取り方②(ぶつからない編)
コライダーの設定
ここまでのスクリプトを眺めてみて下さい。
なんとなくPlayer が Goal に触れれば クリア関連の処理が実行されそうですが、
実際に実行してみても Goal床の手前で止まって何も起こらないはずです。
実はここまで Player にコライダーを設定していなかったため、
Goalオブジェクトに触れた際の Triggerイベントが発火されていないのが原因です。
そこで Playerオブジェクトに CapsuleCollider を追加して、下記の設定とします。
今度こそ‥と思って実行してみても、実はまだ上手く動きません。
これは Player が MovementBase の子要素となっているのが原因で、
以下の記事がちょうど検証をしてくれています。
【Unity】 子オブジェクトの衝突が検知される条件(外部リンク)
そこで MovementBase に Rigidbody を追加します。
物理演算は不要なので、Is Kinematic にチェックを入れておきましょう。
ヘルプUIの表示設定
現状、クリア演出後もヘルプUIが表示されてしまっているので、
ヘルプUIはゲーム状態が Play の場合だけ表示するように変更します。
MovementBaseScript を以下に変更してください。
using PathCreation; using System.Collections; using System.Collections.Generic; using UnityEngine; public class MovementBaseScript : MonoBehaviour { [SerializeField] PathCreator pathCreator; [SerializeField] PlayerScript player; [SerializeField] GameObject helpUI; float speed = 6f; Vector3 endPos; float moveDistance; void Start() { endPos = pathCreator.path.GetPoint(pathCreator.path.NumPoints - 1); } void Update() { // プレイ中以外は無効にする if (GameManagerScript.status != GameManagerScript.GAME_STATUS.Play) { helpUI.SetActive(false); return; } // タップ中は走る if (Input.GetMouseButton(0)) { moveDistance += speed * Time.deltaTime; transform.position = pathCreator.path.GetPointAtDistance(moveDistance, EndOfPathInstruction.Stop); transform.rotation = pathCreator.path.GetRotationAtDistance(moveDistance, EndOfPathInstruction.Stop); player.isRunning = true; helpUI.SetActive(false); } else if (Input.GetMouseButtonUp(0)) { player.isRunning = false; helpUI.SetActive(true); } } }
クリア後にヘルプUIが非表示となっていればOKです。
(関連記事)
プログラムからGameObjectやComponentの有効無効を切り替える
speedの共通化
最後に、今回の記事で PlayerScript に speed を追加しましたが、
MovementBaseScript にも speed があり、管理が少し煩雑になってしまっています。
これを共通化したいと思います。
今回は PlayerScript 側で speed を管理して、MovementBaseScript では PlayerScript側から読み込む実装とします。
まずは PlayerScript で、speed を public に変更します。
public float speed = 6f;
次に MovementBaseScript を下記の通り変更します。
using PathCreation; using System.Collections; using System.Collections.Generic; using UnityEngine; public class MovementBaseScript : MonoBehaviour { [SerializeField] PathCreator pathCreator; [SerializeField] PlayerScript player; [SerializeField] GameObject helpUI; Vector3 endPos; float moveDistance; void Start() { endPos = pathCreator.path.GetPoint(pathCreator.path.NumPoints - 1); } void Update() { // プレイ中以外は無効にする if (GameManagerScript.status != GameManagerScript.GAME_STATUS.Play) { helpUI.SetActive(false); return; } // タップ中は走る if (Input.GetMouseButton(0)) { moveDistance += player.speed * Time.deltaTime; transform.position = pathCreator.path.GetPointAtDistance(moveDistance, EndOfPathInstruction.Stop); transform.rotation = pathCreator.path.GetRotationAtDistance(moveDistance, EndOfPathInstruction.Stop); player.isRunning = true; helpUI.SetActive(false); } else if (Input.GetMouseButtonUp(0)) { player.isRunning = false; helpUI.SetActive(true); } } }
17行目の上にあった、speed の宣言は削除しています。
38行目の speed を取得する所を、PlayerScript から取得する形に変更しています。
念のため動作確認して、変更前と同様に動いていれば成功です。
(関連記事)
Time.deltaTimeの基本的な使い方
おわりに
作業箇所が多かったですが、一旦これでクリア時のプレイヤー関連の演出が完成です。
次回は引き続きクリア関連で、クリア時の紙吹雪の演出を作成したいと思います!
関連リンク ➡ 「初心者のための」Unityゲーム制作 目次
© Unity Technologies Japan/UCL