Unity 6.3 LTS|3D的当てゲームを作ろう⑪ Character ControllerとRigidbodyの違いを理解しよう

PlayerへRigidbodyとCapsule Colliderを設定したUnity画面 Unity

第11回は、前回書き換えたPlayerMoveを詳しく読み解くコード解説回です。Character Controller方式とRigidbody方式の違いを比べ、なぜUpdateとFixedUpdateへ処理を分けたのかを理解します。

今回の目標

コードをコピーして動かすだけでなく、入力、移動方向、接地判定、ジャンプ、物理移動がどの順番でつながっているかを説明できるようになります。

STEP 1

Character ControllerとRigidbodyの違い

比べる項目Character ControllerRigidbody
移動方法controller.Move()で指定量を動かすlinearVelocityやAddForce()で動かす
重力自分で計算して追加するUse GravityでUnityが計算する
ジャンプ上方向の移動速度を自分で管理する上方向へ力を加えられる
衝突キャラクター向けの簡単な当たり判定質量や反発を含む物理演算
必要なColliderComponent自体がカプセル形を持つCapsule Colliderなどを別に付ける
処理を書く場所主にUpdate()入力はUpdate()、物理移動はFixedUpdate()

Character Controllerは操作しやすく、FPSの基本移動に向いています。Rigidbodyは重力、落下、押される動き、力を使うジャンプなど、物理演算と組み合わせやすい方法です。

どちらが正解?

ゲームの作り方によって選びます。今回は物理の力でジャンプすることを学ぶため、Rigidbodyへ変更しました。

STEP 2

Componentの役割がどう変わったか

Character Controller方式では、1つのComponentが移動とカプセル形の衝突をまとめて担当していました。

Rigidbody方式へ変更するためCharacter Controllerを削除する画面
2つの移動方式が競合しないようにCharacter Controllerを削除します。

Rigidbody方式では役割を分けます。

Rigidbody速度、重力、ジャンプの力を計算します。
Capsule ColliderPlayerの体の当たり判定を作ります。
GroundCheck足元が床に触れているかを調べます。
PlayerにRigidbodyとCapsule Colliderを設定したInspector
位置をスクリプトから直接書き換えず、Rigidbodyの速度と力で動かします。
STEP 3

変数を役割ごとに分けて読もう

public float moveSpeed = 4f;
public float jumpForce = 6f;
public float turnSpeed = 10f;

public Animator animator;
public Transform modelRoot;

public Transform groundCheck;
public float groundDistance = 0.25f;
public LayerMask groundLayer;
動きの数値moveSpeedは歩く速さ、jumpForceはジャンプ力、turnSpeedは向きを変える速さです。
見た目animatorはアニメーション、modelRootはキャラクターの向きを担当します。
接地判定groundCheckの位置に、groundDistanceの球を作り、groundLayerだけを調べます。
Rigidbody rb;
Transform cameraTransform;

Vector3 move;
bool jumpRequested;
bool isGrounded;

moveは進む方向、jumpRequestedは「次の物理更新でジャンプする」という予約、isGroundedは床に立っているかを保存します。

STEP 4

StartでRigidbodyとCameraを覚えよう

void Start()
{
    rb = GetComponent<Rigidbody>();
    cameraTransform = Camera.main.transform;
}

GetComponent<Rigidbody>()は、Playerに付いているRigidbodyを探してrbへ保存します。以後は毎回探さず、rbという短い名前で使えます。

Camera.main.transformはMain Cameraの向きです。カメラの前後左右を基準にしてWASD移動を作るために保存します。

STEP 5

Updateではキー入力を読む

float horizontal = 0f;
float vertical = 0f;

if (Keyboard.current.aKey.isPressed)
{
    horizontal -= 1f;
}

if (Keyboard.current.dKey.isPressed)
{
    horizontal += 1f;
}

if (Keyboard.current.sKey.isPressed)
{
    vertical -= 1f;
}

if (Keyboard.current.wKey.isPressed)
{
    vertical += 1f;
}

horizontalは左右、verticalは前後です。AとSはマイナス方向、DとWはプラス方向へ値を変えます。

キー入力は画面の更新に合わせて変化するため、Update()で読みます。スペースキーの「今押された」という入力もUpdateで調べます。

STEP 6

Cameraを基準に移動方向を作る

Vector3 forward = cameraTransform.forward;
Vector3 right = cameraTransform.right;

forward.y = 0f;
right.y = 0f;

forward.Normalize();
right.Normalize();

move =
    forward * vertical +
    right * horizontal;

move = Vector3.ClampMagnitude(move, 1f);

カメラのforwardをWとS、rightをAとDへ使います。これにより、カメラが向いている方向を前として歩けます。

カメラが上下を向いていてもPlayerが空中へ進まないように、Yを0にします。最後にClampMagnitudeで長さを1以下にし、斜め移動だけ速くなることを防ぎます。

STEP 7

CheckSphereで床を調べる

isGrounded = Physics.CheckSphere(
    groundCheck.position,
    groundDistance,
    groundLayer,
    QueryTriggerInteraction.Ignore);
Playerの足元へGroundCheckを配置したSceneビュー
GroundCheckの位置を中心に小さな球を作り、Groundレイヤーとの重なりを調べます。
位置groundCheck.positionが球の中心です。
大きさgroundDistanceが球の半径です。
対象groundLayerに選んだ床だけを調べます。
接地判定に使うGroundレイヤーを作成した画面
床がGroundレイヤーでなければ、見た目では着地していてもisGroundedはfalseになります。
STEP 8

ジャンプを予約する理由

if (Keyboard.current.spaceKey.wasPressedThisFrame &&
    isGrounded)
{
    jumpRequested = true;
}

スペースキーを押した瞬間、さらに床に立っているときだけjumpRequestedをtrueにします。空中ではisGroundedがfalseなので二段ジャンプできません。

入力を読むUpdateと、物理を動かすFixedUpdateは呼ばれるタイミングが違います。Updateで見つけたジャンプ入力を失わないように、boolへ保存してFixedUpdateへ渡します。

Update
  └─ Spaceが押された
       └─ jumpRequested = true

FixedUpdate
  └─ jumpRequestedを確認
       └─ AddForceでジャンプ
       └─ jumpRequested = false
STEP 9

FixedUpdateでRigidbodyを動かす

Vector3 currentVelocity =
    rb.linearVelocity;

rb.linearVelocity =
    new Vector3(
        move.x * moveSpeed,
        currentVelocity.y,
        move.z * moveSpeed);

XとZにはWASDから作った移動速度を入れます。Yには現在のcurrentVelocity.yを残します。

Yを0にしない理由

Yを毎回0にすると、ジャンプの上向き速度と重力による落下速度が消されます。現在のY速度を残すことで、横移動しながら自然に上昇・落下できます。

Character Controllerではcontroller.Move(move * speed * Time.deltaTime)のように移動量を渡しました。Rigidbodyでは1秒あたりの速度をlinearVelocityへ設定するため、ここではTime.deltaTimeを掛けません。

STEP 10

AddForceでジャンプする

if (jumpRequested)
{
    rb.AddForce(
        Vector3.up * jumpForce,
        ForceMode.Impulse);

    jumpRequested = false;
}

Vector3.upは真上、jumpForceは力の大きさです。ForceMode.Impulseは一瞬の衝撃として力を加えるため、ジャンプに向いています。

力を加えた後でfalseへ戻すため、1回のキー入力につきAddForceは1回だけ実行されます。

CHECK

よくある違いと間違いを確認しよう

Character Controllerが残っているRigidbodyと競合するため、今回のPlayerから削除します。
Groundedが常にfalseGroundCheckの位置、Ground Layer、床のColliderを確認します。
ジャンプ後に落ちないUse Gravityがオンで、Y速度を0へ上書きしていないか確認します。
Playerが倒れるRigidbodyのFreeze Rotation XとZをオンにします。
動きがガタつく物理移動をFixedUpdateに置き、Interpolateを選びます。
Prefab化後に撃てないSceneごとのScoreTextとTimerTextを登録し直します。
次回へ

ジャンプアニメーションを調整しよう

次回はisGroundedをAnimatorへ渡す流れを確認し、Jump、Midair、Landの切り替えを調整します。

コメント

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