今回やること
- プロジェクトの作成
- プレイヤーの作成
- ブロック(障害物)の作成
- ブロック管理の作成
- スコアの実装 ←③
- ゲームオーバーの実装 ←③
- リトライの実装 ←③
最後にゲーム的な要素である「スコア」の実装と、ゲームオーバー表示、リトライを実装します。
ページのリンク
- Flappy Bird を作るチュートリアル (1/3)
- Flappy Bird を作るチュートリアル (2/3)
- Flappy Bird を作るチュートリアル (3/3) ←今のページ
スコアの実装
ゲーム管理オブジェクトの作成
スコアなどゲーム全体を管理するオブジェクトを作成します。
最初にダウンロードした素材フォルダから「nasu.png」を Projectビューにドラッグ&ドロップします。
続けて、作成した「nasu」スプライトを、Hierarchyビューにドラッグ&ドロップします。
「nasu」ゲームオブジェクトが作られるので、名前を「GameMgr」に変更します。
nasuオブジェクトを選択した状態で、Windows環境であれば「F2」、MacOSX環境であれば「Enter」で変更できます。
GameMgrオブジェクトは画面中央にあると邪魔なので、Sceneビューでドラッグ&ドロップして画面端に移動させておきましょう。
スクリプトでスコア表示をする
スクリプトでスコア表示の実装をします。
まずはスクリプトコンポーネントを追加します。GameMgrオブジェクトを選択し、Inspectorビューから、Add Component > New Script を選び、スクリプト名は「GameMgr」とします。
スクリプトが追加できたら、GameMgr.cs を開いて以下のように記述します。
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class GameMgr : MonoBehaviour {
// ①スコア
int _score = 0;
void Start() {
}
void Update() {
}
private void FixedUpdate() {
// ②スコア上昇
_score += 1;
}
private void OnGUI() {
// ③スコアを描画
_DrawScore();
}
// ④スコアの描画
void _DrawScore() {
// 文字を大きくする
GUI.skin.label.fontSize = 32;
// 左揃え
GUI.skin.label.alignment = TextAnchor.MiddleLeft;
Rect position = new Rect(8, 8, 400, 100);
GUI.Label(position, string.Format("score:{0}", _score));
}
}
スコア用の変数を追加して、時間経過でスコアを上昇させ、その値を表示します。
①ではスコア用の変数を追加しています。
// ①スコア
int _score = 0;
②でスコア加算用に FixedUpdate()
を追加してその中でスコアを加算しています。
private void FixedUpdate() {
// ②スコア上昇
_score += 1;
}
FixedUpdate()
を使用するのは、決まった間隔で呼び出しが行われるためとなります。
③で OnGUI()
を定義しています。スコアなどのGUIの描画はこの関数で行います。
private void OnGUI() {
// ③スコアを描画
_DrawScore();
}
本来であれば uGUI を使うべきですが、テスト・実験用に作る場合、OnGUI()
を使った方が素早く作れるので、今回は OnGUI()
を使います。
④が実際の描画処理です。
// ④スコアの描画
void _DrawScore() {
// 文字を大きくする
GUI.skin.label.fontSize = 32;
// 左揃え
GUI.skin.label.alignment = TextAnchor.MiddleLeft;
Rect position = new Rect(8, 8, 400, 100);
GUI.Label(position, string.Format("score:{0}", _score));
}
初期状態だとフォントが小さいので GUI.skin.label.fontSize = 32
で大きくします。
GUI.Label()
で文字を描画するのですが、第一引数には描画領域として Rect
で左上の位置と幅・高さを指定します。
数値を文字として描画する場合、string.Format()
を使うと自由な書式で描画できます。
では実行して動作を確認します。
時間経過によりスコアが上昇していくのがわかります。
ゲームオーバーの実装
プレイヤーがブロックに当たったらゲームオーバーとします。
そしてゲームオーバーになったらスコアを増加しないようにします。
①プレイヤーとブロックの衝突、②プレイヤー消滅、この2つはすでに実装しているので、PlayerからGameMgrにゲームオーバーになったことを通知するようにします(②から③の部分)。
GameMgrスクリプトを以下のように修正します。(修正部分は7箇所)
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class GameMgr : MonoBehaviour {
// ①状態定数
enum State {
Main, // メインゲーム
GameOver, // ゲームオーバー
}
// スコア
int _score = 0;
// ②状態
State _state = State.Main;
// ③ゲームオーバーの開始
public void StartGameOver() {
_state = State.GameOver;
}
void Start() {
}
void Update() {
}
private void FixedUpdate() {
if(_state == State.Main) {
// ④メインゲーム中のみスコア上昇
_score += 1;
}
}
private void OnGUI() {
// スコアを描画
_DrawScore();
// ⑤画面の中心座標を計算する
float CenterX = Screen.width / 2;
float CenterY = Screen.height / 2;
if(_state == State.GameOver) {
// ⑥ゲームオーバーの描画
_DrawGameOver(CenterX, CenterY);
}
}
// ⑦ゲームオーバーの描画
void _DrawGameOver(float CenterX, float CenterY) {
// 中央揃え
GUI.skin.label.alignment = TextAnchor.MiddleCenter;
float w = 400;
float h = 100;
Rect position = new Rect(CenterX - w / 2, CenterY - h / 2, w, h);
GUI.Label(position, "GAME OVER");
}
// スコアの描画
void _DrawScore() {
// 文字を大きくする
GUI.skin.label.fontSize = 32;
// 左揃え
GUI.skin.label.alignment = TextAnchor.MiddleLeft;
Rect position = new Rect(8, 8, 400, 100);
GUI.Label(position, string.Format("score:{0}", _score));
}
}
①で「メインゲーム中」「ゲームオーバー」を判定するための enum定数を定義しています。
// ①状態定数
enum State {
Main, // メインゲーム
GameOver, // ゲームオーバー
}
②では、状態を格納する変数を定義しています。
// ②状態
State _state = State.Main;
③では外部からゲームオーバーに切り替えるための関数 StartGameOver
を定義しています。
// ③ゲームオーバーの開始
public void StartGameOver() {
_state = State.GameOver;
}
④では FixedUpdate
関数でのスコア上昇の判定を修正しました。状態変数 _state
がメインゲーム中だったときだけスコア上昇するようにしています。
private void FixedUpdate() {
if(_state == State.Main) {
// ④メインゲーム中のみスコア上昇
_score += 1;
}
}
⑤・⑥は GUI表示である OnGUI()
を修正しています。
private void OnGUI() {
// スコアを描画
_DrawScore();
// ⑤画面の中心座標を計算する
float CenterX = Screen.width / 2;
float CenterY = Screen.height / 2;
if(_state == State.GameOver) {
// ⑥ゲームオーバーの描画
_DrawGameOver(CenterX, CenterY);
}
}
Screen.width
/ Screen.height
で幅と高さを取得し、その半分の値を中心として文字の表示をする関数 _DrawGameOver
を呼び出しています。
実際のゲームオーバーの描画を行なっているのが⑦の _DrawGameOver()
です。
// ⑦ゲームオーバーの描画
void _DrawGameOver(float CenterX, float CenterY) {
// 中央揃え
GUI.skin.label.alignment = TextAnchor.MiddleCenter;
float w = 400;
float h = 100;
Rect position = new Rect(CenterX - w / 2, CenterY - h / 2, w, h);
GUI.Label(position, "GAME OVER");
}
少し計算がややこしいので図で説明します。
CenterX
/ CenterY
は画面の中心です。文字を描画するには文字の左上座標と描画矩形(Rect)の幅(w)と高さ(h)が必要となります。左上の座標を求めるには、幅を2で割った値、高さを2で割った値を中央の座標から引き算します。
長くなりましたが、これで GameMgrスクリプトの準備はできました。
続けて、Playerスクリプトを修正します。
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
// ■プレイヤー
public class Player : MonoBehaviour {
// スプライト番号定義
const int SPR_FALL = 0; // 落下中
const int SPR_JUMP = 1; // ジャンプ中
[SerializeField]
float JUMP_VELOCITY = 1000; // ジャンプ力の定義
public Sprite[] SPR_LIST; // アニメーション用スプライトの保持
public GameObject gameMgr; // ①ゲーム管理
Rigidbody2D _rigidbody; // 物理挙動コンポーネント保持用
SpriteRenderer _renderer; // スプライト描画
GameMgr _gameMgr; // ②ゲーム管理スクリプト
// 開始処理
void Start() {
// 物理挙動コンポーネントを取得
_rigidbody = GetComponent<Rigidbody2D>();
// スプライト描画コンポーネントを取得
_renderer = GetComponent<SpriteRenderer>();
// ③ゲーム管理スクリプトを取得
_gameMgr = gameMgr.GetComponent<GameMgr>();
}
・
・
・
// 衝突判定
private void OnTriggerEnter2D(Collider2D collision) {
// 衝突したので消滅
Destroy(gameObject);
// ④ゲームオーバーを通知
_gameMgr.StartGameOver();
}
}
①でGameMgrオブジェクトを格納する変数を定義します。
②でGameMgrスクリプトを格納する変数を定義します。
③は Start()
でGameMgrオブジェクトからスクリプトを取り出して、_gameMgr
に格納しています。
④でブロックにぶつかって消滅する時に _gameMgr.StartGameOver()
を呼び出すことでゲームオーバーを通知します。
では、Unityエディタに戻って、Player
オブジェクトを選択し、Inspectorビューの Spriteの Game Mgr
に GameMgrオブジェクトを指定します。
では実行して、ブロックに衝突すると画面中央に「GAME OVER」表示がされ、スコアのカウントアップが停止しているのを確認します。
リトライの実装
最後にリトライ処理を実装して終わりとします。
ゲームオーバー時に「Retry」ボタンを押すと、ゲームに再挑戦できるようにします。
シーンの登録
リトライ処理は、シーンを作り直す方法で実装します。
Unityエディタのメニューから File > Build Settings... を選びます。
するとビルド設定画面が表示されるので、「Add Open Scenes」をクリックして、「SampleScene」をビルド対象に設定します。
追加したら、×ボタンを押して設定画面を閉じます。
なお、「SampleScene」とは今までに編集をしていたメインゲームとなるシーンです。
ファイルは Projectビューの Assets > Scenes に存在していて、通常はわかりやすい名称(例えば「MainScene」など)にリネームしておくべきですが、今回は "SampleScene" のままにしておきます。
リトライボタンの実装
リトライボタンは GameMgrスクリプトで実装します。
GameMgrスクリプトを開いて以下のよう修正します。
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
// ①シーンの読み直しに必要
using UnityEngine.SceneManagement;
public class GameMgr : MonoBehaviour {
// 状態定数
enum State {
Main, // メインゲーム
GameOver, // ゲームオーバー
}
// スコア
int _score = 0;
// 状態
State _state = State.Main;
// ゲームオーバーの開始
public void StartGameOver() {
_state = State.GameOver;
}
void Start() {
}
void Update() {
}
private void FixedUpdate() {
if(_state == State.Main) {
// メインゲーム中のみスコア上昇
_score += 1;
}
}
private void OnGUI() {
// スコアを描画
_DrawScore();
// 画面の中心座標を計算する
float CenterX = Screen.width / 2;
float CenterY = Screen.height / 2;
if(_state == State.GameOver) {
// ゲームオーバーの描画
_DrawGameOver(CenterX, CenterY);
// ②リトライボタンの描画
if(_DrawRetryButton(CenterX, CenterY)) {
// ③クリックしたらやり直しする
SceneManager.LoadScene("SampleScene");
}
}
}
// ④リトライボタンの描画
bool _DrawRetryButton(float CenterX, float CenterY) {
float ofsY = 40;
float w = 100;
float h = 64;
Rect rect = new Rect(CenterX - w / 2, CenterY + ofsY, w, h);
if (GUI.Button(rect, "RETRY")) {
// ボタンを押した
return true;
}
return false;
}
// ゲームオーバーの描画
void _DrawGameOver(float CenterX, float CenterY) {
// 中央揃え
GUI.skin.label.alignment = TextAnchor.MiddleCenter;
float w = 400;
float h = 100;
Rect position = new Rect(CenterX - w / 2, CenterY - h / 2, w, h);
GUI.Label(position, "GAME OVER");
}
// スコアの描画
void _DrawScore() {
// 文字を大きくする
GUI.skin.label.fontSize = 32;
// 左揃え
GUI.skin.label.alignment = TextAnchor.MiddleLeft;
Rect position = new Rect(8, 8, 400, 100);
GUI.Label(position, string.Format("score:{0}", _score));
}
}
修正箇所は、
-
using
の追加 -
OnGUI()
の修正 -
_DrawRetryButton()
の追加
の3つです。
まずファイルの先頭で
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
// ①シーンの読み直しに必要
using UnityEngine.SceneManagement;
というように using UnityEngine.SceneManagement;
を追加し、シーン管理である SceneManager を使えるようにします。
次に、OnGUI()
の修正です。
private void OnGUI() {
// スコアを描画
_DrawScore();
// 画面の中心座標を計算する
float CenterX = Screen.width / 2;
float CenterY = Screen.height / 2;
if(_state == State.GameOver) {
// ゲームオーバーの描画
_DrawGameOver(CenterX, CenterY);
// ②リトライボタンの描画
if(_DrawRetryButton(CenterX, CenterY)) {
// ③クリックしたらやり直しする
SceneManager.LoadScene("SampleScene");
}
}
}
②で _DrawRetryButton()
を呼び出し、戻り値が true
なら③のやり直し処理を行います。 SceneManager.LoadScene()
には「シーン名」を文字列で渡します。
④でリトライボタンの描画(とクリック)を行う _DrawRetryButton()
を実装しています。
// ④リトライボタンの描画
bool _DrawRetryButton(float CenterX, float CenterY) {
float ofsY = 40;
float w = 100;
float h = 64;
Rect rect = new Rect(CenterX - w / 2, CenterY + ofsY, w, h);
if (GUI.Button(rect, "RETRY")) {
// ボタンを押した
return true;
}
return false;
}
ofsY
とは 「Offset Y座標」の略で、Y座標を少しずらす値としてよく使われる略語です。
GUI.Button()
はボタンの描画とクリックしたかどうかを判定する関数です。true
を返した場合はボタンをクリックしたと判定されます。
ゲームオーバー時に「RETRY」ボタンを押すことでゲームを遊び直すことができるようになりました。
BlockMgr / GameMgr のスプライト表示を消す
BlockMgr(xbox) / GameMgr(nasu) がスプライト表示されていますが、俺は不要なので消しておきます。
Inspectorビューから Sprite Renderer
のチェックを外すことで表示されなくなります。
再び表示したい場合は、ここにチェックを入れ直すと表示されるようになります。
最後に
まだゲームとして物足りない部分はありますが、これでひとまずゲームは完成です。
- ブロックの出現パターンをランダムでなく決まった形にする
- 接触すると得点になるアイテム
- 一定距離進むとゲームクリア
など色々な要素を加えて、より面白いゲームに作り替えていくと、ゲームプログラムの勉強、ゲームデザインの練習になって良いかもしれません。
なお、今回作成したプロジェクトは以下の場所にアップロードしています。
http://syun777.sakura.ne.jp/tmp/Unity/FlappyBird/TestFlappyBird.zip