はじめに
前回までは接地判定を実装するために接地判定を行うスクリプトまで作成しました。今回はこのコードに記載されているフラグを管理する必要があり、プレイヤー側で読めるようにします。また、FixedUpdateとUpdateの違いについても説明します。
※Unityはバージョンアップの頻度が多く、メニューやレイアウト、表示が変わっていることがあります。
前回、接地判定を実装するために接地判定を行うスクリプト(クリックすると展開されます)
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;
}
}
<PlayerでGroundCheckを読もう>
さて、接地判定ができてもプレイヤー側で読めなければ意味がありません。プレイヤー側でも読めるようにしましょう。プレイヤーのスクリプトを下のようにします。
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Player : MonoBehaviour
{
//インスペクターで設定する
public float speed;
public GroundCheck ground; //new
//プライベート変数
private Animator anim = null;
private Rigidbody2D rb = null;
private bool isGround = false; //new
void Start()
{
//コンポーネントのインスタンスを捕まえる
anim = GetComponent<Animator>();
rb = GetComponent<Rigidbody2D>();
}
void Update()
{
//接地判定を得る
isGround = ground.IsGround(); //new
//キー入力されたら行動する
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);
}
}
publicな変数でgroundを定義しています。これはシリアライズ化されているのでインスペクターでアタッチすることができます。これで返ってきた接地判定をisGroundという変数に入れて扱えるようにします。
<FixedUpdate>
さて、地面を取得するところまで完成しました。ですが、1つやることがあります。今まで、プレイヤーのスクリプトの毎フレームの処理をUpdateに書いてきました。それをFixedUpdateに変更します。
void Update()
{
}
void FixedUpdate()
{
}
実はUnityには特別なメソッドがたくさんありまして、これもその一つです。大きな特徴の一つとして物理エンジンの計算の前にこの処理を行います。アップデートと付いていますが、毎フレーム処理されるとは限りません。難しいことは考えず、物理エンジンの計算の前に毎回、処理されるものと覚えておけば大丈夫です。これによって、接地判定のフラグを物理演算の前に毎回下すことができるようになります。
FixedUpdateを使う意味
さて、2種類のアップデートを使う上でどういった場合に使うかというと、
・Updateはフレームに合わせたいときに使うよ!
・FixedUpdateは物理演算に合わせたいときに使うよ!
・フレームはキー入力と画面に関係するよ!
・物理演算は位置や当たり判定に関係するよ!
と、このように使い方がUpdateとFixedUpdateで違います。さて、今回の場合はRigidbody2Dを操作しています。これは物理エンジンに影響があるのでFixedUpdateにしたわけです。
FixedUpdateに合わせるかUpdateに合わせるか
さて、物理エンジンを使用するからFixedUpdateにすると言いましたが、思考停止でFixedUpdateを選択するのはちょっと待ってほしいです。Rigidbodyが使われたら無意識のうちにFixedUpdateにするのではなく、どっちがいいのかちょっと考えてみてください。
「物理演算を使用するから、FixedUpdateにする。」ではなく「物理演算に合わせたいからFixedUpdateにする」を選択してください。上で接地判定のメソッドのためにFixedUpdateに変更したと言っていますが、ただそれだけならプレイヤーのスクリプトを変更せず、接地判定のほうで完結させればいい話なのです。そのため、何故プレイヤーの方のスクリプトを変更するのかを考えていきます。
何故今回はFixedUpdateを使うのか
今回の場合、Rigidbody2Dのvelocityの値を直接変更するという手法を取っているので、実はUpdateにしようがFixedUpdateにしようがあまり変わりません。物理法則を無視するために直接velocityに値を入れているので物理法則も何もないのです。では何故FixedUpdateにしたのかと言われると、こちらの方が制御しやすいという理由です。ぶっちゃけ今回の場合は大きくは変わらないのですが、いい機会なので覚えましょう。
どちらかに偏った時の事を考えよう
物理演算というのは時間の概念が大事(移動距離の計算などがあるので)なので、フレームという概念の中にいません。フレームはフレームルートによって時間が変動してしまうのであてにならないのです。そのため、UpdateとFixedUpdateは呼ばれ方が違います。ここにちょっと問題があります。Updateは入力と画面の更新に関係し、FixedUpdateは物理的な移動に関係があることに注目してください。
FixedUpdate(移動)
↓
Update(入力と描画)
↓
FixedUpdate(移動)
↓
Update(入力と描画)
↓
以下ループ…
こういう風な呼ばれ方をしている間は大丈夫です。移動した後描画されているので、ちゃんとした位置に描画されます。しかし、これはフレームレートによって
FixedUpdate(移動)
↓
FixedUpdate(移動)
↓
FixedUpdate(移動)
↓
Update(入力と描画)
↓
以下ループ…
となったり、
Update(入力と描画)
↓
Update(入力と描画)
↓
Update(入力と描画)
↓
FixedUpdate(移動)
↓
Update(入力と描画)
↓
以下ループ…
となったりします。順々に呼ばれるわけじゃないんです。どのような呼ばれ方をするのかはフレームレートによります。移動しまくった後に画面が更新されると瞬間移動したように見えます。ゲームでよく見る、処理落ちした後にガクッと移動するのはこれです。
逆に移動の処理が入らずに画面が更新されると止まっているのですが、この場合、見え方はそんなに気にする必要はありません。Updateが連続で入っている時はフレームレートが高いときですので人間の目には視認しづらいです。その為、特に考えなくて大丈夫です。
入力に合わせるべきか
本来なら入力を気にする必要があるのですが、
float horizontalKey = Input.GetAxis("Horizontal");
これは押しっぱなしを検知できるので、問題ありません。押した瞬間だけを検知したい場合、Updateが連続で呼ばれると次のフレームではfalseになってしまいます。「○○を入力した瞬間」という命令を書く場合はUpdateに書いたほうがいいわけです。
しかしながら今回は押しっぱなしを検知しているのでUpdateが連続で呼ばれたとしてもプレイヤーはボタンを押し続けているのでFixedUpdateに入ってきても入力を受け取ることができます。
FixedUpdateが連続で呼ばれる場合もある
では、下の例のようにFixedUpdateが複数回呼ばれるケースに絞って考えましょう。
FixedUpdate(移動)
↓
FixedUpdate(移動)
↓
FixedUpdate(移動)
↓
Update(入力と描画)
↓
以下ループ…
移動が何回も呼ばれるので瞬間移動して見えます。これは、よく処理落ちしたときやフレームレートが低いとみられる現象です。今回の場合、velocity(速さ)に直接値を入れています。ここで、注目してほしいのは「速さ」に値を入れているのであって、transformの時のように「位置」」に値をいれているわけではありません。力を加えなければ「速さ」は減衰しますよね。もし、Updateに移動の処理を書いた場合、速さに値が代入されて、連続2回目以降のFixedUpdateでは何も代入されないので、減衰します。
一方、FixedUpdateに移動処理を書いた場合、移動の前に逐一速さに値が代入されるので、減衰せずにずっと一定です。上の状態はフレームレートが30FPS以下だったり、処理落ちしたときに見られます。通常時は逐一速さが代入されるため、減衰せずずっと一定です。その為、処理落ちしたときと通常時の操作感が少し違うため、これを合わせるためにFixedUpdateにしています。
おわりに
いかがでしたでしょうか。接地判定のスクリプトを呼び出して使うことやFixedUpdateやUpdateの意味も考慮してゲームは開発していく必要があります。現在はついていくだけで精いっぱいだと思いますが、徐々に慣れていきましょう。
投稿者
エンジニアファーストの会社 株式会社CRE-CO 田渕浩之