Unity 2Dアクションの作り方【ゲームオーバー】

ゲームオーバーの作り方【Unity2Dアクションの作り方】【初心者入門講座】【ゲームの作り方】#51

前回はステージを管理するスクリプトを作成しました。今回はゲームオーバーを作成していこうと思います。最後までついて来て頂ければ↑のようなゲームを作る事ができます。

この記事は本のように順を追って解説しています。この記事は途中のページになります。
この記事を見ていて、現在の状況がわからない場合や忘れてしまった事などが出てきたら↓のリンクから目次ページへ飛べますので立ち戻って見てください。

<残機の加算・減算をしよう>

continue blink

さて、前回敵にやられたらコンティニューポイントまで戻るところまで実装しました。

クリックすると展開します
 using System.Collections;
 using System.Collections.Generic;
 using UnityEngine;

 public class Player : MonoBehaviour
 {
     #region//インスペクターで設定する
     [Header("移動速度")] public float speed;
     [Header("重力")] public float gravity;
     [Header("ジャンプ速度")] public float jumpSpeed;
     [Header("ジャンプする高さ")] public float jumpHeight;
     [Header("ジャンプする長さ")] public float jumpLimitTime;
     [Header("接地判定")] public GroundCheck ground;
     [Header("天井判定")] public GroundCheck head;
     [Header("ダッシュの速さ表現")] public AnimationCurve dashCurve;
     [Header("ジャンプの速さ表現")] public AnimationCurve jumpCurve;
     [Header("踏みつけ判定の高さの割合(%)")] public float stepOnRate;
     #endregion

     #region//プライベート変数 
     private Animator anim = null; 
     private Rigidbody2D rb = null; 
     private CapsuleCollider2D capcol = null; 
     private SpriteRenderer sr = null;
     private bool isGround = false; 
     private bool isJump = false; 
     private bool isHead = false;  
     private bool isRun = false; 
     private bool isDown = false; 
     private bool isOtherJump = false; 
     private bool isContinue = false;
     private float jumpPos = 0.0f; 
     private float otherJumpHeight = 0.0f; 
     private float dashTime = 0.0f;
     private float jumpTime = 0.0f; 
     private float beforeKey = 0.0f;
     private float continueTime = 0.0f;
     private float blinkTime = 0.0f;
     private string enemyTag = "Enemy"; 
     #endregion 

     void Start() 
     {
          //コンポーネントのインスタンスを捕まえる
          anim = GetComponent<Animator>();
          rb = GetComponent<Rigidbody2D>();
          capcol = GetComponent<CapsuleCollider2D>();
          sr = GetComponent<SpriteRenderer>();
     } 

     private void Update() 
     {
          if (isContinue)
          {
              //明滅 ついている時に戻る
              if (blinkTime > 0.2f)
              {
                  sr.enabled = true;
                  blinkTime = 0.0f;
              }
              //明滅 消えているとき
              else if (blinkTime > 0.1f)
              {
                  sr.enabled = false;
              }
              //明滅 ついているとき
              else
              {
                  sr.enabled = true;
              }
              //1秒たったら明滅終わり
              if (continueTime > 1.0f)
              {
                  isContinue = false;
                  blinkTime = 0.0f;
                  continueTime = 0.0f;
                  sr.enabled = true;
              }
              else
              {
                  blinkTime += Time.deltaTime;
                  continueTime += Time.deltaTime;
              }
          } 
     } 
     
     void FixedUpdate() 
     {
          if (!isDown)
          {
              //接地判定を得る
              isGround = ground.IsGround();
              isHead = head.IsGround(); 

              //各種座標軸の速度を求める
              float xSpeed = GetXSpeed();
              float ySpeed = GetYSpeed();

              //アニメーションを適用
              SetAnimation();

              //移動速度を設定
              rb.velocity = new Vector2(xSpeed, ySpeed);
          }
          else
          {
              rb.velocity = new Vector2(0, -gravity);
          } 
     }

     /// <summary> 
     /// Y成分で必要な計算をし、速度を返す。 
     /// </summary> 
     /// <returns>Y軸の速さ</returns> 
     private float GetYSpeed() 
     {
          float verticalKey = Input.GetAxis("Vertical");
          float ySpeed = -gravity;

          //何かを踏んだ際のジャンプ
          if (isOtherJump) 
          {
                //現在の高さが飛べる高さより下か
                bool canHeight = jumpPos + otherJumpHeight > transform.position.y;
                //ジャンプ時間が長くなりすぎてないか
                bool canTime = jumpLimitTime > jumpTime;

                if (canHeight && canTime && !isHead) 
                {
                    ySpeed = jumpSpeed;
                    jumpTime += Time.deltaTime;
                } 
                else 
                { 
                     isOtherJump = false;
                     jumpTime = 0.0f;
                } 
           }
           //地面にいるとき
           else if (isGround)
           {
                if (verticalKey > 0)
                {
                    ySpeed = jumpSpeed;
                    jumpPos = transform.position.y; //ジャンプした位置を記録する
                    isJump = true;
                    jumpTime = 0.0f;
                }
                else
                {
                    isJump = false;
                }
           }
           //ジャンプ中
           else if (isJump)
           {
                 //上方向キーを押しているか
           bool pushUpKey = verticalKey > 0;
           //現在の高さが飛べる高さより下か
           bool canHeight = jumpPos + jumpHeight > transform.position.y;
           //ジャンプ時間が長くなりすぎてないか
           bool canTime = jumpLimitTime > jumpTime;

           if (pushUpKey && canHeight && canTime && !isHead)
                 {
                     ySpeed = jumpSpeed;
                     jumpTime += Time.deltaTime;
                 }
                 else
                 {
                     isJump = false;
                     jumpTime = 0.0f;
                 }
          }

          if (isJump || isOtherJump)
          {
              ySpeed *= jumpCurve.Evaluate(jumpTime);
          }
          return ySpeed;
     } 

     /// <summary> 
     /// X成分で必要な計算をし、速度を返す。 
     /// </summary> 
     /// <returns>X軸の速さ</returns> 
     private float GetXSpeed() 
     {
          float horizontalKey = Input.GetAxis("Horizontal");
          float xSpeed = 0.0f;
          if (horizontalKey > 0)
          {
              transform.localScale = new Vector3(1, 1, 1);
              isRun = true;
              dashTime += Time.deltaTime;
              xSpeed = speed;
          }
          else if (horizontalKey < 0)
          {
              transform.localScale = new Vector3(-1, 1, 1);
              isRun = true;
              dashTime += Time.deltaTime;
              xSpeed = -speed;
          }
          else
          {
              isRun = false;
              xSpeed = 0.0f;
              dashTime = 0.0f;
          }

          //前回の入力からダッシュの反転を判断して速度を変える
          if (horizontalKey > 0 && beforeKey < 0)
          {
              dashTime = 0.0f;
          }
          else if (horizontalKey < 0 && beforeKey > 0)
          {
              dashTime = 0.0f;
          }

          beforeKey = horizontalKey;
          xSpeed *= dashCurve.Evaluate(dashTime);
          beforeKey = horizontalKey;
          return xSpeed;
     } 

     /// <summary> 
     /// アニメーションを設定する 
     /// </summary> 
     private void SetAnimation() 
     {
          anim.SetBool("jump", isJump || isOtherJump);
          anim.SetBool("ground", isGround);
          anim.SetBool("run", isRun); 
     } 

    /// <summary> 
  /// コンティニュー待機状態か 
    /// </summary> 
    /// <returns></returns> 
    public bool IsContinueWaiting() 
    { 
        return IsDownAnimEnd(); 
    } 

    //ダウンアニメーションが完了しているかどうか 
    private bool IsDownAnimEnd() 
    { 
        if(isDown && anim != null) 
        { 
             AnimatorStateInfo currentState = anim.GetCurrentAnimatorStateInfo(0); 
             if (currentState.IsName("player_down")) 
             { 
                  if(currentState.normalizedTime >= 1) 
                  { 
                       return true; 
                  } 
             } 
         } 
         return false; 
    }
     
     /// <summary> 
     /// コンティニューする 
     /// </summary> 
     public void ContinuePlayer() 
     {
          isDown = false;
          anim.Play("player_stand");
          isJump = false;
          isOtherJump = false;
          isRun = false;
          isContinue = true;
     } 

     #region//接触判定 
     private void OnCollisionEnter2D(Collision2D collision) 
     {
          if (collision.collider.tag == enemyTag)
          {
              //踏みつけ判定になる高さ
              float stepOnHeight = (capcol.size.y * (stepOnRate / 100f));
     
              //踏みつけ判定のワールド座標
              float judgePos = transform.position.y - (capcol.size.y / 2f) + stepOnHeight;
     
              foreach (ContactPoint2D p in collision.contacts)
              {
                  if (p.point.y < judgePos)
                  {
                      ObjectCollision o = collision.gameObject.GetComponent<ObjectCollision>();
     
                       if (o != null)
                      {
                          otherJumpHeight = o.boundHeight;    //踏んづけたものから跳ねる高さを取得する
                          o.playerStepOn = true;        //踏んづけたものに対して踏んづけた事を通知する
                          jumpPos = transform.position.y; //ジャンプした位置を記録する 
                          isOtherJump = true;
                          isJump = false;
                          jumpTime = 0.0f;
                      }
                      else
                      {
                          Debug.Log("ObjectCollisionが付いてないよ!");
                      }
                  }
                  else
                  {
                      anim.Play("player_down");
                      isDown = true;
                      break;
                  }
              }
          }
     } 
     #endregion
 }

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

 public class StageCtrl : MonoBehaviour
 {
     [Header("プレイヤーゲームオブジェクト")] public GameObject playerObj;
     [Header("コンティニュー位置")] public GameObject[] continuePoint;

     private Player p; 

     // Start is called before the first frame update
     void Start() 
     {
         if (playerObj != null && continuePoint != null && continuePoint.Length > 0) 
         { 
             playerObj.transform.position = continuePoint[0].transform.position; 
             p = playerObj.GetComponent<Player>(); 
             if (p == null) 
             { 
                  Debug.Log("プレイヤーじゃない物がアタッチされているよ!"); 
             } 
         } 
         else 
         { 
             Debug.Log("設定が足りてないよ!"); 
         }
     } 

     // Update is called once per frame 
     void Update() 
     {
         if (p != null && p.IsContinueWaiting()) 
         { 
              if (continuePoint.Length > GManager.instance.continueNum) 
              { 
                  playerObj.transform.position = continuePoint[GManager.instance.continueNum].transform.position; 
                  p.ContinuePlayer(); 
              } 
              else 
              { 
                   Debug.Log("コンティニューポイントの設定が足りてないよ!"); 
              } 
         }
     }
 }

とりあえず、今度は残機を増やしたり減らしたりする処理を実装しましょう。

今まで制作していたゲームマネージャーに残機の加算減算をするメソッドを加えます。

クリックすると展開します
 using System.Collections;
 using System.Collections.Generic;
 using UnityEngine;
 
 public class GManager : MonoBehaviour
 {
     public static GManager instance = null;

     [Header("スコア")] public int score;
     [Header("現在のステージ")] public int stageNum;
     [Header("現在の復帰位置")] public int continueNum;
     [Header("現在の残機")] public int heartNum;
     [Header("デフォルトの残機")] public int defaultHeartNum;
     [HideInInspector] public bool isGameOver = false;
 
     private void Awake()
     {
         if(instance == null)
         {
             instance = this;
             DontDestroyOnLoad(this.gameObject);
         }
         else
         {
             Destroy(this.gameObject);
         }
     }
     
     /// <summary>
     /// 残機を1つ増やす
     /// </summary>
     public void AddHeartNum()
     {
         if(heartNum < 99)
         {
             ++heartNum;
         }
     }
     
     /// <summary>
     /// 残機を1つ減らす
     /// </summary>
     public void SubHeartNum()
     {
         if(heartNum > 0)
         {
             --heartNum;
         }
         else
         {
             isGameOver = true;
         }
     }
 }

ゲームマネージャーに残機を増やしたり減らしたりするメソッドを追加しました。また、ゲームオーバーをインスペクターから触られたら困るのでHideInspectorにしています。

単に減らしたり増やしたりすると無限に増えちゃったり、マイナスになってしまったりするのでこのように処理を追加します。

↓プレイヤー側も敵に当たったら残機を減らす処理を入れましょう。

クリックすると展開します

追加する変数

private bool nonDownAnim = false;

追加するメソッド

//やられた時の処理 New!
private void ReceiveDamage(bool downAnim) 
{ 
    if (isDown)
    {     
         return;
    }
    else
    {
         if (downAnim)
         {
             anim.Play("player_down");
         }
         else
         {
             nonDownAnim = true;
         }
         isDown = true;
         GManager.instance.SubHeartNum();
     }
}

#region//接触判定
private void OnCollisionEnter2D(Collision2D collision)
{
    if (collision.collider.tag == enemyTag)
    {
        //踏みつけ判定になる高さ
        float stepOnHeight = (capcol.size.y * (stepOnRate / 100f));

        //踏みつけ判定のワールド座標
        float judgePos = transform.position.y - (capcol.size.y / 2f) + stepOnHeight;
        foreach (ContactPoint2D p in collision.contacts)
        {
            if (p.point.y < judgePos)
            {
                ObjectCollision o = collision.gameObject.GetComponent<ObjectCollision>();
               if (o != null)
               {
                    otherJumpHeight = o.boundHeight;    //踏んづけたものから跳ねる高さを取得する
                    o.playerStepOn = true;        //踏んづけたものに対して踏んづけた事を通知する
                    jumpPos = transform.position.y; //ジャンプした位置を記録する 
                    isOtherJump = true;
                    isJump = false;
                    jumpTime = 0.0f;
                }
                else
                {
                     Debug.Log("ObjectCollisionが付いてないよ!");
                }
            }
            else
            {
                ReceiveDamage(true); //New!
                break;
             }
         }
     }
}
#endregion

残機を減らす処理は他でも使うので、メソッド化して分離します。

また、ダウンアニメーションをしないでやられるパターン(落下等)もあると思うので、引数にダウンアニメーションをするかどうかというのを渡しています。

<ステージにやられ判定をつけよう>

さて、そろそろゲームオーバー画面を実装したいのですが、ゲームオーバーをテストするに当たっていちいち敵にぶつかっていくのはめんどくさいので先に落ちたらやられてしまうようにしましょう。

まずは、カラのゲームオブジェクトを用意して、Box Collider 2Dをアタッチし、Is Triggerにチェックを入れましょう。そして、ステージの下の部分に広げます。

↑この緑の枠の中に入ったらやられた判定にしようと思います。まだ仮の状態なので適当でOKです。

そして、Add Tagから

新たにDeadAreaとHitAreaというタグを追加します。

add tags

ステージ側のやられ判定のタグをDeadAreaにしてください。

そしてついでなので、よくある針でやられるというのも実装します。

適当に針を書いてみました。

needle
needle

こちらもBox Collider 2Dをアタッチし、Is Triggerにチェックを入れます。こちらの方はTagをHitAreaにします。

そして、プレイヤーのスクリプトにさらに↓を追加します。

追加する変数

private string deadAreaTag = "DeadArea";
private string hitAreaTag = "HitArea";

追加するメソッド

private void OnTriggerEnter2D(Collider2D collision)	
{
        if(collision.tag == deadAreaTag)
	{
            ReceiveDamage(false);
	}
	else if(collision.tag == hitAreaTag)
	{
            ReceiveDamage(true);
	}
}

そして、ダウンアニメーションをしないパターンがあるので、コンティニュー待機状態の部分をちょっと変えます。

/// <summary>
/// コンティニュー待機状態か    
/// </summary>    
/// <returns></returns>    
public bool IsContinueWaiting()
{
        if (GManager.instance.isGameOver)  // New!
        {
            return false;
        }
        else
        {
            return IsDownAnimEnd() || nonDownAnim;  // New!
        }    
}

/// <summary>    
/// コンティニューする   
/// </summary>    
public void ContinuePlayer()    
{
        isDown = false;
        anim.Play("player_stand");
        isJump = false;
        isOtherJump = false;
        isRun = false;
        isContinue = true;
        nonDownAnim = false;    //New!
}

落下した場合は、ダウンアニメーションをしないようにし、針にあたった場合はダウンアニメーションをするようにしています。

そして、コンティニューする時の挙動がそれぞれで変わるので、ゲームオーバー状態だとコンティニューしないようにして、ダウンアニメーションをする場合はアニメーションを待ち、しない場合は即座に待機状態になるようにします。

これで実行すると

take damage

いい感じにやられ判定を置く事ができました。変なところで戻ってますが、下書きなので適当で大丈夫です。

スポンサーリンク

<ゲームオーバー画面を作ろう>

残機がマイナスになってしまったらゲームオーバーにしたいと思います。

まずは真っ黒な画像を用意します。

black

Imageを設置し、WidthとHeightを解像度に合わせて、↑の画像をスプライトにして突っ込みます

screen black out

Colorのアルファ値を下げてあげるとちょっと薄暗い感じにする事ができます。

そして、Textとボタンを置いてゲームオーバーっぽくすればOKです。まぁ、下書きですので適当でOKです。

Textは相変わらずfontSizeに気をつけてください。極力他のテキストと同じ大きさをしようしましょう。

fontSizeの事がよくわからない方は↓の記事を参考にしてみてください。

game over

UGUIを置く順番には注意しましょう。

canvas hierarchy

下にある方が手前に表示されるのでFadeが一番下になるようにします。

次にゲームオーバーなのですが、ゲームオーバーに関連するUIは全部最初においた薄いグレーアウトの子オブジェクトにしてしまうといいと思います。

こうする事で親をSetActiveでオンオフすることによってゲームオーバーを簡単に画面に表示する事ができるようになります。

<ゲームオーバー画面をスクリプトから表示しよう>

ゲームオーバーになってしまったら最初のステージに戻るようにしようと思います。

まずは、ゲームオーバー画面を表示する為にゲームマネージャーに最初から始めるの処理を書きましょう。

ゲームマネージャーに↓の変数を追加します。これはデフォルトでの残機の数です。

[Header("デフォルトの残機")] public int defaultHeartNum;

また、最初から始める用のメソッドを書きます。

/// <summary>
/// 最初から始める時の処理
/// </summary>
public void RetryGame()
{
     isGameOver = false;
     heartNum = defaultHeartNum;
     score = 0; 
     stageNum = 1;
     continueNum = 0; 
}

フラグを下ろしてあげて、残機をデフォルトのものに戻しましょう。スコアも0に戻してあげます。

クリックすると展開します
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class GManager : MonoBehaviour
{
    public static GManager instance = null;

    [Header("スコア")] public int score;
    [Header("現在のステージ")] public int stageNum;
    [Header("現在の復帰位置")] public int continueNum;
    [Header("現在の残機")] public int heartNum;
    [Header("デフォルトの残機")] public int defaultHeartNum; //New!
    [HideInInspector] public bool isGameOver = false;

    private void Awake()
    {
        if (instance == null)
        {
            instance = this;
            DontDestroyOnLoad(this.gameObject);
        }
        else
        {
            Destroy(this.gameObject);
        }
    }

    /// <summary> 
    /// 残機を1つ増やす 
    /// </summary> 
    public void AddHeartNum()
    {
        if (heartNum < 99)
        {
            ++heartNum;
        }
    }

    /// <summary>     
   /// 残機を1つ減らす 
    /// </summary> 
    public void SubHeartNum()
    {
        if (heartNum > 0)
        {
            --heartNum;
        }
        else
        {
            isGameOver = true;
        }
    }

    /// <summary> 
    /// 最初から始める時の処理    New! 
    /// </summary> 
    public void RetryGame()
    {
        isGameOver = false;
        heartNum = defaultHeartNum;
        score = 0;
        stageNum = 1;
        continueNum = 0;
    }
}

インスペクターでの設定も忘れずに。デバッグしやすいようにプレイ中も編集できるようにしていますが、設定を忘れると値が0になってしまいます。

game manager setting

また、ステージコントローラーを以下のように変えます。

クリックすると展開します
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.SceneManagement; //New!

public class StageCtrl : MonoBehaviour
{
    [Header("プレイヤーゲームオブジェクト")] public GameObject playerObj;
    [Header("コンティニュー位置")] public GameObject[] continuePoint;
    [Header("ゲームオーバー")] public GameObject gameOverObj;//New!
    [Header("フェード")] public FadeImage fade;//New!

    private Player p;
    private int nextStageNum; //New!
    private bool startFade = false; //New!
    private bool doGameOver = false; //New!
    private bool retryGame = false; //New!
    private bool doSceneChange = false; //New!

    // Start is called before the first frame update
    void Start()
    {
        if (playerObj != null && continuePoint != null && continuePoint.Length > 0 && gameOverObj != null && fade != null)
        {
            gameOverObj.SetActive(false); // New!
            playerObj.transform.position = continuePoint[0].transform.position;
            p = playerObj.GetComponent<Player>();
            if (p == null)
            {
                Debug.Log("プレイヤーじゃない物がアタッチされているよ!");
            }
        }
        else
        {
            Debug.Log("設定が足りてないよ!");
        }
    }

    // Update is called once per frame
    void Update()
    {
        //ゲームオーバー時の処理
        if (GManager.instance.isGameOver && !doGameOver) //New!
        {
            gameOverObj.SetActive(true);
            doGameOver = true;
        }
        //プレイヤーがやられた時の処理
        else if (p != null && p.IsContinueWaiting() && !doGameOver)  //New!
        {
            if (continuePoint.Length > GManager.instance.continueNum)
            {
                playerObj.transform.position = continuePoint[GManager.instance.continueNum].transform.position;
                p.ContinuePlayer();
            }
            else
            {
                Debug.Log("コンティニューポイントの設定が足りてないよ!");
            }
        }

        //ステージを切り替える
        if (fade != null && startFade && !doSceneChange)
        {
            if (fade.IsFadeOutComplete())
            {
                //ゲームリトライ
                if (retryGame)
                {
                    GManager.instance.RetryGame();
                }
                //次のステージ
                else
                {
                    GManager.instance.stageNum = nextStageNum;
                }
                SceneManager.LoadScene("stage" + nextStageNum);
                doSceneChange = true;
            }
        }
    }

    /// <summary>
    /// 最初から始める New!
    /// </summary>
    public void Retry()
    {
        ChangeScene(1); //最初のステージに戻るので1
        retryGame = true;
    }

    /// <summary>
    /// ステージを切り替えます。 New!
    /// </summary>
    /// <param name="num">ステージ番号</param>
    public void ChangeScene(int num)
    {
        if (fade != null)
        {
            nextStageNum = num;
            fade.StartFadeOut();
            startFade = true;
        }
    }
}

ゲームオーバーのオブジェクトをSetActiveでコントロールし、ゲームオーバーになったら出現するようにします。

ゲームオーバーになってしまったら最初のステージに戻りたいですが、クリアーしたら次のステージに進みたいので↓のように番号でステージ移動ができるようにしています。

/// <summary>
/// ステージを切り替えます。
/// </summary>
/// <param name="num">ステージ番号</param>
public void ChangeScene(int num)
{
    if (fade != null)
    {
        nextStageNum = num;
        fade.StartFadeOut();
        startFade = true;
    }
}

これはステージのシーン名を「stage+番号」に統一することでできます。シーン名に規則性を持たせておくと、処理が楽にできていいですね。

シーン名が現在違うようになっている方はプロジェクトウィンドウで普通にリネームできます。(リネームする際はセーブを忘れずに)

そしてゲームオーバーでRetryボタンを押した時は↓の最初から始めるメソッドを呼んであげればOKです。

/// <summary>
/// 最初から始める
/// </summary>
public void Retry()
{
    ChangeScene(1); //最初のステージに戻るので1
    retryGame = true;
}

ステージ1にシーンチェンジしようとしているので結果、最初に戻ることができます。

現在のステージがステージ1だった場合ちょっと無駄な処理のように思えますが、シーンをロードし直すことによって状況が元に戻る為安全です。

ロードし直さないと敵が消えたままになっていたり、アイテムが消えたままになってしまうのでシーンをロードし直すといいと思います。

また、プレイヤーがゲームオーバーになった時動かないように移動を制限します。プレイヤーのスクリプトのFixedUpdate内を↓のように変更します。

void FixedUpdate()    
{
        if (!isDown && !GManager.instance.isGameOver) //New!
        {
            //接地判定を得る
            isGround = ground.IsGround();
            isHead = head.IsGround();

            //各種座標軸の速度を求める
            float xSpeed = GetXSpeed();
            float ySpeed = GetYSpeed();

            //アニメーションを適用
            SetAnimation();

            //移動速度を設定
            rb.velocity = new Vector2(xSpeed, ySpeed);
        }
        else
        {
            rb.velocity = new Vector2(0, -gravity);
        }    
}

各種インスペクターでの設定を忘れずに。

stage control inspector game over fade

↓リトライボタンを押したらStageCtrlのRetryのメソッドを呼ぶようにしましょう。

この状態で再生すると

retry game

これでゲームオーバーを作成する事ができました。(再生中インスペクターから直接残機を減らしてますが気にしないでください)

また、↓のようにまとめてしまって、プレハブにしておくと、別のステージを作る際に便利かもしれません。

stage controller prefab

<まとめ>

今回実装したスクリプトは↓です。

クリックすると展開します
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class GManager : MonoBehaviour
{
    public static GManager instance = null;

    [Header("スコア")] public int score;
    [Header("現在のステージ")] public int stageNum;
    [Header("現在の復帰位置")] public int continueNum;
    [Header("現在の残機")] public int heartNum;
    [Header("デフォルトの残機")] public int defaultHeartNum;
    [HideInInspector] public bool isGameOver = false;

    private void Awake()
    {
        if (instance == null)
        {
            instance = this;
            DontDestroyOnLoad(this.gameObject);
        }
        else
        {
            Destroy(this.gameObject);
        }
    }

    /// <summary>
    /// 残機を1つ増やす
    /// </summary>
    public void AddHeartNum()
    {
        if (heartNum < 99)
        {
            ++heartNum;
        }
    }

    /// <summary>
    /// 残機を1つ減らす
    /// </summary>
    public void SubHeartNum()
    {
        if (heartNum > 0)
        {
            --heartNum;
        }
        else
        {
            isGameOver = true;
        }
    }


    /// <summary>
    /// 最初から始める時の処理
    /// </summary>
    public void RetryGame()
    {

        isGameOver = false;
        heartNum = defaultHeartNum;
        score = 0;
        stageNum = 1;
        continueNum = 0;
    }
}
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class Player : MonoBehaviour
{
    #region//インスペクターで設定する
    [Header("移動速度")] public float speed;
    [Header("重力")] public float gravity;
    [Header("ジャンプ速度")] public float jumpSpeed;
    [Header("ジャンプする高さ")] public float jumpHeight;
    [Header("ジャンプする長さ")] public float jumpLimitTime;
    [Header("接地判定")] public GroundCheck ground;
    [Header("天井判定")] public GroundCheck head;
    [Header("ダッシュの速さ表現")] public AnimationCurve dashCurve;
    [Header("ジャンプの速さ表現")] public AnimationCurve jumpCurve;
    [Header("踏みつけ判定の高さの割合(%)")] public float stepOnRate;
    #endregion


    #region//プライベート変数
    private Animator anim = null;
    private Rigidbody2D rb = null;
    private CapsuleCollider2D capcol = null;
    private SpriteRenderer sr = null;
    private bool isGround = false;
    private bool isJump = false;
    private bool isHead = false;
    private bool isRun = false;
    private bool isDown = false;
    private bool isOtherJump = false;
    private bool isContinue = false;
    private bool nonDownAnim = false;
    private float jumpPos = 0.0f;
    private float otherJumpHeight = 0.0f;
    private float dashTime = 0.0f;
    private float jumpTime = 0.0f;
    private float beforeKey = 0.0f;
    private float continueTime = 0.0f;
    private float blinkTime = 0.0f;
    private string enemyTag = "Enemy";
    private string deadAreaTag = "DeadArea";
    private string hitAreaTag = "HitArea";
    #endregion

    void Start()
    {
        //コンポーネントのインスタンスを捕まえる
        anim = GetComponent<Animator>();
        rb = GetComponent<Rigidbody2D>();
        capcol = GetComponent<CapsuleCollider2D>();
        sr = GetComponent<SpriteRenderer>();
    }

    private void Update()
    {
        if (isContinue)
        {
            //明滅 ついている時に戻る
            if (blinkTime > 0.2f)
            {
                sr.enabled = true;
                blinkTime = 0.0f;
            }
            //明滅 消えているとき
            else if (blinkTime > 0.1f)
            {
                sr.enabled = false;
            }
            //明滅 ついているとき
            else
            {
                sr.enabled = true;
            }
            //1秒たったら明滅終わり
            if (continueTime > 1.0f)
            {
                isContinue = false;
                blinkTime = 0.0f;
                continueTime = 0.0f;
                sr.enabled = true;
            }
            else
            {
                blinkTime += Time.deltaTime;
                continueTime += Time.deltaTime;
            }
        }
    }

    void FixedUpdate()
    {
        if (!isDown && !GManager.instance.isGameOver)
        {
            //接地判定を得る
            isGround = ground.IsGround();
            isHead = head.IsGround();

            //各種座標軸の速度を求める
            float xSpeed = GetXSpeed();
            float ySpeed = GetYSpeed();

            //アニメーションを適用
            SetAnimation();

            //移動速度を設定
            rb.velocity = new Vector2(xSpeed, ySpeed);
        }
        else
        {
            rb.velocity = new Vector2(0, -gravity);
        }
    }

    /// <summary>
    /// Y成分で必要な計算をし、速度を返す。
    /// </summary>
    /// <returns>Y軸の速さ</returns>
    private float GetYSpeed()
    {
        float verticalKey = Input.GetAxis("Vertical");
        float ySpeed = -gravity;

        //何かを踏んだ際のジャンプ
        if (isOtherJump)
        {
            //現在の高さが飛べる高さより下か
            bool canHeight = jumpPos + otherJumpHeight > transform.position.y;
            //ジャンプ時間が長くなりすぎてないか
            bool canTime = jumpLimitTime > jumpTime;

            if (canHeight && canTime && !isHead)
            {
                ySpeed = jumpSpeed;
                jumpTime += Time.deltaTime;
            }
            else
            {
                isOtherJump = false;
                jumpTime = 0.0f;
            }
        }
        //地面にいるとき
        else if (isGround)
        {
            if (verticalKey > 0)
            {
                ySpeed = jumpSpeed;
                jumpPos = transform.position.y; //ジャンプした位置を記録する
                isJump = true;
                jumpTime = 0.0f;
            }
            else
            {
                isJump = false;
            }
        }
        //ジャンプ中
        else if (isJump)
        {
            //上方向キーを押しているか
            bool pushUpKey = verticalKey > 0;
            //現在の高さが飛べる高さより下か
            bool canHeight = jumpPos + jumpHeight > transform.position.y;
            //ジャンプ時間が長くなりすぎてないか
            bool canTime = jumpLimitTime > jumpTime;

            if (pushUpKey && canHeight && canTime && !isHead)
            {
                ySpeed = jumpSpeed;
                jumpTime += Time.deltaTime;
            }
            else
            {
                isJump = false;
                jumpTime = 0.0f;
            }
        }

        if (isJump || isOtherJump)
        {
            ySpeed *= jumpCurve.Evaluate(jumpTime);
        }
        return ySpeed;
    }


    /// <summary>
    /// X成分で必要な計算をし、速度を返す。
    /// </summary>
    /// <returns>X軸の速さ</returns>
    private float GetXSpeed()
    {
        float horizontalKey = Input.GetAxis("Horizontal");
        float xSpeed = 0.0f;

        if (horizontalKey > 0)
        {
            transform.localScale = new Vector3(1, 1, 1);
            isRun = true;
            dashTime += Time.deltaTime;
            xSpeed = speed;
        }
        else if (horizontalKey < 0)
        {
            transform.localScale = new Vector3(-1, 1, 1);
            isRun = true;
            dashTime += Time.deltaTime;
            xSpeed = -speed;
        }
        else
        {
            isRun = false;
            xSpeed = 0.0f;
            dashTime = 0.0f;
        }

        //前回の入力からダッシュの反転を判断して速度を変える
        if (horizontalKey > 0 && beforeKey < 0)
        {
            dashTime = 0.0f;
        }
        else if (horizontalKey < 0 && beforeKey > 0)
        {
            dashTime = 0.0f;
        }

        xSpeed *= dashCurve.Evaluate(dashTime);
        beforeKey = horizontalKey;
        return xSpeed;
    }

    /// <summary>
    /// アニメーションを設定する
    /// </summary>
    private void SetAnimation()
    {
        anim.SetBool("jump", isJump || isOtherJump);
        anim.SetBool("ground", isGround);
        anim.SetBool("run", isRun);
    }


    /// <summary>
    /// コンティニュー待機状態か
    /// </summary>
    /// <returns></returns>
    public bool IsContinueWaiting()
    {
        if (GManager.instance.isGameOver)
        {
            return false;
        }
        else
        {
            return IsDownAnimEnd() || nonDownAnim;
        }
    }

    //ダウンアニメーションが完了しているかどうか
    private bool IsDownAnimEnd()
    {
        if (isDown && anim != null)
        {
            AnimatorStateInfo currentState = anim.GetCurrentAnimatorStateInfo(0);
            if (currentState.IsName("player_down"))
            {
                if (currentState.normalizedTime >= 1)
                {
                    return true;
                }
            }
        }
        return false;
    }

    /// <summary>
    /// コンティニューする
    /// </summary>
    public void ContinuePlayer()
    {
        isDown = false;
        anim.Play("player_stand");
        isJump = false;
        isOtherJump = false;
        isRun = false;
        isContinue = true;
        nonDownAnim = false;
    }

    //やられた時の処理
    private void ReceiveDamage(bool downAnim)
    {
        if (isDown)
        {
             return;
        }
        else
        {
             if (downAnim)
             {
                 anim.Play("player_down");
             }
             else
             {
                 nonDownAnim = true;
             }
             isDown = true;
             GManager.instance.SubHeartNum();
        }
    }

    #region//接触判定
    private void OnCollisionEnter2D(Collision2D collision)
    {
        if (collision.collider.tag == enemyTag)
        {
            //踏みつけ判定になる高さ
            float stepOnHeight = (capcol.size.y * (stepOnRate / 100f));

            //踏みつけ判定のワールド座標
            float judgePos = transform.position.y - (capcol.size.y / 2f) + stepOnHeight;

            foreach (ContactPoint2D p in collision.contacts)
            {
                if (p.point.y < judgePos)
                {
                    ObjectCollision o = collision.gameObject.GetComponent<ObjectCollision>();
                    if (o != null)
                    {
                        otherJumpHeight = o.boundHeight;    //踏んづけたものから跳ねる高さを取得する
                        o.playerStepOn = true;        //踏んづけたものに対して踏んづけた事を通知する
                        jumpPos = transform.position.y; //ジャンプした位置を記録する
                        isOtherJump = true;
                        isJump = false;
                        jumpTime = 0.0f;
                    }
                    else
                    {
                        Debug.Log("ObjectCollisionが付いてないよ!");
                    }
                }
                else
                {
                    ReceiveDamage(true);
                    break;
                }
            }
        }
    }


    private void OnTriggerEnter2D(Collider2D collision)
    {
        if(collision.tag == deadAreaTag)
	{
            ReceiveDamage(false);
	}
	else if(collision.tag == hitAreaTag)
	{
            ReceiveDamage(true);
	}
    }
    #endregion
}
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.SceneManagement; //New!

public class StageCtrl : MonoBehaviour
{
    [Header("プレイヤーゲームオブジェクト")] public GameObject playerObj;
    [Header("コンティニュー位置")] public GameObject[] continuePoint;
    [Header("ゲームオーバー")] public GameObject gameOverObj;//New!
    [Header("フェード")] public FadeImage fade;//New!

    private Player p;
    private int nextStageNum; //New!
    private bool startFade = false; //New!
    private bool doGameOver = false; //New!
    private bool retryGame = false; //New!
    private bool doSceneChange = false; //New!

    // Start is called before the first frame update
    void Start()
    {
        if (playerObj != null && continuePoint != null && continuePoint.Length > 0 && gameOverObj != null && fade != null)
        {
            gameOverObj.SetActive(false); // New!
            playerObj.transform.position = continuePoint[0].transform.position;
            p = playerObj.GetComponent<Player>();
            if (p == null)
            {
                Debug.Log("プレイヤーじゃない物がアタッチされているよ!");
            }
        }
        else
        {
            Debug.Log("設定が足りてないよ!");
        }
    }

    // Update is called once per frame
    void Update()
    {
        //ゲームオーバー時の処理
        if (GManager.instance.isGameOver && !doGameOver) //New!
        {
            gameOverObj.SetActive(true);
            doGameOver = true;
        }
        //プレイヤーがやられた時の処理
        else if (p != null && p.IsContinueWaiting() && !doGameOver)  //New!
        {
            if (continuePoint.Length > GManager.instance.continueNum)
            {
                playerObj.transform.position = continuePoint[GManager.instance.continueNum].transform.position;
                p.ContinuePlayer();
            }
            else
            {
                Debug.Log("コンティニューポイントの設定が足りてないよ!");
            }
        }

        //ステージを切り替える
        if (fade != null && startFade && !doSceneChange)
        {
            if (fade.IsFadeOutComplete())
            {
                //ゲームリトライ
                if (retryGame)
                {
                    GManager.instance.RetryGame();
                }
                //次のステージ
                else
                {
                    GManager.instance.stageNum = nextStageNum;
                }
                SceneManager.LoadScene("stage" + nextStageNum);
                doSceneChange = true;
            }
        }
    }

    /// <summary>
    /// 最初から始める New!
    /// </summary>
    public void Retry()
    {
        ChangeScene(1); //最初のステージに戻るので1
        retryGame = true;
    }

    /// <summary>
    /// ステージを切り替えます。 New!
    /// </summary>
    /// <param name="num">ステージ番号</param>
    public void ChangeScene(int num)
    {
        if (fade != null)
        {
            nextStageNum = num;
            fade.StartFadeOut();
            startFade = true;
        }
    }
}

何かうまくいかない事があった場合は↓の記事を参考にしてみてください

最低限↓の動画の要件を満たしていない質問は受けかねるので、ご理解ください。

デバッグのやり方【Unity初心者入門講座】【ゲームの作り方】#31

また、筆者も間違えることはありますので、何か間違っている点などありましたら、動画コメント欄にでも書いていただけるとありがたいです。


タイトルとURLをコピーしました