はじめに
お疲れ様です。第4回です。
今日やること
- ポーズ画面・ゲームクリア画面・ゲームオーバー画面の作成
前回のおさらい
前回(https://ch-random.net/post/1944/)は、ボールを当てて壊せるブロックの追加と、ゲームクリア・ゲームオーバー判定の作成を行いました。
パネルの作成
今回は、UI(ユーザーインターフェース)をゲームに追加していきます。
具体的には、Escキーでポーズ画面を開けるようにして、前回追加したゲームクリア・ゲームオーバー判定の処理を変えてそれぞれゲームクリア画面、ゲームオーバー画面が出るようにします。
まずは、画面の本体となるパネル(Panel)を追加しましょう。
ヒエラルキーで右クリック→「UI」→「パネル」の順にクリックします。
すると、パネル(Panel)の他にキャンバス(Canvas)とEventSystemというオブジェクトが追加されます。(パネルはキャンバスの子オブジェクトとなります)
キャンバスは全てのUIの親オブジェクトとなるもので、それ自体は透明です。
EventSystemはUIに関するイベント(クリックやマウスオーバーなど)を扱うオブジェクトです。特にいじる必要はないので無視して構いません。
この白い半透明のものがパネルですが、追加したばかりの状態だとこのようにとてつもなく大きいです。(左下がゲーム画面で、右上のやつがパネルの中央部分です)
このままだとUIを編集するのが難しいので、キャンバスの設定を変更していきます。
Canvasのインスペクターから、「レンダーモード」を「スクリーンスペース - カメラ」に設定して、「レンダーカメラ」にMain Cameraを選択します。
これでキャンバスの大きさとカメラの描画範囲が一致するようになりました。
後はパネルのインスペクターの「色」からパネルの色を設定します。今回は黒色にしておきます。
テキストの追加
パネルに要素を追加していきましょう。
Panelオブジェクトを右クリック→「UI」→「テキスト - TextMeshPro」をクリックします。
するとこのようなウィンドウが出てくるので、「Import TMP Essentials」をクリックします。これで、テキスト(TextMeshPro)を使う準備をすることができます。
下の「Import TMP Examples & Extras」は押さなくても良いです。
これでテキストが追加されます。インスペクターからテキストの内容やフォントなどを変更できますので、「Font Size」と「Alignment」を変更し、
移動ツールを使って位置を変更して、黄色い枠(テキストの表示範囲)を広げていい感じにします。
ボタンの追加
次はボタンを追加していきます。今回はリスタート用のボタンと、ゲームを終了する用のボタンを作りましょう。
今度は、Panel右クリック→「UI」→「ボタン - TextMeshPro」をクリックします。
まずはリスタート用のボタンを作ります。名前をRestartButtonに、スケールをデフォルトの2倍にしておきました。
ボタンのテキストはボタンの子オブジェクトになっているので、テキストをRestartに変えておきます。
これをコピーして、ゲーム終了用のExitButtonも作っておきます。
ボタンの処理の実装
次は、ボタンの処理を実装していきます。ボタンの処理はpublicメソッドを用意することで実装できます。
プレイヤーのスクリプトを編集しましょう。以下の2つのメソッドをPlayerスクリプトに追加してください。
public void RestartButton()
{
SceneManager.LoadScene(0);
}
public void ExitButton()
{
Application.Quit();
}
リスタートボタンを押すとシーンを読み込み直し、終了ボタンを押すとアプリケーションを終了するようにします。
その後、ボタンのインスペクターから「クリック時()」という項目で処理の設定を行います。
「+」ボタンを押してPlayerオブジェクトを指定し、「No Function」のところを「Player」→「RestartButton()」に変更します。これでリスタート用ボタンの処理は完成です。
終了用ボタン(ExitButton)にも同様に、PlayerスクリプトのExitButtonメソッドを割り当てましょう。
ちなみに、テストプレイではApplication.Quit()を実行しても何も起きません。
ポーズ・ゲームクリア・ゲームオーバー画面の実装
では、実際に作ったUIを使えるようにしていきましょう。
まずはポーズ画面・ゲームクリア画面の実装を行います。Playerスクリプトを以下のように編集します。
public class Player : MonoBehaviour
{
public GameObject blocks;
public GameObject panel;
public TextMeshProUGUI text;
// Start is called before the first frame update
void Start()
{
//リスタート時にtimeScaleを元に戻す
Time.timeScale = 1;
}
// Update is called once per frame
void Update()
{
//速度
float speed = 3f;
// 自身のRigidbody2Dを取得してrbに代入
Rigidbody2D rb = GetComponent<Rigidbody2D>();
// 速度を0にする
rb.velocity = new Vector2(0, 0);
if (Input.GetKey(KeyCode.LeftArrow))
{
// もし左矢印キーが押されたら速度ベクトルを(-3, 0)にする
rb.velocity = new Vector2(-speed, 0);
}
else if (Input.GetKey(KeyCode.RightArrow))
{
// もし右矢印キーが押されたら速度ベクトルを(3, 0)にする
rb.velocity = new Vector2(speed, 0);
}
//ポーズ画面
if (Input.GetKeyDown(KeyCode.Escape))
{
if (Time.timeScale == 1)
{
panel.SetActive(true);
text.text = "Pause";
Time.timeScale = 0;
}
else
{
panel.SetActive(false);
Time.timeScale = 1;
}
}
//ゲームクリア判定
if (blocks.transform.childCount == 0)
{
panel.SetActive(true);
text.text = "Game Clear!";
Time.timeScale = 0;
}
}
//リスタートボタン
public void RestartButton()
{
SceneManager.LoadScene(0);
}
//終了ボタン
public void ExitButton()
{
Application.Quit();
}
}
どこをどう変えたのかわかりにくいと思うので、変更部分だけ赤く囲っておきました↓
上から順に
- パネルとテキストのオブジェクトを格納する変数の追加
- リスタート時にtimeScale(後で説明)を0にする
- Escキーでポーズ画面の開閉ができるようにする
- ゲームクリア判定のSceneManager.LoadScene(0);の部分をパネルを表示する処理に変更
となっています。
Time.timeScaleは時間の経過する速さを決める変数です。これを0にすると時間が止まり、1にすると元通りになります。
また、パネルの表示・非表示はSetActiveというメソッドで行っています。これはGameObjectクラスの持つメソッドです。
テキストの内容の変更はTextMeshProUGUIクラスのtextという変数を変更して設定できます。(TextMeshProUGUIの変数名もtextにしたのでややこしくなってますが...)
スクリプトを保存したら、前回やったようにオブジェクトの指定を行います。
ゲーム開始時にはパネルは表示されないようにしたいので、非表示にしておきます。
Panelオブジェクトを選択して、インスペクター上部の名前の右のチェックボックスからチェックを外します。
これでPanelオブジェクトは非アクティブ(Inactive)状態になりました。これはPlayerスクリプトでいうとpanel.SetActive(false);を実行した後と同じ状態です。
これで、Escキーを押すとポーズ画面が表示されるようになりました。(ゲームクリア画面も)
同様に、Floorスクリプトを編集してゲームオーバー画面も実装します。
public class Floor : MonoBehaviour
{
public GameObject panel;
public TextMeshProUGUI text;
private void OnCollisionEnter2D(Collision2D collision)
{
panel.SetActive(true);
text.text = "Game Over!";
Time.timeScale = 0;
}
}
処理の内容はPlayerスクリプトのほうとほぼ同じです。
Floorスクリプトを保存したら、先程と同じようにパネルとテキストのオブジェクトをインスペクターから指定してください。
これでゲームオーバー画面も実装できました。
スクリプトの修正
これで一応3つの画面の実装は完了ですが、不具合が残っています。
今の状態ではゲームオーバーやゲームクリア画面でEscキーを押すとゲームが再開されてしまうので、これを無効化します。
Playerスクリプト、Floorスクリプトを少しだけ変えます。
public class Player : MonoBehaviour
{
public static bool isPausable; //追加
public GameObject blocks;
public GameObject panel;
public TextMeshProUGUI text;
// Start is called before the first frame update
void Start()
{
//リスタート時にtimeScaleを元に戻す
Time.timeScale = 1;
//リスタート時にポーズ可能にする
isPausable = true; //追加
}
// Update is called once per frame
void Update()
{
//速度
float speed = 3f;
// 自身のRigidbody2Dを取得してrbに代入
Rigidbody2D rb = GetComponent<Rigidbody2D>();
// 速度を0にする
rb.velocity = new Vector2(0, 0);
if (Input.GetKey(KeyCode.LeftArrow))
{
// もし左矢印キーが押されたら速度ベクトルを(-3, 0)にする
rb.velocity = new Vector2(-speed, 0);
}
else if (Input.GetKey(KeyCode.RightArrow))
{
// もし右矢印キーが押されたら速度ベクトルを(3, 0)にする
rb.velocity = new Vector2(speed, 0);
}
//ポーズ画面
if (isPausable && Input.GetKeyDown(KeyCode.Escape)) //追加
{
if (Time.timeScale == 0)
{
panel.SetActive(false);
Time.timeScale = 1;
}
else
{
panel.SetActive(true);
text.text = "Pause";
Time.timeScale = 0;
}
}
//ゲームクリア判定
if (blocks.transform.childCount == 0)
{
panel.SetActive(true);
text.text = "Game Clear!";
Time.timeScale = 0;
isPausable = false; //追加
}
}
//リスタートボタン
public void RestartButton()
{
SceneManager.LoadScene(0);
}
//終了ボタン
public void ExitButton()
{
Application.Quit();
}
}
public class Floor : MonoBehaviour
{
public GameObject panel;
public TextMeshProUGUI text;
private void OnCollisionEnter2D(Collision2D collision)
{
panel.SetActive(true);
text.text = "Game Over!";
Time.timeScale = 0;
Player.isPausable = false; //追加
}
}
PlayerクラスにisPausableというstatic変数(下記参照)を追加して、その値によってポーズできるかどうかを変えています。
これでゲームクリア画面およびゲームオーバー画面でEscキーを無効化できました。
課題(C#プログラミング入門4)
C#のプログラミングについての解説です。
今回はstatic修飾子と、クラスの継承についてです。
static修飾子
Unityでスクリプトを作る際には使用頻度は少なめですが、今回の内容でも出てきたstatic修飾子について解説しておきます。
static修飾子はメンバ変数やメソッドにつけることができ、staticがつけられた(静的)変数やメソッドはインスタンス(第3回参照)を作らなくても使用することができます。
ただし、静的メソッドから動的な(staticがついていない)変数やメソッドを扱うことができないので注意です。
静的メンバに他のクラスからアクセスする際は、「クラス名.メンバ名」でアクセスできます。
以下に静的変数を用いてインスタンスの数を数えるサンプルコードを置いておきます。
public class Player : MonoBehaviour
{
void Start()
{
Umamusume kitasan_black = new Umamusume("キタサンブラック", 162);
Debug.Log(Umamusume.Amount);
Umamusume satono_crown = new Umamusume("サトノクラウン", 163);
Debug.Log(Umamusume.Amount);
Umamusume cheval_grand = new Umamusume("シュヴァルグラン", 160);
Debug.Log(Umamusume.Amount);
}
//中略
}
public class Umamusume{
public static int Amount = 0;
public string name;
public int height;
public Umamusume(string name, int height){
this.name = name;
this.height = height;
Amount++;
}
}
実行結果は以下のようになります。
1
2
3
クラスの継承
クラスを作る際に、他のクラスを"継承"して新しくクラスを作ることができます。
以下がクラスの継承の例です。(Umamusumeクラスを継承して、新しくKitasanBlackクラスを作る例)
public class Player : MonoBehaviour
{
void Start()
{
KitasanBlack kitasan_black = new KitasanBlack();
kitasan_black.Print();
}
//中略
}
public class Umamusume{
public string name;
public int height;
public Umamusume(){}
public Umamusume(string name, int height){
this.name = name;
this.height = height;
}
public void Print(){
Debug.Log("名前:" + name);
Debug.Log("身長:" + height + "cm");
}
}
public class KitasanBlack : Umamusume{
public KitasanBlack(){
this.name = "キタサンブラック";
this.height = 162;
}
}
実行結果:
名前:キタサンブラック
身長:162cm
KitasanBlackクラスではnameとheight、Printメソッドの定義をしていないのにそれらが使えるようになっています。
これはUmamusumeクラスを継承して作っているので、Umamusumeクラスがもともと持つ変数やメソッドは引き継がれているからです。
(この例だとキタサンブラックを複数人作れることになるので実際はあまりこういう使い方しませんが、まあ説明のためなので良いでしょう...)
Unityにおいて重要なのは、全てのスクリプトはMonoBehaviorクラスを継承しているということです。MonoBehaviourクラスはStartメソッドやUpdateメソッドを元から持っていて、その内容を上書きする形でいつも使っています。