キャラクターを動かす:3 通りのうちどれが自分向きか
キャラクターを移動させるには?
3 つのうち 1 つだけ選び、2 つ同時に使わないこと。直接操るプレイヤーには CharacterController、物理に押させたいものには Rigidbody、衝突しないものだけに transform.Translate。すべての速度に Time.deltaTime を掛けなければ、速い機械ほどゲームが速く進みます。
Unity で物体を動かす方法は三つありますが、混ぜて使うことはできません。選び方を間違えると、キャラクターが壁にめり込んだり、まったく動かなかったり、120 Hz のスマホでは二倍の速さで走ったりします。
同じ壁を三つの方法で迎え撃ちます。CharacterController だけが壁に沿ってスライドし、Rigidbody だけが木箱を押せ、transform.Translate はそこに壁があることすら知りません。
一つ選ぶ —— 判断すべてはこの一点
CharacterController —— プレイヤーを直接操作する場合に使います。壁に沿ってスライドし、段差ものぼり、物理エンジンに押されて揺らぐこともありません。三人称・一人称のゲームのほとんどがこれを使っています。
Rigidbody —— 物理エンジンに任せるべきものすべて:転がるボール、押されて動く木箱、ラグドールなど。動きを指示するのはあなた、実際に何が起きるかを決めるのは物理エンジンです。
transform.Translate —— 何とも衝突しないもの専用です:浮かぶアイテム、流れる雲、UI 要素など。動き方はテレポートに近く、他の物を押すことはありません。
CharacterController
コンポーネントを追加したら、Move() で動かします。引数はそのフレームの移動距離であって速度ではありません。だから中に deltaTime が入っているのです。
using UnityEngine;
[RequireComponent(typeof(CharacterController))]
public class PlayerMove : MonoBehaviour
{
public float speed = 6f;
public float gravity = -20f;
CharacterController cc;
float verticalSpeed;
void Awake()
{
cc = GetComponent<CharacterController>();
}
void Update()
{
// Input is a direction, length 0 to 1. Never a distance.
Vector3 input = new Vector3(Input.GetAxisRaw("Horizontal"), 0f, Input.GetAxisRaw("Vertical"));
if (input.sqrMagnitude > 1f) input.Normalize();
// Gravity has to be applied by hand: a CharacterController ignores physics.
if (cc.isGrounded && verticalSpeed < 0f) verticalSpeed = -2f;
verticalSpeed += gravity * Time.deltaTime;
Vector3 move = input * speed + Vector3.up * verticalSpeed;
cc.Move(move * Time.deltaTime);
}
}Rigidbody
覚えることは二つだけ、あとは自然についてきます:transform ではなく Rigidbody に書き込むこと、Update ではなく FixedUpdate で行うこと。物理は専用の時計で動いていて、transform に書き込んでも物理エンジンには何も伝わらず、平気でオブジェクトを元に戻してしまいます。
using UnityEngine;
[RequireComponent(typeof(Rigidbody))]
public class BallMove : MonoBehaviour
{
public float speed = 8f;
Rigidbody rb;
Vector3 input;
void Awake()
{
rb = GetComponent<Rigidbody>();
}
void Update()
{
// Read input here: Update runs once per frame, so no press is missed.
input = new Vector3(Input.GetAxisRaw("Horizontal"), 0f, Input.GetAxisRaw("Vertical"));
if (input.sqrMagnitude > 1f) input.Normalize();
}
void FixedUpdate()
{
// Move here: FixedUpdate runs on the physics clock.
rb.MovePosition(rb.position + input * speed * Time.fixedDeltaTime);
}
}input を Update で読み、FixedUpdate で使う理由
FixedUpdate は既定では 1 秒に 50 回動きますが、ゲームの描画は 120 フレームかもしれません。そのため FixedUpdate の中で Input.GetKeyDown を読むと、物理の刻みの間で起きた入力を取りこぼします。
FixedUpdate の中では Time.fixedDeltaTime、Update の中では Time.deltaTime を使ってください。実は Unity はどちらの文脈でも正しい値を返しますが、対応した名前を書いておくと意図が読みやすくなります。
カメラが Rigidbody に追従するなら、Interpolate を Interpolate に設定してください。さもないと動きが 50 Hz で震えているように見えます。
transform.Translate
void Update()
{
transform.Translate(Vector3.forward * speed * Time.deltaTime);
}これを作るレッスン
移動、ジャンプ、ブロックへの着地
この記事はそれだけで完結するレシピです。コースでは同じものを、5 つのタブを貫くひとつのプロジェクトの一部として作ります — キャラクターと戦闘 のレッスン 02。