Unity 2Dアクション作り方【敵・踏んづける編】

前回、敵に当たり判定をつけて、衝突を判定をするところまでできました。今回は敵を踏んづけた時の処理を追加していこうと思います。

↑の動画でも解説しています。動画と併用してもらうとわかりやすいかなと思います。わからない、うまくいかない事があったら質問される前に、一回、動画の方で手順を確認してください

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

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

さて、「挙動がクリボーの様な」敵を作成していきます。

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

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

こんな感じでした。

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

 2. ふんづけるとプレイヤーがちょっと跳ねる

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

<敵を踏んづけてジャンプしよう>

ジャンプする高さのパラメータをつけよう

さて、前回、敵と接触したらやられてしまうところまで作りましたが、このままでは敵を踏んづけた時でもやられてしまいます。マリオのようにしたいなら、敵を踏んづけた時にちょっと上昇して敵を倒したいです。

踏んづけた時のスクリプトを書きたいところですが、その前に一工夫しましょう。

恐らく、後々、踏んづけたものに対してジャンプする高さを変えることがあると思います。

例えば、バネとかトランポリンとか色々あります。全部プレイヤーのスクリプトの中に書いていては大変なので、各種対象物に対してパラメーターをもたせたいと思います。

色々便利にする為に新しくスクリプトを書いていきます。

色々衝突関連の処理を便利にしたいので、自分はObjectCollisionという名前をつけました。

現在↓のような状態です。

 using System.Collections;
 using System.Collections.Generic;
 using UnityEngine;
 
 public class ObjectCollision : MonoBehaviour
 { 
     private void Start()
     {
     }
 
     private void Update()
     {
     } 
 }

本来であれば、汎用的でいろんなところに使えて便利なものを作りたいところなんですが、いきなり色々やろうとすると難しい事を覚えないといけません。

なので、もっといいやり方はあるけど、初心者用の簡単なやり方で実践してみましょう。

まず、StartとUpdateを消して、2つパラメータを追加します。

public float boundHeight;
public bool playerStepOn;

このスクリプトの役割は、プレイヤー側には飛ぶ高さを渡して、敵側には踏まれた事を通知するという橋渡し的なものになっています。

boundHeightはプレイヤーに踏まれた時、プレイヤーが飛ぶ高さです。

playerStepOnというのはプレイヤーに踏まれたというフラグです。

playerStepOnはフラグなのでインスペクターでいじって欲しくありません。自分ではわかっていても、たまたまクリックしてしまう可能性があります。

その為、playerStepOnはインスペクターに映らないようにします。ついでにboundHeightに注釈文も追加しておくと

 using System.Collections;
 using System.Collections.Generic;
 using UnityEngine;
 
 public class ObjectCollision : MonoBehaviour
 {
     [Header("これを踏んだ時のプレイヤーが跳ねる高さ")]public float boundHeight;
 
     /// <summary>
     /// このオブジェクトをプレイヤーが踏んだかどうか
     /// </summary>
     [HideInInspector]public bool playerStepOn;
 }

↑のようにしました。boundHeightはHeaderで注釈文をインスペクターでも映るようになっています。

playerStepOnにくっついている<summary>はVisualStudioの機能で他スクリプトからでもコメントが見れる機能でしたね。これはフラグなので他のスクリプトから見る事を前提にするのでこれを書きました。

そして[HideInInspector]をつけると、その変数はpublicであってもインスペクター上に映らなくなります

このスクリプトを敵にくっつけてみると

object collision script

このようにplayerStepOnは表示されません。

プレイヤー側を跳ねさせてみよう

さて、パラメータを2つだけ持ったスクリプトを書いたわけですが、今度はプレイヤー側のスクリプトに移りたいと思います。

クリックすると展開します
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;
     #endregion

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

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


     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 (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)
          {
              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);
          anim.SetBool("ground", isGround);
          anim.SetBool("run", isRun);
     }

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

前回、敵と接触した際の接触判定を作りました。

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

ここに処理を追加していきます。

まず、敵を踏んづけた時に敵は自分の足元より下の位置にいなければいけません。

その為、まず衝突した位置を把握しましょう。

ちょっとここは難しいので丁寧に説明します。

衝突した位置情報は実は複数あります

これはまぁ、そうで、何かにぶつかった時一箇所だけじゃなくて複数の箇所ぶつかっているかもしれないという事です。

その為、衝突の位置情報は配列で返ってきます

現在Collision2Dを変数名collisionにしているのでそれを使うと

collision.contacts

この、contactsというものが衝突の位置情報を持っています。contactsというのは ContactPoint2D型の配列です。

ContactPoint2Dは衝突情報を持ったクラスです。

この変数を取得しようと思うと

ContactPoint2D[] points = collision.contacts;

になります。

配列になっているので、衝突情報を全てみていかなければいけません。何故なら敵を踏んだと同時に当たっている可能性もあるので。(今回の四角い敵ではほぼ起きませんが、形状が複雑な敵ほど起こるようになってきます。)

適当に書いた絵で申し訳ないですが、例えばこんな感じの恐竜型の敵を作ったとして、

Triceratops

踏む判定と敵との接触でのやられ判定が同時に起こる可能性があります。

まぁ、こんな複雑な当たり判定の敵作らないのが一番平和でいいんですが(ボソッ

その為、全ての接触判定をみる必要があるのです。

さて、では全ての情報を見るにはどうすれいいかと言うと、ループ文を使用します。ただし、今回の場合、何箇所当たったのかわからないので、何回ループすればいいかわかりません。

こういう場合にちょうどいいループ文があったのを覚えていますでしょうか。忘れてしまった方、わからない方は↓の記事を見てみてください。

配列のループ文

はい。答えを言うと、こう言う場合にちょうどいいのはforeach文です。

foreach (ContactPoint2D p in collision.contacts)
{ 
}

↑のように書きます。配列である一要素一要素でループしてくれて、その一つ一つの要素を変数pを作ってその中に入れています。

foreachは簡単に配列のループを書けるのですが、実はちょっと重いのでこだわる場合はfor文とかを使った方がいいです。が、微々たる違いなのでここではforeachを使います。

この変数pから衝突した座標をとります。衝突した座標のとり方は

p.point

になります。

foreach (ContactPoint2D p in collision.contacts)
{
   if (敵と衝突した位置が足元だったら)
   {
    //もう一度跳ねる
   }
   else
   {
    //ダウンする
   }
}

このようにするとうまくいきそうです。

スポンサーリンク

Collider2Dの衝突の計算

では「敵と衝突した位置が足元だったら」と言う条件を考えると

p.point.y < transform.position.y

と、基本形はこんな感じになります。transform.position.yはプレイヤーの中心位置になります。

「衝突位置が、プレイヤーの中心より下だったら」という条件になります。

さて、ここで問題が出てきます。「プレイヤーの中心位置より下」という条件にしてしますと、「プレイヤーの2分の1以下大きさの敵」が出てきてしまったらアウトになります。

collision point judge

こんな感じで、プレイヤーよりちっちゃい敵を作りづらくなってしまいます。

その為、少し計算しましょう。計算するにはプレイヤーの当たり判定の大きさが必要になってきます。

まずはカプセルコライダー2Dのインスタンスを捕まえましょう。

private CapsuleCollider2D capcol = null;  
void Start()
{
    capcol = GetComponent<CapsuleCollider2D>();
}

このカプセルコライダーの高さをとってくるには

capcol.size.y

これでいけます。

これを条件式に当てはめると

p.point.y < transform.position.y - (capcol.size.y / 2f)

この状態で、衝突位置が足元より下という判定になります。

transform.position.yが元々中心位置なので、コライダーの高さの半分の高さを引けばちょうど一番下の位置になります。

しかし、これだけはまだちょっと足りないです。

踏みつけの判定範囲を設定しよう

このままでは、衝突位置というのは割と誤差が生まれやすいのと、カプセルコライダーの球状の部分があるので完全に足元以下の判定にするとうまくいきません。

判定がシビアすぎるので余白を持たせてあげる必要があります。この余白が踏みつけ判定の大きさになります。

どんなキャラクターにするかでこの踏みつけ判定にしたい大きさは変わると思うので、インスペクターから設定できるようにしておきましょう。

[Header("踏みつけ判定の高さの割合")] public float stepOnRate;

今回はキャラクター足元から何割の高さを当たり判定にするようにします。

//踏みつけ判定になる高さ
float stepOnHeight = (capcol.size.y * (stepOnRate / 100f));

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

一番下の足元部分から、キャラクターの高さから指定した割合分足しています。

これで当たり判定の高さができました。

これを条件式に当てはめると

//踏みつけ判定になる高さ
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)
   {
    //もう一度跳ねる
   }
   else
   {
    //ダウンする
   }
}

このような形になります。

ループ文の外で計算しているのは、同じ計算をループ内で何回もしてしまうからです。無駄な処理が発生してしまうのでなるべくこういうのには気をつけるといいと思います。

一時的に値を保持しておく変数を作っても構わないので、なるだけループ内の計算を減らしましょう。

通常ジャンプとは別のジャンプ判定を作ろう

さて、あとはif文の中身を書いていけばいけそうです。

ダウンした時の処理は前回作成したので、それをそのまま当てはめればOKです。

もう一度跳ねる処理を書いていきましょう。

前々回でプログラムを整理してたおかげでY座標の操作する場所がわかりやすくなっていると思います。プログラム整理はこういう時に役に立つわけですね。

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

          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)
          {
              ySpeed *= jumpCurve.Evaluate(jumpTime);
          }
          return ySpeed;
     }

この状態がY座標をコントロールしているメソッドです。

プレイヤーがコントロールするジャンプは上ボタンを押した長さでジャンプする高さが変わっていたので、それとはまた別のジャンプの処理を作ってあげる必要があります。

別のジャンプのフラグを作ってあげましょう。そして、別のジャンプで飛ぶ高さを設定してあげます。

private bool isOtherJump = false;
private float otherJumpHeight = 0.0f;

そして、他の要因でジャンプした時の処理を書くと、普通にジャンプした時と同じように

//何かを踏んだ際のジャンプ
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;
   }
}

こうします。普通のジャンプとほぼ一緒ですが、上ボタンを押していたらという条件が無くなっています。

それと、アニメーションカーブを適用しているところにも通るようにしましょう。

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

他要因のジャンプでもアニメーションカーブを適用できるように「もしくは」の条件でisOtherJumpを追加しました。

そして、地面にいるときは別のジャンプを解除して欲しく、なおかつ「別のジャンプ」に「通常のジャンプ」をして欲しくないのでif文の真ん中に入れます。

そして、地面にいる時、別のジャンプのフラグの解除を記述すると↓のようになります。

/// <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;
}

さて、Y座標の調整ができたので、ぶつかった時のコライダーの方へ戻りましょう。

     #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)
                 {
                     //もう一度跳ねる
                 }
                 else
                 {
                     //ダウンする
                 }
             }
         }
     }
     #endregion

Y座標の設定が終わったのでこの「もう一度跳ねる」の部分はパラメーターを調整さえすれば良さそうです。

敵についているスクリプトから情報をとってくる

ここで覚えていますでしょうか。この記事の最初の方で、便利になるようにスクリプトを切り分けたものがありましたよね?

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

public class ObjectCollision : MonoBehaviour
{
    [Header("これを踏んだ時のプレイヤーが跳ねる高さ")]public float boundHeight;

    /// <summary>
    /// このオブジェクトをプレイヤーが踏んだかどうか
    /// </summary>
    [HideInInspector]public bool playerStepOn;
}

接触した際に、敵についている↑のスクリプトから情報を取得します。

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が付いてないよ!");
}

これで、踏んづけたものによって跳ねる高さを変える事ができ、なおかつ敵側でめんどくさい計算をしなくても踏まれた事を知ることができるようになりました。

本来なら、挙動が違う敵を作れば作るほど新しいスクリプトを書かなくてはいけないのですが、このパラメーターを別のスクリプトに切り分けた事でいちいち取得するスクリプトが増えるたびにプレイヤーのスクリプトを変えると言う事をしなくてもよくなりました。

if (o != null)
{
}

この部分はNullチェックと言って、例えば敵にCollisionObjectをつけ忘れた時、無いインスタンスを参照するとエラーになってしまうので回避しています。もしNullだった場合はログを出すようにしました。

ダウンする時の処理は以前の物を参照して

anim.Play("player_down");
isDown = true;

これで、いいんですが、ダウンの処理があった場合、跳ねて欲しく無いのでループを抜けるために

break;

を追加します。

これで、敵を踏む判定ができました。

     #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

追加した処理はループの中に入っていますが、足元の判定が複数回行われる可能性は低く、踏んづけた時しかこの処理をしないので、ループ内に書いています。

ちなみに、このループの回数は複数の敵に接触した時に何回も回るわけではなく、1つの敵で複数個所接触した時に回ります。複数の敵に接触した場合、OnCollisionEnter2Dが何回も呼ばれます。その為、足元の判定が複数回行われる可能性が低いわけですね。

最後にアニメーションのところをちょっと変えましょう

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

ジャンプするアニメーションのところに、通常ジャンプのフラグと別のジャンプのフラグのどちらかでも立っていたらジャンプのアニメーションをするようにします。

アニメーターで、ジャンプ(下降中)からジャンプ(上昇中)への遷移を繋いで、

jump arrow

Has Exit Timeを切って、Transition Durationを0にして、Conditionsをjumpのtrueにしましょう

jump arrow setting

現在のプレイヤーのスクリプトは↓のような感じになりました。

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 bool isGround = false;
     private bool isJump = false;
     private bool isRun = false;
     private bool isHead = false; 
     private bool isDown = false;
     private bool isOtherJump = 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 string enemyTag = "Enemy";
     #endregion

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

     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);
     }

     #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
 }

そして、インスペクター上のStep On Rateの設定を忘れないようにして、

player_setting_step_on_rate

敵にObject Collisionをつけて跳ねる高さを設定してあげれば

object collision height setting

このように敵を踏んだ時に跳ねて、ちゃんと横からぶつかった時ややられるようにできました。

enemy jump

ちょっとアニメーションが変に見えますが、これはこのホームページにあげる際にgifに変換してるせいです。皆さんのお手元ではちゃんとアニメーションしていると思います。

と、言うわけで敵の当たり判定と踏んづけた時の判定でした。今回は少し難しかったかもしれませんが、わからなくてもいいのでなんとなくでもやっていただけたらなと思います。

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

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

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


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