はじめに
今回はUnityのC#を用いて接地判定を実装したいと思います。プレイヤー操作の移動は実装していますが、接地判定ができていないので実装していきます。プレイヤー操作の移動までは終わっているとの前提で話します。
※Unityはバージョンアップの頻度が多く、メニューやレイアウト、表示が変わっていることがあります。
プレイヤー操作の移動の回でプレイヤーにつけたスクリプト(クリックすると展開されます)
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Player : MonoBehaviour
{
//インスペクターで設定する
public float speed;
//プライベート変数
private Animator anim = null;
private Rigidbody2D rb = null;
void Start()
{
//コンポーネントのインスタンスを捕まえる
anim = GetComponent<Animator>();
rb = GetComponent<Rigidbody2D>();
}
void Update()
{
//キー入力されたら行動する
float horizontalKey = Input.GetAxis("Horizontal");
float xSpeed = 0.0f;
if (horizontalKey > 0)
{
transform.localScale = new Vector3(1, 1, 1);
anim.SetBool("run", true);
xSpeed = speed;
}
else if (horizontalKey < 0)
{
transform.localScale = new Vector3(-1, 1, 1);
anim.SetBool("run", true);
xSpeed = -speed;
}
else
{
anim.SetBool("run", false);
xSpeed = 0.0f;
}
rb.velocity = new Vector2(xSpeed, rb.velocity.y);
}
}
<接地判定とは>
接地判定というのは、文字通り地面についているかどうかの判定です。何故、この判定が必要かというと「地面についているときのみ」やりたいことがあるからです。例えば、ジャンプは地面についている時だけしてほしいですよね。2段ジャンプを作るにしても空中でジャンプしているのか地面でジャンプしているのかを判別しなければいけません。このように地面にいるかどうかを判別したい時が、色々出てくるので接地判定をとります。
<当たり判定のない判定Is Trigger>
さて、接地判定をとりたいのですが、これも移動と同じで様々な方法があります。しかし、移動と違って重さにそこまで差異はないので一番簡単な方法を取ろうと思います。
適当に空のオブジェクトを作成してください。名前はGroundCheckとかわかりやすいものにして、プレイヤーの子オブジェクトにしてください。そしてこれにAdd Componentしていきます。今度はBox Collider 2Dを追加してください。Box Collider 2DがついたらIs Triggerというところをチェックしてください。当たり判定のないコライダーってなんの意味があるんだよって話ですが、このIs Triggerにチェックを入れることによって当たり判定の侵入を検知できるようになります。ちょっと今のままではわかりづらいので、新しいBox Collider 2Dの位置を変えましょう。
<足元にTrigger判定をおく>
接地判定を行うために、先ほど作ったゲームオブジェクトをCapsule Collider 2Dの下に持ってきてください。足元にもう一個緑の四角があるのがわかるかと思います。こういう感じに配置してください。Capsule Collider 2Dと重ならないように注意してください。コライダーの大きさはBox Collider 2DのSizeのパラメーターをいじれば変えることができます。このIs Triggerが入ったコライダーは当たり判定がない代わりに、この緑の四角の中に何か当たり判定があるものが入ってきたら検知できます。(Unreal EngineでいうOverlapと似ていますが正確には違います。)緑の四角の中に地面が入っていたら「接地している」という判定にすれば良さそうです。
では、どうやって当たり判定の侵入を検知するのかというと、Box Collider 2Dがついているゲームオブジェクトにある特別なメソッドが記述してあるスクリプトをアタッチすることで検出することができます。
private void OnTriggerEnter2D(Collider2D collision)
{
Debug.Log("何かが判定に入りました");
}
private void OnTriggerStay2D(Collider2D collision)
{
Debug.Log("何かが判定に入り続けています");
}
private void OnTriggerExit2D(Collider2D collision)
{
Debug.Log("何かが判定をでました");
}
これがスクリプトの中に書いてあると特別な機能を持ちます。書いてある通り、Enterで侵入を検知、Stayで侵入し続けていて、Exitで判定外に出たことを検知することができます。新しいスクリプトを作成して上を書きます。
作成した接地判定スクリプト(クリックすると展開されます)
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class GroundCheck : MonoBehaviour
{
private void OnTriggerEnter2D(Collider2D collision)
{
Debug.Log("何かが判定に入りました");
}
private void OnTriggerStay2D(Collider2D collision)
{
Debug.Log("何かが判定に入り続けています");
}
private void OnTriggerExit2D(Collider2D collision)
{
Debug.Log("何かが判定をでました");
}
}
<地面を地面と認識できるようにしよう>
さて、現状のままだと何かの当たり判定がGroundCheckの範囲内に入ってきていることはわかりますが、何が入ってきているのかはわかっていません。地面以外に、敵などを作ったときに、敵を踏んだ瞬間地面と錯覚してしまう可能性があります。ではどうすればよいかというと、Tagを使用します。ヒエラルキーでTilemapを選択した後、インスペクターのTagのところをクリックしてAdd Tag...をクリックします。(Tilemapはここで説明しています。)
このタグというのはゲームオブジェクトを分類分けできるものです。自分で好きなタグを作って、ゲームオブジェクトを分類分けすることができます。Tagsの+を押して、タグを追加しましょう。名前は「Ground」にしました。「Save」をクリックすることでTagに作成したタグが追加されます。もう一度TilemapのTagをクリックすれば出てきます。Tilemapのタグを先ほど作ったタグにしましょう。それではこれを取得したいと思います。先ほどのメソッドを書き換えます。
private string groundTag = "Ground";
private void OnTriggerEnter2D(Collider2D collision)
{
if (collision.tag == groundTag)
{
Debug.Log("何かが判定に入りました");
}
}
private void OnTriggerStay2D(Collider2D collision)
{
if (collision.tag == groundTag)
{
Debug.Log("何かが判定に入り続けています");
}
}
private void OnTriggerExit2D(Collider2D collision)
{
if (collision.tag == groundTag)
{
Debug.Log("何かが判定をでました");
}
}
これらの特別なメソッドには引数にColider2D型でcollisionというものが入ってきています。このcollisionというものはTrigger判定の中に張ってきたコライダーになります。要は侵入してきたものです。「.tag」で侵入してきたもののタグを見ます。そして、それの名前が「先ほど作ったタグだった場合」という判定をつけることによって、地面かどうか判定をします。これはどちらでもいいのですが、こういう文字列の指定は間違いを誘発しやすいのと、使いまわしがしやすいように変数にしています。
<接地判定のフラグを用意しよう>
さて、では接地判定に使うフラグを作りましょうか。おそらく大半の人が地面に入ったらフラグをオンにして、地面から出ていったらフラグをオフにすると思っているかなと思います。
private string groundTag = "Ground";
public bool isGround = false;
private void OnTriggerEnter2D(Collider2D collision)
{
if (collision.tag == groundTag)
{
isGround = true;
}
}
private void OnTriggerExit2D(Collider2D collision)
{
if (collision.tag == groundTag)
{
isGround = false;
}
}
isGroundが地面にいるという判定をにしたとすると、上を想像された方は多いかと思いますが、残念ながらこれでは足りません。接地判定を作ると下のようになります。
private string groundTag = "Ground";
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)
{
isGroundEnter = true;
}
}
private void OnTriggerStay2D(Collider2D collision)
{
if (collision.tag == groundTag)
{
isGroundStay = true;
}
}
private void OnTriggerExit2D(Collider2D collision)
{
if (collision.tag == groundTag)
{
isGroundExit = false;
}
}
GroundCheckについているスクリプトを上のように書き換えてください。コードが長くなってきてみるのが面倒かもしれませんが、書いてあることは難しくありません。下のように各種フラグを用意します。
private bool isGroundEnter, isGroundStay, isGroundExit;
地面に入るか居続けた場合、接地判定をオンにして、地面から出た場合オフにするという処理です。Exitはいらない気がしますが、OnTriggerStayは止まっていると呼ばれないという特性があるのでExitでフラグをオフしてあげる必要があります。さて、何故このような書き方をするかというと、それはEnterとStayとExitのうち2つもしくは3つが同時に呼ばれるタイミングが存在するからです。この時、フラグを各種類で持っておかないと、地面に接地しているのにExitしているのでisGroundがfalseになってしまう恐れがあります。その為、あんなにフラグを用意しています。そしてそれらのフラグから判断して、publicなメソッドから結果を返すことができるようにしています。これで他のスクリプトから接地判定を読むことができます。
最後に各種フラグを下すのを忘れずに。ずっとオンになってしまうので。しかし、isGroundのフラグだけは保持します。これはStayが止まっていると呼ばれないからです。
おわりに
いかがでしたでしょうか。接地判定は地面に入る場合と出た場合のみを考慮するだけでなく、その他の条件も考えないといけません。また、今回のコードに記載しているフラグを下す必要があるため、このメソッドは物理判定のたびに毎回呼ぶ必要があるので次回解説したいと思います。
投稿者
エンジニアファーストの会社 株式会社CRE-CO 田渕浩之