この講座ではUnity 初心者の方でも作れる「スイカゲーム風パズルゲーム」を作成します。
前回は果物をランダムに表示させ、マウスで左右に移動して落下させるところまで作成しました。
前回の記事:

今回は同じ果物同士がぶつかったときに合体する仕組みを作成していきます。
また、次に出てくる果物があらかじめ分かるようなUIも作成します。
最後にゲーム性として重要なスコア機能も追加していきましょう。
果物を合体させる処理を作ろう
まずは果物を合体させる仕組みを考えてみましょう。
今回の合体処理では、果物に自分のレベルを持たせる方式をとります。
ここでいうレベルとは、第2回で GameManager2D の fruitPrefabs に登録した順番のことです。
たとえば、fruitPrefabs の0番目に登録したさくらんぼをレベル0、1番目に登録したいちごをレベル1として扱います。
表にすると、以下のようになります。
| レベル | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |
| 果物 | ![]() |
![]() |
![]() |
![]() |
![]() |
![]() |
![]() |
![]() |
![]() |
同じレベルの果物同士がぶつかったときだけ、次のレベルの果物に変更してレベルアップさせます。
- ぶつかった相手が果物かどうかを確認
- 自分と相手の果物レベルが同じかどうかを確認
→ レベルが同じなら、同じ果物同士がぶつかったということ - 自分か相手がすでに合体処理中ではないかも確認
(この確認を入れないと、同じ衝突から合体処理が何度も呼ばれてしまい、次のレベルの果物が複数生成されるバグが発生します) - 条件を満たしていたら、自分のレベルに1を足して次のレベルの果物を生成
- 合体前の自分と相手の果物を削除
マージパズルの合体機能のスクリプトを書こう
では、この仕組みを実際にスクリプトで作成していきましょう。
Project ウィンドウから Assets > Scripts フォルダを開きます。Scripts フォルダ内で右クリックし、Create > MonoBehaviour Script を選択します。作成したスクリプトの名前は「FruitItem」にしましょう。
名前を変更できたら、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 |
using UnityEngine; public class FruitItem : MonoBehaviour { public int level = 0; 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 = GameManager2D.fruitPrefabs[nextLevel]; Vector3 mergePosition = (transform.position + otherFruit.transform.position) / 2f; isMerging = true; otherFruit.isMerging = true; Instantiate(nextFruitPrefab, mergePosition, Quaternion.identity); Destroy(otherFruit.gameObject); Destroy(gameObject); } } |
(↑Updateメソッドは使わないので削除しています)
スクリプトの解説
果物同士がぶつかったときに同じレベルの果物なら次のレベルの果物へ合体させる処理を実装ています。
スクリプトの大まかな流れとしては以下の通り。
- 果物に自分のレベルを持たせる
- ぶつかった相手に FruitItem スクリプトが付いているか確認
→ FruitItem が取得できなければ、相手は果物ではないので処理を終了 - FruitItem が取得できた場合は、自分と相手のレベルが同じか確認
→ レベルが違う場合は、違う果物同士なので処理を終了 - 自分か相手がすでに合体処理中ではないか確認
→ ここで確認しておくことで、同じ果物が複数回合体してしまうことを防ぎます。 - ここまでの確認を通過したら、次のレベルの果物を生成
- 合体前の自分と相手の果物を削除
変数宣言
|
1 |
public int level = 0; |
level は、この果物のレベルを表す変数です。
public にしているので、アタッチしたゲームオブジェクトのInspector画面から果物ごとにレベルを設定できます。
|
1 |
private bool isMerging = false; |
isMerging は、この果物が合体処理中かどうかを管理する変数です。この変数を使うことで、同じ果物から合体処理が何度も実行されることを防ぎます。
|
1 |
private GameManager2D GameManager2D; |
GameManager2D はシーン内にある GameManager2D を入れておく変数です。
合体後に生成する果物プレハブは GameManager2D の fruitPrefabs に登録しているので、このスクリプトから GameManager2D にアクセスできるようにしています。
Start の処理
Startメソッドではシーン内にある GameManager2D を探して取得しています。
|
1 2 3 4 5 |
void Start() { //シーン内から GameManager2D を探して取得 GameManager2D = FindAnyObjectByType<GameManager2D>(); } |
FindAnyObjectByTypeメソッドはシーン内から指定した種類のコンポーネントを探す処理です。<>で囲んだコンポーネントを探してくれます。
今回は GameManager2Dコンポーネントを探して、GameManager2D変数に入れています(同じ名前で少々ややこしいですが、<>の中に書いてるのがコンポーネントの型で左辺にあるのが保存用変数名です。左辺は他の命名にしてもOKです)。
これでFruitItem スクリプトから GameManager2D の fruitPrefabs を使えるようになります。
衝突判定の処理
|
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 |
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 = GameManager2D.fruitPrefabs[nextLevel]; Vector3 mergePosition = (transform.position + otherFruit.transform.position) / 2f; isMerging = true; otherFruit.isMerging = true; Instantiate(nextFruitPrefab, mergePosition, Quaternion.identity); Destroy(otherFruit.gameObject); Destroy(gameObject); } |
今回新しく OnCollisionEnter2D を使います。
OnCollisionEnter2D は、 Collider (当たり判定)同士がぶつかった瞬間に呼ばれる関数です。
|
1 |
void OnCollisionEnter2D(Collision2D collision) |
OnCollision には、主に次の3種類があります。
- OnCollisionEnter2D
ぶつかった瞬間に1回だけ呼ばれます。 - OnCollisionStay2D
ぶつかっている間、繰り返し呼ばれます。 - OnCollisionExit2D
ぶつかっていた状態から離れた瞬間に1回だけ呼ばれます。
今回は、果物同士がぶつかった瞬間に合体判定をしたいので、OnCollisionEnter2D を使います。
(Collision2D collision) の collision には、「何にぶつかったのか」という衝突情報が入っています。たとえば、ぶつかった相手の GameObject を調べたいときは、collision.gameObject を使います。
今回の処理では、ぶつかった相手が果物かどうかを調べるために、相手の GameObject から FruitItem スクリプトを取得しています。
|
1 |
FruitItem otherFruit = collision.gameObject.GetComponent<FruitItem>(); |
GetComponentメソッドは、<>の中で指定したゲームオブジェクトに付いているコンポーネントを取得する処理です。
ここではぶつかった相手に FruitItem が付いているかを確認し、その中身をotherFruitに格納しています。
|
1 2 3 4 |
if (otherFruit == null) { return; } |
otherFruit が null の場合、相手に FruitItem が付いていないという意味です。つまり、ぶつかった相手は果物ではありません。
床や壁にぶつかったときは合体処理をする必要がないので return で処理を終了します。
相手が果物だった場合は合体できる条件を確認します。
|
1 2 3 4 |
if (level != otherFruit.level || isMerging || otherFruit.isMerging) { return; } |
level != otherFruit.level は、自分と相手のレベルが違うという条件です。レベルが違う場合は、違う果物同士なので合体しません。
|| は「または」という意味です。
たとえば、「A または B または C」という条件がある場合、どれか1つでも true なら条件を満たします。今回のif文では、
- 自分と相手のレベルが違う
- 自分が既にに合体処理中
- 相手が既に合体処理中
このどれか1つでも当てはまる場合、return で処理を終了します。
isMerging の確認を入れないと、同じ衝突から合体処理が複数回呼ばれてしまいます。
場合によっては果物が無限生成され、処理落ちします。そのため、合体処理では「既に合体処理中か」を確認することが重要です。
ここまで通過すれば合体できると判断できます。
|
1 |
int nextLevel = level + 1; |
次に生成する果物のレベルを用意します。
自分の level に1を足して、nextLevel に入れています。
たとえば、level が1のいちごの場合、1を足すことで nextLevel は2のぶどうになります。
| レベル | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |
| 果物 | ![]() |
![]() |
![]() |
![]() |
![]() |
![]() |
![]() |
![]() |
![]() |
|
1 |
GameObject nextFruitPrefab = GameManager2D.fruitPrefabs[nextLevel]; |
次に、GameManager2D の fruitPrefabs から nextLevel に対応する果物プレハブを取得します。
fruitPrefabs は、GameManager2D に登録した果物プレハブの一覧です。nextLevel の番号を使うことで、次に生成する果物プレハブを選ぶことができます。
|
1 |
Vector3 mergePosition = (transform.position + otherFruit.transform.position) / 2f; |
ここでは、合体後の果物を生成する位置を決めています。
- transform.position は、自分の位置
- otherFruit.transform.position は、相手の果物の位置
この2つの位置を足して2で割ることで、自分と相手のちょうど真ん中の位置を求めています。これにより、合体後の果物を2つの果物の間に自然に生成できます。
|
1 2 |
isMerging = true; otherFruit.isMerging = true; |
次に、自分と相手を合体処理中にします。
自分だけでなく、相手の isMerging も true にしています。正直この処理はなくても大丈夫かもしれませんが奇跡的に同じ果物が別の果物とも同時に合体してしまう可能性を防いでいます。
|
1 |
Instantiate(nextFruitPrefab, mergePosition, Quaternion.identity); |
Instantiate は、Prefab から新しいオブジェクトを生成する処理です。
今回は、nextFruitPrefab を mergePosition の位置に生成しています。
Quaternion.identity は、回転なしで生成するという意味です。
|
1 2 |
Destroy(otherFruit.gameObject); Destroy(gameObject); |
最後に合体前の相手の果物と自分の果物を削除します。
これで同じ果物同士がぶつかったときに元の2つの果物が消え、次のレベルの果物が1つ生成されるようになります。
動作を確認しよう
ここまで書けたら、CTRL + S で必ず保存して Unityエディタに戻りましょう。
まずは、作成した FruitItem スクリプトを果物の Prefab に設定します。Project ウィンドウから Assets > Prefabs フォルダを開きます。Prefabs フォルダの中にある果物を、Shift キーを使ってすべて選択しましょう。
選択できたら、Inspector から Add Component をクリックし、Scripts > FruitItem を選択します。
これで、すべての果物Prefabに FruitItem スクリプトを追加できます。
次に、それぞれの果物に Level を設定します。まず、さくらんぼを選択して、Inspector から Level を確認します。
さくらんぼは 0 のままで大丈夫です。
次に、いちごを選択して、Level を 1 に変更します。
このように、表を参考にしながら、それぞれの果物に Level を設定しましょう。
| レベル | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |
| 果物 | ![]() |
![]() |
![]() |
![]() |
![]() |
![]() |
![]() |
![]() |
![]() |
スイカまでできたら、一度再生して確認します。
同じ果物同士がぶつかって、次のレベルの果物に合体できたら成功です。
ダブルスイカのエラー
では、スイカとスイカが合体したときはどうなるでしょうか。実際に試してみましょう。
Hierarchy から GameManager を選択します。
Inspector で Max Start Level を 8 に変更しましょう。
これで、スイカが出てくるようになります。再生して、スイカ同士を合体させてみましょう。
すると、エラーが出てしまいます。
これは、FruitItem スクリプトで、存在しないレベル9の果物を取得しようとしているためです。
| 6 | 7 | 8 | 9 |
![]() |
![]() |
![]() |
? ? ? |
現在の fruitPrefabs には、スイカの次の果物が登録されていません。そのため、スイカ同士が合体したときに、スイカの次のレベルの果物を取得しようとしてエラーになります。
直し方は簡単です。
次のレベルの果物が fruitPrefabs に存在するかを確認し、存在する場合だけ次の果物を生成するようにします。存在しない場合は、次の果物を生成せず、スイカ同士が消えるだけになります。
これで、ダブルスイカを作ったときのエラーを防ぐことができます。
スクリプトを書こう
では、実際に修正してみましょう。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 |
using UnityEngine; public class FruitItem : MonoBehaviour { public int level = 0; 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); } Destroy(otherFruit.gameObject); Destroy(gameObject); } } |
スクリプトの解説
今回追加したのは、スイカの次に生成する果物がない場合でもエラーが出ないようにする処理です。
まず、次の果物を入れるための nextFruitPrefab を null で用意します。
|
1 |
GameObject nextFruitPrefab = null; |
次に、nextLevel が fruitPrefabs の数を超えていないかを確認します。
|
1 2 3 4 |
if (nextLevel < GameManager2D.fruitPrefabs.Length) { nextFruitPrefab = GameManager2D.fruitPrefabs[nextLevel]; } |
fruitPrefabs.Length は、fruitPrefabs に登録されている果物の数です。
nextLevel が fruitPrefabs.Length より小さいときだけ、GameManager2D の fruitPrefabs から次の果物Prefabを取得します。
これにより、次の果物が用意されている場合は nextFruitPrefab に果物Prefabが入り、用意されていない場合は null のままになります。
そのあと、次の果物を生成する前にif文で確認します。
|
1 2 3 4 |
if (nextFruitPrefab != null) { Instantiate(nextFruitPrefab, mergePosition, Quaternion.identity); } |
nextFruitPrefab != null は、nextFruitPrefab に果物Prefabが入っているかを確認する条件です。
nextFruitPrefab に果物Prefabが入っている場合だけ、Instantiateメソッドで次の果物を生成します。
反対に、nextFruitPrefab が null の場合は、Instantiateメソッドの処理は実行されません。その場合は、次の果物を生成せず、その下にある削除処理へ進みます。
|
1 2 |
Destroy(otherFruit.gameObject); Destroy(gameObject); |
次の果物が用意されていない場合でも合体前の2つの果物だけを削除できます。
↑処理の流れの違いとしてはこんな形になっています。
動作を確認しよう
ここまで書けたら、CTRL + S で必ず保存して Unity に戻りましょう。
再生して、ダブルスイカを作ってみましょう。
スイカ同士がぶつかったときに、次の果物が生成されず、スイカが消えるだけになっていれば成功です。
確認できたら、一度再生を止めましょう。
(Unityでは、再生中に Inspector画面から数値を変更しても、再生を止めると元に戻ってしまいます。)
確認ができたら、GameManager の Max Start Level を忘れずに 3 に戻しておきましょう。
Unity UIを使って次に出てくる果物がわかるようにしよう
今のように成り行きで果物が出てくるだけでも、それはそれでおもしろいかもしれません。
ですが、次に出てくる果物が予めわかるとプレイヤーは「次の果物はここに落とそう」と考えながら遊びやすくなります。
工夫や戦略が生まれてくるわけですね。これらはゲームの面白さを高めてくれます。
そこで今回は次に出てくる果物を画面に表示してみましょう。
表示するUIを用意しよう
まずは「Next」という案内テキストを作成します。
Hierarchy で右クリックし、UI > Text – TextMeshPro を選択します。作成したオブジェクトの名前を「Next」に変更しましょう。
TextMeshPro を初めて使う場合、TMP Importer が表示されます。Import TMP Essentials を選択しましょう。インポートが終わったら、TMP Importer の画面を閉じます。
ゲームにおけるUIとは
少々今さらですが、UIとはゲーム画面に表示する文字やボタンなどのことです。例えばスコア、体力ゲージ、ボタン、説明テキストなどはUIとして作成します。
今回作成するNextオブジェクトは、次に出る果物を表示する場所だとプレイヤーに伝えるための案内テキストです。
UnityではUIを作成すると、親に Canvas というオブジェクトも作成されます。Canvas はUIを表示するための土台になるオブジェクトです。UIの文字やボタンは、基本的に Canvas の中に配置して使います。
次に、作成された Canvas を選択しましょう。
Inspector から UI Scale Mode を Scale With Screen Size に変更します。
UI Scale Mode とは?
UI Scale Mode は、画面サイズが変わったときに UI の大きさをどのように扱うかを決める設定です。
Scale With Screen Size にすると画面サイズに合わせて UI の大きさが調整されます。
これによりゲーム画面のサイズが変わっても、UI が極端に小さくなったり大きくなったりしません。
次に、Next を選択して位置とサイズを以下のように調整しましょう。
テキストは「Next」にしておきます。
次は、実際に次の果物を表示する部分を作ります。
Hierarchy で右クリックし、Create Empty を選択します。作成したオブジェクトの名前を「NextFruit」に変更しましょう。
作成した NextFruit を選択し、以下のように位置を調整します。
次に、果物の画像を表示するためのコンポーネントを追加します。Inspector の Add Component から Rendering > Sprite Renderer を選択して追加しましょう。
Sprite Renderer とは?
Sprite Renderer は、2D画像を表示するためのコンポーネントです。
壁や床、果物にも Sprite Rendererコンポーネントが付いています。
今回は NextFruit に Sprite Renderer を追加することで、次に出てくる果物の画像を表示できるようにします。
スクリプトを書こう
ここまでの開発で出てくる果物はランダムに決めています。
では、次の果物を表示させるにはどうすればよいのでしょうか。
仕組みは簡単です。
あらかじめ次の果物までランダムで決めて保存する形にしておきます。
つまり、ランダムで抽選するタイミングを1つ早くするだけです。果物が出てくる瞬間にランダムで決めるのではなく、出てくる1つ前の段階で次の果物を決めておきます。
そして、現在の果物を落としたら、次の果物を現在の果物として使い、また新しく次の果物を決めます。
このように果物を順番にずらしていくことで、次に出てくる果物を表示できます。
では、実際にスクリプトから制御してみましょう。
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 |
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; private int currentLevel; private GameObject currentFruit; private float Timer; private int nextLevel; // 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; } // Update is called once per frame void Update() { 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; } } |
スクリプトの解説
このスクリプトでは現在出す果物に加え、次に出す果物もあらかじめ決めておきます。
変数宣言
|
1 2 |
[Header("UI")] public SpriteRenderer nextFruitPreview; |
nextFruitPreview は、次に出てくる果物の画像を表示するための変数です。
publicにしているので、Inspector画面から先ほど作成した NextFruit を設定できます。SpriteRenderer は、2D画像を表示するためのコンポーネントです。
|
1 |
private int nextLevel; |
nextLevel は次に出てくる果物のレベルを入れておく変数です。
currentLevel は今から生成する果物、nextLevel はその次に生成する果物を格納するために使います。
Start の処理
Startメソッドでは、最初に currentLevel と nextLevel をランダムに決めています。
|
1 2 3 4 5 6 7 8 |
void Start() { currentLevel = RandomStartLevel(); nextLevel = RandomStartLevel(); CreateFruit(); //次の果物を表示 nextFruitPreview.sprite = fruitPrefabs[nextLevel].GetComponent<SpriteRenderer>().sprite; } |
CreateFruitメソッドで currentLevel の果物を生成します。
nextFruitPreview に nextLevel の果物画像を表示します。ここで先ほどCanvas UIで設定した画像表示コンポーネントを操作しています。
|
1 2 |
//次の果物を表示 nextFruitPreview.sprite = fruitPrefabs[nextLevel].GetComponent<SpriteRenderer>().sprite; |
fruitPrefabs[nextLevel] で次に出てくる果物Prefabを取得しています。
その果物に付いているSpriteRendererコンポーネントから sprite を取得し、nextFruitPreview.sprite に入れています。
ここまでの処理で NextFruit に次の果物の画像が表示されます。
マウスを離したときの処理
次に、マウスを離したときの処理を見ていきましょう。
|
1 2 3 4 5 6 7 8 9 10 11 12 |
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; } |
ここでは、nextLevel を currentLevel に入れています。Next に表示にしていた果物を、次に生成する果物として使うということです。
そのあと、nextLevel を新しくランダムで決め直します。
↑まさにこのような処理を構築しています
最後に Next の表示を更新します。
|
1 2 |
//次の果物を表示 nextFruitPreview.sprite = fruitPrefabs[nextLevel].GetComponent<SpriteRenderer>().sprite; |
新しく決まった nextLevel の果物画像を、nextFruitPreview に表示しています。
果物を落とすたびに Next の表示も更新されます。
動作を確認しよう
ここまで書けたら、CTRL + S で必ず保存してUnityに戻りましょう。
Hierarchy から GameManager を選択します。
Inspector画面の Next Fruit Preview に、NextFruit を設定しましょう。
設定できたら再生して確認します。
画面に次に出てくる果物の画像が表示されていれば成功です。果物を落としたあと、Next に表示されていた果物が次に出てくることも確認しておきましょう。
スコアを追加しよう
ただ果物を合体させるだけではゲームとして少し物足りません。
そこで、合体したときにスコアが増えるようにしてゲームに意味を持たせていきましょう。
スコアテキストを用意しよう
まずはスコアを表示するためのテキストを作成します。
Hierarchy で Canvas を選択、右クリックし、UI > Text – TextMeshPro を選択します。作成したオブジェクトの名前を「ScoreText」に変更しましょう。
作成した ScoreText を選択して、位置とサイズを以下のように調整しましょう。
次に、Inspector から Auto Size にチェックを入れます。
Auto Size にチェックを入れると文字数に合わせてテキストの大きさが自動で調整されます。
今回はスコアの数字が増えていくので文字数が増える可能性があります。その際に文字がはみ出ないように設定しておきます。
スクリプトを書こう
ScoreText にスコアを表示できるようにしていきましょう。
まずは GameManager2D にスコアを管理する処理を追加します。そのあと、FruitItem から 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 |
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; private int currentLevel; private GameObject currentFruit; private float Timer; private int nextLevel; private int score = 0; // 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 (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(); } } |
スクリプトの解説
このスクリプトではスコアの値を GameManager2D で管理し、その値を ScoreText に表示します。
変数宣言
|
1 |
using TMPro; |
TextMeshProのUIをスクリプトで使うため、using TMPro; を追加しています。これを書いておくことで、TextMeshPro UGUI(Unityエンジンに搭載されているリッチな文字表示機能)を使えるようになります。
|
1 2 3 |
[Header("UI")] public SpriteRenderer nextFruitPreview; public TextMeshProUGUI scoreText; |
scoreText は、画面にスコアを表示するための TextMeshPro の UI です。
public にしているので、Inspector から先ほど作成した ScoreText を設定できます。
|
1 |
private int score = 0; |
score は、現在のスコアを入れておく変数です。
スコアは 0、10、20 のような整数で管理するので int を使います。
Start の処理
Start では、ゲーム開始時にスコア表示をセットしています。
|
1 2 3 4 5 6 7 8 9 10 |
void Start() { currentLevel = RandomStartLevel(); nextLevel = RandomStartLevel(); CreateFruit(); //次の果物を表示 nextFruitPreview.sprite = fruitPrefabs[nextLevel].GetComponent<SpriteRenderer>().sprite; //スコアUIをセット scoreText.text = "Score: " + score.ToString(); } |
scoreText.text = で文字を変更できます。
score.ToString() は、int の score を文字として表示するための処理です。Text に表示する内容は文字として扱うため、数値の score を ToString で文字に変換しています。
これによりゲーム開始時は Score: 0 と表示されます。
AddScore の処理
今回追加した重要な処理がAddScore関数です。
|
1 2 3 4 5 |
public void AddScore(int addScore) { score += addScore; scoreText.text = "Score: " + score.ToString(); } |
AddScore関数は、スコアを増やすための関数です。たとえば、AddScore(200) と書くと、現在のスコアに200が加算されます。
int addScore は、加算するスコアの値を受け取るための変数です。
中の処理はシンプルです。
|
1 |
score += addScore; |
現在の score に addScore を足しています。
|
1 |
scoreText.text = "Score: " + score.ToString(); |
scoreText.text を更新して画面に表示されているスコアも新しい値に変えています。
これでスコアの数値を増やす処理と画面表示を更新する処理を AddScore を呼び出して行えるようになります。
果物のスクリプトを書こう
次はFruitItem から AddScore を呼び出して、果物が合体したときにスコアを増やします。
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 |
using UnityEngine; public class FruitItem : MonoBehaviour { public int level = 0; 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); } Destroy(otherFruit.gameObject); Destroy(gameObject); } } |
スクリプトの解説
FruitItem に追加したのは合体したときにスコアを加算する処理です。
|
1 |
GameManager2D.AddScore(nextLevel * 10); |
GameManager2D の AddScore 関数を呼び出しています。
nextLevel * 10 は、次の果物のレベルをもとにスコアを決める計算です。
nextLevel が2の場合、2 * 10 なので20点が加算されます。
合体して大きい果物になるほど加算されるスコアも大きくなります。
今回追加したもう1つの処理がelseブロックです。
|
1 2 3 4 |
else { GameManager2D.AddScore(nextLevel * 20); } |
else{~~}は、if文の条件に当てはまらなかったときに実行される処理です。
今回のif文は、nextFruitPrefab がある場合に次の果物を生成する処理でした。つまりelseに進むのは、次の果物が用意されていない場合です。
今回のゲームでは
“次の果物が用意されていない = 最大レベルの果物が合体した”
という意味になります。
最大レベルの果物が合体したときはボーナスとして nextLevel * 20 のスコアを加算しています。
動作を確認しよう
GameManager2D と FruitItem を CTRL + S で必ず保存して Unity に戻りましょう。
Hierarchy から GameManager を選択します。
Inspector画面の Score Textコンポーネントに、作成した ScoreTextオブジェクトを設定しましょう。
設定できたらUnityシーンを保存し、再生して確認します。
同じ果物同士を合体させたときに、Score の数字が増えれば成功です。
プロジェクトファイルのダウンロード
今回の講座で作ったプロジェクトファイルのダウンロードリンクを用意しました。
まとめ
今回は同じ果物同士がぶつかったときに合体する仕組みを作成しました。
果物ごとにレベルを設定し、同じレベルの果物だけが合体できるようになりました。
また、Next の表示を作成し次に出てくる果物がわかるようにしました。
最後に Score UI を追加し、果物が合体したときにスコアが増えるようにしました。
これで果物を合体させながらスコアを伸ばしていくゲームらしい仕組みができました!
次回はゲームオーバーとリトライ機能を作成します。果物が一定のラインを超えたらゲームオーバーになるようにします。
さらにリトライボタンでゲームを最初からやり直せるようにします。
最後に果物を落としたときや合体したときの効果音も追加し、ゲームらしさをさらに高めていきましょう。
次の記事:

現場レベルのゲーム制作が、すべてここで学べます。




























































コメント