0から2Dアクションバトルゲームを作ろう!⑥UIを表示してみよう

0からアクションゲームを作ろう講座その6です。
uGUIによるユーザーインターフェースを作成し、数値データを画面上に表示させましょう!

この章でやること

uGUI(UnityでサポートされているUI機能)を用いてユーザーインターフェースを実現していきます。
といっても今回はデータの表示のみを行い、データの入力(ボタン入力)は次章で行います。

UI(ユーザーインターフェース)とはユーザーが「製品等と接するすべての部分」を差しますが、GUIとはその中で視覚的部分を指します。(UIについては後半で詳しく解説しています)

UIを適切に配置すればプレイヤーが遊びやすいゲームというものを実現する事が出来ます。
また、デザインに優れたUIはゲームの見栄えを良くします。

目標とする動作

今回は前章の状態からキャラクターの動作は変わりません。
GUIとして新しく画面に「プレイヤーの残り体力」「ボスの残り体力」を表示していきます。

プレイヤーの体力表示は初めは文字で実現し、その後ハートの画像での表現に改良します。
ボスの体力表示は文字のみです。ここは9章にて改良を行い、その時にゲージでの表示に変更します。

 

UIとは?

UI(ユーザーインターフェース)とは、ゲームの場合はゲームプレイヤーとプログラムの間で情報のやり取りをするインターフェースです。
「プレイヤーに情報を伝える」UIと「プレイヤーからの情報を受けとる」UIの2種類に分けられ、前者は文章や画像などで表現されます。
後者にはボタン入力やテキストボックスによる文字入力等があります。

サンプルゲームのUIを確認

1章にて公開したサンプルゲームでもUIが使われています。
どれがUI機能で作られたものなのかを確認しましょう。

上の緑で囲んだ3つのUIは全て、「プレイヤーに情報を伝える」UIになります。

また、上のタイトルも同様に「プレイヤーに情報を伝える」UIですが、下のスタートボタンは「プレイヤーからの情報を受けとる」UIになります。

Canvasで出来る事

CanvasはUnityでUIを作成する際、それらを配置する領域として(オブジェクトとして)存在されます。
作成したUIは全てCanvasの子オブジェクトになります。逆に言えばCanvasは全てのUIオブジェクトの親になります。(Canvasを複数用意している場合等は除きます)

初期設定ではCanvas領域内に配置されたUIは全てそのままゲーム画面上に最前面で表示されます。
ゲーム画面を映しているMainCameraオブジェクトがどれだけ移動・回転をしてもUIの表示に変化はありません。

カメラとの位置関係を指定したい場合の為に、Canvasオブジェクトに付いているCanvasコンポーネントで描画方式を変更できます。
今回は特に変更しないため他の方式について覚えなくて良いのですが、今後のゲーム開発で「UIを3Dマップ内に配置したい」等の状況が出てくるかもしれません。
その時存在だけでも思い出せるよう、一度確認しておくと良いでしょう。

◯Render Modeの設定

  • Screen Space – Overlay
    初期設定であり、今回のゲームではこれを使用します。
    全てのUIはカメラの位置と無関係に最前面に表示されます。
  • Screen Space – Camera
    指定したカメラと位置関係が固定されるモードです。
    カメラを動かしても見え方は変わりませんが、非UIオブジェクトが手前にあるとUIが隠れます。
  • World Space
    1つの平面オブジェクトとして3D空間内に配置されるモードです。
    宙に浮いたタッチパネルのような表現等に使えます。

UIの種類

UnityでサポートされているUIの種類は以下になります。
これは一例であり、他にもいくつかの種類が存在します。

  • Text
    文字列を表示します。スクリプトを使えば任意のデータも表示できます。
  • Image(Raw Image)
    画像(テクスチャ)を表示します。
  • Scroll View
    狭い領域内にある他UIをスクロールして表示できます。
  • Toggle
    ユーザーがオンやオフにできるチェックボックスです。
  • Button
    ユーザーが押下できるボタンです。押下時に指定したメソッドを呼び出せます。
  • Dropdown
    ユーザーがリスト中のパラメータから1つを選択できるボックスです。
  • Input Field
    ユーザーが文字列を入力できるフィールドです。
  • Slider
    ユーザーがマウスドラッグにより値を変更できるバーです。

ユーザーが操作したり入力できるUIは、全てその設定値をスクリプト側で取得する事が可能です。
また、これらはあくまで標準機能として存在するUIであり、複数の機能を組み合わせたり独自で開発を行えばあらゆるUI表現が可能になります。

UI作成時の注意点

UIを配置するにあたってゲーム画面の解像度(縦横比)は常に意識しておく必要があります。
何の対策もせずに解像度を変えると、配置していたUIは大きく崩れるか見切れてしまいます。

特にプレイ環境によって解像度が細かく変化するスマートフォンゲームは「Canvas Scaler」コンポーネントの設定を調節した上で各UIオブジェクトのAnchor等の設定もしておく必要があります。
今回は解像度を固定するので説明は割愛しますが、今一度テストプレイ画面の解像度を確認し、1章で設定した960×600以外になっていたら変更をしてください。

テキストを表示しよう

それでは実際にUIをゲーム画面に表示させてみましょう。
最も基本的なUIとなるテキストから使用していきます。

Canvasオブジェクトの作成

全てのUIオブジェクトにとって必要となるCanvasオブジェクトの作成を行います。
なおCanvasが無い状態でUIを作成すると自動的にCanvasも作成されるので、この項目はスキップが可能です。

オブジェクト作成の手順は今までとほぼ同じですが、選択するものが違います。
今までは[2D Object]でしたが、今回は[UI]タブ下の[Canvas]を選択します。

するとCanvasオブジェクトが作成されました。これはあくまでUIを表示する領域なので、まだ何も配置していない今画面に変化はありません。
オブジェクト名やコンポーネントの設定も特に変更しなくて良いでしょう。

Textオブジェクトの作成と設定

続いてTextコンポーネントを持つTextオブジェクトを作成しましょう。
自動的にCanvasの子オブジェクトとなるので、階層の設定は不要です。

これで文字が表示されているのですが、シーンビュー上ではうまく表示がなされていないと思います。
Canvasの領域はこれまでゲームオブジェクトを配置してきた場所と無関係に設定されるので、大抵は非常に広大な領域になっているのです。
このままでは配置がしづらいので、シーンビューの表示を縮小する必要があるのですがマウスホイールで行っていると大変です。
そんな時はヒエラルキーウィンドウでオブジェクト名をダブルクリックすると、一発でそれを見やすい所まで表示位置と拡大率を変更してくれます。

この中にTextオブジェクトがあるのですが、文字列が小さい上に黒色とやや見づらくなっています。
文字列は後から変更するとして、もう少し見やすいように変更してみましょう。

Textコンポーネントの設定を以下のように変えてみます。

  • Font Size
    文字列サイズを指定する項目です。今回は「36」にします。
  • Alignment
    文字のアライメントを指定します。今回は左と真ん中のボタンを選択し、中央左揃えにします。
  • Color
    文字色を指定します。右の四角をクリックすると色の指定ウィンドウが開きます。今回は白色にします。(RGBA全て最大値)

設定が終わると、今度は何も表示がされません。Textの表示領域が足りていないからです。
この場合Rect Transformコンポーネントへのパラメータの変更が必要です。
なお、Rect TransformコンポーネントはUIオブジェクトのみが使用するTransformコンポーネントの代わりです。いくつかのUI向け設定項目が追加されています。

ここでは余裕をもって500×60に指定します。シーンビューで輪郭をドラッグする事でもアバウトに変更できます。
ちなみにシーンビュー上でUIを移動等させる時は、ツールを[Rect Tool]にしておく事をおすすめします。

あとは座標を変更するだけです。この文字を見やすいゲーム画面の位置に移動させましょう。
このTextは後でプレイヤーのHPを表示する為に使います。ですので画面上部辺りに置くと良いでしょう。
座標の指定も数値の入力か、シーンビュー上でのドラッグどちらでも可能です。


サンプルとしてこの辺りに配置してみました。ゲームビューで実際の見え方を確認しつつ調整していきましょう。

オブジェクトの親子関係

今ヒエラルキーウィンドウを確認すると分かる事として、ゲームオブジェクトには親子関係というものが存在します。
子のオブジェクトは親に対して1階層下に位置しています。

基本的には「AオブジェクトのパーツとしてBオブジェクトを配置」する時に、BはAの子オブジェクトとして設定します。
親は全ての子オブジェクトと一まとめに扱える性質があり、例えば親オブジェクトをプレハブ化すると子オブジェクトもまとめて1つのプレハブ内に収まります。
その場合スクリプト等でインスタンスを生成すると親子関係を維持したまま全てのオブジェクトが出現します。

また親のオブジェクトが位置を変えた時、全ての子は同じだけ位置が変化します。つまり子は特別な処理が無い限り常に親との相対座標は不変となります。
例えば巨大なボスのパーツを別オブジェクトで作成する時、それはボスの子として配置すると良いでしょう。

親子関係はスクリプト内でも変更できます。(インスタンス生成時なら、Instantiate()メソッドの引数でも親の指定が出来ます。)

ボス体力表示用Text作成

先ほど作成したTextオブジェクトはこの後プレイヤーの残り体力を表示させる為に使います。
スクリプトの実装に移る前に、予めボスの体力表示用Textも用意しておきましょう。

プレイヤーのものと設定は同じで良いため、単純にオブジェクトのコピーペーストを行います。

複製したオブジェクトの親がCanvasになっている事を確認してください。違っていた場合、親子関係はオブジェクトのドラッグ操作で変更可能です。

後は複製したオブジェクトを先ほどのオブジェクトの下辺りに配置し、オブジェクト名も両者区別しやすいように変更しておきましょう。

テキストの内容をスクリプトで変えよう

UIによって文字の表示が実現しました。しかし現在は何の意味も持たない文字列が出ているのみです。
今回はここにプレイヤーとボスの残り体力を表示させるようにしてみましょう。

GameManagerオブジェクト

UIをスクリプトで扱う前にちょっとした準備をします。
それはゲーム全体の進行を管理する「GameManager」オブジェクト及びスクリプトの作成です。

UIの制御という機能は、プレイヤーの仕事でもボスの仕事でも、ましてや弾の仕事でもありません。
ゲーム中に出たり消えたりする事のない第3のスクリプトが全体の制御をするべきです。

それを今回はGameManagerというクラスに行わせる事にします。
このスクリプトは今後ゲームオーバー処理やタイトル画面への移行処理等も行います。まさにゲームマネージャー(ゲームの管理者)という存在になる訳です。
なお、本当はゲームの進行管理とUI管理は別スクリプトで行うのが望ましいのですが、この講座ではゲームの開発規模が小さい事もありまとめてこのスクリプトで行います。

まずGameManagerオブジェクトの作成ですが、これは今までと手順は同じです。
ただしSpriteRendererが不要なので「Create Empty」で作成します。

そしてGameManagerスクリプトを作成してオブジェクトに取り付けします。
どちらも名前が同じで紛らわしい場合はオブジェクト側の名前を変えても構いません。


(GameManagerスクリプトを作成すると他スクリプトと違う歯車のアイコンになりますが、気にしなくてOKです。)

スクリプトでのUI制御

全てのUI機能はスクリプトによる制御が可能です。UIの表示内容を変更する事も、UIへの操作を検知する事もスクリプト上で可能です。

ただしUIに関する処理を行う時は「これからUIの機能を使いますよ」という宣言をスクリプト中で行わないといけません。
それが2章で登場したusing文です。
具体的には「using UnityEngine.UI;」という文が必要になり、これで初めてUI機能へのアクセスが可能になります。

どのUIのどの機能にはどのメンバ変数からアクセスするのか、といった情報は覚えるのが大変なので都度スクリプトリファレンスから調べると良いでしょう。

プレイヤーとボスの残り体力をテキスト表示

それでは仕様に基づいてプレイヤーの残り体力を先ほど作成したTextオブジェクトに表示させてみましょう。

GameManager.cs

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI; // UI機能の利用に必要なusing文

// ゲーム管理スクリプト
public class GameManager : MonoBehaviour {

	// メンバ変数宣言
	public Text text_PlayerHealth;	// テキスト(プレイヤー残り体力表示用)
	public Text text_BossHealth;    // テキスト(ボス〃)

	// プレイヤーの残り体力をUIに適用(PlayerControllerから呼び出される)
	// 引数health : 残り体力
	public void SetPlayerHealthUI (int health)
	{
		text_PlayerHealth.text = "プレイヤー体力 : " + health.ToString ();
	}

	// ボスの残り体力をUIに適用(BossControllerから呼び出される)
	public void SetBossHealthUI (int health)
	{
		text_BossHealth.text = "ボス体力 : " + health.ToString ();
	}
}

外部から持ってくる情報は、表示を変更させたいTextコンポーネントの2つです。
これはpublicメンバ変数の特性を利用して、インスペクターウィンドウから設定すればOKです。
(コンポーネントのセットはゲームオブジェクトの時と同じです。そのコンポーネントが入ったオブジェクトをドラッグアンドドロップします。)

そしてUIの表示を変更するタイミングですが、プレイヤー/ボスの体力に変化があった時それぞれのスクリプトからGameManagerのメソッドを呼び出す形にします。
本当はGameManager内のUpdate()中で各キャラクターの体力を監視する処理にしても構いません。この後のスクリプトが理解出来たら書き換えてみても良いでしょう。

まだUIの変更をする2つのメソッドが一度も呼ばれていないので、テストプレイをしても変化はなしです。
プレイヤーとボスのスクリプトを書き換え、この処理を呼び出してあげましょう。

PlayerController.cs

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class PlayerController : MonoBehaviour
{
	// メンバ変数宣言
	public GameManager gameManager;	// ゲームマネージャー
	public GameObject bulletPrefab; // 弾のプレハブ
	private int health; // 体力

	// 起動時に1回だけ呼び出されるメソッド
	void Start ()
	{
		health = 3; // 初期体力をセット
		
		// 初期体力をUIに表示
		gameManager.SetPlayerHealthUI (health);
	}

	// 毎フレーム呼び出されるメソッド
	void Update ()
	{
		(省略)
	}

	// 当たり判定内に他オブジェクトが侵入した際呼び出されるメソッド
	void OnTriggerEnter2D (Collider2D collider)
	{
		// ボスが発射した弾でなければ処理を終了
		if (collider.gameObject.name != "EnemyBullet")
		{ // 接触オブジェクト名がEnemyBulletで無ければ
			return; // メソッド終了
		}

		// 弾オブジェクトを消滅させる
		Destroy (collider.gameObject);

		// 自身の体力を1減らす
		health--;
		// 現在体力をUIに表示
		gameManager.SetPlayerHealthUI (health);
		// プレイヤー消滅処理
		if (health <= 0)
		{// プレイヤーの体力が0以下
			Destroy (gameObject); // 自オブジェクト消去
		}
	}
}

Start()での初期化時と、被弾して体力が減った時の2箇所で先ほどのメソッドを呼び出しています。
これで体力の変化がUIに適用されます。
スクリプトへのGameManagerコンポーネントの紐づけは忘れずに行ってください。(GameManagerオブジェクトを該当箇所にドラッグ&ドロップで行えます)

同様にボスのスクリプトも変更させてGameManagerとの紐づけをします。
BossController.cs

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class BossController : MonoBehaviour {

	// メンバ変数宣言
	public GameManager gameManager; // ゲームマネージャー
	public GameObject bulletPrefab; // 弾のプレハブ
	public GameObject playerObj;	// プレイヤーオブジェクト
	public int health;  // 体力
	private float time; // 経過時間(秒)

	// 起動時に1回だけ呼び出されるメソッド
	void Start ()
	{
		// 初期体力をUIに表示
		gameManager.SetBossHealthUI (health);
	}

	// 毎フレーム呼び出されるメソッド
	void Update ()
	{
		(省略)
	}

	// プレイヤーへの角度計算メソッド
	float GetAngleToPlayer ()
	{
		(省略)
	}

	// 弾を発射するメソッド
	// 第1引数 speed : 速度
	// 第2引数 angle : 角度
	// 第3引数 limitTime : 生存時間(秒)
	void Shot (float speed, float angle, float limitTime)
	{
		(省略)
	}

	// 当たり判定内に他オブジェクトが侵入した際呼び出されるメソッド
	// 引数:接触オブジェクトしたオブジェクトのCollider情報
	void OnTriggerEnter2D (Collider2D collider)
	{
		// プレイヤーが発射した弾でなければ処理を終了
		if (collider.gameObject.name != "PlayerBullet")
		{ // 接触オブジェクト名がPlayerBulletで無ければ
			return; // メソッド終了
		}

		// 弾オブジェクトを消滅させる
		Destroy (collider.gameObject);

		// 自身の体力を1減らす
		health--;
		// 現在体力をUIに表示
		gameManager.SetBossHealthUI (health);
		// ボス消滅処理
		if (health <= 0)
		{// ボスの体力が0以下
			Destroy (gameObject); // 自オブジェクト消去
		}
	}
}

キャラのスクリプトではGameManagerのメソッドを呼び出しているだけなので、using文の追加は不要です。

動作確認


テキストUIの表示内容が各キャラクターの残り体力を示すようになりました。
前章でボスの攻撃を強くしすぎて確認が難しい場合は、一時的に易化させましょう。

stringについて

ここまでのスクリプトを見て分かるとおり、Textコンポーネントの表示内容はメンバ変数textで変更可能です。
このtextには文字列を代入するのですが、文字列を扱う変数の型は「string」型です。

string型では基本的に何文字でも1つの変数内に収める事は出来ます。(一応上限はあります。)
また数値変数と同じように文字列同士の演算が可能であり、サンプルコードでは「”プレイヤー体力 : ” + health.ToString ();」という構文が出ています。
これは「プレイヤー体力 : 」という文字列と変数healthを文字列化したものを合体させ、1つの文字列として扱えるようにしています。

プレイヤーの体力を見やすくしよう

キャラの残り体力が表示されるようになった事により、初見のユーザーでも「後何回被弾しても良いのか」が分かるようになりました。
既にUIとしては成立しているのですが、実際は文字のみの表現というのは少し分かりにくく、デザインとしても初歩なものです。
アクションゲームなのでパッと見ただけでも理解できるUIを目指した方が良いでしょう。

そこで今回はプレイヤーキャラの残り体力表示を文字から画像に変更したいと思います。
例えば体力1つ分をハートマーク1個で表現すれば、直感で意味が理解できる上に見やすくなりますね。

ボスの場合は体力がプレイヤーよりも多いので別の形式で表現しますが、少し発展的な内容になるのでこちらは以降の章で実装します。

サンプル画像とインポート

それではプレイヤーの体力を表すアイコンを用意しましょう。

こちらもサンプルを以下に用意していますが、当たり判定などが絡まない為他の画像に切り替えるのは簡単です。
お好みの画像を使用すると良いでしょう。

インポートは恒例のドラッグアンドドロップ操作です。テクスチャ名は「LifeIcon」としておきますが、違っていても問題ありません。

Imageオブジェクトの作成

これから画像での表示に切り替えていきます。
まずはプレイヤー体力表示のTextをオブジェクトごと削除してしまいましょう。
そしてメニューの[GameObject]から[UI]→[Image]を選択し、Imageオブジェクトを作成します。

オブジェクトの設定も行います。
まずオブジェクト名は「PlayerHealthIcon_1」とします。(このオブジェクトは残り体力1に位置するアイコンとなる為、分かりやすくしておきます。)
そしてImageコンポーネントの[Source Image]パラメータに先ほどインポートしたアイコン画像をセットします。

プレイヤーの初期体力及び最大体力は3です。なのでこのオブジェクトをコピーペーストし、2つを新たに複製します。
PlayerHealthIcon_1は画面左上辺りに配置し、残りのオブジェクト(PlayerHealthIcon_2とPlayerHealthIcon_3)はこれに続くように並ばせて配置しましょう。

これでUIオブジェクトの準備は完了です。後はスクリプト制御で「被弾時に表示を消す」ようにすれば完成ですね。

体力減少時にImageを消去

処理方式は何パターンかあるのですが、今回は最も単純な手法でスクリプトを組んでみます。

GameManager.cs

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI; // UI機能の利用に必要なusing文

// ゲーム管理スクリプト
public class GameManager : MonoBehaviour {

	// メンバ変数宣言
	public Text text_BossHealth;    // テキスト(ボス残り体力表示用)
	public GameObject imageObj_Health_1; // プレイヤー残り体力1を示すUI
	public GameObject imageObj_Health_2; // プレイヤー残り体力2を示すUI
	public GameObject imageObj_Health_3; // プレイヤー残り体力3を示すUI

	// プレイヤーの残り体力をUIに適用(PlayerControllerから呼び出される)
	// 引数health : 残り体力
	public void SetPlayerHealthUI (int health)
	{
		// 残り体力によって非表示にすべき体力アイコンを消去する
		if (health == 2)
		{ // 体力2になった場合
			Destroy (imageObj_Health_3); // 3つめのアイコンを消去
		}
		else if (health == 1)
		{ // 体力1になった場合
			Destroy (imageObj_Health_2); // 2つめのアイコンを消去
		}
		else if (health == 0)
		{ // 体力0になった場合
			Destroy (imageObj_Health_1); // 1つめのアイコンを消去
		}
	}

	// ボスの残り体力をUIに適用(BossControllerから呼び出される)
	public void SetBossHealthUI (int health)
	{
		text_BossHealth.text = "ボス体力 : " + health.ToString ();
	}
}

先ほど配置Imageオブジェクトを、プレイヤーの体力減少時に呼ばれるSetPlayerHealthUI()メソッド内で順番に消去しています。
一度に体力が2以上減る事はなく、体力の回復もないためこの手法で問題はありません。

後は新しく追加したpublicメンバ変数に先ほどのImageオブジェクトをセットするだけです。


これで被弾時に体力アイコンが1つずつ消えていく処理が完成しました。

ちなみにif文とともに出てくるelseは「〇〇の場合はAの処理、または●●の場合はBの処理」という条件分岐を行う際「または」にあたる記述となります。
条件分岐の構文としてよく必要となるので覚えておくと良いでしょう。

もう一つ補足としてこのスクリプトを見やすくしたい場合、新しく3つ用意したpublicメンバ変数を「配列」で宣言して使用するとその後の処理もスマートに書けます。
今必須の知識ではなく、また少々長くなってしまう為説明は割愛します。「同じような用途の変数が大量に出てくる」場合に「配列」というキーワードを思い出すと良いと思います。(配列について忘れた場合は、こちらからどうぞ)

 

ここまでのおさらい

Unity独自のUI機能を使用して、ゲーム画面上に文字や画像などを表示させる方法を学びました。
そしてスクリプト上でそれらを制御し、文字の表示内容を変更したりしました。

UIオブジェクトは基本的なゲームオブジェクトと同じですが差異もあります。RectTransformというコンポーネントが標準で備わっているほか、必ずCanvasオブジェクトの子に配置されます。
UIは初期設定では常にゲーム画面の最前面に表示されます。これを変えたい場合、CanvasコンポーネントのRenderModeパラメータを変更する必要があります。

また、スクリプトでUIを扱うときは「using UnityEngine.UI;」という文がスクリプトの最初に必要です。

次章で学ぶこと

UIの実装も完了し、ゲームとして最低限の状態が整いつつあります。

次章ではここに様々な仕様を追加で実装していき、もう少し「ゲームらしいゲーム」を目指していきます。
ここからは応用的な内容も増えていきますので、難しい時は基本に立ち返りつつ少しずつ進めると良いでしょう。

 

<前回>   <=  「⑤ボスの攻撃を作ろう

<次回>  =>  「⑦ゲームに要素を追加していこう

オススメアプリ!渋谷で気軽に友達を作れるアプリが登場!