【Unity入門】2Dアクションを作ろう【敵・当たり判定編】

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

<マリオのクリボーみたいな敵を作ってみよう>

まず、敵を作るにあたって最初なので簡単に作れるものにしようと思います。

と、いうわけで、マリオのクリボーのような比較的動きとしては簡単な敵を作っていきます。クリボーのようなと言っても、別にデザインを似せる必要はなく、「挙動がクリボーの様な」という意味です。

まず、クリボーの動きをあげると

  1. 衝突するとプレイヤーはやられる
  2. ふんづけるとプレイヤーがちょっと跳ねて敵はやられる
  3. 画面内に入ると動き出す
  4. 動いている間はひたすら一定方向に動く
  5. 壁に当たると反対側へ動く

こんな感じでしょうか。

今回はこの当たり判定の部分

  1. 衝突するとプレイヤーはやられる

この部分を実装していこうと思います。

<敵の下書きを作ろう>

このサイトでは下書きを作って、後でちゃんとした素材で上書きするという手法を取っています。下書きから入った方が必要な素材数の把握や、かかる時間などを把握しやすいからです。また、思いもよらないトラブルに出会った場合、下書きだと対処しやすいです。というわけで、例によって敵の下書きをパパッと作ってしまいましょう。

大きさは適当で大丈夫ですが、2のべき乗だとメモリ的にお得です。
とりあえず自分は128×128で作りました。

↓歩きモーション1

enemy_walk1

↓歩きモーション2

enemy_walk2

↓やられた

enemy_dead

まぁ、こんな感じで適当でいいです。

こういう風に作ると、とりあえず敵を移動させたくなりますが、先に移動を実装しちゃうと踏んづけるのにいちいちタイミングを合わせるのがめんどくさいので、デバッグのやりやすさを取って、当たり判定から実装します。

<当たり判定をつけよう>

四角い敵のコライダー

とりあえず、シーンに敵をおいて当たり判定をつけましょう。

プレイヤーと同じ様に設定したいところなんですがCapsule Collider 2Dだとちょっと形が違うのでBox Collider 2Dを使います。

あれ?Box Collider 2Dだとコライダーに引っかかるんじゃなかったっけ?という話なんですが、実は角を丸めることができます。

とりあえず、Box Collider 2Dをつけて、当たり判定を実際のサイズよりちょっと小さくしてください。

enemy_box_collider

当たり判定の緑の枠が見えやすいようにテクスチャを薄くしています。

Box Collider 2DのところのEdge Radiusという項目をいじってみてください。

設定した大きさのコライダーよりちょっと大きな角が丸いコライダーができたと思います。

これで角がつっかえることがなくなりました。

このように、四角い形状の場合でも角に丸みをつけることで動かせるようになります。

Rigidbody2Dも忘れずつけておきましょう。ConstaintsのFreeze Rotation Zにチェックする事を忘れずに。

add rigidbody 2d

基本的な2Dコライダー

さて、今回は四角いコライダーを使いましたが、四角以外の形状にしたい場合は

では、四角以外の形状の敵を作りたい場合はどうすればいいのでしょうか。

答えは形状に合わせてコライダーの種類を変えればいいのです。

コライダーには色々な種類があります。基本形となるコライダーは↓の3種類です。これらを組み合わせて形を作るといいと思います。

使いたい形状に合わせてコライダーを使い分けてください。

  • Circle Collider 2D ・・・円形のコライダー
  • Box Collider 2D ・・・四角形のコライダー
  • Capsule Collider 2D ・・・カプセル型のコライダー

複数コライダーを使う場合

コライダーを複数組み合わせても構いませんが、その場合注意点があります。

1. たくさんつけすぎると重くなる
2. 同じゲームオブジェクトに同じコライダーを複数付けるとGetComponentでインスタンスを捕まえた時に判別しにくい

これらの点に注意しましょう。

↓複数コライダーをつけてみた例です。

any collider

ちなみに、同じゲームオブジェクトおよび子オブジェクトにつけたコライダーは重なっても大丈夫で、まとめて一つのコライダーであるかのように扱われます。

同じ種類のコライダーを複数使いたい場合は、子オブジェクトに持たせた方がいいかもしれません。インスタンスを捕まえる時にめんどくさいので。

same collider

このように、同じコライダーを複数使う場合は子オブジェクトに持たせるといいです。注意点としては、必ずRigidbodyは一番親につける事です。子に付けるとそれはそれで挙動が変わります。

特殊な2Dコライダー

少し毛色の変わったコライダーが他にも3種類ありますが、これらは使い方がわかってから使うようにした方がいいかも。同じ種類のコライダーと衝突できなかったり、重くなったりするので取り扱いは慎重に。

  • Polygon Collider 2D・・・自由に形状を決められるコライダー
  • Edge Collider 2D・・・自由に形状を決められるコライダー
  • Composite Collider 2D・・・Box Collider 2D と Polygon Collider 2D を結合するコライダー

以前に使用した↓のように自由な形状で使えます。

edit collider

これらは、動くオブジェクトではなく、ステージのような固定されているものに使うといいかもしれません。

<敵との衝突判定をとる>

さて、敵の当たり判定ができたので、敵とぶつかった時の判定を作っていきましょう。

地面との接地判定を作った時のようにタグで判別したいと思います。敵用のタグを追加しましょう。

add tag

自分はEnemyという名前でタグを作成しました。

add enemy tag

タグを追加したら、敵のタグの変更を忘れずに。

それでは、このEnemyタグとの衝突判定を取りたいと思います。

今までのプレイヤーのスクリプトに衝突判定を追加しました。

クリックすると展開します
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 AnimationCurve dashCurve;
    [Header("ジャンプの速さ表現")]public AnimationCurve jumpCurve;
    #endregion

    #region//プライベート変数
    private Animator anim = null;
    private Rigidbody2D rb = null;
    private string groundTag = "Ground";
    private string enemyTag = "Enemy"; //New!   
    private bool isGroundEnter, isGroundStay, isGroundExit;
    private bool isGround = false;
    private bool isJump = false;
    private bool isRun = false;
    private float jumpPos = 0.0f;
    private float dashTime, jumpTime;
    private float beforeKey;
    #endregion

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

    void FixedUpdate()
    {
        GroundCheck();
        rb.velocity = new Vector2(SetX(), SetY());
        SetAnimation();
    }
    
    /// <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;
            }
            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)
        {
            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);
        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

//New!     
    #region//接触判定
    private void OnCollisionEnter2D(Collision2D collision)
    {
        if (collision.collider.tag == enemyTag)
        {
            Debug.Log("敵と接触した!");
        }
    }
    #endregion
}

さて、このスクリプトの中で今回、追加されているのは

    private string enemyTag = "Enemy";

     #region//接触判定
     private void OnCollisionEnter2D(Collision2D collision)
     {
         if (collision.collider.tag == enemyTag)
         {
             Debug.Log("敵と接触した!");
         }
     }
     #endregion

この部分ですね。

このOnCollisionEnter2DというのもUnityがあらかじめ用意してくれている特別な関数で、コライダーの衝突を検知する際に使用します。

接地判定の時に用いたOnTrigger○○2Dと似た感じですね。ただし、OnTriggerの場合は引数がCollider2Dであるのに対して、OnCollisionの場合は引数がCollision2Dと違うので注意してください。

OnTriggerの場合は当たり判定の無い領域にCollider2Dが入ってきたというものです。

OnCollisionの場合は当たり判定のあるもの同士がぶつかった「衝突」です。その為引数がCollider2DではなくCollision2Dなのです。

Collision2Dはコライダー ではなく、衝突を表すため

collision.collider.tag

このように、衝突から、衝突した際のコライダーにアクセスし、更にその中のタグにアクセスする必要があります。

このOnCollision○○2Dも、Enter,Stay,ExitとOnTriggerの時と同じように色々種類があるのですが、今回は敵に当たったらやられるようにしたいのでEnterだけでOKです。

では、これで再生してみましょう。

collision-player

とりあえず、これで敵と接触する判定を作れました。

では、敵と接触したらダウンするようにしましょう。以前作成したアニメーションがあると思います。

player down

アニメーションの事がわからない、忘れてしまったという人は↓の記事へどうぞ

さて、以前はアニメーションを線でつないでいましたが、今回は繋がずに行こうと思います。何故なら、やられモーションはどの状態からでも遷移する上に、ダウンした後は何の状態にも遷移しないからです。

まぁ、要はたくさん線を繋ぐのがめんどくさいし、メリットもないので省略しましょうという事です。ダウンに関しては行ったり来たりがないですからね。

その為に追加するコードは↓です。

anim.Play("player_down");

animはアニメーターを突っ込んでいる変数です。

今まではSetBoolでアニメーターに対してパラメータを渡していましたが、中にあるアニメーションを直接再生する事もできます。それがPlayです。

Play(“アニメーターに追加したアニメーションの名前”);

で再生できます。

更に、ダウンしたというフラグを作ってダウンしたら動かないようにしましょう。

    #region//接触判定
     private void OnCollisionEnter2D(Collision2D collision)
     {
         if (collision.collider.tag == enemyTag)
         {
             anim.Play("player_down");
             isDown = true;
         }
     }
     #endregion

敵と接触したら、アニメーションを再生してフラグを立てます。

    private bool isDown = false; 
     void FixedUpdate()
     {
         if (!isDown)
         {
             GroundCheck();
             rb.velocity = new Vector2(SetX(), SetY());
             SetAnimation();
         }
     }

そして、フラグが立っている状態であるなら操作をできないようにします。

loop time non check

アニメーションのループを切るのを忘れないようにしましょう。

player_down_animation

<まとめ>

はい。これで敵との接触判定ができました。

しかし、このままでは敵を踏んづけてもやられてしまうので、それを次回解説していきたいと思います。

今回作成したスクリプトは↓のような感じです。

クリックすると展開します
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 AnimationCurve dashCurve;
     [Header("ジャンプの速さ表現")]public AnimationCurve jumpCurve;
     #endregion
 
     #region//プライベート変数
     private Animator anim = null;
     private Rigidbody2D rb = null;
     private string groundTag = "Ground";
     private string enemyTag = "Enemy"; //New!   
     private bool isGroundEnter, isGroundStay, isGroundExit;
     private bool isGround = false;
     private bool isJump = false;
     private bool isRun = false;
     private bool isDown = false; 
     private float jumpPos = 0.0f;
     private float dashTime, jumpTime;
     private float beforeKey;
     #endregion
 
     void Start()
     {
         //コンポーネントのインスタンスを捕まえる
         anim = GetComponent<Animator>();
         rb = GetComponent<Rigidbody2D>();
     }
 
     void FixedUpdate()
     {
         if (!isDown)
         {
             GroundCheck();
             rb.velocity = new Vector2(SetX(), SetY());
             SetAnimation();
         }
     }
     
     /// <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;
             }
             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)
         {
             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);
         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

//New!     
     #region//接触判定
     private void OnCollisionEnter2D(Collision2D collision)
     {
         if (collision.collider.tag == enemyTag)
         {
             anim.Play("player_down");
             isDown = true;
         }
     }
     #endregion
 }

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

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

<オススメの本>

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

<オンラインスクール>

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



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