【Unity入門】2Dアクションを作ろう【ステージ管理】

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

<ステージを管理するスクリプトを作ろう>

スタート地点を決めよう

さて、ゲームをスタートするに当たって、スタートする地点を決めたいと思います。

最初からプレイヤーをスタート地点に置けばいいのでは?と思うかもしれませんが、プレイヤーがやられてしまった時にスタート地点に戻ってこないといけないのでスタート地点を決めます。

まずはカラのゲームオブジェクトを作成してください。適当にスタート地点とわかる名前に変えるといいと思います。

自分はStartPosという名前にしました。

そして、ゲームオブジェクトの左上の灰色の箱みたいな奴をクリックしてください。

↓のようなメニューが出てきたと思います。

game object color icon

適当に好きな色の奴を選んでください。できたら上2段の横長の物がいいと思います。

game object scene view

そうすると、カラだったゲームオブジェクトがシーンビュー上で映るようになります。

これは何かというとただの目印で、ゲームとは何の関係もありません。

ゲームとは関係ないただの目印であるという点が重要で、要は編集する上で「場所」を示すのに便利です。

これをスタート地点にしたいところに置きましょう。

start position select

ポイントとしては、キャラクターの中心座標分高い位置に置くことです。

このポジションを取得してキャラクターを配置します。

ステージコントローラーを作成しよう

それではステージをコントロールするスクリプトを書きましょう。

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

public class stageCtrl : MonoBehaviour
{

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

    void Start()
    {
        if (playerObj != null && continuePoint != null && continuePoint.Length > 0)
        {
            playerObj.transform.position = continuePoint[0].transform.position;
        }
    }

    void Update()
    {
        
    }
}

さて、このスクリプトでポイントとなっているのはpublicに配列を持ってきている点です。

[Header("コンティニュー位置")]public GameObject[] continuePoint;

コンティニュー位置としていますが、0番目がスタート地点になります。

if (playerObj != null && continuePoint != null && continuePoint.Length > 0)

これは、インスペクターで設定し忘れた時用の対策です。配列.Lengthというのは配列の中身が何個用意されているのかを知る事ができます。要は中身が1個も無かったら処理は行わないという事です。

playerObj.transform.position = continuePoint[0].transform.position;

そして、Startでプレイヤーの位置をスタート地点と同じ位置にするようにします。

これをカラのゲームオブジェクトを作って貼り付けると↓のようになります。

stage control inspector

配列のところにSizeとなっていますね。ここに数字を入れると

inspector element

このように中身を入れる箱が出来上がります。Sizeというのは配列の大きさになります。publicにすることによってインスペクターで大きさを指定できるようになったわけです。

これでコンティニューの作り方もだいたいわかったと思いますが、コンティニューポイントを作成するにはサイズを2以上にして、コンティニューポイントを通過した時、プレイヤーが戻ってくる位置を0から1に変えてあげればOKになります。

もちろんSizeの数は自由にできるのでステージによってコンティニューポイントを増やすことも可能になります。

set player object start object

あとはインスペクターにプレイヤーとスタート地点のゲームオブジェクトをはめ込んであげればOKになります。

<スタート地点に戻ってこよう>

アニメーション終了判定を得よう

さて、では敵にやられたらスタート地点に戻ってくる処理を作ろうと思います。

その前に、やられた時のアニメーションが終わっているかどうかの判定をとりたいと思います。いきなりスタート地点に戻ってしまっては何が起こったかわからなくなってしまいます。

プレイヤーのスクリプトに↓の関数を追加します。

クリックすると展開します
    /// <summary>
    /// ダウンアニメーションが終わっているかどうか
    /// </summary>
    /// <returns><c>true</c>, if down animation end was ised, <c>false</c> otherwise.</returns>
    public bool IsDownAnimEnd()
    {
        if (isDown && anim != null)
        {
            AnimatorStateInfo currentState = anim.GetCurrentAnimatorStateInfo(0);
            if (currentState.IsName("player_down"))
            {
                if (currentState.normalizedTime >= 1)
                {
                    return true;
                }
            }
        }
        return false;
    }

プレイヤーのアニメーションの情報を拾ってくるには↓のようにする必要があります。

AnimatorStateInfo currentState = anim.GetCurrentAnimatorStateInfo(0);

このAnimatorStateInfoというのが現在再生中のアニメーション情報になります。

if (currentState.IsName("player_down"))

これが、プレイヤーが再生中のアニメーションがplayer_downかどうかを判定しています。

if (currentState.normalizedTime >= 1)

これでアニメーションの終了判定をしています。normalizedTimeというのはアニメーションの全体の再生時間を1とした場合の数値になります。

1で100%再生、0.1で10%再生が終わったみたいな感じです。

1以上を指定しているのでアニメーションの再生が完全に終わっていることを意味しています。「1」で完全に終了し、ループすると1より上になります。

ここで注意して欲しいのは、この「1」という数字になるのはアニメーションが他に矢印が設定されていない場合に限ります。

animation state

このようにplayer_downが他から孤立している為、この判定を取る事ができます。

矢印を設定していても遷移さえしなければ判定を取る事ができますが、ちょっとプログラムとしてわかりづらくなってしまうので注意が必要です。

この関数を用意することによって他のスクリプトからプレイヤーのダウンアニメーションが終わっている判定を取る事ができるようになりました。

やられたらスタート地点に戻ってこよう

今度はステージコントローラーからプレイヤーのスクリプトを見て、ダウンアニメーションが終わっていればスタート地点に戻す処理を入れていきます。

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

public class stageCtrl : MonoBehaviour
{

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

    private player p;
    void Start()
    {
        if (playerObj != null && continuePoint != null && continuePoint.Length > 0)
        {
            playerObj.transform.position = continuePoint[0].transform.position;
            p = playerObj.GetComponent<player>();
        }
    }

    // Update is called once per frame
    void Update()
    {
        if (GManager.instance != null && continuePoint.Length > GManager.instance.continueNum)
        {
            if (p.IsDownAnimEnd())
            {
                playerObj.transform.position = continuePoint[GManager.instance.continueNum].transform.position;
            }
        }
    }
}

関数を用意した事で簡単にプレイヤーの情報を得られるようになりました。

if (p.IsDownAnimEnd())
{         playerObj.transform.position = continuePoint[GManager.instance.continueNum].transform.position;
}

プレイヤーのダウンアニメーションが終わったら現在のコンティニューポイントの位置に移動します。

0はスタート地点なのでゲームマネージャーのコンティニューポイントの数値をいじっていなければスタート地点に戻ります。

これで、ゲームマネージャーの値を変えてあげればコンティニューする位置も指定できるようになりました。

return start point

さて、戻ってきたはいいものの、ダウンしぱなっしなので、フラグを解除できるようにしましょう。

プレイヤーに↓のような関数を追加します。

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

ステージコントローラー側のスクリプトからコンティニューした事をプレイヤーに伝えるようにしましょう。

        if (GManager.instance != null && continuePoint.Length > GManager.instance.continueNum)
         {
             if (p.IsDownAnimEnd())
             {
                 playerObj.transform.position = continuePoint[GManager.instance.continueNum].transform.position;
                 p.ContinuePlayer();
             }
         }

これでちゃんと戻ってくる事ができるようになりました。

<コンティニューした演出を加えよう>

さて、このままではやられたらパッと戻ってきてしまって実感があんまりわかないので、コンティニューしたという演出を加えてみましょう。

演出と言っても難しい事はせず、ただ点滅させたいと思います。

プレイヤーのスクリプトの中に↓の変数を追加します。

private bool isContinue = false; 
private float continueTime,blinkTime; 
private SpriteRenderer sr = null;

Start内で↓のようにインスタンスを捕まえるのを忘れずに

sr = GetComponent<SpriteRenderer>();

そしてUpdate内に↓を追加します。

クリックすると展開します
    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 = 0f;
                continueTime = 0f;
                sr.enabled = true;
            }
            else
            {
                blinkTime += Time.deltaTime;
                continueTime += Time.deltaTime;
            }
        }
    }

今までFixedUpdateに処理を書いていましたが、今回はレンダリングに関する事なのでUpdateに書きます。

フラグがたったら0.1秒ごとにSpriteRendererをオンオフして点滅する処理を書いています。

blinkTimeというのが0.1秒ごとを測るのに使用している変数で、continueTimeというのが全体の時間になります。0.1秒の点滅を1秒間続けたらフラグを下ろして点滅終わりにします。

↓コンティニューした時にフラグをオンにするようにします。

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

これで再生すると

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 stepOnRate;
    [Header("ジャンプ速度")]public float jumpSpeed;
    [Header("ジャンプする高さ")]public float jumpHeight;
    [Header("ダッシュの速さ表現")]public AnimationCurve dashCurve;
    [Header("ジャンプの速さ表現")]public AnimationCurve jumpCurve;
    #endregion

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

    private 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 = 0f;
                continueTime = 0f;
                sr.enabled = true;
            }
            else
            {
                blinkTime += Time.deltaTime;
                continueTime += Time.deltaTime;
            }
        }
    }

    private void FixedUpdate()
    {
        if (!isDown)
        {
            GroundCheck();
            rb.velocity = new Vector2(SetX(), SetY());
            SetAnimation();
        }
    }
    
    /// <summary>
    /// ダウンアニメーションが終わっているかどうか
    /// </summary>
    /// <returns><c>true</c>, if down animation end was ised, <c>false</c> otherwise.</returns>
    public 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;
    }
    
    /// <summary>
    /// 接地しているかどうかの判定をとる
    /// </summary>
    /// <returns><c>true</c>, 接地している, <c>false</c> 接地していない</returns>
    private void GroundCheck()
    {
        if(isGroundEnter || isGroundStay)
        {
            isGround = true;
        }
        else if(isGroundExit)
        {
            isGround = false;
        }
        isGroundEnter = false;
        isGroundStay = false;
        isGroundExit = false;
    }
    
    /// <summary>
    /// Y成分で必要な計算をし、速度を返す。
    /// </summary>
    /// <returns>The y.</returns>
    private float SetY()
    {
        float verticalKey = Input.GetAxis("Vertical");
        float ySpeed = -gravity;

        //地面にいるとき
        if(isGround)
        {
            if (verticalKey > 0)
            {
                ySpeed = jumpSpeed;
                jumpPos = transform.position.y;        //ジャンプした位置を記録する
                isJump = true;
            }
            else
            {
                isJump = false;
            }
            isOtherJump = false;
            jumpTime = 0.0f;
        }
        //何かを踏んだ際のジャンプ
        else if (isOtherJump)
        {
            //現在の高さがジャンプした位置から自分の決めた位置より下ならジャンプを継続する
            if (jumpPos + otherJumpHeight > transform.position.y)
            {
                ySpeed = jumpSpeed;
                jumpTime += Time.deltaTime;
            }
            else
            {
                isOtherJump = false;
                jumpTime = 0.0f;
            }
        }
        //ジャンプ中
        else if(isJump)
        {
            //上ボタンを押されている。かつ、現在の高さがジャンプした位置から自分の決めた位置より下ならジャンプを継続する
            if (verticalKey > 0 && jumpPos + jumpHeight > transform.position.y)
            {
                ySpeed = jumpSpeed;
                jumpTime += Time.deltaTime;
            }
            else
            {
                isJump = false;
                jumpTime = 0.0f;
            }
        }
        if (isJump || isOtherJump)
        {
            ySpeed *= jumpCurve.Evaluate(jumpTime);
        }
        return ySpeed;
    }

    /// <summary>
    /// X成分で必要な計算をし、速度を返す。
    /// </summary>
    /// <returns>The x.</returns>
    private float SetX()
    {
        float xSpeed;
        float horizontalKey = Input.GetAxis("Horizontal");
        
        if (horizontalKey > 0)
        {
            transform.localScale = new Vector3(1, 1, 1);
            isRun = true;

            xSpeed = speed;
            dashTime += Time.deltaTime;
        }
        else if (horizontalKey < 0)
        {
            transform.localScale = new Vector3(-1, 1, 1);
            isRun = true;
            xSpeed = -speed;
            dashTime += Time.deltaTime;
        }
        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);
    }

    #region//接地判定
    private void OnTriggerEnter2D(Collider2D collision)
    {
        if (collision.tag == groundTag)
        {
            isGroundEnter = true;
        }
    }

    private void OnTriggerStay2D(Collider2D collision)
    {
        if (collision.tag == groundTag)
        {
            isGroundStay = true;
        }
    }
    
    private void OnTriggerExit2D(Collider2D collision)
    {
        if (collision.tag == groundTag)
        {
            isGroundExit = true;
        }
    }
    #endregion
    
    #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;        //踏んづけたものに対して踏んづけた事を通知する
                        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;
    void Start()
    {
        if (playerObj != null && continuePoint != null && continuePoint.Length > 0)
        {
            playerObj.transform.position = continuePoint[0].transform.position;
            p = playerObj.GetComponent<player>();
        }
    }

    // Update is called once per frame
    void Update()
    {
        if (GManager.instance != null && continuePoint.Length > GManager.instance.continueNum)
        {
            if (p.IsDownAnimEnd())
            {
                playerObj.transform.position = continuePoint[GManager.instance.continueNum].transform.position;
                p.ContinuePlayer();
            }
        }
    }
}

<わからない事があったら>

このサイトの説明ではよくわからなかったとか、もっと知りたい事などがあれば、また別の勉強方法があるので違った切り口を使ってみるのもいいと思います。

<オススメの本>

本で詳しい解説がされているので書籍を買ってみるというのも手の一つです。最近はKindle版があるので届くまで待つ事もなく場所も取らないのでとても良いです。

<オンラインスクール>

オンラインスクールでは人に質問する事ができるので、行き詰まってしまった方にオススメです。 無料体験もあるので試しに見てみるのも手ですよ



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