この講座ではUnity初心者の方でも作れる「スイカゲーム風パズルゲーム」を作成します。
前回は同じ果物同士がぶつかったときに合体する処理、次に出てくる果物を表示するUI、合体したときにスコアが増える仕組みを作成しました。
前回の記事:

今回はゲームとして区切りを作るためにゲームオーバー、リトライ、効果音を追加していきます。
ゲームオーバーを作ろう
今回のゲームでは、果物が上に作成するラインに一定時間触れ続けたらゲームオーバーにします。
ゲームオーバー用の UI を用意しよう
まずは、Game Overと表示するUIを用意していきましょう。
Canvas を選択して、右クリックから UI > TextMeshPro – Text を選択します。
作成したテキストの名前を GameOverText に変更しましょう。
作成した GameOverText を選択して、位置とサイズを以下のように変更しましょう。
テキストは GameOver にしておきます。
また、色は赤にしてみましょう。
Game ビューを確認してみましょう。このままだと、背景によっては GameOver の文字が少し見づらくなることがあります。
そこで、文字にふちを付けて見やすくします。GameOverText を選択した状態で、Inspector の下のほうにある Outline にチェックを入れ、Thickness を 0.25 にします。
これで文字に黒いふちが付き、GameOver の文字が見やすくなります(この設定はTextMeshProで使う用のデータセットに対して行ってるので同じFont Assetを使った箇所の文字のふちも自動で変わります)。
次に、GameOverText を非アクティブオブジェクトにしておきます。
Inspector の左上にあるチェックボックスを外しましょう。
これで GameOverText が非アクティブになります。
非アクティブオブジェクトとは?
非アクティブオブジェクトとは、Scene 上には存在しているけれど、ゲーム中には表示されず、動作もしない状態のオブジェクトです。
今回の GameOverText は、ゲーム開始時から表示されていると困ります。そのため、最初は非アクティブにして隠しておきます。あとでスクリプトからアクティブに切り替えることで、ゲームオーバーになった時だけ GameOverText を表示できます。
スクリプトを書こう
今回はゲームオーバー判定領域を用意し、その領域に果物が一定時間触れた時にゲームオーバーにする処理を作成します。
ゲームオーバーになったらGameOverText を表示してGameManager2D から果物の操作を止める処理を実行します。
まずは仕組みを考えていきましょう。
ゲームオーバー判定に果物が触れた瞬間「ラインに触れている果物の数」に1を足します。
逆に、果物がゲームオーバー判定から離れた瞬間「ラインに触れている果物の数」から1を引きます。そして、「ラインに触れている果物の数」が1以上の状態が一定時間続いたら、ゲームオーバーにします。
なぜ加算、減算で管理するの?
ここで、bool 型の true と false だけで管理する場合を考えてみましょう。
例えば、左側でみかんがゲームオーバー判定に触れ続けていて、あと0.5秒でゲームオーバーになる状態だったとします。そのタイミングで、右側でいちごが一瞬だけゲームオーバー判定に触れて、すぐに離れたとします。bool 型だけで管理していると、いちごが離れた瞬間に一瞬だけ false になってしまいます。すると、みかんはまだゲームオーバー判定に触れ続けているのに、ゲームオーバーまでのタイマーがリセットされてしまいます。
これでは複数の果物が同時に接触する場面で正しく判定できません。
そこで今回は、「何個の果物がラインに触れているか」を数で管理します。
触れたら1を足し、離れたら1を引くことで、別の果物が一瞬触れても、すでに触れている果物の判定を邪魔しないようにできます。
GameManager2D には、ゲームオーバーにするための関数を用意します。
この関数ではゲームオーバーのフラグをオンにして、GameOverText をアクティブオブジェクトに変更して表示します。
まずはGameManager2Dスクリプトから修正・追記していきましょう。
Project ウィンドウから Assets > Scripts フォルダを開き、GameManager2D をダブルクリックで開きます。
開けたら、以下のように追記・修正しましょう。
GameManager2D
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 |
using TMPro; using UnityEngine; public class GameManager2D : MonoBehaviour { [Header("Fruits")] public GameObject[] fruitPrefabs; public int maxStartLevel = 3; [Header("SpawnSettings")] public float spawnY = 4f; public float minX = -2.5f; public float maxX = 2.5f; public float nextFruitDelay = 0.8f; [Header("UI")] public SpriteRenderer nextFruitPreview; public TextMeshProUGUI scoreText; public GameObject gameOverText; private int currentLevel; private GameObject currentFruit; private float Timer; private int nextLevel; private int score = 0; private bool isGameOver = false; // Start is called once before the first execution of Update after the MonoBehaviour is created void Start() { currentLevel = RandomStartLevel(); nextLevel = RandomStartLevel(); CreateFruit(); //次の果物を表示 nextFruitPreview.sprite = fruitPrefabs[nextLevel].GetComponent<SpriteRenderer>().sprite; //スコアUIをセット scoreText.text = "Score: " + score.ToString(); } // Update is called once per frame void Update() { if (isGameOver) { return; } if (currentFruit == null) { Timer += Time.deltaTime; if (Timer > nextFruitDelay) { CreateFruit(); Timer = 0; } return; } if (Input.GetMouseButton(0)) { Vector3 mousePosition = Camera.main.ScreenToWorldPoint(Input.mousePosition); float x = Mathf.Clamp(mousePosition.x, minX, maxX); currentFruit.transform.position = new Vector3(x, spawnY, 0f); } if (Input.GetMouseButtonUp(0)) { currentFruit.GetComponent<Rigidbody2D>().simulated = true; currentFruit.GetComponent<Collider2D>().enabled = true; currentFruit = null; currentLevel = nextLevel; nextLevel = RandomStartLevel(); //次の果物を表示 nextFruitPreview.sprite = fruitPrefabs[nextLevel].GetComponent<SpriteRenderer>().sprite; } } int RandomStartLevel() { return Random.Range(0, maxStartLevel + 1); } void CreateFruit() { currentFruit = Instantiate(fruitPrefabs[currentLevel], new Vector3(0f, spawnY, 0f), Quaternion.identity); currentFruit.GetComponent<Rigidbody2D>().simulated = false; currentFruit.GetComponent<Collider2D>().enabled = false; } public void AddScore(int addScore) { score += addScore; scoreText.text = "Score: " + score.ToString(); } public void GameOver() { isGameOver = true; gameOverText.SetActive(true); } } |
スクリプトの解説
内部では次の順番で処理しています。
- GameOverText を入れるための変数を用意
- ゲームオーバーになったかどうかを管理する isGameOver を用意
- Update の最初で、isGameOver が true かどうかを確認
true だった場合は return で処理を終了します。 - GameOver関数を作成
この関数は後で作成するゲームオーバー判定のスクリプトから呼び出します。GameOver関数が呼ばれると、isGameOver を true にします。さらに、gameOverText.SetActive(true) で GameOverText を表示します。
変数宣言
|
1 2 3 4 |
[Header("UI")] public SpriteRenderer nextFruitPreview; public TextMeshProUGUI scoreText; public GameObject gameOverText; |
gameOverText は、ゲームオーバー時に表示する GameOverTextのゲームオブジェクト を入れるための変数です。
GameObject はUnityのゲームオブジェクトを入れるための型です。今回は非アクティブにしておいた GameOverText をスクリプトから表示するために使います。
|
1 |
private bool isGameOver = false; |
isGameOver は、ゲームオーバーになったかどうかを管理するフラグ用の変数です。
bool は、true または false のどちらかを入れる型です。今回の場合、false なら通常プレイ中、true ならゲームオーバー中として使います。
🚩フラグとは?
フラグとは、「今どの状態か」を管理するための目印のようなものです。
「ゲームオーバー中か」、「ジャンプ中か」、「攻撃中か」などを判断するためによく使います。
今回の isGameOver は、ゲームオーバーになったかどうかを判断するフラグです。
最初は false にしておき、ゲームオーバーになったら true に変更します。Update の最初でこのフラグを確認することで、ゲームオーバー後の操作を止めることができます。
Updateメソッドの処理
Updateメソッドの先頭に、次の処理を追加しています。
|
1 2 3 4 |
if (isGameOver) { return; } |
Updateはゲーム中に毎フレーム呼ばれる処理です。ゲームオーバー後も Update がそのまま動き続けると新しい果物が出たり、マウスで操作できたりしてしまいます。
そこで、Update の一番最初で isGameOver を確認しています。
- isGameOver が false の間は、if文の中に入らないので今までどおりゲームが進みます。
- isGameOver が true になると、if文の中に入りreturn文が実行されます。
return文はその場で処理を終了する命令です。つまり、Update の先頭で return されると、その下に書かれている果物の生成や操作の処理は実行されません。
GameOver関数の処理
最後にGameOver関数を追加しています。
|
1 2 3 4 5 |
public void GameOver() { isGameOver = true; gameOverText.SetActive(true); } |
この関数はゲームオーバーになったときに呼び出す処理です。
あとで作成するゲームオーバー判定のスクリプトから、このGameOver関数を呼び出します。
GameOver関数が呼ばれると、まず isGameOver = true; が実行されます。これにより、ゲームオーバー中であるというフラグを立てます。
isGameOver が true になると、次のフレームから Update の先頭にある判定で return されるようになります。
次に gameOverText.SetActive(true); が実行されます。
SetActive(true) は非アクティブにしていたオブジェクトをアクティブにする処理です。今回は最初に非表示にしておいた GameOverText を表示するために使っています。
つまり、GameOver関数ではゲームオーバーの状態に切り替える処理とGameOverText を表示する処理を行っています。
ゲームオーバー判定領域用のスクリプトを書こう
次はゲームオーバーの判定領域用のスクリプトを書いていきます。最初にスクリプトを作って、後から領域用のゲームオブジェクトを作成します。
Project ウィンドウから Assets > Scripts フォルダを開きます。
右クリックから Create > MonoBehaviour Script を選択し、名前を GameOverLine にしましょう。
作成したスクリプトをダブルクリックで開き、以下を追記・修正しましょう。
GameOverLine
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 |
using UnityEngine; public class GameOverLine : MonoBehaviour { public float gameOverTime = 2f; private int touchingFruitCount = 0; private float timer = 0f; private GameManager2D gameManager2D; // Start is called once before the first execution of Update after the MonoBehaviour is created void Start() { gameManager2D = FindAnyObjectByType<GameManager2D>(); } // Update is called once per frame void Update() { if (touchingFruitCount > 0) { timer += Time.deltaTime; if (timer >= gameOverTime) { gameManager2D.GameOver(); } } else { timer = 0f; } } void OnTriggerEnter2D(Collider2D collision) { if (collision.GetComponent<FruitItem>() != null) { touchingFruitCount++; } } void OnTriggerExit2D(Collider2D collision) { if (collision.GetComponent<FruitItem>() != null) { touchingFruitCount--; } } } |
スクリプトの解説
このスクリプトはゲームオーバー判定を行う領域に果物が一定時間触れ続けたらゲームオーバーにするためのスクリプトです。
変数宣言
|
1 |
public float gameOverTime = 2f; |
gameOverTime は、何秒触れ続けたらゲームオーバーにするかを決める変数です。
初期値は2なので、2秒触れ続けるとゲームオーバーになります。
|
1 |
private int touchingFruitCount = 0; |
touchingFruitCount は、ゲームオーバー判定に触れている果物の数を入れる変数です。
intは整数を入れる型なので、0個、1個、2個のような数を管理するときに使います。
|
1 |
private float timer = 0f; |
timer は、果物がゲームオーバー判定に触れている時間を数える変数です。
|
1 |
private GameManager2D gameManager2D; |
gameManager2D は、GameManager2Dコンポーネントを入れるための変数です。
この変数を使い、GameOverLineスクリプトから GameManager2Dコンポーネントの GameOver() 関数を呼び出せます。
Startメソッドの処理
|
1 2 3 4 |
void Start() { gameManager2D = FindAnyObjectByType<GameManager2D>(); } |
Start()では、FindAnyObjectByType<GameManager2D>() を使って、Scene 内にある GameManager2D を探して取得しています。
GameOverLineスクリプトからゲームオーバー処理を直接行うのではなく、GameManager2Dに用意したGameOver() 関数を呼び出す形にしています。
Updateメソッドの処理
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
void Update() { if (touchingFruitCount > 0) { timer += Time.deltaTime; if (timer >= gameOverTime) { gameManager2D.GameOver(); } } else { timer = 0f; } } |
Update()では、ゲームオーバー判定に果物が触れているかを毎フレーム確認しています。
|
1 |
if (touchingFruitCount > 0) |
touchingFruitCount が1以上の場合は、今ゲームオーバー判定用領域に果物が触れている状態です。
|
1 |
timer += Time.deltaTime; |
その間は timer に Time.deltaTime を足していきます。
|
1 |
if (timer >= gameOverTime) |
そして、timer が gameOverTime 以上になったら、指定した時間だけ果物がゲームオーバー判定に触れ続けたことになります。
|
1 |
gameManager2D.GameOver(); |
そのタイミングで、GameManager2D.GameOver() を呼び出してゲームオーバーにします。
|
1 2 3 4 |
else { timer = 0f; } |
touchingFruitCount が0の場合は、ゲームオーバー判定に触れている果物がありません。そのため、timer を 0f に戻します。これにより、果物が一度ラインから離れた場合は、ゲームオーバーまでの時間を最初から数え直しています。
OnTriggerEnter2D の処理
|
1 2 3 4 5 6 7 |
void OnTriggerEnter2D(Collider2D collision) { if (collision.GetComponent<FruitItem>() != null) { touchingFruitCount++; } } |
OnTriggerEnter2D は、トリガーに設定した Collider2D に別の Collider2D型を持つオブジェクト(=collisionと名付けている)が入った瞬間に呼ばれます。
|
1 |
if (collision.GetComponent<FruitItem>() != null) |
ここでは、触れた相手であるcollisionにFruitItemコンポーネントが付いているかを確認しています。
|
1 |
touchingFruitCount++; |
FruitItemコンポーネントが付いていれば果物なので、touchingFruitCount に1を足します。これでゲームオーバー判定に触れている果物の数が増えます。
++とは?
touchingFruitCount++ は、touchingFruitCount に1を足す書き方でインクリメントと呼ばれます。
つまり、次の処理と同じ意味です。
|
1 |
touchingFruitCount = touchingFruitCount + 1; |
反対に、touchingFruitCount– は、touchingFruitCount から1を引く書き方でデクリメントと呼ばれます。
つまり、次の処理と同じ意味です。
|
1 |
touchingFruitCount = touchingFruitCount - 1; |
このように、++ と — は、数を1つだけ増やしたり減らしたりするときによく使われます。
OnTriggerExit2D の処理
|
1 2 3 4 5 6 7 |
void OnTriggerExit2D(Collider2D collision) { if (collision.GetComponent<FruitItem>() != null) { touchingFruitCount--; } } |
OnTriggerExit2Dメソッドは、トリガーに設定した Collider から別の Collider である collision が出た瞬間に呼ばれます。
つまり、ここではゲームオーバー判定用領域から何らかのCollider 2D型を持つcollisionが出た時をまず検知します。
|
1 |
if (collision.GetComponent<FruitItem>() != null) |
その検知した離れていく相手がFruitItemコンポーネントを持っているかを確認しています。
|
1 |
touchingFruitCount--; |
FruitItemコンポーネントが付いていれば果物なので、touchingFruitCount から1を引きます。これで、ゲームオーバー判定から離れた果物の数を反映できます。
このように、触れたときに1を足し、離れたときに1を引くことで、今ゲームオーバー判定に触れている果物の数を正確に管理できます。
トリガー(Trigger) とは?
今回のスクリプトでは、OnTriggerEnter2D と OnTriggerExit2D を使っています。
前回までは、OnCollisionEnter2D を使って果物同士の衝突を判定しました。
Collision は、物理的にぶつかったことを判定する仕組みです。例えば、果物同士がぶつかる、床に当たって止まる、壁に当たって跳ね返る、といった物理的な動きに使います。
一方で Trigger は、物理的にぶつけるためではなく、「その場所に入ったか」、「その場所から出たか」を調べるためのセンサーです。
今回は果物がそのラインに触れているかを調べるためのセンサーとして使います。
動作を確認しよう
ここまでできたら、Ctrl + S で必ず保存して Unity に戻りましょう。
まずヒエラルキー画面で GameManagerオブジェクトを選択します。
Inspector画面の Game Over Text と書いてあるところにHierarchy にある GameOverText をドラッグ&ドロップして設定しましょう。
次に、判定用のオブジェクトを作成します。Hierarchy で右クリックして、Create Empty を選択します。作成したオブジェクトの名前を GameOverLine に変更しましょう。
作成したGameOverLineオブジェクトに、GameOverLineスクリプトをドラッグ&ドロップしてアタッチします。
次に、GameOverLine を選択して、位置とサイズを以下のように変更します。
続いて、当たり判定用の 2D Colliderコンポーネントを追加します。GameOverLine を選択した状態で、Inspector の Add Component をクリックします。Physics 2D > Box Collider 2D を追加しましょう。
ここで重要なのは、この当たり判定は物理的にぶつけるためではなく、センサーとして使うことです。
そのためにBox Collider 2D の Is Trigger にチェックを入れましょう。

↑BoxCollider2Dコンポーネントをアタッチすると黄色い枠が表示されると思います。
この枠がGameOverLineの当たり判定領域で果物が一定時間以上触れるとゲームオーバーになるわけですね。
ここまでできたらゲームを再生して確認します。
果物が上まであふれて、GameOverLineオブジェクトに一定時間触れ続けたときに GameOverTextオブジェクトがアクティブになり、Game Overと表示されれば成功です。
リトライ処理を作ろう
現状はゲームオーバーになったら一度再生を止めないともう一度遊ぶことができません。
そこでゲームオーバーになったときにリトライボタンを表示し、ボタンを押すと最初からやり直せるようにします。
リトライボタンを用意しよう
Hierarchy画面から Canvas を選択します。右クリックから UI > Button – TextMeshPro を選択して、名前を RetryButton に変更しましょう。
Button を作成すると、ボタンの中にテキストが自動で一緒に作成されます。今回はボタン画像だけを使うので、子オブジェクトにあるテキストは削除しておきましょう。
次に、RetryButton の位置とサイズを以下のように変更します。
ImageコンポーネントのSource Imageに設定されているスプライト画像は最初に用意したリトライボタンの画像に変更しておきましょう。以下の画像のように検索するかProject画面からリトライボタン画像をドラッグ&ドロップして設定すればOKです。
ここで今回の要になる部分を確認しておきます。
Buttonコンポーネントの中には、On Click という項目があります。
ここに関数を設定しておくとボタンが押されたときにその関数を呼び出せます。
試しに確認してみましょう。
On Click の + をクリックします。そこに Hierarchy画面から GameManager をドラッグ&ドロップして設定し、呼び出す関数に GameOver を設定してみましょう。
この状態でゲームを再生し、ボタンを押してみます。
ボタンを押したときゲームオーバーになると思います。
このように、Buttonコンポーネントにはクリックされたときに指定した関数を呼び出せる便利な機能があります。
注意点
プロジェクトの規模が大きくなった時、スクリプト以外の場所から関数をたくさん呼び出してしまうと、どこから処理が動いているのかわかりにくくなるのです。
原因がわかりにくいバグにつながることもあるため、Buttonコンポーネントから関数を呼び出す使い方は必要最低限にしておくことをおすすめします。
今回はリトライボタンのように、ボタンを押したときに行う処理がはっきりしているので、On Click を使って設定していきます。
スクリプトを書こう
今回はGameManager2DスクリプトにRetry関数を作成し、その関数を RetryButtonコンポーネントの On Click から呼び出します。
Projectウィンドウから Assets > Scripts フォルダを開き、GameManager2D をダブルクリックで開きます。
開けたら、以下のように追記しましょう。
GameManager2D
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 |
using TMPro; using UnityEngine; using UnityEngine.SceneManagement; public class GameManager2D : MonoBehaviour { [Header("Fruits")] public GameObject[] fruitPrefabs; public int maxStartLevel = 3; [Header("SpawnSettings")] public float spawnY = 4f; public float minX = -2.5f; public float maxX = 2.5f; public float nextFruitDelay = 0.8f; [Header("UI")] public SpriteRenderer nextFruitPreview; public TextMeshProUGUI scoreText; public GameObject gameOverText; public GameObject retryButton; private int currentLevel; private GameObject currentFruit; private float Timer; private int nextLevel; private int score = 0; private bool isGameOver = false; // Start is called once before the first execution of Update after the MonoBehaviour is created void Start() { currentLevel = RandomStartLevel(); nextLevel = RandomStartLevel(); CreateFruit(); //次の果物を表示 nextFruitPreview.sprite = fruitPrefabs[nextLevel].GetComponent<SpriteRenderer>().sprite; //スコアUIをセット scoreText.text = "Score: " + score.ToString(); } // Update is called once per frame void Update() { if (isGameOver) { return; } if (currentFruit == null) { Timer += Time.deltaTime; if (Timer > nextFruitDelay) { CreateFruit(); Timer = 0; } return; } if (Input.GetMouseButton(0)) { Vector3 mousePosition = Camera.main.ScreenToWorldPoint(Input.mousePosition); float x = Mathf.Clamp(mousePosition.x, minX, maxX); currentFruit.transform.position = new Vector3(x, spawnY, 0f); } if (Input.GetMouseButtonUp(0)) { currentFruit.GetComponent<Rigidbody2D>().simulated = true; currentFruit.GetComponent<Collider2D>().enabled = true; currentFruit = null; currentLevel = nextLevel; nextLevel = RandomStartLevel(); //次の果物を表示 nextFruitPreview.sprite = fruitPrefabs[nextLevel].GetComponent<SpriteRenderer>().sprite; } } int RandomStartLevel() { return Random.Range(0, maxStartLevel + 1); } void CreateFruit() { currentFruit = Instantiate(fruitPrefabs[currentLevel], new Vector3(0f, spawnY, 0f), Quaternion.identity); currentFruit.GetComponent<Rigidbody2D>().simulated = false; currentFruit.GetComponent<Collider2D>().enabled = false; } public void AddScore(int addScore) { score += addScore; scoreText.text = "Score: " + score.ToString(); } public void GameOver() { isGameOver = true; gameOverText.SetActive(true); retryButton.SetActive(true); } public void Retry() { SceneManager.LoadScene(SceneManager.GetActiveScene().name); } } |
スクリプトの解説
今回の変更では、ゲームオーバー時にリトライボタンを表示しボタンを押すと現在のシーンを再読み込みする処理を作っています。
まず、SceneManager機能を使えるようにするために以下の1行を追加しています。
|
1 |
using UnityEngine.SceneManagement; |
SceneManagerはシーンの読み込みに使うUnityエンジンに内蔵された機能です。
今回は今遊んでいるシーンをもう一度読み込むために使います。
変数宣言
|
1 2 3 4 5 |
[Header("UI")] public SpriteRenderer nextFruitPreview; public TextMeshProUGUI scoreText; public GameObject gameOverText; public GameObject retryButton; |
retryButtonは、ゲームオーバー時に表示する RetryButton を入れるための変数です。
のちほどGameOverTextオブジェクトと同じように最初は非アクティブにして隠します。
そしてゲームオーバーになったタイミングでスクリプトから表示させます。
GameOver関数の処理
GameOver() 関数には、次の処理を追加しています。
|
1 2 3 4 5 6 |
public void GameOver() { isGameOver = true; gameOverText.SetActive(true); retryButton.SetActive(true); } |
SetActive(true) は、非アクティブになっているオブジェクトをアクティブにする処理です。ゲームオーバーになった瞬間に GameOverText だけでなく、RetryButton も表示されます。
Retry関数の処理
最後にRetry() 関数を追加しています。
|
1 2 3 4 |
public void Retry() { SceneManager.LoadScene(SceneManager.GetActiveScene().name); } |
この関数が呼ばれると、現在開いているシーンをもう一度読み込みます。
|
1 |
SceneManager.GetActiveScene().name |
SceneManager.GetActiveScene() で、現在アクティブなシーンを取得しています。そのあとに .name を付けることで、そのシーンの名前を取得しています。
|
1 |
SceneManager.LoadScene(SceneManager.GetActiveScene().name); |
LoadScene()は、()内に指定したシーンを読み込む処理です。今回は現在のシーン名を指定しているので今遊んでいるシーンを再読み込みします。
Unityでゲームを再生して、止めてからもう一度再生するとゲームは最初の状態に戻ります。
今回のリトライはそれと同じように現在のシーンを読み込み直して最初から遊べるようにする仕組みです。
動作を確認しよう
ここまでできたら、Ctrl + S で必ず保存して Unity に戻りましょう。
まず、RetryButtonオブジェクトを選択します。
先ほど確認用に On Click を GameOver に設定しました。呼び出す関数を Retry に変更しましょう。GameManager2D > Retry() を選択します。
次に、RetryButton は GameOverText と同じように最初は隠しておきます。Inspector の左上にあるチェックボックスを外し、非アクティブオブジェクトにしておきましょう。
次に、Hierarchy画面のGameManagerオブジェクトを選択します。
Inspector画面の Retry Button に、Hierarchy画面にある RetryButton をドラッグ&ドロップで割り当てましょう。
ここまでできたらゲームを再生して確認します。
果物が上まであふれてゲームオーバーになったときにリトライボタンが表示され、そのボタンを押して最初からやり直せれば成功です。
効果音を追加しよう
スイカゲーム風パズルの面白さとして合体するときの音の気持ちよさも大切です。
見た目だけでも遊べますが音があると操作したときの反応がわかりやすくなりゲームとしての手触りもよくなります。
今回は次の4つに効果音を追加していきます。
- ドロップ音
- 合体音
- ゲームオーバー音
- ダブルスイカ音
下準備をしよう
音を再生するためにまずは下準備をします。
GameManagerオブジェクトを選択して、Inspector画面の Add Component から Audio > Audio Source を追加しましょう。
Audio Source は、Unityで音を再生するためのコンポーネントです。これを付けることで効果音を再生できるようになります。
次に、Project ウィンドウの Assets内で右クリックします。Create > Folder を選択して、名前を Sounds にしましょう。
以下のリンクから4つの効果音をダウンロードしてください。
ダウンロードできたら、作成した Sounds フォルダに入れましょう。
ドロップ音とゲームオーバー音を作成しよう
まずはドロップ音とゲームオーバー音を追加します。
この2つを一緒に作成する理由はどちらも GameManager2Dスクリプトから制御できるからです。
ドロップ音はマウスを離して果物を落とした瞬間に再生します。ゲームオーバー音はGameOver() 関数が呼ばれたタイミングで再生します。
それでは実際に追加していきましょう。
Project ウィンドウから Assets > Scripts フォルダを開きます。
GameManager2Dスクリプトをダブルクリックで開き、以下のように追記しましょう。
GameManager2D
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 |
using TMPro; using UnityEngine; using UnityEngine.SceneManagement; public class GameManager2D : MonoBehaviour { [Header("Fruits")] public GameObject[] fruitPrefabs; public int maxStartLevel = 3; [Header("SpawnSettings")] public float spawnY = 4f; public float minX = -2.5f; public float maxX = 2.5f; public float nextFruitDelay = 0.8f; [Header("UI")] public SpriteRenderer nextFruitPreview; public TextMeshProUGUI scoreText; public GameObject gameOverText; public GameObject retryButton; [Header("Sound")] public AudioSource audioSource; public AudioClip dropSound; public AudioClip gameOverSound; private int currentLevel; private GameObject currentFruit; private float Timer; private int nextLevel; private int score = 0; private bool isGameOver = false; // Start is called once before the first execution of Update after the MonoBehaviour is created void Start() { currentLevel = RandomStartLevel(); nextLevel = RandomStartLevel(); CreateFruit(); //次の果物を表示 nextFruitPreview.sprite = fruitPrefabs[nextLevel].GetComponent<SpriteRenderer>().sprite; //スコアUIをセット scoreText.text = "Score: " + score.ToString(); } // Update is called once per frame void Update() { if (isGameOver) { return; } if (currentFruit == null) { Timer += Time.deltaTime; if (Timer > nextFruitDelay) { CreateFruit(); Timer = 0; } return; } if (Input.GetMouseButton(0)) { Vector3 mousePosition = Camera.main.ScreenToWorldPoint(Input.mousePosition); float x = Mathf.Clamp(mousePosition.x, minX, maxX); currentFruit.transform.position = new Vector3(x, spawnY, 0f); } if (Input.GetMouseButtonUp(0)) { currentFruit.GetComponent<Rigidbody2D>().simulated = true; currentFruit.GetComponent<Collider2D>().enabled = true; currentFruit = null; currentLevel = nextLevel; nextLevel = RandomStartLevel(); //次の果物を表示 nextFruitPreview.sprite = fruitPrefabs[nextLevel].GetComponent<SpriteRenderer>().sprite; //ドロップ音再生 audioSource.PlayOneShot(dropSound); } } int RandomStartLevel() { return Random.Range(0, maxStartLevel + 1); } void CreateFruit() { currentFruit = Instantiate(fruitPrefabs[currentLevel], new Vector3(0f, spawnY, 0f), Quaternion.identity); currentFruit.GetComponent<Rigidbody2D>().simulated = false; currentFruit.GetComponent<Collider2D>().enabled = false; } public void AddScore(int addScore) { score += addScore; scoreText.text = "Score: " + score.ToString(); } public void GameOver() { isGameOver = true; gameOverText.SetActive(true); retryButton.SetActive(true); audioSource.PlayOneShot(gameOverSound); } public void Retry() { SceneManager.LoadScene(SceneManager.GetActiveScene().name); } } |
スクリプトの解説
今回の変更で果物を落としたときのドロップ音とゲームオーバーになったときのゲームオーバー音を再生できるようにしています。
変数宣言
|
1 2 3 4 |
[Header("Sound")] public AudioSource audioSource; public AudioClip dropSound; public AudioClip gameOverSound; |
audioSource は、音を再生するための Audio Source を入れる変数です。
先ほどGameManagerオブジェクトに付けた Audio Sourceコンポーネントをここに割り当てて音を鳴らします。
|
1 |
public AudioClip dropSound; |
dropSound は、果物を落としたときに再生する効果音を入れる変数です。
AudioClip は、音声ファイルを入れるための型です。Projectウィンドウに入れた効果音ファイルを Inspector画面から割り当てて使います。
|
1 |
public AudioClip gameOverSound; |
gameOverSound は、ゲームオーバーになったときに再生する効果音を入れる変数です。
ドロップ音の処理
ドロップ音は、Input.GetMouseButtonUp(0) の中で再生しています。
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
if (Input.GetMouseButtonUp(0)) { currentFruit.GetComponent<Rigidbody2D>().simulated = true; currentFruit.GetComponent<Collider2D>().enabled = true; currentFruit = null; currentLevel = nextLevel; nextLevel = RandomStartLevel(); //次の果物を表示 nextFruitPreview.sprite = fruitPrefabs[nextLevel].GetComponent<SpriteRenderer>().sprite; //ドロップ音再生 audioSource.PlayOneShot(dropSound); } |
Input.GetMouseButtonUp(0) は、左クリックを離した瞬間に実行される処理です。この中で dropSound を再生することで、果物を落としたタイミングに合わせて音が鳴るようになります。
|
1 2 |
//ドロップ音再生 audioSource.PlayOneShot(dropSound); |
このPlayOneShot関数の処理でdropSound に設定した効果音を1回だけ再生しています。
PlayOneShot関数とは?
PlayOneShot関数は指定した効果音を1回だけ再生する処理です。
効果音はドロップ音や合体音のように短い音を重ねて鳴らすことが多いです。そのような一度だけ鳴らしたい音はPlayOneShotで再生します。
ゲームオーバー音の処理
ゲームオーバー音は、GameOver() 関数の中で再生しています。
|
1 2 3 4 5 6 7 |
public void GameOver() { isGameOver = true; gameOverText.SetActive(true); retryButton.SetActive(true); audioSource.PlayOneShot(gameOverSound); } |
GameOver() 関数は、ゲームオーバーになったときに呼び出される関数です。ここで gameOverText と retryButton を表示したあとに、gameOverSound を再生しています。
これで、ゲームオーバーになったタイミングでゲームオーバー音が1回鳴ります。
動作を確認しよう
ここまでできたら、Ctrl + S で必ず保存して Unity に戻りましょう。
Hierarchy画面でGameManagerオブジェクトを選択します。
Inspector画面に Sound という項目が追加されています。
まずは Audio Source と記載されている欄に Hierarchy画面の GameManagerオブジェクトをドラッグ&ドロップで紐づけます。
次に Drop Sound と Game Over Sound の欄にそれぞれ音声アセットを以下のように割り当てます。Projects画面のSoundsフォルダにある音声ファイルを以下の画像を参考にしてドラッグ&ドロップすればOKです。
割り当てできたらUnityエディタでゲームを再生して確認してみましょう。
現在はゲームオーバー音が何重にも重なって連続で聞こえると思います。GameOver() 関数が何回も呼ばれていることが原因です。
GameOver() 関数を呼び出している GameOverLineスクリプトに問題があります。
Projectウィンドウから Assets > Scripts フォルダを開き、GameOverLine をダブルクリックで開きましょう。
スクリプトを開いたらUpdateメソッドを見てみてください。
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
void Update() { if (touchingFruitCount > 0) { timer += Time.deltaTime; if (timer >= gameOverTime) { gameManager2D.GameOver(); } } else { timer = 0f; } } |
timer変数が gameOverTime 以上になったあとgameManager2D.GameOver(); を呼び出しています。
しかし、一度 GameOver() を呼んだあとにその処理を止める仕組みがありません。
Updateメソッドは毎フレーム実行されるので、条件を満たしている間は GameOver() が何度も呼ばれてしまいます。その結果、ゲームオーバー音も何度も再生され、音が重なって聞こえてしまいます。
そこで、以下のように追記しましょう。
GameOverLine
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 |
using UnityEngine; public class GameOverLine : MonoBehaviour { public float gameOverTime = 2f; private int touchingFruitCount = 0; private float timer = 0f; private GameManager2D gameManager2D; // Start is called once before the first execution of Update after the MonoBehaviour is created void Start() { gameManager2D = FindAnyObjectByType<GameManager2D>(); } // Update is called once per frame void Update() { if (gameManager2D.gameOverText.activeSelf) { return; } if (touchingFruitCount > 0) { timer += Time.deltaTime; if (timer >= gameOverTime) { gameManager2D.GameOver(); } } else { timer = 0f; } } void OnTriggerEnter2D(Collider2D collision) { if (collision.GetComponent<FruitItem>() != null) { touchingFruitCount++; } } void OnTriggerExit2D(Collider2D collision) { if (collision.GetComponent<FruitItem>() != null) { touchingFruitCount--; } } } |
スクリプトの解説
今回追加したのは、次の部分です。
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
void Update() { if (gameManager2D.gameOverText.activeSelf) { return; } if (touchingFruitCount > 0) { timer += Time.deltaTime; if (timer >= gameOverTime) { gameManager2D.GameOver(); } } else { timer = 0f; } } |
この処理は、gameManager2D にある gameOverText がアクティブになっているかを確認しています。
activeSelf は、そのオブジェクト自身がアクティブかを調べるためのものです。
gameOverText がアクティブなら、もうゲームオーバーになっていると判断できます。
アクティブならTrueを返すのでif文の中の処理を実行します。 return で Update の処理をここで終了します。
|
1 2 3 4 |
if (gameManager2D.gameOverText.activeSelf) { return; } |
これにより、その下にある gameManager2D.GameOver(); が再び実行されなくなります。結果として、GameOver() 関数が何度も呼ばれることを防ぎ、ゲームオーバー音が重なって再生される問題を防止できます。
動作を確認しよう
ここまでできたら、Ctrl + S で保存してUnityエディタに戻りましょう。
ゲームを再生して確認します。
ゲームオーバー時にゲームオーバー音が何度も重なって再生されなければ成功です。
果物のマージ音、ダブルスイカのマージ音を作成しよう
マージの処理は FruitItem スクリプトで制御しています。そのため、マージ音も FruitItem から制御すれば簡単です。
Project ウィンドウから Assets > Scripts フォルダを開き、FruitItem をダブルクリックで開きます。
開けたら、以下のように追記しましょう。
FruitItem
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 |
using UnityEngine; public class FruitItem : MonoBehaviour { public int level = 0; [Header("Sound")] public AudioClip mergeSound; private bool isMerging = false; private GameManager2D GameManager2D; // Start is called once before the first execution of Update after the MonoBehaviour is created void Start() { //シーン内から GameManager2D を探して取得 GameManager2D = FindAnyObjectByType<GameManager2D>(); } void OnCollisionEnter2D(Collision2D collision) { FruitItem otherFruit = collision.gameObject.GetComponent<FruitItem>(); if (otherFruit == null) { return; } if (level != otherFruit.level || isMerging || otherFruit.isMerging) { return; } int nextLevel = level + 1; GameObject nextFruitPrefab = null; if (nextLevel < GameManager2D.fruitPrefabs.Length) { nextFruitPrefab = GameManager2D.fruitPrefabs[nextLevel]; } Vector3 mergePosition = (transform.position + otherFruit.transform.position) / 2f; isMerging = true; otherFruit.isMerging = true; if (nextFruitPrefab != null) { Instantiate(nextFruitPrefab, mergePosition, Quaternion.identity); GameManager2D.AddScore(nextLevel * 10); } else { GameManager2D.AddScore(nextLevel * 20); } GameManager2D.audioSource.PlayOneShot(mergeSound); Destroy(otherFruit.gameObject); Destroy(gameObject); } } |
スクリプトの解説
この追加では果物がマージしたときに効果音を再生できるようにしています。
変数宣言
|
1 2 |
[Header("Sound")] public AudioClip mergeSound; |
mergeSound は、果物がマージしたときに鳴らす効果音を入れる変数です。
AudioClip は音声ファイルを入れるための型です。UnityのInspector画面から効果音を割り当てることで果物ごとに違うマージ音を設定できます。
マージ音を鳴らす処理
マージ音は、果物を削除する前に再生しています。
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
if (nextFruitPrefab != null) { Instantiate(nextFruitPrefab, mergePosition, Quaternion.identity); GameManager2D.AddScore(nextLevel * 10); } else { GameManager2D.AddScore(nextLevel * 20); } GameManager2D.audioSource.PlayOneShot(mergeSound); Destroy(otherFruit.gameObject); Destroy(gameObject); |
すでに GameManager2D に用意した Audio Source を使って、mergeSound を1回だけ再生しています。
今回、Audio Source は GameManager2D にまとめてあります。そのため、FruitItem側では新しく Audio Source を用意せず、GameManager2D の Audio Source を使って音を鳴らします。
動作を確認しよう
ここまでできたら、Ctrl + S で必ず保存して Unity に戻りましょう。
Project ウィンドウから Assets > Prefabs フォルダを開きます。まず、スイカ以外の Prefab をすべて選択しましょう。CTRL キーを押しながらクリックでまとめて選択することができます。
Inspector から、マージ音を以下のように設定します。
次に、スイカの Prefab を選択します。
スイカのマージ音を以下のように設定しましょう。
このように、スイカだけ別のマージ音を設定することでダブルスイカになったときの音をほかのマージ音と差別化できます。
設定できたら、ゲームを再生して確認してみましょう。
通常の果物がマージしたときにマージ音が鳴り、スイカ同士がマージしたときに歓声が鳴れば成功です。
プロジェクトファイルのダウンロード
今回の講座で作ったプロジェクトファイルのダウンロードリンクを用意しました。
まとめ
ここまででスイカゲーム風マージパズルゲームが完成しました!
実際遊んでみるとかなり夢中になれるかと思います^^
- 果物をランダムに表示しマウスで左右に動かして落下させる処理
- 同じ果物同士がぶつかったときにマージする処理
- 次に出てくる果物を表示
- スコアの加算
- ゲームオーバー&リトライ
- 効果音
とスイカゲーム風マージパズルに必要な要素を順を追って追加してきました。
ただし、ここをゴールにせずUnityゲーム開発のスタートラインにしてください。
今回作成したプロジェクトはここからまだまだ自由に改造できます。
例えばハイスコアをセーブして保存できるようにしたり、ランキング機能を追加したり、果物の種類を増やしたりできます。
ここまで学んできた内容を活かしてぜひ自分だけのパズルゲーム作りに挑戦してみてください!
現場レベルのゲーム制作が、すべてここで学べます。




















































コメント