U
13 / 22 · 8 分
FAQ

キャラクターを動かす: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 が入っているのです。

PlayerMove.cs — attach to the object that has the CharacterController
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 に書き込んでも物理エンジンには何も伝わらず、平気でオブジェクトを元に戻してしまいます。

BallMove.cs — attach to the object that has the Rigidbody
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

Drifter.cs — for something with no collider, or one set to trigger
void Update()
{
    transform.Translate(Vector3.forward * speed * Time.deltaTime);
}

これを作るレッスン

移動、ジャンプ、ブロックへの着地

この記事はそれだけで完結するレシピです。コースでは同じものを、5 つのタブを貫くひとつのプロジェクトの一部として作ります — キャラクターと戦闘 のレッスン 02。