Unity 2Dアクションの作り方【動く床・落ちる床】【ギミック】

make 2d action thumbnail

さて、前回で音をつけるところまでいきました。今回からちょっとギミックを追加していきたいと思います。最後までついて来て頂ければ↑のようなゲームを作る事ができます。

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

スポンサーリンク

<下からのみすり抜ける床>

様々なギミックの床を作る前に、下から一方通行ですり抜ける床を作っておかないと各種ギミック床が下から乗ることができないので、まずは下からのみすり抜けられるようにします。

とりあえず、適当に下書きで書いた床を配置して、Box Collider 2Dをくっつけます。

Add ComponentでPlatform Effector 2Dというのを貼り付けます。

add component platform effector 2d

そしてボックスコライダーのインスペクターでUsed By Effectorにチェックを入れましょう。

collider effect 2d

こうすることで、PlatformEffector2Dを使用することができるようになります。

このPlatformEffector2Dはコライダーを一方通行にしてくれる便利なコンポーネントです。これをくっつけるだけで一方通行になるのでUnity様様ですね。

↓のような感じになります。

platform effector 2d scene view

↓のように一方通行にすり抜けることができます。

platform effector 2d jump

このコンポーネントはパラメータが複雑なので、基本的にデフォルトのまま置いておいた方がいいかもしれません。

↑の半円がなんなのかとか、パラメータをいじってみたい方は↓の記事を参考にしてください。

ここで1つポイントがあります。

move floor collision size

必ず下からのみすり抜ける床の当たり判定をプレイヤーの接地判定より薄くする事です。

こうしないと地面の中で接地判定になってしまってストンと落ちたり地面の中で多段ジャンプができるようになってしまいます。

スポンサーリンク

<動く床>

動く床のスクリプト解説

それでは動く床を実装していきます。

ただ床を動かすスクリプトを作っても汎用性に欠けるので、オブジェクトを指定した経路通りに動くスクリプトを作成します。

クリックで展開します
 using System.Collections;
 using System.Collections.Generic;
 using UnityEngine;
 
 public class MoveObject : MonoBehaviour
 {
     [Header("移動経路")]public GameObject[] movePoint;
     [Header("速さ")]public float speed = 1.0f;
 
     private Rigidbody2D rb;
     private int nowPoint = 0;
     private bool returnPoint = false;
 
     private void Start()
     {
         rb = GetComponent<Rigidbody2D>();
         if (movePoint != null && movePoint.Length > 0 && rb != null)
         {
             rb.position = movePoint[0].transform.position;
         }
     }
 
     private void FixedUpdate()
     {
         if(movePoint != null && movePoint.Length > 1 && rb != null)
         {
             //通常進行
             if (!returnPoint)
             {
                 int nextPoint = nowPoint + 1;
 
                 //目標ポイントとの誤差がわずかになるまで移動
                 if (Vector2.Distance(transform.position, movePoint[nextPoint].transform.position) > 0.1f)
                 {
                     //現在地から次のポイントへのベクトルを作成
                     Vector2 toVector = Vector2.MoveTowards(transform.position, movePoint[nextPoint].transform.position, speed * Time.deltaTime);
 
                     //次のポイントへ移動
                     rb.MovePosition(toVector);
                 }
                 //次のポイントを1つ進める
                 else
                 {
                     rb.MovePosition(movePoint[nextPoint].transform.position);
                     ++nowPoint;
 
                     //現在地が配列の最後だった場合
                     if (nowPoint + 1 >= movePoint.Length)
                     {
                         returnPoint = true;
                     }
                 }
             }
             //折返し進行
             else
             {
                 int nextPoint = nowPoint - 1;
 
                 //目標ポイントとの誤差がわずかになるまで移動
                 if (Vector2.Distance(transform.position, movePoint[nextPoint].transform.position) > 0.1f)
                 {
                     //現在地から次のポイントへのベクトルを作成
                     Vector2 toVector = Vector2.MoveTowards(transform.position, movePoint[nextPoint].transform.position, speed * Time.deltaTime);
 
                     //次のポイントへ移動
                     rb.MovePosition(toVector);
                 }
                 //次のポイントを1つ戻す
                 else
                 {
                     rb.MovePosition(movePoint[nextPoint].transform.position);
                     --nowPoint;
 
                     //現在地が配列の最初だった場合
                     if (nowPoint <= 0)
                     {
                         returnPoint = false;
                     }
                 }
             }
         }
     }
 }

スクリプト内のコメントを読んでもらえればほぼわかると思いますが、一応解説すると

if (Vector2.Distance(transform.position, movePoint[nextPoint].transform.position) > 0.1f)

Vector2.Distanceというのは2つの位置の距離を測るメソッドです。現在位置と次の位置との距離を測って、距離が小さくなければという判定をしています。

Time.deltaTimeを使用する場合、ぴったりな値になりづらく誤差が生じるのでこのようにちょっと幅を持たせています。

//現在地から次のポイントへのベクトルを作成
Vector2 toVector = Vector2.MoveTowards(transform.position, movePoint[nextPoint].transform.position, speed * Time.deltaTime);

これはコメントに書いてある通りなんですが、現在地から次のポイントへのベクトルを作成しています。speed * Time.deltaTimeというのがここで生成される最大のベクトルの長さになるので、少しずつ移動することを表しています。

//次のポイントへ移動
rb.MovePosition(toVector);

さらに↑も重要ポイントです。動く床はコライダーを持っているのでTransform系で移動させると重くなってしまいます。その為物理演算系で移動させる為Rigidbody2Dで移動させています。

今までと同じようにvelocityを使いたいところですが、velocityだと「速さ」である為、正確に位置が取りづらい為MovePositionを使用しています。

MovePositionはその位置までオブジェクトを移動させるという意味になります。

ちなみに

rb.position

rb.MovePosition

の2種類が存在するのですが、上が空間転移で下が瞬間移動です。

瞬間移動の話は↓の記事でまとめてあります。興味があったら見てみてください。

空間転移は移動経路にコライダーがあっても無視します。瞬間移動は移動経路にコライダーがあると補間されます。

この辺の表現は解説されている方によってマチマチですね。自分は物理計算が入らない移動を空間転移、物理計算が入る移動を瞬間移動と表現しています。

動く床のスクリプトの使用方法

では、このスクリプトの使い方を説明します。

ステージ管理の時に解説した事と同じことをします。

まずは、カラのゲームオブジェクトを作成し、ゲームオブジェクトの左上の灰色の箱みたいな奴をクリックしてください。

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

game object color icon

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

game object scene view

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

床は、このゲームオブジェクトの位置を順番に移動するようになります。

move floor number

↑のように設置しました。

設置したゲームオブジェクトをインスペクターで設定します↓

move object inspector kinematic

Rigidbody2Dもつけましょう。

ポイントとしては、Body TypeをKinematicにしましょう。

Kinematicについての解説は↓の記事で行なっていますので詳しく知りたい方は参考にしてみてください。

回転しないようにFreezeRotationのZにもチェックを入れます。

この状態で再生すると↓のようになります。

move floor

さて、これでオブジェクトを指定した経路通りに動かす事はできました。これは床以外の物にも使用できるので便利です。

しかし、動く床で使用する場合、上に乗っているプレイヤーが滑ってしまっているのがわかるかと思います。

ちょっと動く床としてはアレなので動く床なりの工夫をします。

動く床で滑らないようにする

さて、動く床で滑らないようにする対策として、プレイヤーを床の子オブジェクトにするというやり方が有名です。

しかしながら、その対策だと下の動きに非常に弱くなります。あとこのサイトではキャラクターの反転をスケールでしているので子オブジェクトにしてしまうと大きさが変になってしまいます。

また、Surface Effector2Dを使う対策も存在するのですが、うまく動作しませんでした。

また、Physic Material 2Dで摩擦係数を上げて滑らなくする方法もあるのですが、これはプレイヤーが横に動きづらくなってしまいます。

その為、何か他のものに頼るのではなく、ちゃんと速度計算してあげる必要がありそうです。

using System.Collections;
 using System.Collections.Generic;
 using UnityEngine;
 public class MoveObject : MonoBehaviour
 {
     [Header("移動経路")] public GameObject[] movePoint;
     [Header("速さ")] public float speed = 1.0f;


     private Rigidbody2D rb;
     private int nowPoint = 0;
     private bool returnPoint = false;
     private Vector2 oldPos = Vector2.zero;
     private Vector2 myVelocity = Vector2.zero;

     private void Start()
     {
          rb = GetComponent<Rigidbody2D>();
          if (movePoint != null && movePoint.Length > 0 && rb != null)
          {
              rb.position = movePoint[0].transform.position;
              oldPos = rb.position;
          }
     }

     public Vector2 GetVelocity()
     {
          return myVelocity;
     }

     private void FixedUpdate()
     {
          if (movePoint != null && movePoint.Length > 1 && rb != null)
          {
              //通常進行
              if (!returnPoint)
              {
                  int nextPoint = nowPoint + 1;

                  //目標ポイントとの誤差がわずかになるまで移動
                  if (Vector2.Distance(transform.position, movePoint[nextPoint].transform.position) > 0.1f)
                  {
                      //現在地から次のポイントへのベクトルを作成
                      Vector2 toVector = Vector2.MoveTowards(transform.position, movePoint[nextPoint].transform.position, speed * Time.deltaTime);

                      //次のポイントへ移動
                      rb.MovePosition(toVector);
                  }
                  //次のポイントを1つ進める
                  else
                  {
                      rb.MovePosition(movePoint[nextPoint].transform.position);
                      ++nowPoint;
                      //現在地が配列の最後だった場合
                      if (nowPoint + 1 >= movePoint.Length)
                      {
                          returnPoint = true;
                      }
                  }
              }
             //折返し進行
              else
              {
                  int nextPoint = nowPoint - 1;

                  //目標ポイントとの誤差がわずかになるまで移動
                  if (Vector2.Distance(transform.position, movePoint[nextPoint].transform.position) > 0.1f)
                  {
                      //現在地から次のポイントへのベクトルを作成
                      Vector2 toVector = Vector2.MoveTowards(transform.position, movePoint[nextPoint].transform.position, speed * Time.deltaTime);

                      //次のポイントへ移動
                      rb.MovePosition(toVector);
                  }
                  //次のポイントを1つ戻す
                  else
                  {
                      rb.MovePosition(movePoint[nextPoint].transform.position);
                      --nowPoint;
                      //現在地が配列の最初だった場合
                      if (nowPoint <= 0)
                      {
                          returnPoint = false;
                      }
                  }
              }
              myVelocity = (rb.position - oldPos) / Time.deltaTime;
              oldPos = rb.position;
          }
     }
 }

↓この2つの変数を追加して速度を求めます。

private Vector2 oldPos = Vector2.zero;
private Vector2 myVelocity = Vector2.zero;

oldPosに前のフレームの位置を保存します。そして現在の位置から引く事で進んだ距離が出せます。

myVelocity = (rb.position - oldPos) / Time.deltaTime;

速さ = 道のり / 時間 なので、Time.deltaTimeで割ってあげれば床の速さが出ます。

ここでタグを追加しましょう。Add Tagから

change game object tag

MoveFloorというのを追加します。

そして、動く床のタグをMoveFloorにします。

move floor

今度は接地判定にこのMoveFloorも地面だと認識させます。そして、プレイヤーの足元で床に触れた場合、その床の速度をとってくるようにします。

クリックすると展開します
using System.Collections;
 using System.Collections.Generic;
 using UnityEngine;
 public class GroundCheck : MonoBehaviour
 {
     private string groundTag = "Ground";
     private string moveFloorTag = "MoveFloor";
     private bool isGround = false; 
     private bool isGroundEnter, isGroundStay, isGroundExit;

     //接地判定を返すメソッド
     public bool IsGround()
     {
          if (isGroundEnter || isGroundStay)
          {
              isGround = true;
          }
          else if (isGroundExit)
          {
              isGround = false;
          }
          isGroundEnter = false;
          isGroundStay = false;
          isGroundExit = false;
          return isGround;
     }

     private void OnTriggerEnter2D(Collider2D collision)
     {
          if (collision.tag == groundTag || collision.tag == moveFloorTag)
          {
              isGroundEnter = true;
          }
     }

     private void OnTriggerStay2D(Collider2D collision)
     {
          if (collision.tag == groundTag || collision.tag == moveFloorTag)
          {
              isGroundStay = true;
          }
     }

     private void OnTriggerExit2D(Collider2D collision)
     {
          if (collision.tag == groundTag || collision.tag == moveFloorTag)
          {
              isGroundExit = true;
          }
     }
 }
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;
     [Header("ジャンプする時に鳴らすSE")] public AudioClip jumpSE;
     [Header("やられた鳴らすSE")] public AudioClip downSE;
     [Header("コンティニュー時に鳴らすSE")] public AudioClip continueSE;
     #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 isDown = false;
     private bool isOtherJump = false;
     private float jumpPos = 0.0f;
     private float otherJumpHeight = 0.0f;
     private float dashTime, jumpTime;
     private float beforeKey;
     private string enemyTag = "Enemy";
     private string moveFloorTag = "MoveFloor";
     private bool isContinue = false;
     private float continueTime, blinkTime;
     private SpriteRenderer sr = null;
     private MoveObject moveObj;
     #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 = 0f;
                  continueTime = 0f;
                  sr.enabled = true;
              }
              else
              {
                  blinkTime += Time.deltaTime;
                  continueTime += Time.deltaTime;
              }
          }
     }

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

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

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

              //移動速度を設定
              Vector2 addVelocity = Vector2.zero;
              if(moveObj != null)
              {
                  addVelocity = moveObj.GetVelocity();
              }
              rb.velocity = new Vector2(xSpeed, ySpeed) + addVelocity;
          }
          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 && jumpTime < jumpLimitTime)
              {
                  ySpeed = jumpSpeed;
                  jumpPos = transform.position.y; //ジャンプした位置を記録する
                  isJump = true;
                  jumpTime = 0.0f;
                  GManager.instance.PlaySE(jumpSE);
              }
              else
              {
                  isJump = false;
              }
          }
          //何かを踏んだ際のジャンプ
          else if (isOtherJump)
          {
      if (jumpPos + otherJumpHeight> transform.position.y && jumpTime < jumpLimitTime && !head.IsGround())
              {
                  ySpeed = jumpSpeed;
                  jumpTime += Time.deltaTime;
              }
              else
              {
                  isOtherJump = false;
                  jumpTime = 0.0f;
              }
          }
          //ジャンプ中
          else if (isJump)
          {
              //上ボタンを押されている。かつ、現在の高さがジャンプした位置から自分の決めた位置より下ならジャンプを継続する
      if (verticalKey> 0 && jumpPos + jumpHeight > transform.position.y && jumpTime < jumpLimitTime && !head.IsGround())
              {
                  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 || isOtherJump);
          anim.SetBool("ground", isGround);
          anim.SetBool("run", isRun);
     }

     /// <summary>
     /// ダウンアニメーションが終わっているかどうか
     /// </summary>
     /// <returns>終了しているかどうか</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;
          GManager.instance.PlaySE(continueSE);
     }

     #region//接触判定
     private void OnCollisionEnter2D(Collision2D collision)
     {
           if (!isDown && !GManager.instance.isGameOver)
          { 
                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;
                            GManager.instance.SubHeartNum();
                            GManager.instance.PlaySE(downSE);
                            break;
                        }
                    }
                }
                //動く床
                else if (collision.collider.tag == moveFloorTag)
                {
                    //踏みつけ判定になる高さ
                    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)
                        {
                            moveObj = collision.gameObject.GetComponent<MoveObject>();
                        }
                    }
                }
            }
       }

     private void OnCollisionExit2D(Collision2D collision)
     {
          if (collision.collider.tag == moveFloorTag)
          {
              //動く床から離れた
              moveObj = null;
          }
     }
     #endregion
 }

接触判定でタグが動く床だった場合、足元で接触しているかどうかを見て、その床についているMoveObjectをとってくるようにしています。

          //動く床
          else if (collision.collider.tag == moveFloorTag)
          {
              //踏みつけ判定になる高さ
              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)
                  {
                      moveObj = collision.gameObject.GetComponent<MoveObject>();
                  }
              }
          }

そして、取得した床から速度を持ってきて、プレイヤーの速度に加算してあげます。

//移動速度を設定
Vector2 addVelocity = Vector2.zero;
if(moveObj != null)
{
     addVelocity = moveObj.GetVelocity();
}
rb.velocity = new Vector2(xSpeed, ySpeed) + addVelocity;

そして床から離れたらnullを入れてあげる事で加算する速度を無しにします。

private void OnCollisionExit2D(Collision2D collision)
{
     if (collision.collider.tag == moveFloorTag)
     {
         //動く床から離れた
         moveObj = null;
     }
}

これで動く床にちゃんと計算された形で乗る事ができるようになりました。

friction move floor

こういった物理法則を無視するオブジェクトにちゃんと対応できるようにするためにAddForceなどを使わずvelocityで計算していたわけですね。

スポンサーリンク

<落ちる床>

落ちる床を作成する際にする工夫

続いて落ちる床を実装していきます。

落ちる床を作る際にちょっとした工夫を施します。

hierarchy fall floor

↑のように3つのゲームオブジェクトに分けます。

このようにオブジェクトを分けるのは演出をする為です。

いきなりパッと落下されるとプレイヤーも困ると思うので、ちょっとふるふると震動してから落ちるようにしたいと思います。

完成イメージとしては↓のような感じです。

fall down floor

この時、コライダーまで揺れてしまうと、プレイヤーも震動してしまうので、これを回避するために、コライダーとレンダラーを分けています。

ではこれらのゲームオブジェクトの設定を順に解説していきます。

FloorSpriteの設定(上から2番目)

hierarchy fall floor

これは震動を表現する際にコライダーも一緒に揺れてしまっては困るため、見た目だけ分けた状態する為のものです。

fall sprite inspector

見た目だけ表示したいだけなのでSprite Rendererで絵を表示させて終わりです。

Triggerの設定(上から3番目)

これはプレイヤーが上に乗ったかどうかを判定する為のオブジェクトです。

PlatformEffector2Dの効果で下から一方通行で乗れるせいで普通の衝突判定では上に乗っているのか、下に当たっているのかわかりません。

その為、別で判定を用意する必要があります。

trigger inspector

↑のようにBox Collider 2DでFloorSprite(上から2番目)で出している見た目より少し上の位置に判定を持ってきます。

Box Collider 2DのIs Triggerにチェックを入れます。

そして、↓のスクリプトを貼り付けます。

クリックすると展開します
using System.Collections;
 using System.Collections.Generic;
 using UnityEngine;
 public class PlayerTriggerOn : MonoBehaviour
 {
     [Header("プレイヤーの下から何%が範囲内に入ってればOKとみなすか")] public float playerOnRate;

       private string playerTag = "Player";
       private BoxCollider2D col;
       private bool isOn = false;
       private bool callFixed = false;
       private bool isEnter, isStay, isExit;

       private void Start()
       {
            col = GetComponent<BoxCollider2D>();
            if (col == null)
            {
                Debug.Log("ボックスコライダーがついていません");
            }
       }

       /// <summary> 
       /// プレイヤーが上に乗っているかどうか 
       /// </summary> 
       public bool IsPlayerOn()
       {
            if(isEnter || isStay)
            {
                isOn = true;
            }
            else if(isExit)
            {
                isOn = false;
            }
            return isOn;
       }

       private void FixedUpdate()
       {
            callFixed = true;
       }

       private void LateUpdate()
       {
            if (callFixed)
            {
                //フラグを元に戻します
                isEnter = false;
                isStay = false;
                isExit = false;
                callFixed = false;
            }
       }

       private void OnTriggerEnter2D(Collider2D collision)
       {
            if (collision.tag == playerTag)
            {
                isEnter = JudgePlayerOn(collision);
            }
       }

       private void OnTriggerStay2D(Collider2D collision)
       {
            if (collision.tag == playerTag)
            {
                isStay = JudgePlayerOn(collision);
            }
       }

       private void OnTriggerExit2D(Collider2D collision)
       {
            if (collision.tag == playerTag)
            {
                isExit = true;
            }
       }

       private bool JudgePlayerOn(Collider2D collision)
       {
            //踏みつけ判定になる高さ
            float stepOnHeight = (collision.bounds.size.y * (playerOnRate / 100f));
            //踏みつけ判定のワールド座標
            float judgePos = collision.transform.position.y - (collision.bounds.size.y / 2f) + stepOnHeight;
       
            //プレイヤーの下から指定した%分上の範囲内にいる時乗っているとみなします
            if (judgePos > transform.position.y - (col.size.y / 2))
            {
                return true;
            }
            return false;
       }
 }

ポイントとしては

//踏みつけ判定になる高さ
float stepOnHeight = (collision.bounds.size.y * (playerOnRate / 100f));
//踏みつけ判定のワールド座標
float judgePos = collision.transform.position.y - (collision.bounds.size.y / 2f) + stepOnHeight;

//プレイヤーの下から指定した%分上の範囲内にいる時乗っているとみなします
if (judgePos > transform.position.y - (col.size.y / 2))
{
     isOn = true;    
}

この部分ですね。

ただのOnTrigger2Dでは頭が範囲内に入っているのか、足が範囲内に入っているのかわからないのでこのような計算をします。

collision.transform.position.y

↑がプレイヤーの中心位置になるので、ここから足元の位置を計算して、そこからインスペクターで設定した割合を足します。

その範囲がトリガーの中に入っていればOKとなります。

そして、いつまでも乗っているとは限らないのでLateUpdateでフラグを降ろします。

private void LateUpdate()
{
       if (callFixed)
       {
           //フラグを元に戻します
           isEnter = false;
           isStay = false;
           isExit = false;
           callFixed = false;
       }
}

このcallFixedがあるのはFixedUpdateが確かに呼ばれた後にフラグを下ろすようにしています。これはOnTrigger系がFixedUpdateの後に呼ばれるからです。

なぜこのようにしているかと言うと、このスクリプトに汎用性を持たせたい為、Updateからフラグを呼んでも大丈夫にする為です。

LateUpdateとFixedUpdateの関係がよくわからない方は↓の記事をご覧ください。

FallFloorの設定(1番上)

それでは本体を作成していきます。

fall floor inspector

基本的には↑の「下からのみすり抜ける床」と同じ作りにします。ただしSprite Rendererだけ無い状態です。

また、このRigidbody2DにはPhysics Material 2Dをセットしません。動きづらくなってしまうので。

ここに↓のスクリプトをセットします。

クリックすると展開します
 using System.Collections;
 using System.Collections.Generic;
 using UnityEngine;
 
 public class FallDownFloor : MonoBehaviour
 {
     [Header("スプライトがあるオブジェクト")]public GameObject spriteObj;
     [Header("プレイヤーの判定をするスクリプト")] public PlayerTriggerOn trigger;
     [Header("振動幅")] public float vibrationWidth = 0.05f;
     [Header("振動速度")] public float vibrationSpeed = 30.0f;
     [Header("落ちるまでの時間")] public float fallTime = 1.0f;
     [Header("落ちていく速度")] public float fallSpeed = 10.0f;
     [Header("落ちてから戻ってくる時間")] public float returnTime = 5.0f;
 
     private bool isOn;
     private bool isFall;
     private bool isReturn;
     private Vector3 spriteDefaultPos;
     private Vector3 floorDefaultPos;
     private BoxCollider2D col;
     private Rigidbody2D rb;
     private Vector2 fallVelocity;
     private SpriteRenderer sr;
     private float timer = 0.0f;
     private float fallingTimer = 0.0f;
     private float returnTimer = 0.0f;
     private float blinkTimer = 0.0f;
 
 
     private void Start()
     {
         //初期設定
         col = GetComponent<BoxCollider2D>();
         rb = GetComponent<Rigidbody2D>();
         if (spriteObj != null && trigger != null && col != null && rb != null)
         {
             spriteDefaultPos = spriteObj.transform.position;
             fallVelocity = new Vector2(0, -fallSpeed);
             floorDefaultPos = gameObject.transform.position;
             sr = spriteObj.GetComponent<SpriteRenderer>();
             if(sr == null)
             {
                 Debug.Log("fallDownFloor インスペクターに設定し忘れがあります");
                 Destroy(this);
             }
         }
         else
         {
             Debug.Log("fallDownFloor インスペクターに設定し忘れがあります");
             Destroy(this);
         }
     }
 
     private void Update()
     {
         //プレイヤーが1回でも乗ったらフラグをオンに
         if (trigger.IsPlayerOn())
         {
             isOn = true;
         }
 
         //プレイヤーがのってから落ちるまでの間
         if (isOn && !isFall)
         {
             //震動する
             spriteObj.transform.position = spriteDefaultPos + new Vector3(Mathf.Sin(timer * vibrationSpeed) * vibrationWidth,0,0);
             
             //一定時間たったら落ちる
             if(timer > fallTime)
             {
                 isFall = true;
             }
             
             timer += Time.deltaTime;
         }
         
         //一定時間たつと明滅して戻ってくる
         if (isReturn)
         {
             //明滅 ついている時に戻る
             if (blinkTimer > 0.2f)
             {
                 sr.enabled = true;
                 blinkTimer = 0.0f;
             }
             //明滅 消えているとき
             else if (blinkTimer > 0.1f)
             {
                 sr.enabled = false;
             }
             //明滅 ついているとき
             else
             {
                 sr.enabled = true;
             }
 
             //1秒たったら明滅終わり
             if (returnTimer > 1.0f)
             {
                 isReturn = false;
                 blinkTimer = 0f;
                 returnTimer = 0f;
                 sr.enabled = true;
             }
             else
             {
                 blinkTimer += Time.deltaTime;
                 returnTimer += Time.deltaTime;
             }
         }
     }
 
     private void FixedUpdate()
     {
         //落下中
         if (isFall)
         {
             rb.velocity = fallVelocity;
             
             //一定時間たつと元の位置に戻る
             if (fallingTimer > fallTime)
             {
                 isReturn = true;
                 transform.position = floorDefaultPos;
                 rb.velocity = Vector2.zero;
                 isFall = false;
                 timer = 0.0f;
                 fallingTimer = 0.0f;
             }
             else
             {
                 fallingTimer += Time.deltaTime;
                 isOn = false;
             }
         }
     }
 }

基本的にコメントを見ていただけるとほとんどわかると思いますが、↑で作成したTriggerからプレイヤーが乗ったという判定を受け取ったら横に震動するようにします。

//震動する
spriteObj.transform.position = spriteDefaultPos + new Vector3(Mathf.Sin(timer * vibrationSpeed) * vibrationWidth,0,0);

ここで震動させています。Rigidbody2Dを使っていないのは、絵だけに分離したため物理的な挙動で動かす必要が無い為です。

Mathf.Sin(timer * vibrationSpeed)

これで震動させています。三角関数のSinは角度によって-1〜1の範囲をくるくる回る性質があります。

正弦波でググっていただけるとわかると思いますが、角度に時間を入れてあげることによって-1〜1の間で震動しているように見えます。

これに自分で設定した震動幅をかけてあげることによって震動を実装する事ができます。

一定時間震動したら、今度は落下します。

rb.velocity = fallVelocity;

落下はコライダーが関係するのでRigidbody2Dで動かします。FixedUpdateに書いている点にも注意が必要です。

コンティニューや戻ってしまう場合も考えて、落下してしばらくしたら位置を戻してあげなければいけません。

パッと戻られても困るので、プレイヤーの時と同じく明滅するようにします。

fall down floor inspector

あとはインスペクターの値を設定すればOKです。

fall down floor

はい、これで落ちる床を実装する事ができました。

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



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