AX 爱鲜报

综合 / 前端 · 2026-11-03 08:36 · 8 阅读 · 0 赞

游戏镜头设计:从跟随到防穿墙的完整指南

本文以2D平台游戏为例,详细讲解镜头跟随的设计:从最省事的一行代码出发,逐步加入平滑、死区、前瞻、平台吸附、垂直跟随、像素对齐、关卡边界、Boss战构图,最后扩展到3D镜头的锁定与防穿墙。

游戏镜头设计:从跟随到防穿墙的完整指南

玩家很少夸镜头,因为镜头做对了,根本感觉不到它。从一行把镜头钉在角色身上的代码出发,一步步加上平滑、死区、前瞻和跳跃时不晃的垂直跟随,再处理画面抖动、关卡边界,最后到 3D 镜头的锁定与防穿墙。

玩家会夸一款游戏的角色、关卡和配乐,有时连菜单都会夸上几句,却很少有人夸它的镜头。

这并不奇怪。镜头做对了,你根本感觉不到它,只会觉得这游戏玩着舒服:往前跑,看得见前面的坑;起跳时,脚下的世界稳稳当当;打 Boss,画面刚好装得下你们两个。

镜头做错了,五分钟就能让人想放下手柄。角色挪一步,整个画面跟着抖一下;跳一次,地面就沉下去又浮上来;跑到一半,前面的敌人直到快撞上了才从屏幕边缘冒出来。

这篇文章拿一个 2D 平台游戏的角色做例子:它最快每秒跑 8 格,一次能跳 3 格高。角色一行不改,我们只改镜头,从最省事的一行代码出发,一个问题一个问题地补上去。你会发现,“玩着舒服”这几个字背后,是一串可以算、也可以调的数字。

镜头用正交投影,orthographicSize 取 5,也就是竖直方向能看到 10 格。画面比例是 16:9,水平方向大约能看到 17.8 格。

左:镜头钉在角色身上,跳跃时整个世界上下翻涌;右:同一次跳跃,地面稳稳不动

左:镜头钉在角色身上,跳跃时整个世界上下翻涌;右:同一次跳跃,地面稳稳不动

01 最省事的写法:把镜头钉在角色身上

几乎每个人写的第一版镜头,都是这样:

void LateUpdate()
{
    transform.position = new Vector3(
        target.position.x,
        target.position.y,
        -10f);
}

角色走到哪,镜头就跟到哪,角色永远站在屏幕正中央。它不会跟丢,也不会出错,可玩上几分钟,毛病就一个接一个冒出来。

第一,角色的每个小动作,都变成了整个世界的动作。 玩家站在平台边缘,左右轻点方向键找落脚点,角色只挪了零点几格,屏幕上却是整片背景在来回晃。

第二,每跳一次,地面都要翻涌一次。 角色跳起 3 格,镜头也跟着升起 3 格。在玩家眼里,角色仿佛一直悬在原地,反倒是脚下的地面先沉下去、再浮上来。一关跳上几百次,容易晕的人很快就受不了了。

第三,它不给你留任何余地。 角色全速往右跑,屏幕右边缘离它只有约 8.9 格。一个坑从出现在屏幕上,到你一脚踩空,只有 1.1 秒。

接下来的几节,就是逐个解决这三个问题。

02 平滑:让镜头追上去,而不是贴上去

最先想到的改进,是不让镜头一步到位,而是每帧追上一部分距离。很多人会这样写:

cam.position = Vector3.Lerp(
    cam.position,
    target.position,
    0.1f);

它在 60 帧下看起来恰到好处,可这个 0.1 是“每帧”追上一成,而不是“每秒”。帧率一变,镜头的手感就跟着变:60 帧时追上九成距离要 0.36 秒,掉到 30 帧要 0.73 秒,到了 144 帧的高刷屏上又只要 0.15 秒。同一份代码,在不同的手机上是三种不同的镜头。

把“每帧追一成”换成“每秒按固定的比例衰减”,就和帧率无关了:

[SerializeField]
float lambda = 6.32f;

cam.position = Vector3.Lerp(
    cam.position,
    target.position,
    1f - Mathf.Exp(
        -lambda * Time.deltaTime));

λ 越大,镜头追得越紧。追上九成距离需要 ln 10 ÷ λ 秒,λ 取 6.32 时正好是 0.36 秒,和 60 帧下每帧 0.1 的感觉一样,只是现在换到任何帧率都不变。这种写法后面会反复用到。

有了平滑,镜头不再像焊死在角色身上那样生硬。可前面那三个毛病,一个也没解决。小碎步时画面照样晃,只是晃得柔和了一点;跳跃时地面照样翻涌;第三个问题甚至更糟了,因为平滑的镜头总是落在角色身后,这一点我们讲前瞻时再细算。

03 死区:让角色在一个小框里自由走动

第一个真正的改动,是在屏幕中间画一个看不见的小框。角色在框里怎么走,镜头都不动;只有角色要走出框时,镜头才被它“推”着走。

这个框叫死区(Dead Zone)。

屏幕中央的死区:角色在框里走动时镜头不动,碰到边缘才推着镜头走

屏幕中央的死区:角色在框里走动时镜头不动,碰到边缘才推着镜头走

实现时,我们先不直接移动镜头,而是维护一个“镜头想看的点”,叫它 focus:

// 格,框宽 1.5 格
[SerializeField]
float deadZoneHalfWidth = 0.75f;

Vector2 focus;

void UpdateDeadZone(Vector2 target)
{
    float dx = target.x - focus.x;

    if (dx > deadZoneHalfWidth)
        // 顶到右边框,推着走
        focus.x += dx - deadZoneHalfWidth;
    else if (dx < -deadZoneHalfWidth)
        // 顶到左边框
        focus.x += dx + deadZoneHalfWidth;
}

角色只要还在框里,focus 就纹丝不动;一旦越过框边,focus 就刚好跟到“角色贴着框边”的位置,不多也不少。

框的宽度要拿捏好。1.5 格大约是角色身宽的一倍半,正好吸收找落脚点时的小碎步。再宽下去,角色要跑出好一段才推得动镜头,玩家会觉得镜头反应迟钝,像是被角色拖着走。

死区还有一个不那么显眼的好处:画面静止的时间变多了。 画面不动的时候,玩家的眼睛才能放松下来,去看关卡本身。一直在动的画面,哪怕动得再平滑,也是一种负担。

04 前瞻:往哪跑,就给哪边多留点空间

死区治好了“晃”,却让第三个问题雪上加霜。

现在算一笔账。角色全速往右跑时,会一直顶着死区的右边框,比屏幕中线靠右 0.75 格。镜头又加了平滑,而用指数平滑去追一个匀速移动的目标,镜头会稳定地落后 v ÷ λ,也就是 8 ÷ 6.32 ≈ 1.27 格。两者一叠加,角色站在中线右边约 2 格的地方,前方只剩 6.9 格。那个坑留给玩家的反应时间,从 1.1 秒缩到了 0.86 秒。

我们真正想要的恰好相反:角色往右跑,镜头就该往右多看一段。 这叫前瞻(Look-ahead)。

前瞻:角色往右跑时,镜头向前偏移,前方留出的空间比身后多

前瞻:角色往右跑时,镜头向前偏移,前方留出的空间比身后多

做法是在 focus 上叠加一个朝运动方向的偏移:

// 格
[SerializeField]
float lookAheadDistance = 3f;
// 偏移变化的快慢
[SerializeField]
float lookAheadLambda = 2f;

float lookAhead;

void UpdateLookAhead(
    Vector2 velocity, float dt)
{
    // 按速度比例前瞻:
    // 站着不动就不偏,全速才偏满
    float target = Mathf.Clamp(
        velocity.x / maxSpeed,
        -1f, 1f) * lookAheadDistance;

    lookAhead = Mathf.Lerp(
        lookAhead, target,
        1f - Mathf.Exp(
            -lookAheadLambda * dt));
}

偏移 3 格以后,前方能看到的距离从 6.9 格变成 9.9 格,坑出现到脚下的时间也从 0.86 秒变成了约 1.24 秒。多出来的这将近 0.4 秒,刚好够玩家看清、判断、起跳。

写前瞻有两个讲究。

一是跟着速度走,而不是跟着按键走。 如果一按方向键就立刻偏满,玩家原地左右点一下,镜头就要甩出去 3 格再甩回来,死区好不容易挡住的晃动又回来了。按速度的比例来偏,角色站着时就不偏,跑起来才慢慢偏过去。

二是要慢,比镜头本身的跟随还要慢。 全速转身时,偏移要从 +3 格变成 −3 格,足足 6 格。要是一下子翻过去,整个画面会猛地甩向另一边。λ 取 2,偏移追上九成要 ln 10 ÷ 2 ≈ 1.15 秒,画面是从容地转过去的。

还可以再讲究一点:转身以后先别急着翻转前瞻,等角色朝新方向走出一小段,或者方向键按住超过半秒,再开始转。战斗中玩家常常左右来回转身,这样镜头就不会跟着来回甩。

05 垂直方向:跳起来的时候,世界不该跟着晃

水平方向有了死区和前瞻,垂直方向却要换一种思路。

道理很简单:往左往右走,是玩家想去某个地方;而大多数跳跃,只是为了越过一个障碍,跳完还会落回原来的高度。镜头随着每一次跳跃起起落落,完全是白费力气。

所以平台游戏的镜头,在垂直方向上通常只认一件事:角色此刻站在哪个高度的地面上。 这叫平台吸附(Platform Snapping)。

平台吸附:跳跃过程中镜头高度不变,落到更高的平台上之后才平滑地升上去

平台吸附:跳跃过程中镜头高度不变,落到更高的平台上之后才平滑地升上去

// 地面放在屏幕中线以下 2 格
[SerializeField]
float groundOffset = 2f;
// 低于地面线 1 格,就算在往下掉
[SerializeField]
float fallThreshold = 1f;
// 下落时,画面中心放在角色下方 1.5 格
[SerializeField]
float fallLookDown = 1.5f;
// 高于地面线 4 格才跟上去
[SerializeField]
float riseThreshold = 4f;

// 镜头认定的“地面高度”
float baseY;
bool falling;

void UpdateVertical(
    Vector2 target, bool grounded)
{
    if (grounded)
    {
        falling = false;
        // 站稳了,才更新地面高度
        baseY = target.y;
    }
    else if (target.y < baseY
        - fallThreshold)
        // 掉下去了,不能再等落地
        falling = true;
    else if (target.y > baseY
        + riseThreshold)
        // 被弹得太高,跟上去
        baseY = target.y - riseThreshold;

    focus.y = falling
        ? target.y - fallLookDown
        : baseY + groundOffset;
}

先说 groundOffset。镜头中心定在地面以上 2 格,角色的脚就落在屏幕中线下方 2 格、离屏幕底边还有 3 格的位置,头顶则留出了 7 格。一次 3 格高的跳跃,怎么也碰不到屏幕上沿。平台游戏里值得看的东西,多半在头顶和前方,脚下留 3 格就够了。

再说两个阈值。普通的跳跃最高离地 3 格,不到 4 格的 riseThreshold,所以整个跳跃过程中,baseY 一动不动,画面稳如磐石。只有落到一个更高的平台上、grounded 重新为真时,镜头才升到新的高度。

往下掉就是另一回事了。角色从平台边缘走下去时,脚下有什么,玩家必须马上看到,因为那里可能是尖刺、敌人,或者一个无底深渊。

普通的跳跃总会落回原来的高度,或者落到更高的地方。只有走下平台、掉进坑里,角色才会低于地面线。所以只要比地面线低了 1 格,就可以断定它在往下掉。这时镜头不再守着地面线,而是直接跟住角色,并把画面中心放在角色下方 1.5 格,脚下一下子能看到 6.5 格。尖刺也好,深渊也好,都会提前进入画面。

上去可以慢,下来必须快

平滑也是同样的道理。镜头往上走,是去看一个已经站稳的新平台,慢一点无妨;往下走,看的是你正在掉进去的地方,晚一帧都可能错过要命的东西。所以垂直方向的平滑要分成两个速度:

[SerializeField]
float lambdaX = 6.32f;
[SerializeField]
float lambdaUp = 3f;
[SerializeField]
float lambdaDown = 8f;

λ 为 3 时,镜头升上去追上九成要约 0.77 秒;λ 为 8 时,往下只要约 0.29 秒。

06 把零件拼起来

到这里,镜头有了三个零件:死区算出 focus.x,前瞻给出一段偏移,平台吸附算出 focus.y。把它们加起来,就是“镜头想去的位置”,最后再用平滑让镜头真正走过去:

public class PlatformerCamera
    : MonoBehaviour
{
    [SerializeField]
    PlayerMotor player;

    Vector2 smoothed;

    void LateUpdate()
    {
        float dt = Time.deltaTime;
        Vector2 target = player.Position;

        UpdateDeadZone(target);
        UpdateLookAhead(
            player.Velocity, dt);
        UpdateVertical(
            target, player.Grounded);

        Vector2 desired = new Vector2(
            focus.x + lookAhead, focus.y);

        float ly = desired.y < smoothed.y
            ? lambdaDown : lambdaUp;

        smoothed.x = Mathf.Lerp(
            smoothed.x, desired.x,
            1f - Mathf.Exp(-lambdaX * dt));
        smoothed.y = Mathf.Lerp(
            smoothed.y, desired.y,
            1f - Mathf.Exp(-ly * dt));

        transform.position = new Vector3(
            smoothed.x, smoothed.y, -10f);
    }
}

注意水平和垂直是分开平滑的,各用各的 λ。这个细节看着不起眼,却是很多“镜头说不上哪里别扭”的根源。用一个 Vector3.Lerp 同时处理两个方向,要么水平跟得太松,要么垂直跟得太紧,怎么调都差一口气。

如果游戏里还有镜头震动,别把它和跟随写在一起。把震动放在镜头的子物体上,跟随逻辑留在父物体上:父物体负责“去哪”,子物体只管在原地晃。两件事互不干扰,震完以后,镜头会准确地回到它该在的位置。

07 画面在抖:两个最常见的原因

镜头写好了,一跑起来,却发现角色在抖。不是镜头在晃,而是角色本身一顿一顿地颤。这几乎是每个 2D 项目都会撞上的坑,原因通常是下面两个之一。

物理每秒走 50 步,屏幕每秒画 144 帧

角色的移动一般放在 FixedUpdate 里,这样物理计算的步长是固定的。Unity 默认的物理步长是 0.02 秒,也就是每秒 50 步。可屏幕每秒要画 60 帧、120 帧,甚至 144 帧。

这意味着在 144 帧的屏幕上,大约每三帧里只有一帧,角色的位置真的变了,另外两帧它都停在原地。镜头却在 LateUpdate 里每一帧都平滑地往前走。于是在屏幕上,角色相对镜头一会儿往后缩一点,一会儿又往前蹿一下。角色每秒跑 8 格,每一步物理更新走 0.16 格,按每格 16 像素算,就是两三个像素的来回颤动,肉眼看得清清楚楚。

解决办法是让镜头跟随插值后的位置,而不是物理位置。如果角色用的是 Rigidbody2D,把它的 interpolation 设为 Interpolate,Unity 会在两次物理更新之间补出平滑的中间位置,镜头跟着 transform.position 走就行。如果移动是你自己写的,就要记下上一步和这一步的位置,按 (Time.time − Time.fixedTime) ÷ Time.fixedDeltaTime 的比例在两者之间插值。

像素画落在了像素之间

第二种抖只在像素风游戏里出现。平滑后的镜头位置可以是任意小数,比如 12.3471 格。按每格 16 像素算,镜头就停在了两个像素之间,整幅画面每一帧都要重新采样,细线忽粗忽细,边缘不停地闪。

顺手的修法,是把镜头位置对齐到像素网格。但千万别直接改 smoothed:

const float PixelsPerUnit = 16f;

// 平滑的逻辑位置保持原样,
// 只在最后输出时对齐像素
Vector2 snapped = new Vector2(
    Mathf.Round(
        smoothed.x * PixelsPerUnit)
        / PixelsPerUnit,
    Mathf.Round(
        smoothed.y * PixelsPerUnit)
        / PixelsPerUnit);

transform.position = new Vector3(
    snapped.x, snapped.y, -10f);

如果把取整后的值写回 smoothed,下一帧的平滑就会从取整后的位置算起。λ 取 6.32 时,60 帧下每帧只走剩余距离的一成。剩下的距离一旦小于 0.3125 格,这一成就不到半个像素,一取整就成了零。结果镜头永远停在离目标 5 个像素的地方,再也走不到头。

逻辑上的位置用小数算,画面上的位置按像素对齐,两件事分开来做,几乎所有“取整”带来的麻烦都能这样解决。

08 让镜头读懂关卡:边界、区域与拉远

到目前为止,镜头只知道角色在哪,却不知道关卡长什么样。这会带来两个问题。

别让玩家看到关卡外面

角色走到关卡最左边时,镜头依然尽职地把它摆在中间,结果屏幕左半边露出了关卡外的一片虚空。

解决办法是限制镜头中心的活动范围。可见区域的一半,竖直方向是 orthographicSize,水平方向再乘以画面比例:

[SerializeField]
Rect levelBounds;

Vector2 ClampToBounds(Vector2 center,
    Camera cam)
{
    float halfH = cam.orthographicSize;
    float halfW = halfH * cam.aspect;

    float minX = levelBounds.xMin
        + halfW,
        maxX = levelBounds.xMax - halfW;
    float minY = levelBounds.yMin
        + halfH,
        maxY = levelBounds.yMax - halfH;

    // 关卡比屏幕还窄时,干脆居中
    center.x = minX > maxX
        ? levelBounds.center.x
        : Mathf.Clamp(center.x, minX, maxX);
    center.y = minY > maxY
        ? levelBounds.center.y
        : Mathf.Clamp(center.y, minY, maxY);
    return center;
}

这一步要在平滑之前做,限制的是 desired。放到平滑之后,镜头会先冲出边界、再被硬拽回来,在关卡边缘一顿一顿的。

让关卡告诉镜头该看哪里

有些地方,光靠跟随算法是不够的。一口高高的竖井,需要镜头往下多留一些;一扇只有从远处才看得见的门,需要镜头提前偏过去一点;一间 Boss 房,需要镜头拉远,把整个场地装进画面。

这些不该写死在镜头代码里,而应该交给关卡:在场景里摆一些触发区域,角色走进去,就临时改写镜头的某几个参数,比如偏移、orthographicSize 和边界;走出来,再平滑地恢复原样。

以 Boss 战为例,最好的构图是让玩家和 Boss 始终同时在画面里。镜头中心取两者的中点,orthographicSize 则根据两者的距离来定。

Boss 战:两者离得近时镜头推近,拉开距离后镜头跟着拉远,始终把两者都装进画面

Boss 战:两者离得近时镜头推近,拉开距离后镜头跟着拉远,始终把两者都装进画面

算法只有几行:

// 两边各留 3 格
[SerializeField]
float framingMargin = 3f;
[SerializeField]
float minSize = 5f, maxSize = 8f;

float FitSize(Vector2 a, Vector2 b,
    float aspect)
{
    float halfW = Mathf.Abs(a.x - b.x)
        * 0.5f + framingMargin;
    float halfH = Mathf.Abs(a.y - b.y)
        * 0.5f + framingMargin;
    return Mathf.Clamp(Mathf.Max(halfH,
        halfW / aspect), minSize, maxSize);
}

玩家和 Boss 相距 20 格时,水平方向需要 10 + 3 = 13 格的半宽,除以 16:9 的比例,orthographicSize 约为 7.3。两者靠近,镜头就推近;拉开,镜头就拉远。但它不会小于 5,也不会大于 8,再拉远下去,角色就小得看不清自己在做什么了。

orthographicSize 本身的变化也要做平滑,而且边界限制要按变化中的尺寸来算。否则拉远的一瞬间,画面会先露出关卡外面,再被推回来。

09 3D 镜头:锁定敌人,也别钻进墙里

换到 3D 第三人称游戏,镜头多了一个维度,也多了两个 2D 里没有的难题。

锁定:让角色和敌人同时入画

按下锁定键后,镜头要把玩家和敌人同时放进画面。常见的做法是:从敌人往玩家拉一条线,把镜头放在这条线越过玩家之后的延长线上,再抬高一些、往旁边偏一点,让玩家站在画面一侧,不至于正好挡住敌人。

Vector3 toPlayer = (
    player.position - enemy.position);
toPlayer.y = 0f;
Vector3 back = toPlayer.normalized;
Vector3 side = Vector3.Cross(
    Vector3.up, back);

Vector3 desired = player.position
    // 退到玩家身后 4.5 米
    + back * 4.5f
    // 抬高到肩膀上方
    + Vector3.up * 1.8f
    // 往一侧偏,让出敌人的位置
    + side * 1.2f;

Quaternion look =
    Quaternion.LookRotation(
        (enemy.position + player.position)
        * 0.5f + Vector3.up - desired);

镜头看向两人中点稍高一点的地方。这样玩家在画面一侧的近处,敌人在画面中央的远处,谁也挡不住谁。两人拉开距离时,还可以像 Boss 战那样,按距离把镜头往后拉。

防穿墙:缩进要快,退回要慢

3D 镜头最常见的灾难,是玩家贴着墙一转身,镜头整个钻进了墙里,屏幕上只剩一片墙壁的背面。

防穿墙:从角色肩膀向镜头发出球形射线,碰到墙就把镜头拉到墙前面

防穿墙:从角色肩膀向镜头发出球形射线,碰到墙就把镜头拉到墙前面

解决办法是每一帧从角色的肩膀,朝镜头原本该在的位置发一个球形射线。碰到东西,就把镜头放在碰撞点前面:

[SerializeField]
float cameraRadius = 0.25f;
[SerializeField]
LayerMask obstacleMask;

float currentDistance;

void ResolveCollision(
    Vector3 pivot, Vector3 dir,
    float desiredDistance, float dt)
{
    float allowed = desiredDistance;
    if (Physics.SphereCast(
        pivot, cameraRadius, dir,
        out RaycastHit hit,
        desiredDistance, obstacleMask))
        allowed = hit.distance;

    if (allowed < currentDistance)
        // 往里缩:立刻
        currentDistance = allowed;
    else
        // 往外退:慢慢来
        currentDistance = Mathf.Lerp(
            currentDistance, allowed,
            1f - Mathf.Exp(-3f * dt));

    transform.position =
        pivot + dir * currentDistance;
}

这里又是一快一慢。往里缩必须立刻完成,晚一帧,玩家就会看到墙的背面;往外退却要慢慢来,否则角色从柱子后面走过时,镜头会猛地缩进来、又猛地弹出去,像在抽搐。

之所以用球形射线而不用普通射线,是因为镜头前面还有一个近裁剪面。普通射线刚好擦过墙角时,镜头本身没碰到墙,画面的边角却已经切了进去。0.25 米的半径,正好给近裁剪面留出余量。

别让玩家晕

最后说一件常被忽略的事:3D 镜头是晕动症的头号来源。常见的诱因有这么几个:视野角太窄,跟随时镜头带着滚转,走路时镜头跟着上下颠,以及镜头转得比玩家推摇杆还快。

视野角一般从 60 到 70 度起步,宽一些更不容易晕;镜头几乎永远不该绕着前后轴滚转;颠簸和震动都应该在设置里留一个开关。这些都花不了多少工夫,却能让一部分玩家把游戏玩下去。

10 一张调参表

把前面的参数收拢到一起。它们不是标准答案,只是一个不错的起点:

参数 值 含义 调大以后
orthographicSize 5 竖直方向看到 10 格 看得更多,角色更小
水平 λ 6.32 0.36 秒追上九成 跟得更紧
死区半宽 0.75 格 框宽 1.5 格 更稳,也更迟钝
前瞻距离 3 格 全速时前方多看 3 格 看得更远,角色离中心更远
前瞻 λ 2 转身约 1.15 秒转过去 转身甩得更猛
地面偏移 2 格 脚下留 3 格,头顶留 7 格 看到更多上方
下落阈值 1 格 低于地面线 1 格算下落 更晚发现在往下掉
下落前瞻 1.5 格 下落时脚下看到 6.5 格 看到更多下方
上升阈值 4 格 普通跳跃不触发 弹跳时画面更稳
向上 λ 3 约 0.77 秒 升得更快
向下 λ 8 约 0.29 秒 跟得更快
Boss 战边距 3 格 两侧各留 3 格 画面更松,角色更小
防穿墙回退 λ 3 缩进立刻,退回约 0.77 秒 退得更快,更容易抽搐

调镜头时,有一个习惯比调参本身更重要:把镜头的“想法”画出来。

在 Scene 视图里用 OnDrawGizmos 画出死区的框、加上前瞻之后的目标点、baseY 那条地面线,还有关卡边界。镜头的毛病,十有八九出在它“想去哪”,而不是它“怎么过去”。只盯着最终画面,你很难分清是哪一种;把这几样东西画出来,一眼就知道该动哪个参数。

写在最后

回头看,这篇文章讲来讲去,其实只有一句话:镜头是玩家的眼睛,它不该抢戏。

平滑让它不再生硬;死区让它在不必动的时候不动;前瞻让它在玩家需要看远的时候看远;平台吸附让它在一次次跳跃里稳住地平线;插值和像素对齐让画面不抖不闪;边界和区域让关卡有机会告诉它哪里值得看;防穿墙则保证它无论如何都不会挡在玩家面前。

每一招拆开来,都只是几行代码、一两个参数。可当它们全都做对的时候,玩家什么也不会说。

他们只会觉得,这游戏玩着真舒服。

标签 2D动画 3D 游戏开发 Unity 镜头设计

评论

登录后才可评论

0 条评论

  • 暂无评论,来写第一条吧。