U
章 03镜头与操作6 课 — 点击打开
01 / 06 · 8 分钟
镜头与操作

跟随镜头

到目前为止的项目

Assets/

Scenes/

Arena.unity

Knight

Scripts/

CharacterSwitcher.csSwapCamera.cs→FollowCamera.cs

你已经有的这一课新增这一课替换

打开 Arena.unity。tab 01 里的 SwapCamera 已经会跟着当前角色走,但它是硬甩过去的,还老盯着脚,并且随随便便就穿过墙。这一页用三个步骤把它重写一遍,这里是第一步。

死死贴在角色身上的相机很廉价;带一点延迟追上去的相机才有游戏感。

动态图解

角色先动,相机随后在几帧之内追上来。

跟随脚本

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 每一帧都会被重置,运动就变得弹来弹去、完全不对。它必须是一个字段,因为 SmoothDamp 就靠它在帧与帧之间保存状态。

  • 在 Hierarchy 里把 Main Camera 拖成角色的子物体,它就会继承父物体的所有旋转,跟这个脚本互相打架。让相机留在场景根部,跟随这件事交给脚本。