SG-ゲームオーバーの実装!part11

現在の状態では一生ゲームが遊べます。ゲームが終了するための条件を追加して、このゲームを完成させましょう。

目次

ゲーム終了判定をとる

ゲーム終了の判定方法ですが、囲いの中からフルーツが出たタイミングで終わらせたいと思います。

囲いの内側のコライダーを設定

  • 空のGameObjectを作成します
    • 名前変更:Inside
    • Transformをリセット
  • AddComponent:BoxCollider2D
    • IsTriggerにチェックを入れる
    • Offset/Sizeを調整して囲いの中のはみ出してほしくない大きさに調整してください
フェアリー

見やすくしたい場合は、境界線用のLineRendererを追加してもいいですね。

飛び出したフルーツが通知を投げる

フルーツはトリガーモードの当たり判定から出たタイミングでイベントを通知します。また、落とした後にそもそも内側に入らなかった場合はそれでもNGとします。ゲームオーバーの条件をまとめると以下

  • 内側に入っていたフルーツが外に出てしまった
    • Triggerモードの当たり判定からExitしたら通知
    • ただし進化のための削除では無視したいため、削除フラグを考慮する
  • 落下したフルーツが、そもそも内側に入れなかった
    • リリースされてから1秒間監視します
public class Fruits : MonoBehaviour
{
    public static UnityEvent OnGameOver = new UnityEvent();
    private bool isInside = false;

    IEnumerator Start()
    {
        Rigidbody2D rb = GetComponent<Rigidbody2D>();
        while (rb.isKinematic)
        {
            yield return null;
        }
        yield return new WaitForSeconds(1.0f);
        if (!isInside)
        {
            OnGameOver.Invoke();
        }
    }
    private void OnTriggerEnter2D(Collider2D other)
    {
        isInside = true;
    }

    private void OnTriggerExit2D(Collider2D other)
    {
        if (isDestroyed)
        {
            return;
        }
        OnGameOver.Invoke();
    }

}

ゲームオーバー時の処理

とりあえず通知を投げる処理は出来ました。ゲームオーバーで実装したい内容は次。

  • ゲームオーバーの表示を出す
    • 本来はハイスコアなどを表示するのが良い
  • 操作ができなくなる
    • FruitsDropperの機能を停止する
  • フルーツを動かさなくする
    • 物理的な動きをしないようにする
    • 万が一当たり判定で当たると困るので当たり判定も切る

ゲームオーバーの表示を出す

ゲームオーバー用の表示を制御するスクリプトを用意してもいいですが、今回はDisplayScoreスクリプトの間借りをします。

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

public class DisplayScore : MonoBehaviour
{
    public TextMeshProUGUI scoreText;
    private int score = 0;

    public GameObject gameOverPanel;

    private void Start()
    {
        Fruits.OnScoreAdded.AddListener(AddScore);
        AddScore(0);
        Fruits.OnGameOver.AddListener(() => Debug.Log("Game Over"));

        gameOverPanel.SetActive(false);
        Fruits.OnGameOver.AddListener(() => gameOverPanel.SetActive(true));
    }

    private void AddScore(int score)
    {
        this.score += score;
        scoreText.text = this.score.ToString();
    }
}

スクリプトが更新できたらゲームオーバーの表示が分かるUIを作成します。UIが準備できたらインスペクターにセットしてください。

操作が出来ないようにする

今回は一例だと思ってください。利用しているスクリプトはFruitsDropperなので、そのスクリプトが動かないようにします。ちなみに変更が入っているのはStartメソッド内だけです。

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

public class FruitsDropper : MonoBehaviour
{
    [SerializeField] private RandomFruitsSelector randomFruitsSelector;
    public float moveSpeed = 5f;

    [SerializeField] private float coolTime = 1f;
    private Fruits fruitsInstance;

    private void Start()
    {
        StartCoroutine(HandleFruits(coolTime));
        Fruits.OnGameOver.AddListener(() => enabled = false);
    }

    private IEnumerator HandleFruits(float delay)
    {
        yield return new WaitForSeconds(delay);
        var fruitsPrefab = randomFruitsSelector.Pop();
        fruitsInstance = Instantiate(fruitsPrefab, transform.position, Quaternion.identity);
        fruitsInstance.transform.SetParent(transform);
        fruitsInstance.GetComponent<Rigidbody2D>().isKinematic = true;
    }

    private void Update()
    {
        if (Input.GetKeyDown(KeyCode.Space) && fruitsInstance != null)
        {
            fruitsInstance.GetComponent<Rigidbody2D>().isKinematic = false;
            fruitsInstance.transform.SetParent(null);
            fruitsInstance = null;
            StartCoroutine(HandleFruits(coolTime));
        }

        float horizontal = Input.GetAxisRaw("Horizontal") * moveSpeed * Time.deltaTime;
        float x = Mathf.Clamp(transform.position.x + horizontal, -2.5f, 2.5f);
        transform.position = new Vector3(x, transform.position.y, transform.position.z);
    }
}

フルーツを動かさなくする

最後なのでFruits.csの最終形を記載します。ちなみにこちらはAwakeのみ変更。

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

public enum FRUITS_TYPE
{
    さくらんぼ = 0,
    いちご,
    ぶどう,
    オレンジ,
    かき,
    りんご,
    なし,
    もも,
    パイナップル,
    メロン,
    すいか,
}

public class Fruits : MonoBehaviour
{
    public FRUITS_TYPE fruitsType;
    private static int fruits_serial = 0;
    private int my_serial;
    public bool isDestroyed = false;

    [SerializeField] private Fruits nextFruitsPrefab;
    [SerializeField] private int score;

    public static UnityEvent<int> OnScoreAdded = new UnityEvent<int>();
    public static UnityEvent OnGameOver = new UnityEvent();
    private bool isInside = false;

    private void Awake()
    {
        my_serial = fruits_serial;
        fruits_serial++;

        OnGameOver.AddListener(() =>
        {
            Rigidbody2D rb = GetComponent<Rigidbody2D>();
            rb.isKinematic = true;

            BoxCollider2D boxCollider = GetComponent<BoxCollider2D>();
            boxCollider.enabled = false;
        });
    }

    IEnumerator Start()
    {
        Rigidbody2D rb = GetComponent<Rigidbody2D>();
        while (rb.isKinematic)
        {
            yield return null;
        }
        yield return new WaitForSeconds(1.0f);
        if (!isInside)
        {
            OnGameOver.Invoke();
        }

    }

    private void OnCollisionEnter2D(Collision2D other)
    {
        if (isDestroyed)
        {
            return;
        }

        if (other.gameObject.TryGetComponent(out Fruits otherFruits))
        {
            if (otherFruits.fruitsType == fruitsType)
            {
                if (nextFruitsPrefab != null && my_serial < otherFruits.my_serial)
                {
                    OnScoreAdded.Invoke(score);

                    isDestroyed = true;
                    otherFruits.isDestroyed = true;
                    Destroy(gameObject);
                    Destroy(other.gameObject);

                    Vector3 center = (transform.position + other.transform.position) / 2;
                    Quaternion rotation = Quaternion.Lerp(transform.rotation, other.transform.rotation, 0.5f);
                    Fruits next = Instantiate(nextFruitsPrefab, center, rotation);

                    // 2つの速度の平均をとる
                    Rigidbody2D nextRb = next.GetComponent<Rigidbody2D>();
                    Vector3 velocity = (GetComponent<Rigidbody2D>().velocity + other.gameObject.GetComponent<Rigidbody2D>().velocity) / 2;
                    nextRb.velocity = velocity;

                    float angularVelocity = (GetComponent<Rigidbody2D>().angularVelocity + other.gameObject.GetComponent<Rigidbody2D>().angularVelocity) / 2;
                    nextRb.angularVelocity = angularVelocity;
                }
            }
        }
    }

    private void OnTriggerEnter2D(Collider2D other)
    {
        isInside = true;
    }

    private void OnTriggerExit2D(Collider2D other)
    {
        if (isDestroyed)
        {
            return;
        }
        OnGameOver.Invoke();
    }
}
よかったらシェアしてね!
  • URLをコピーしました!
  • URLをコピーしました!

この記事を書いた人

コメント

コメント一覧 (8件)

  • ゲームオーバー時に処理を通知するソースはScriptsファイルのどこに書き込めばいいですか?
    よろしくお願いします。

    • 今回の実装ではFruits.OnGameOverイベントを受け取ると、ゲームオーバーの処理が動くようにしています。
      独自のゲームオーバー処理を実装したい場合、新規にスクリプトファイルを作成して、イベントを受け取れるようにすることで実装可能です

      イベントの実装方法が分かる前提での説明になってしまいますが、ゲームオーバーの処理を別途実装する場合はそのようになります。
      どういったものを実装したいかなど、具体的な質問いただければもう少し希望する回答に近づけると思います

  • すみません言葉足らずでした。
    Part1からPart11まで説明に沿ってアプリ開発していました。
    Part11の飛び出したフルーツが通知を投げるところで

    元々の文の

    public class Fruits : MonoBehaviour
    {
    public FRUITS_TYPE fruitsType; 含む下の分全てを

    public class Fruits : MonoBehaviour
    {
    public static UnityEvent OnGameOver = new UnityEvent();〜〜

    に書き換えたところ

    Assets/Scripts/Fruits.cs(46,13): error CS0103: The name ‘isDestroyed’ does not exist in the current context

    Assets/Scripts/DisplayScore.cs(13,15): error CS0117: ‘Fruits’ does not contain a definition for ‘OnScoreAdded’

    とエラーが出てきてしまって対応法がわからず困っています。
    まだ初学者のため、何か助言を頂けたらとても助かります。

    • エラー内容はisDestroyedって変数がないよ!OnScoreAddedがないよ!というものになります。
      過去のスクリプトで追加しているメンバー変数などは省略しているため、それらの変数が消えているのが原因です。

      Fruitsスクリプトの最終形は一応こちらの記事の最後にありますので、現在の自分のスクリプトと、全体を見比べながら何が足りていない時にどういうエラーが出るのかなどを調べながら修正するといい勉強になると思います。

      ちなみに
      isDestroyedが追加されたのはPart05
      OnScoreAddedが追加されたのはPart10
      こういったソースコードの修正は、ブログのほうが動画より伝わりづらいですね。リアルタイムな返答が欲しい場合は配信に来ていただければ動かしながら解説しますのでぜひお越しくださいませ

  • ご丁寧な返信ありがとうございます。
    記事の見返しとGoogle検索をしてプロジェクトを完成させ、Unityからxcodeにビルドし実機にアプリを移動しアプリを開いて画面を表示させるまでは行けたのですが肝心の操作ができませんでした。
    何か助言頂けたら幸いです。

    • プロジェクト完成、おめでとうございます。
      しかもXcode経由でスマホにまでインストールまで持っていくとはすごいですね。

      アプリが動かないことに関してですが、単純に入力系がキーボードからしか受付を行っていないためです。
      スマートフォンで動かすようにするには左右移動のボタンやジョイスティック、落下させるボタンなどを追加する必要があります。
      今回の記事内容はPC版を想定しているため、スマホでプレイするには追加のカスタマイズが必要になります。

      Joystickの追加自体はこちらの動画が参考になると思います。ブログ用のページは用意してたんですが、消してました。
      このあたりに関してはジョイスティックではなくボタンでも代用できますので、お好みの実装を試してみてくださいませ
      https://youtu.be/gzu8jKUg86E

  • pc版を想定して作られていたんですね、参考になりました。
    動画を閲覧させて頂きました。joystickの一連の動かし方等の流れは理解できたのですが
    作成したスイカゲームにどう当てはめるのかをご教授いただけると幸いです。
    よろしくお願いします。

コメントする

目次