ミニゲームを作ってUnityを学ぶ![ひつじコレクション編]
###第9回目: ゲームの開始と終了
前回はサクサクっと画面内にミニマップを表示する機能を実装しました。
次はひと通り遊べるようになったひつじコレクションについて、ゲームとしての開始と終わりの概念を実装します。
今回の実装はゲームとしての見栄えに大きな変化はありませんが、オンラインランキングを実装する際など後々重要な仕組みになります。
#シーンを制御する
一般的なUnityのプロジェクトは例えばタイトル画面のシーン、実際にプレイするシーン、結果表示のシーンなど複数のシーンから構成されていて、それらを適切なタイミングで切り替えることでゲームとしての開始や終了を表現しています。
一方で今回のひつじコレクションのように1つのシーンで成り立ってしまう小規模なゲームの場合は、この開始と終了をシーン内の状態を制御することで表現します。
- ゼロポジションに「SceneMain」という名前の空オブジェクトを配置
- 同じく「SceneMain」という名前のスクリプトを作成し、それをSceneMainオブジェクトにアタッチ
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class SceneMain : MonoBehaviour
{
private GameController mGame;
void Awake()
{
mGame = GameController.Instance;
mGame.Init();
}
//--------
// 状態 //
//---------------------------------------------------------------------------------
private enum STATE
{
LOAD_STAGE,
PLAY,
GAME_OVER
}
private STATE mState = STATE.LOAD_STAGE;
//--------
// 更新 //
//---------------------------------------------------------------------------------
void Update()
{
switch (mState)
{
// ステージの生成
case STATE.LOAD_STAGE:
mGame.StageManager.LoadStage(1);
mState = STATE.PLAY;
break;
// プレイ中
case STATE.PLAY:
if (mGame.IsGameOver)
{
mState = STATE.GAME_OVER;
}
break;
}
}
}
プロジェクト開始時にステージをファイルから生成してPLAY状態へ遷移。
GameControllerのプロパティ**「IsGameOver」**がtrueになったタイミングでGAME_OVER状態へ遷移。
このように適切なタイミングでシーンの状態「mState」を変更することによってUpdate()の処理を切り替えています。
また併せてGameControllerとStageManagerについて、SceneMainに対応するための修正を行います。
- SceneMain#Awake()でGameControllerの初期化を行うため、StageManagerの該当部分を修正
void Start()
{
GameController game = GameController.Instance;
game.Init();
game.StageManager = this;
mConstructor = GetComponent<StageConstructor>();
LoadStage(1);
StartCoroutine("PopupFollower");
}
↓以下のように修正
void Start()
{
GameController.Instance.StageManager = this;
mConstructor = GetComponent<StageConstructor>();
StartCoroutine("PopupFollower");
}
- GameControllerを修正
public void Init()
{
Application.targetFrameRate = 30;
Scorer = new Scorer();
}
//----------
// フラグ //
//---------------------------------------------------------------------------------
public bool IsGameOver { get; set; }
GameControllerにはゲームオーバーに関するフラグを追加し、Init()ですっかり忘れていたフレームレートの設定を行いました。
これ以降、各スクリプトのUpdate()は1秒間に30回の頻度で呼び出されるようになります。
Enterキーでゲームを開始する
今まではプロジェクトを実行した瞬間から操作可能な、いわゆるゲームが始まっている状態でしたが、この開始方法をプレイヤーが自分のタイミングで決定できるように修正していきます。
###Playerオブジェクト(ユニティちゃん)の制御
まずはユニティちゃんについて、プロジェクト開始時点ではプレイヤーの操作入力を受け付けない状態にします。
- PlayerActionを修正
void Start()
{
mTrans = GetComponent<Transform>();
mRigid = GetComponent<Rigidbody>();
mAnim = GetComponent<PlayerAnimation>();
修正 State = STATE.DEFAULT;
}
public void OnIdle()
{
State = STATE.IDLE;
}
- StageConstructorにメソッドを追加
public void OnActivePlayer()
{
Player.GetComponent<PlayerAction>().OnIdle();
}
初期状態のStateをIDLEからDEFAULTに変更し、新しくOnIdle()を追加しました。
これによってユニティちゃんはStageConstructor#OnActivePlayer()が実行されるまでプレイヤーの操作入力を受け付けない状態になります。
###チェイサーの制御
敵キャラクターについても同様に、プロジェクト開始時点ではAI行動を無効にしてその場に待機させます。
- ChaserModelを修正
public void Init(float speed, float incSpeed)
{
mSpeed = speed;
mIncSpeed = incSpeed;
mAi.ApplySpeed(mSpeed);
ここを削除 IsActive = true;
}
- StageConstructorにメソッドを追加
public void OnActiveChaser()
{
foreach (ChaserModel model in mChaserList)
{
model.IsActive = true;
}
}
public void OffActiveChaser()
{
foreach (ChaserModel model in mChaserList)
{
model.gameObject.SetActive(false);
}
}
敵キャラクターはStageConstructorのOnActiveChaser()が実行されるまでAIによる移動を行わずその場に待機します。
また同時に、全てのChaser~オブジェクトを非アクティブにするためのメソッド、OffActiveChaser()を追加しています。
###ポップアップの制御
さらにフォロワーのポップアップについて、こちらもプロジェクト開始時点ではポップアップさせるかどうかの判定を行わないようにします。
- StageManagerを修正
void Start()
{
GameController.Instance.StageManager = this;
mConstructor = GetComponent<StageConstructor>();
ここを削除 StartCoroutine("PopupFollower");
}
ゲーム開始時の初期化
上記のユニティちゃん、敵キャラクター、ポップアップそれぞれに対応するため、StageManagerにゲーム開始時の初期化メソッドを追加します。
- StageManagerにメソッドを追加
public void StartGame()
{
// プレイヤーとチェイサーの行動開始
mConstructor.OnActivePlayer();
mConstructor.OnActiveChaser();
// コルーチンを開始
StartCoroutine("PopupFollower");
}
Enterキーを受け付ける
最後に、SeneMainにEnterキーの入力を監視する仕組みを実装します。
- SceneMainを修正
private enum STATE
{
LOAD_STAGE,
追加 WAIT_ENTER_KEY,
PLAY,
GAME_OVER
}
void Update()
{
switch (mState)
{
// ステージの生成
case STATE.LOAD_STAGE:
mGame.StageManager.LoadStage(1);
修正 mState = STATE.WAIT_ENTER_KEY;
break;
// Enter入力でゲームを開始
追加 case STATE.WAIT_ENTER_KEY:
if (WaitEnter())
{
mGame.StageManager.StartGame();
mState = STATE.PLAY;
}
break;
// プレイ中
case STATE.PLAY:
if (mGame.IsGameOver)
{
mState = STATE.GAME_OVER;
}
break;
}
}
//--------
// 入力 //
//---------------------------------------------------------------------------------
private bool WaitEnter()
{
if (Input.GetKeyDown(KeyCode.Return)) return true;
return false;
}
以上でEnterキーによってゲームを開始する仕組みが完成しました。
プロジェクトを実行後、Enterキーを押すことでゲームが開始します。
#ゲームの終了処理を実装
先ほどStageManagerにゲーム開始時の初期化メソッドを追加したように、同じくStageManagerに終了処理に関するメソッドを追加します。
- StageManagerにメソッドを追加
public void EndGame()
{
// チェイサーを除去
mConstructor.OffActiveChaser();
// コルーチン終了
StopAllCoroutines();
// フラグを立てる
GameController.Instance.IsGameOver = true;
}
EndGame()ではゲームの終了に必要な処理を行うと同時に、フラグを立てることでシーンの状態をGAME_OVERに遷移させています。
###アニメーションイベントの設定
ゲームオーバーのタイミングはプレイヤーがチェイサーと接触してその場で回転。
その後のダウンアニメーションで地面に倒れ込んだ直後に設定します。
- PlayerActionにメソッドを追加
public void EndDownAnimation()
{
GameController.Instance.StageManager.EndGame();
}
今回のようにアニメーション再生時の特定タイミングでメソッドを呼び出す場合は第1回で解説したようにアニメーションイベントを利用します。
- UnityChan/SD_unitychan/Animations/SD_unitychan_motion_humanoidを選択
- Animationsタグ内、Clipsに表示されている項目からKneelDownのクリップを選択
- さらに下に進み、Events項目を開いて以下のようにイベントを設定
ユニティちゃんがその場に倒れ込んだタイミングでEndDownAnimation()が呼び出され、さらにStageManager.EndGame()が実行されます。
#リスタート機能を実装する
以上でゲームに開始と終了の概念を実装できました。
最後に、ゲームをはじめからやり直す機能を追加します。
- GameControllerにシーンを再読み込みするメソッドを追加
//-------------
// シーン管理 //
//---------------------------------------------------------------------------------
public void OnRestartButton()
{
SceneManager.LoadScene(SceneManager.GetActiveScene().name);
}
- SceneMainを修正してリスタートの入力を受け付ける
void Update()
{
CheckBackspaceInput();
switch (mState)
{
// ステージの生成
case STATE.LOAD_STAGE:
mGame.StageManager.LoadStage(1);
mState = STATE.WAIT_ENTER_KEY;
break;
// Enter入力でゲームを開始
case STATE.WAIT_ENTER_KEY:
if (WaitEnter())
{
mGame.StageManager.StartGame();
mState = STATE.PLAY;
}
break;
// プレイ中
case STATE.PLAY:
if (mGame.IsGameOver)
{
mState = STATE.GAME_OVER;
}
break;
}
}
private void CheckBackspaceInput()
{
if (Input.GetKeyDown(KeyCode.Backspace)) mGame.OnRestartButton();
}
SceneMainの現在のmStateに関係なく、BackSpaceキーの入力を感知したタイミングでシーンを再読み込みすることでゲームを開始前の状態へ戻します。
*ゲームをリスタートした際に画面が暗くなってしまう場合は以下の設定を行ってください。
- メニュー>Window>Lighting>Settingsを選択
- Auto Generateのチェックを外す
- Generate Lightingをクリック
GameControllerについての注意
シーンを再読み込みすると各オブジェクトやそれにアタッチされているスクリプトなどは破棄されて新しく生成されなおしますが、シングルトンかつシーン内に配置されていないクラスGameControllerに関しては生成されている唯一のインスタンスが破棄されず同じインスタンスをそのまま参照する形になります。
つまりゲームオーバーの際に一度trueになったIsGameOverフラグはシーンを再読み込みしてもtrueになったままの状態ですので、ゲームを完全に開始前の状態に戻すにはこのフラグを初期化する必要があります。
- GameControllerを修正
public void Init()
{
Application.targetFrameRate = 30;
追加 IsGameOver = false;
Scorer = new Scorer();
}
前回から見栄えは全く変わりありませんが、これでひつじコレクションに開始・終了の概念とやり直しの機能を実装することができました。
この作品はユニティちゃんライセンス条項の元に提供されています