U
タブ 03カメラと入力6 レッスン — タップで開く
01 / 06 · 8 分
カメラと入力

追従カメラ

ここまでのプロジェクト

Assets/

Scenes/

Arena.unity

Knight

Scripts/

CharacterSwitcher.csSwapCamera.cs→FollowCamera.cs

すでにあるものこのレッスンで追加このレッスンで置き換え

Arena.unity を開いてください。tab 01 の SwapCamera はすでに操作中のキャラクターについて行きますが、カクンと瞬間移動し、足元ばかりを見たまま、壁も平気で抜けてしまいます。この tab ではそれを三つのステップで作り直します。ここが第一段目です。

プレイヤーに張り付いたカメラは安っぽい印象になります。少し遅れて追い掛けるカメラだからこそ、ゲームらしい手触りが出ます。

アニメーション図

キャラクターが先に動き、カメラはそれから数フレームをかけて追いつきます。

追従スクリプト

FollowCamera.cs — remove SwapCamera from Main Camera first
using UnityEngine;

public class FollowCamera : MonoBehaviour
{
    [SerializeField] private Transform target;
    [SerializeField] private Vector3 offset = new Vector3(0f, 6f, -8f);
    [SerializeField] private float smoothTime = 0.15f;
    [SerializeField] private Vector3 lookAtOffset = new Vector3(0f, 1.5f, 0f);

    private Vector3 velocity;      // SmoothDamp keeps its own state here

    void LateUpdate()
    {
        if (target == null) return;

        Vector3 wanted = target.position + offset;
        transform.position = Vector3.SmoothDamp(transform.position, wanted, ref velocity, smoothTime);
        transform.LookAt(target.position + lookAtOffset);
    }
}

Lerp か SmoothDamp か

✓ Vector3.Lerp(a, b, t * dt)

読みやすい一方で、実際のなめらかさはフレームレートに少し左右されます。

✓ Vector3.SmoothDamp(...)

バネのような動きで、フレームレートに左右されず、smoothTime は実際の秒数で指定します。

操作中のキャラクターを追いかける

target を Knight にハードコードしてしまうと、2 と 3 を押した瞬間に壊れます。SwapCamera がそうしていたのと同じく、切り替え側の答えを聞くのが正解です。

C# スクリプト
[SerializeField] private CharacterSwitcher switcher;

void LateUpdate()
{
    Transform target = switcher.ActiveCharacter;    // not a fixed reference
    Vector3 wanted = target.position + offset;

    transform.position = Vector3.SmoothDamp(transform.position, wanted, ref velocity, smoothTime);
    transform.LookAt(target.position + lookAtOffset);
}

なぜ必ず LateUpdate にするのか

  • Unity はすべての Update を順番が決まらない状態で実行します。キャラクターが動く前にカメラが走ってしまうこともあります。

  • LateUpdate はすべての Update が終わった後に走るため、カメラはそのフレームの最終位置を必ず見られます。

  • 間違えたときに出る症状は、キャラクターが移動中に一二ピクセルはっきり分かるほど震えることです。

先読み — 進む先まで見せる

カメラを進んでいる方向へ少しずらすだけで、プレイヤーは先に何が来るかを早めに察知できます。必要なコードは三行です。

C# スクリプト
[SerializeField] private float lookAhead = 2.5f;
private CharacterController targetCC;

void LateUpdate()
{
    Vector3 ahead = targetCC.velocity.normalized * lookAhead;
    ahead.y = 0f;

    Vector3 wanted = target.position + offset + ahead;
    transform.position = Vector3.SmoothDamp(transform.position, wanted, ref velocity, smoothTime);
    transform.LookAt(target.position + lookAtOffset);
}

カメラが壁を突き抜けるのを止める

add before assigning the position
[SerializeField] private LayerMask obstacles;

Vector3 pivot = target.position + lookAtOffset;
Vector3 dir = (wanted - pivot).normalized;
float dist = Vector3.Distance(pivot, wanted);

// is there a wall between the character and where the camera wants to be?
if (Physics.SphereCast(pivot, 0.3f, dir, out RaycastHit hit, dist, obstacles))
    wanted = hit.point - dir * 0.25f;      // pull the camera in front of it

よくあるミス

  • smoothTime が 0 だと昔の SwapCamera と同じく瞬間移動になり、0.5 秒を超えるとカメラが遅れすぎてプレイヤーが画面の外へ走り出てしまいます。0.1~0.25 秒の範囲から調整を始めてください。

  • velocity を LateUpdate の中で宣言すると、SmoothDamp が毎フレームリセットされて動きがバネのように跳ねて狂います。フレームをまたいで状態を保つ場所なので、必ずフィールドのままにしてください。

  • Hierarchy で Main Camera をキャラクターの子にしてしまうと、親の回転をすべて引き継いでこのスクリプトと取り合いになります。カメラはシーンのルートに置き、追従はスクリプトに任せてください。