このパートで行うこと
今回のパートでは、プレイヤーが扉に入ったら次のステージに切り替わるという流れを実装する。
また、プレイヤーのHP(Food)が0以下になることによって、ゲームオーバーにもさせる。
これでこのゲームも開始から終了までの一通りのゲームが流れることになる。
前回の記事はこちら。
⇒
パート⑥:アイテムを入手してHPを回復できるようにする@2Dローグライク公式tutorial解説【Unity2018】
※この記事は、unity公式チュートリアル「2D Roguelike tutorial」を解説した連載記事です。
この連載記事共通の事項(実施している環境等)は以下の記事を参照ください。
⇒ 表紙:2Dローグライク公式tutorial解説【Unity2018】
扉プレハブの設定
前回のパートのアイテムと同じように、扉に当たり判定をつけていこう。
タグの設定を「Exit」に、
Box Collider 2D
コンポーネントをアタッチして、忘れずにIsTrigger
にもチェックをいれよう。
ステージの切り替え
このゲームはプレイヤーが扉に入る(当たる)と、ステージが切り替える。
切り替わる際に、「Day 1」「Day 2」「Day 3」…
と切り替わるたびにステージのレベルが1つずつ上がっていく。
ステージレベルを表示する
最初に、ステージレベルを表示するための準備を整えよう。
前回のパートで登場したuGUIのコンポーネントを利用する。
まずは背景用の画面全体のべた塗り画像のゲームオブジェクトを作成する。
(ヒエラルキーの)「Canvas」ゲームオブジェクトを右クリック -> UI -> Image
Image
コンポーネントをもった「Image」というゲームオブジェクトが作成されるので「LevelImage」に名前を変えておこう。
Image
コンポーネントは、名前の通りUI上の画像を表示する機能をもっている。
Image#SourceImage
には、スプライト画像を指定することができるが、指定しない場合は、矩形の画像としてが表示される。
ただの四角の画像。
今回描画したいのは背景用の画面全体のべた塗り画像である。
前回のパートで説明した通り、RectTransform
は矩形範囲を指定できるものなので、
画面全体を矩形範囲として指定することもできる。
以下のように水平・垂直方向ともstretch 状態にしておく。
Altキーを押すと自然に画面全体に画像が伸びるのでやってみてほしい。
矢印が伸びたタイミングでAltキーを押している。
次は、現在のレベルを文字で表示しよう。これは前回のパートでやっているので思い出そう。
作成したゲームオブジェクトは、こちらもわかりやすいように「LevelText」と名前を変えておく。
また、先程作成した「LavelImage」と同時のタイミングで表示/非表示を切り替えるものとなるので、
「LavelImage」の子オブジェクトとして作成しよう。
このような感じになる。
インスペクターで「LavelImage」のText
コンポーネントの設定を以下の通りに設定しておこう。
設定項目 | 設定値 |
---|---|
Text |
Day 1 |
Font |
PressStart2P-Regular |
FontSize |
32 |
Alignment |
水平・垂直方向とも中央 |
Horizonal Overflow |
Overflow |
Vertical Overflow |
Overflow |
Color |
#FFFFFF(白) |
ステージの切り替え
次のステージに切り替えるには今まで使用しているシーンを再読み込みすることで行う。
まずはPlayer
スクリプトに機能を追加していこう。
protected override void Start(){ base.Start(); this.food = GameManager.instance.playerFoodPoints; //←追加 this.foodText.text = "Food:" + this.food; } private void OnDisable(){ GameManager.instance.playerFoodPoints = this.food; } private void OnTriggerEnter2D(Collider2D other){ if(other.tag == "Food"){ food += pointPerFood; other.gameObject.SetActive(false); this.foodText.text = "+" + pointPerFood + " Food:" + this.food; } else if(other.tag == "Soda"){ food += pointPerSoda; other.gameObject.SetActive(false); this.foodText.text = "+" + pointPerSoda + " Food:" + this.food; } else if(other.tag == "Exit") { //追加!! Invoke("Restart", this.restartLevelDelay); this.enabled = false; } } private void Restart(){ //再読み込み SceneManager.LoadScene(SceneManager.GetActiveScene().buildIndex, LoadSceneMode.Single); }
わかりやすいところから説明していく。
OnTriggerEnter2D()
に扉プレハブのif文を追加している。
OnTriggerEnter2D()
はコライダーと当たった時に呼ばれるメソッドだった。
扉とプレイヤーがぶつかったときにInvoke()
でrestartLevelDelay
秒待ったあとRestart()
を実行する。
Restart()
では現在実行しているシーンの再読み込みを行っている。
また、OnTriggerEnter2D()
では、Player
スクリプトを無効にしており、
そのタイミングでOnDisable()
が呼ばれる。
OnDisable()
では、現在のHP(food
)の値を次のステージに引き継ぎたいため、
シーンの再読み込みを行っても、値を保持しつづけられるGameManager#playerFoodPoints
に退避させている。
GameManager
がシーンの再読み込みを行っても、値を保持しつづけられるのは、
以前のパートでGameManager
をDontDestroyOnLoad
にして常に消えないオブジェクトとなっているためである。
⇒ パート②:静的な物体(床・外壁・出口)を配置する@2Dローグライク公式tutorial解説【Unity2018】
最後にシーンが読み込まれた後に呼ばれるStart()
でGameManager#playerFoodPoints
に一時退避させていた値を、
自身のfood
に代入している。
という流れである。
次にGameManager
にも変更を加える。
using System.Collections; using UnityEngine; using UnityEngine.UI; using UnityEngine.SceneManagement; public class GameManager : MonoBehaviour { public int playerFoodPoints = 100; //プレイヤーの初期HP private int level = 1; //ステージレベル private bool doingSetup; //trueならセットアップ中 private GameObject levelImage; private Text levelText; private float levelStartDelay = 2f; //その他のフィールドは変更なし void InitGame() { this.doingSetup = true; this.levelImage = GameObject.Find("LevelImage"); this.levelText = GameObject.Find("LevelText").GetComponent<Text>(); this.levelText.text = "Day " + level; this.levelImage.SetActive(true); Invoke("HideLevelImage", this.levelStartDelay); boardScript.SetupScene(); } private void HideLevelImage() { this.levelImage.SetActive(false); this.doingSetup = false; } [RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.AfterSceneLoad)] static public void CallbackInitialization() { SceneManager.sceneLoaded += OnSceneLoaded; } static private void OnSceneLoaded(Scene arg0, LoadSceneMode arg1) { instance.level++; //ステージレベルを上げる instance.InitGame(); } void Update() { if (playersTurn || wait || this.doingSetup) //doingSetupを追加。セットアップ中は処理しないように。 { return; } StartCoroutine(WaitTurn()); } //その他のメソッドは変更なし }
InitGame()
で作成した「LevelImage」「LevelText」ゲームオブジェクトを取得し、
「LevelText」の文字を現在の ”Day ” + ステージレベルに更新し、levelStartDelay
秒表示してから、
HideLevelImage()
でステージレベルの描画をやめて、ステージの描画に戻す。
CallbackInitialization()
についている、[RuntimeInitializeOnLoadMethod]
はゲーム起動時に1度だけ実行するようにするAttributeだ。
つまり、CallbackInitialization()
はゲーム起動時に一度だけ実行される。
CallbackInitialization()
で行っている、
SceneManager.sceneLoaded += OnSceneLoaded;
上記は、シーンが読み込まれた際に行う処理を設定している。
つまり、ステージが切り替わるたびにシーンの再読み込みをしているので、OnSceneLoaded()
はステージが切り替わるたびに呼ばれることになる。
OnSceneLoaded()
では、ステージレベルをひとつ上げ、InitGame()
を呼んでステージのセットアップを行う。
この状態でゲームを起動してみよう。
NullReferenceException: Object reference not set to an instance of an object Completed.GameManager.OnSceneLoaded (Scene arg0, LoadSceneMode arg1) (at Assets/_Complete-Game/Scripts/GameManager.cs:69) UnityEngine.SceneManagement.SceneManager.Internal_SceneLoaded (Scene scene, LoadSceneMode mode) (at C:/buildslave/unity/build/artifacts/generated/bindings_old/common/Core/SceneManagerBindings.gen.cs:245)
上記のエラーが出てしまう場合は、
完成版である「_Complete-Game」のスクリプトが動いてしまっている。
これは意図した動きではないため、
エラーが出ている箇所の「_Complete-Game」配下のGameManager#CallbackInitialization()
についている、
[RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.AfterSceneLoad)]
をコメントアウトして動かないようにしておこう。*1
//コメントアウトしておく。 //[RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.AfterSceneLoad)] static public void CallbackInitialization(){ //register the callback to be called everytime the scene is loaded SceneManager.sceneLoaded += OnSceneLoaded; }
ゲームが正常に動いただろうか。
- プレイヤーが扉に当たるとステージが切り替わること。
- ステージのレベルが上がっていること
- HP(Food)の値が引き継がれていること
を確認しよう。
ゲームオーバー
最後にゲームオーバーを実装しよう。
Player
にゲームオーバーになっているかチェックを行うメソッドCheckIfGameOver()
を追加し、
歩く都度呼ばれるAttemptMove()
でHPが減るので、その都度チェックを行うようにする。
protected override void AttemptMove<T>(int xDir, int yDir) { food --; foodText.text = "Food: " + food; base.AttemptMove<T>(xDir, yDir); CheckIfGameOver(); GameManager.instance.playersTurn = false; } private void CheckIfGameOver(){ if(this.food <= 0) { GameManager.instance.GameOver(); } }
GameManager
では、ゲームオーバーになったときの、ステージレベルをゲームの結果として表示する。
public void GameOver(){ this.levelText.text = "After " + this.level + " days, you starved"; this.levelImage.SetActive(true); this.enabled = false; }
これでゲームが開始から終了まで一通り流れができたことになる。
HP(Food)が0になるとゲームオーバーになる。
次回予告
今回のパートでゲームの流れが一通り完成した。
これで残る登場人物は1つとなった。そう、残るは 敵 のみである。
次回のパートで敵プレハブを作成し、プレイヤーを追うように移動したり、プレイヤーへ攻撃したりさせる。
⇒ 次パートはこちら。
*1:もちろん完成版を動かす場合は元に戻す必要がある