【Unity入門】2Dアクションを作ろう【動く床・落ちる床】

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

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

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

とりあえず、適当に下書きで書いた床を配置して、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

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

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

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

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

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

しかしながら、その対策だと下の動きに非常に弱くなります

先ほどのgifをよーく見ていただくと、上昇している時は滑っておらず、下降している時に滑っています。

これは床が下降している時、床とプレイヤーとの接触が弱くなってしまう為です。

これに対応するにはPhysic Material 2Dを作成します。

Projectウィンドウで右クリックしCreate>Physics Material 2Dで作成できます。

create physics material 2d

できた物をインスペクターで見ると↓のようになっています。

physics material 2d inspector

このFrictionを1000くらいにしましょう。

Frictionというのは摩擦係数です。

摩擦係数をめちゃ高くして滑ってしまうことを防ぎます。

ところで公式から「Frictionの値は0〜1で有効だ」という事が言われていますが、1以上の値を入れた時に明らかに挙動が変わったのでこの値の有効範囲は0〜1ではないと思います。よくあるUnityの不思議の1つです。

もしかしたら0〜1なのはPhysics Materialで、Physics Material 2Dは1以上も有効という事なのかもしれません。Unityさんはとてもよく頻繁にややこしい言い方をしますからね。

ちなみに、このFrictionを設定しても人によっては滑ってしまう場合があります。それは重力や質量の数値を変える事ができるからです。これらの値からくる下向きの力が少ないと滑ります。↑の値でも滑ってしまう方は1000より大きい値にしてください。

rigidbody 2d set physics material

できたら動く床の方のRigidbody2DのMaterialに作ったPhysics Material 2Dを入れましょう。プレイヤー側に入れてしまうと普通の地面での移動が困難になります。

friction move floor

はい。これで動く床を作成する事ができました。

ここで注意点として、横にのみ動く床の場合は摩擦が強すぎて横に動けなくなるので、摩擦係数を小さいものに変更してください。

縦と横、どのように動くのかで摩擦係数を調節してください

スポンサーリンク

<落ちる床>

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

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

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

hierarchy fall floor

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

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

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

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

fall down floor

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

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

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

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

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 playerTriggerObject : MonoBehaviour
 {
     private string playerTag = "Player";
     private bool isOn = false;
     private bool callFixed = false;
     
     /// <summary>
     /// プレイヤーが上に乗っているかどうか
     /// </summary>
     /// <returns><c>true</c>, if player on was ised, <c>false</c> otherwise.</returns>
     public bool IsPlayerOn()
     {
         return isOn;
     }
 
     private void FixedUpdate()
     {
         callFixed = true;
     }
 
     private void LateUpdate()
     {
         if (callFixed)
         {
             //フラグを元に戻します
             isOn = false;
             callFixed = false;
         }
     }
 
     private void OnTriggerStay2D(Collider2D collision)
     {
         if (collision.tag == playerTag)
         {
             //プレイヤーの下から4分の1が範囲内にいる時乗っているとみなします
             if(collision.transform.position.y - (collision.bounds.size.y / 4.0f) > transform.position.y)
             {
                 isOn = true;
             }
         }
     }
 }

ポイントとしては

//プレイヤーの下から4分の1が範囲内にいる時乗っているとみなします
if(collision.transform.position.y - (collision.bounds.size.y / 4.0f) > transform.position.y)

この部分ですね。

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

collision.transform.position.y

これは中心座標になるのでプレイヤーの真ん中になります。

- (collision.bounds.size.y / 4.0f)

プレイヤーの真ん中からプレイヤーの大きさの4分の1引きます。

真ん中から4分の1引いているので、プレイヤーの当たり判定の一番下から4分の1の高さまでの範囲がコライダーの範囲内に入っていたら乗っているとみなすようにします。

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

何故、OnTriggerExit2Dを使わないかと言うと、なんか不安定だからです。コライダーがごちゃつくと呼ばれない事が稀にあって信頼性がそこまで高くないのでわざわざこうしています。

コリジョン関係が不安定なのは自分だけでしょうか?Stayは何フレームも呼ばれるので1回呼ばれなかったとしても大丈夫なのですがEnterやExitのような1フレーム間しか呼ばれない系は筆者は信頼していません。

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

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

プレイヤーのスクリプトを作った時はLateUpdateなんて使ってなかったじゃないかと思う方もいらしゃるかもしれませんが、あれは1スクリプト内で完結している為Updateでフラグを降ろしてもよかったのですが、複数のスクリプトで読み取る可能性がある場合はLateUpdateでフラグを降ろします。

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 playerTriggerObject 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の間で震動しているように見えます。

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

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

     private void FixedUpdate()
     {
         //落下中
         if (isFall)
         {
             rb.velocity = fallVelocity;
         {
     {

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

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

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

fall down floor inspector

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

fall down floor

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

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



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