导语:评审会开到第四十分钟,老板只记住一个画面:主角从两米平台跳下,像羽毛一样飘了 1.8 秒才落地。那一刻没人再讨论精修的材质和 Lumen 光影——手感塌了,一切都白搭。竖切(Vertical Slice)的意义,就是在成本最低的灰盒阶段把这类问题一次性炸出来。本章把前八章的系统全部点亮:跑起来、跳起来、调准手感、配齐调试兵器,最后把卷一的验收单拍在桌上。
本章地图
mindmap
root((第9章 竖切收口))
竖切思维
先做最难看的30秒
验收标准三要素
灰盒尺寸
门宽120 走廊240
跳跃公式定垂直尺度
灰盒阶段锁尺寸
手感调参
CMC参数混音台
相机滞后
跳跃链路串起前8章
调试兵器
控制台
VisLog回放
日志分级
打包验收
性能基线
卷一清单9.1 先做最难看的 30 秒
补课:本节补策划基础——竖切方法论与验收标准的写法。有游戏策划经验的读者可以直接跳到 9.2。
竖切这个词常被误解成"做一小段精美的 demo"。恰恰相反:竖切要的是纵向切穿游戏的所有系统——输入、移动、动画、渲染、打包——每一层都切到,但每一层只切最薄的一片。软件工程里对应的实践叫曳光弹(Tracer Bullet),出自《The Pragmatic Programmer》:不追求单点完美,先让弹道从头到尾亮一遍,确认枪是直的。
它的反面是"横切":先把某一个系统做到满分,再做下一个。横切在工程上几乎总是灾难——渲染管得再漂亮,输入到显示的链路一天没通,项目就一天没有"游戏",只有零件。更现实的风险是集成爆炸:每个系统在隔离环境里都正常,拼到一起全部不对,而这时已经没有预算回头了。竖切把集成风险前置到第一周,代价是几周灰盒;横切把集成风险压到交付前,代价是整个项目。
那"30 秒"怎么选?标准只有一个:它必须是核心循环(Core Loop)的最小完整循环。「回响峡谷」的核心循环是"探索 → 跨越障碍 → 到达新区域",最小完整循环就是一段谷口动线加一次跳跃。它不需要战斗、不需要背包、不需要存档——那些是循环上的枝叶。判断枝叶有个简单的问法:砍掉它,循环还转吗?转,就是枝叶,竖切里一律不做。
为什么这第一发子弹必须难看?因为美术是游戏开发里迭代成本最高的东西,而尺寸和手感是成本最低的。一组对照:灰盒里挪一面墙,30 秒;美术化之后挪同一面墙,关卡美术半天、光照重烘二十分钟、导航网格重生成、碰撞重设——还得先说服那个画了一周墙的美术。沉没成本会绑架判断:东西越好看,越没人舍得推翻,问题就这样被一路带进量产。
所以竖切的第一刀切在最丑的 30 秒上:一段谷口动线、一次跳跃、一个终点。角色是胶囊,墙是 BSP 灰块,没有贴图。灰盒阶段的验收目标很明确,盯住三件事:动线成不成立、手感顺不顺、60fps 扛不扛得住。好不好看不在验证清单上。
验收标准怎么写
竖切死在纸面上的原因,十有八九是验收标准写成了许愿。"手感要爽""氛围要对""玩得停不下来"——这类句子没法验收,因为没人能证伪。能用的验收标准只有三要素:
- 可观察:描述外部行为,不描述内心感受。"按下跳跃后 3 帧内离地",而不是"跳跃要干脆"。
- 可测量:带数字阈值。"10 次跳跃尝试成功 ≥ 9 次""1080p 下 GPU 帧时间 ≤ 12ms"。
- 可复现:给出触发步骤与测试环境。"Development 打包版,谷口平台起跳,RTX 3060 级显卡"。
一条合格的竖切验收长这样:"在打包版中,主角从谷口出发,沿主路经断桥跳跃到达灯塔门口,全程 45 秒内可完成,跳跃成功率 ≥ 90%,全程帧时间不超 16.6ms。"每一个字都能被打勾或打叉。
stateDiagram-v2
[*] --> 灰盒
灰盒 --> 白模: 玩法与动线验收通过并锁尺寸
白模 --> 竖切: 光影氛围与节奏达标
竖切 --> [*]: 卷一验收全绿
灰盒 --> 灰盒: 手感与尺寸迭代,代价数小时
白模 --> 灰盒: 动线推翻,代价数天
竖切 --> 白模: 尺寸推翻,代价数周原型的保真度分三级,每一级都有进入条件。灰盒验证玩法与尺寸,通过后锁尺寸进白模(简单材质、定型光照);白模验证光影氛围与动线节奏,通过后进竖切(代表性美术落地)。注意图里那条最贵的回退边:竖切阶段推翻尺寸,代价是数周;灰盒阶段推翻尺寸,只要一个下午。锁尺寸必须发生在最便宜的时候。
flowchart TD
A["提出假设: 30秒核心体验"] --> B[灰盒实现最省版本]
B --> C["试玩并记录: 计时/成功率/帧时间"]
C --> D{验收标准达标?}
D -- 否 --> E["定位: 尺寸 / 手感 / 引导"]
E --> B
D -- 是 --> F[冻结该层, 提升保真度]
F --> G{竖切层全部冻结?}
G -- 否 --> B
G -- 是 --> H[封板评审]竖切是循环——提出假设,用最省的灰盒实现,试玩记录,对照验收标准。不达标就修,达标就冻结这一层,进入下一保真度。冻结(freeze)是关键词:每一层封板后不再回头,回头的代价你在上面的状态图里已经看到了。
gantt
title 回响峡谷竖切 · 两周计划
dateFormat YYYY-MM-DD
section 第一周 灰盒与手感
灰盒搭建与尺寸锁定 :a1, 2026-07-01, 2d
移动手感调参 :a2, after a1, 2d
跳跃链路打通与内测 :a3, after a2, 1d
section 第二周 工具与封板
调试兵器库接入 :b1, after a3, 2d
打包与性能基线 :b2, after b1, 1d
验收清单逐项过单 :b3, after b2, 1d
封板评审 :milestone, after b3, 0d两周是这个循环的最小健康周期:第一周灰盒加手感,第二周工具加封板。排得再满,最后一天也必须空出来给验收清单逐项过单——验收本身就是开发的一部分,拎出来单独走一遍只是形式。
最后说评审纪律。竖切评审最容易犯的错误是放 PPT:截图精美、结论喜人,但没人真的玩过。规矩只有一条——评审会上手柄必须交到评审手里,让他从谷口走到灯塔门口,全程掐表。手感这种东西,嘴上说"还行"和手上"跳了三次没过断桥"是两个物种。很多项目的手感问题之所以成了漏网之鱼,根源是发现的人只看了视频,手里没握过手柄。
9.2 灰盒不是随便堆方块:尺寸就是玩法
引擎的长度单位是厘米(1uu = 1cm),所有尺寸讨论都可以直接拿真实世界对照。「回响峡谷」主角用引擎默认胶囊:半高 88cm、半径 34cm,一个 176cm 高、68cm 宽的人形。灰盒的所有数字,都从这颗胶囊往外推。
一条尺度链:人 → 门 → 走廊 → 房间
| 元素 | 锁定尺寸 | 推导依据 |
|---|---|---|
| 角色胶囊 | 176 × 68cm | 引擎默认,动它牵一发动全身 |
| 门洞 | 宽 120cm × 高 220cm | 1.76 倍胶囊宽:能过,但微微蹭肩的紧张感 |
| 走廊 | 净宽 240cm | 两倍门宽:两人并行、侧移闪避都够 |
| 战斗走廊 | 净宽 ≥ 300cm | 需要绕背的战斗,再加一格余量 |
| 层高 | 300cm | 跳 100cm 后头顶余量 20cm,不压抑 |
| 台沿 | 100 / 140 / 220cm 三档 | 跳上 / 攀越 / 不可攀,垂直动线的语法 |
门宽 120cm 不是拍脑袋。开到 150cm,过门毫无感觉;收到 90cm,玩家会卡在门框上骂街。120cm 是"刚好能过、余量不大"的甜点位。恐怖氛围游戏的走廊同理:240cm 是"能战斗但略局促"的宽度,想要开阔感就上 320cm,代价是压迫感被稀释。尺寸即体验,这件事在灰盒阶段就要定死。
门高 220cm 的依据在相机而不在角色:第三人称镜头悬在角色头顶斜后方,过门时俯仰角会抬,220cm 的门楣刚好不挡视线又不至于让空间显矮。楼梯踏步高 20cm、踏面 30cm,沿用的是建筑规范里的人体工学——玩家的腿是真人比例,违背直觉的台阶会立刻被感觉出来,玩家也许说不出哪里不对。
台沿的三档高度是垂直动线的语法:100cm 是"能跳上",等于一句陈述句;140cm 是"要攀越",播放攀越动画、打断移动节奏,等于一个逗号;220cm 以上跳不上去也攀不了,等于句号,逼玩家读别的路。灰盒阶段就用这三种高度写句子,关卡想表达"走这边"还是"此路不通",全靠高度差说话。
水平方向同样由角色定尺度,只是换算因子从跳跃变成了步速。步速 450cm/s,意味着玩家 10 秒走 45 米——这就是场景"一段"的天然长度:短于 30 米显得碎,长于 80 米不塞内容就是跑路。开放区域同理,直径按"跑一圈多少秒"设计,而不是按视觉上的壮观程度。很多关卡美术做完才发现"地图好大但好空",病根是灰盒期没拿步速量过地。速度决定世界的大小,这句话到第 16 章做无缝大世界时还要再用一次。
跳跃决定垂直尺度
跳跃是关卡垂直方向的度量衡。引擎默认 JumpZVelocity = 420cm/s、重力加速度 980cm/s²,竖直上抛的顶点高度由中学物理给出:
代入默认值得 h ≈ 90cm,滞空约 0.86s。这 90cm 就是灰盒的"垂直货币":希望玩家能跳上去的台沿做 80cm(顶点高度打九折留容错);想逼玩家另找路线的,台沿做 150cm 以上。水平方向同理,跳跃距离 = 水平速度 × 滞空时间:默认 600cm/s 跑速能跳出 5 米开外,参数不调,断桥缺口做 2 米就是散步。9.3 会把速度和滞空拧到「回响峡谷」需要的味道——调完之后跳距约 3.2 米,断桥缺口按跳距的 80% 做到 2.6 米,有挑战,不挫败。
为什么灰盒阶段就要锁尺寸
因为下游所有系统都按尺寸生产,而它们的重做成本一个比一个高:
- 动画:步速 450cm/s 对应特定步频,动线一变,混合空间的采样点全要重调(第 11 章)。
- 相机:弹簧臂长度按走廊净高定,层高一改,俯仰角余量全变。
- 光照:Lumen 的探针密度、体积雾厚度都按空间尺度调(第 7、10 章)。
- 导航与流送:NavMesh 格子、World Partition 的单元尺寸(第 16 章),全都吃尺寸这碗饭。
灰盒阶段改尺寸,动的是 BSP 方块,30 秒;美术落地后改尺寸,动的是上面整条链。所以「回响峡谷」的规矩是:灰盒验收会上,尺寸表打印出来贴在墙上,所有人签字;谁想改,先写清代价。
还有一条反直觉的忠告:别动默认胶囊尺寸。很多团队一上来就想把胶囊改成"贴合角色体型",比如改成 150cm 的娇小主角。这一改的连锁反应是:所有门重新量、所有台阶重新调、动画根运动的速度曲线全部重标、相机碰撞球重新测。引擎默认的 88cm 半高、34cm 半径之所以是默认,是因为整套工具链——从 CMC 的落地求解到模板的动画资产——都围绕它调过。角色体型差异应该由网格体和动画表达,胶囊保持不动。等整条团队都对后果有数了,再谈定制。
图里把三样东西画进同一坐标系:120cm 的门、240cm 的走廊,以及那条被公式算死的跳跃抛物线。以后每次有人提议"这里加宽一点",就把这张图拍出来,问他打算重调哪几个系统。
9.3 手感拆解:CharacterMovement 是台混音器
手感听起来很玄,拆开全是数字:从输入到画面变化的延迟,和速度变化的曲线形状。引擎把这两件事的旋钮集中在 UCharacterMovementComponent(下称 CMC)上——它就是角色移动的混音台,每个推子拧大拧小都有明确的主观后果。
关键参数速查
| 参数 | 引擎默认 | 拧大 | 拧小 |
|---|---|---|---|
| MaxWalkSpeed | 600cm/s | 风一样,世界变小,操控变糙 | 沉重,跑图像受刑 |
| MaxAcceleration | 2048cm/s² | 起步即满速,遥控车式的廉价感 | 起步肉,像推冰箱 |
| BrakingDecelerationWalking | 2048cm/s² | 松手急停,呆板机械 | 刹车距离长,溜冰感 |
| GroundFriction | 8.0 | 抓地稳 | 地面抹油,漂移 |
| AirControl | 0.05 | 空中随意变向,平台跳跃游戏标配 | 起跳即交给抛物线,真实系 |
| RotationRate.Yaw | 640°/s | 转身跟手但容易滑步 | 有重量感,镜头等身体 |
| JumpZVelocity | 420cm/s | 跳得高飘得久 | 贴地,压抑 |
| GravityScale | 1.0 | 下坠利落 | 月球漫步,飘 |
| CameraLagSpeed(弹簧臂) | 常用 8–12 | 镜头锁死,快但易晕 | 镜头松,电影感,过弯易穿墙 |
三个经验判据帮你校准:起步加速到 90% 速度在 150ms 内算"跟手";停止距离约一步算"利落"(600cm/s 配 2048 刹车,停距 ≈ 88cm);跳跃滞空压在 0.8s 内算"不飘"。「回响峡谷」对标《最后生还者》的沉重感,但沉重不等于慢——要的是"有惯性的快"。
调参之前还有一笔延迟账要算清楚。玩家感知到的手感,起点是手指,终点是光子:输入采样要等下一个 Game 线程帧(60fps 下平均 8ms),逻辑处理占 1 帧,渲染队列再排队 1–2 帧,显示器本身还有 10ms 上下的延迟——加起来 50–80ms,也就是 3–5 帧。这段物理延迟改不掉,能改的是曲线:起步加速快一点,玩家就觉得"跟手",哪怕真实延迟没变。手感调参的本质,是在固定延迟上用曲线形状补偿知觉。这也是为什么别在 30fps 的环境下调手感——帧率翻倍后延迟账全变,参数要重拧一遍。
// 回响峡谷 · 角色基类:移动手感参数集中在 C++,蓝图只读微调
AEchoCharacter::AEchoCharacter()
{
UCharacterMovementComponent* Move = GetCharacterMovement();
// 地面:起步约 120ms 到位,停步距离一步以内
Move->MaxWalkSpeed = 450.f; // 比默认 600 慢,世界更"大"
Move->MaxAcceleration = 2200.f;
Move->BrakingDecelerationWalking = 2600.f;
Move->GroundFriction = 8.f;
// 跳跃:高度不变(≈100cm),滞空 0.86s→0.71s,落地更利落
Move->JumpZVelocity = 560.f;
Move->GravityScale = 1.6f;
Move->AirControl = 0.25f; // 空中可变向,但飞不动
Move->WalkableFloorZ = 0.68f; // 容忍约 47° 坡
// 转身:身体跟随移动方向,不锁镜头
bUseControllerRotationYaw = false;
Move->bOrientRotationToMovement = true;
Move->RotationRate = FRotator(0.f, 540.f, 0.f);
// 相机:滞后速度 8,上限 60cm,急停不穿帮
CameraBoom->bEnableCameraLag = true;
CameraBoom->CameraLagSpeed = 8.f;
CameraBoom->CameraLagMaxDistance = 60.f;
}几处关键取舍:跳速 560 配 1.6 倍重力,按 9.2 的公式算,跳高仍是 100cm 左右,但滞空从 0.86s 压到 0.71s——高度不降,节奏利落,这就是"有重量的跳跃"。AirControl 0.25 给空中变向留了活路,但不至于飞;相机滞后加了 60cm 上限,防止急停时镜头弹出去穿帮。
手感风格没有标准答案
quadrantChart
title "手感风格四象限"
x-axis "低惯性" --> "高惯性"
y-axis "低响应" --> "高响应"
quadrant-1 "重量跟手·回响峡谷目标"
quadrant-2 "轻快灵敏·平台跳跃"
quadrant-3 "松垮迟钝·反面教材"
quadrant-4 "沉重迟缓·硬核模拟"
"蔚蓝类平台跳跃": [0.15, 0.9]
"最后生还者": [0.7, 0.82]
"魂系动作": [0.85, 0.35]
"太空模拟": [0.2, 0.25]
"回响峡谷当前参数": [0.68, 0.8]平台跳跃游戏(《蔚蓝》一类)钉在左上:零惯性、瞬时响应;《最后生还者》在右上:响应快但有明显惯性;魂系在右下:响应都刻意放慢;太空模拟在左下:两者皆松。调参之前先想清楚自己的目标象限——参数表里的每一行,都是朝那个点拧的。
调参方法上有个纪律:一次只拧一个旋钮,每组参数都存成可回滚的 preset。手感是多元函数,两个参数同时动,你就永远不知道是哪一刀起了效。对照测试用盲测:让试玩者不知道当前是哪组参数,只记录成功率和主观分。还有一个现在就要知道的坑——参数和动画是耦合的。步速 450cm/s 配的是对应步频的动画,速度拧到 600 而动画不换,脚下立刻开始滑步。滑步问题的完整解法(混合空间、根运动)是第 11 章的主菜,本章只要记住:调完速度,一定用 slomo 0.3 慢放检查脚底。
一次跳跃,把前八章串成一条线
sequenceDiagram
autonumber
participant K as 键盘(空格)
participant I as Enhanced Input·第5章
participant P as Controller/Character·第3章
participant M as CMC移动组件·第3/5章
participant A as AnimInstance·第11章
participant R as 渲染管线·第6-8章
K->>I: 按下空格
I->>I: MappingContext命中IA_Jump, Pressed到Triggered
I->>P: 触发回调
P->>M: Character::Jump()
M->>M: Z向速度写入560, MovementMode切Falling
M-->>A: 每帧暴露Velocity与bIsFalling
A->>A: 状态机切JumpStart段
M->>R: 帧末同步Transform
R->>R: Game线程到Draw线程到GPU, Nanite/Lumen/VSM求值
R-->>K: 起跳帧落屏, 全程3到5帧把这张图和前面八章对照着看:
- 第 2 章的反射系统让 CMC 的速度、落地状态这些 UPROPERTY 能被动画蓝图和调试器直接读到,不用手写胶水代码。
- 第 3 章的框架决定 Jump() 写在 Character 上——如果当时图省事塞进了 PlayerController,这张图从第 4 步就断了。
- 第 4 章的分工体现在参数归属:默认值 C++ 写死,策划在蓝图子类里覆盖微调,改完不用重编译。
- 第 5 章的 Enhanced Input 负责前 3 步,IA_Jump 的 Trigger 配置决定长按连跳是什么手感。
- 第 6 章的三线程交接决定从"按下"到"看见"是 3–5 帧而不是 1 帧,手感调参必须把这段延迟算进去。
- 第 7 章的 Nanite/Lumen 和第 8 章的材质决定落地瞬间:尘土特效用材质参数集触发的那一下,渲染端扛不扛得住。
一条跳跃链路,八个章节各司其职。这就是为什么竖切要放在卷一最后:它是前八章所有知识的集成测试。
这条链也是排障地图。跳跃出问题时的定位顺序固定:先在 CMC 打断点看 Jump() 有没有被调(没调到 → 第 5 章输入层);再打印 MovementMode 是否切到 Falling(没切 → 第 3 章框架层);再看速度曲线对不对(不对 → 本章参数层);最后画面不对才查渲染。从输入往显示方向顺藤摸瓜,永远比对着最终画面瞎猜快。
9.4 调试兵器库:控制台、VisLog、日志分级
竖切阶段 40% 的时间在写功能,60% 的时间在回答"它为什么这样"。三件兵器按出场率排序,分工先看这张表:
| 兵器 | 回答的问题 | 时间轴 | 典型命令/入口 |
|---|---|---|---|
| 控制台 | 此刻发生了什么 | 实时 | stat unit、profilegpu、slomo |
| Visual Logger | 刚才那 5% 发生了什么 | 可倒带 | VisLog 录制与回放 |
| 日志分级 | 发行版里发生了什么 | 事后 | UE_LOG、[Core.Log]、-LogCmds |
选兵器的口诀:能稳定复现的用控制台,偶发的用 VisLog,只在打包版出现的靠日志。三个场景各配一个真实战例。
控制台:随身的听诊器
反引号打开控制台,几个命令用到项目结束:
stat fps/stat unit:帧时间四连——Frame/Game/Draw/GPU,瓶颈在哪一眼锁定(第 6 章的读法)。profilegpu:GPU 账单逐 Pass 拆解。slomo 0.3:慢动作,验证动画过渡和落地帧的神器。show Collision:把所有碰撞体画出来,卡墙问题当场现形。p.VisualizeMovement 1:把 CMC 的速度向量、落点预测实时画在角色身上。
真实排障场景:打包版帧率只有 45。stat unit 显示 Game 3ms、Draw 5ms、GPU 21ms——CPU 无罪,GPU 超载。profilegpu 一看,屏幕空间反射吃掉 8ms:有人把后处理体积里的 SSR 质量拉满后忘了收回。r.SSR.Quality=2 一条命令验证猜想,帧率回到 60。整个定位五分钟,前提是先测量、别猜——这条纪律第 18 章还要再讲一遍。
注意:带 ECVF_Cheat 标记的控制台变量在 Shipping 构建里会被剥离。Test/Shipping 包下调不了属于正常现象,别在那儿怀疑人生。
Visual Logger:能倒带的黑匣子
有些 bug 的复现率是 5%,盯着屏幕看一百次也抓不到。Visual Logger(VisLog)的思路是飞行记录仪:把 Actor 每帧的位置、速度、自定义事件录下来,事后逐帧回放。它的搭档 Gameplay Debugger 默认用撇号键呼出,是 AI 与导航问题的标配。两个工具的入口在 5.8 的 Tools 菜单下,具体位置与开关以官方文档"Visual Logger"和"Gameplay Debugger"词条为准。
真实排障场景:测试报告"偶发跳不上断桥边的台阶,概率大概十分之一"。这种问题靠猜能调一周。挂上 VisLog 录制,让测试正常玩;复现后倒带逐帧看 CMC 状态——失败那一帧,地面判定的坡度是 45.2°,而 WalkableFloorZ 默认 0.71(约 44.8°)。台阶边缘的法线抖动让落点被判成"陡坡",角色沿坡滑落。修法是把 WalkableFloorZ 调到 0.68——一行参数,bug 绝迹,根本不用动关卡。没有 VisLog,这个问题的典型下场是把台阶削低 10cm,然后在别处埋三个新 bug。
日志分级:发行版里最后的眼睛
UE_LOG 的七个级别 Fatal/Error/Warning/Display/Log/Verbose/VeryVerbose,本质是个信噪比旋钮。开发期 Verbose 随便开;打包版只留 Warning 及以上——日志 IO 在机械硬盘上能抖出帧尖峰。分级配置写在 DefaultEngine.ini 的 [Core.Log] 段,按 Category 独立设级,也可以用启动参数 -LogCmds 临时覆盖。
Category 的命名要趁早立规矩:统一 LogEcho 前缀加系统名(LogEchoMove、LogEchoCombat),禁止往 LogTemp 里倒垃圾。LogTemp 里的日志没有独立开关,三个月后就变成谁也不敢删、谁也不看的噪音墙,真正关键的 Warning 被淹在里面。
真实排障场景:玩家反馈启动后黑屏 8 秒才进主菜单,编辑器里怎么都复现不了——PIE 的 shader 早就编译好了。打包版把 LogShaderCompilers 和 LogStreaming 提到 Display 级,日志里四千多条 shader 同步编译请求排成一列,答案写在脸上:PSO 缓存没预热,首次启动在硬编译。对策是异步预缓存加启动画面遮罩,黑屏压到 1.5 秒。这个 bug 在编辑器里永远不会出现——这是 9.5 必须打包的又一个理由。
# 伪代码:调试绘制统一收口,Shipping 编译期整体剥离
DEBUG_SWITCH = {"Move": True, "AI": False, "Combat": True}
def dbg_point(actor, pos, text="", category="Move", color=RED):
if BUILD_SHIPPING: # 发行版零开销
return
if not DEBUG_SWITCH.get(category): # 按类别开关
return
draw_debug_sphere(pos, radius=12, color=color, life=ONE_FRAME)
if text:
draw_debug_string(pos + UP * 30, text, color)
vislog.record(actor, category, pos, text) # 顺带可回放
# 用法:跳跃落地判定被拒时打点留证
def on_floor_rejected(hit):
angle = degrees(acos(hit.normal.z))
dbg_point(self, hit.point, f"slope={angle:.1f}°", "Move")
log("EchoMove", VERBOSE,
f"floor reject: {angle:.1f}° > limit, z={hit.normal.z:.3f}")封装的意义在于收口:所有调试绘制走同一个函数,Shipping 里编译期整体剥离,运行期零开销;开关按类别独立,VisLog 录制顺带完成。散落在代码各处的裸 DrawDebugSphere,三个月后就是一堆没人敢删的垃圾。
顺手提一件第四兵器:Cheat Manager。角色基类里留一个继承自 UCheatManager 的调试类,写上 God、Fly、TeleportToCheckpoint 这类作弊命令,PIE 和控制台都能直接调。它和上面三件不冲突:前三件回答"为什么",Cheat Manager 负责"快进到出事的地方"——跳关测试时每天能省下以小时计的跑图时间。
9.5 打包:PIE 说的不算,安装包说的才算
PIE(编辑器内运行)是个善意的骗子:编辑器进程替你扛了 2–5ms 开销,资源早已驻留内存,shader 缓存是热的,崩溃有编辑器兜底。在 PIE 里验收竖切,相当于在跑步机上测马拉松。9.4 那个黑屏 8 秒的 bug 就是铁证——编辑器里它不存在。
| 维度 | PIE | 打包版 |
|---|---|---|
| 帧时间 | 含编辑器开销,普遍偏高 2–5ms | 真实 |
| 资源加载 | 大部分已驻留,加载时间失真 | 走 pak,真实 IO 路径 |
| shader | 缓存常是热的 | 首启可能硬编译 |
| 插件/模块 | 全部加载 | 只加载标记进打包的 |
| 崩溃处理 | 编辑器兜底,栈不一定准 | 独立 Crash Reporter |
这张表也是排查指南:凡是"编辑器里好好的、打包版不对"的问题,先在这五行里对号入座,能解决八成的玄学。
:: 回响峡谷 v0.9 · Win64 Development 打包
set UE5="C:\Program Files\Epic Games\UE_5.8"
%UE5%\Engine\Build\BatchFiles\RunUAT.bat BuildCookRun ^
-project="D:\EchoCanyon\EchoCanyon.uproject" ^
-platform=Win64 -clientconfig=Development ^
-build -cook -stage -pak -package ^
-archivedirectory="D:\EchoCanyon\Releases\v0.9"跑完得到一个双击就能启动的目录。打包后过四道关:启动无插件缺失报错;Saved/Logs 下日志无 Error 级记录;主菜单到关卡的加载时间符合预期;帧率达到基线。常见的坑有三个:新加的地图没进 Project Settings 的打包地图列表;插件只在编辑器启用、没标记进打包;软引用资产没被 cook——第三个用 Reference Viewer 查引用链解决。
打包频率本身就是纪律:固定每周一包,雷打不动。打包失败的修复优先级高于一切新功能——打包管线是项目的逃生通道,通道堵着的时候往里搬新家具,是最典型的慢性自杀。这条纪律听起来夸张,做过长周期项目的人都知道它救过多少次命。
性能基线:给卷二留一把尺子
灰盒版是全书性能最健康的一刻,必须趁现在把数字记下来。「回响峡谷」的基线(Development 打包版,RTX 3060 级显卡,1080p,Nanite/Lumen/VSM 全开):Frame 约 4.2ms,Game 1.6ms,Draw 1.1ms,GPU 3.9ms,折合约 240fps。这组数字写进工程根目录的 README,命名 v0.9-baseline。
基线的用法在卷二:每加一层美术——光照、动画、特效、音频——打包重测一次,和基线对账。对账流程固定四步:重测 stat unit 四数;与基线求差值;差值超 1ms 的系统写进对账表,标明负责人和取舍结论;每周评审过一遍对账表。GPU 预算红线画在 12ms:16.6ms 里扣掉 Game 线程和余量,美术随便怎么造,GPU 超线就降级优化,没有例外。预算制度到第 18 章展开,但记账的习惯从今天的基线开始。这套"基线 + 对账"的做法,卷三做「远港 Online」时会原样搬进服务器 tick 预算。
顺手把包体大小也记进基线。灰盒版通常 2GB 上下,其中引擎内容与启动开销占大头,自己的关卡资产不到零头。这个数字现在看着无聊,等卷二美术资产涌进来、包体翻十倍时,你会需要它来回答"体积都涨哪去了"。
9.6 卷一验收清单:进卷二之前,先过这道门
以下每一条都在打包版上验证,PIE 里的勾不算数。全绿,卷二见;任何一项红灯,回炉对应章节,别带着债往前走。
能跑
- [ ] WASD 移动,起步到满速主观无延迟感(实测 ≤150ms 达 90%)
- [ ] 松手停步距离 ≤ 100cm,无滑冰感
- [ ] 相机跟随无穿墙、无甩晕,滞后上限生效
能跳
- [ ] 跳跃顶点 100±10cm,滞空 ≤ 0.8s
- [ ] 断桥缺口(2.6m)10 跳 ≥ 9 成
- [ ] 100cm 台沿 10 跳 ≥ 9 成;150cm 台沿跳不上(动线语法成立)
能看
- [ ] 1080p 下 GPU 帧时间 ≤ 12ms,全程 60fps
- [ ] Nanite/Lumen/VSM 开启,无渲染报错
- [ ] 材质无粉红缺贴图,材质参数集联动生效
能打包
- [ ] Development 包双击启动,无插件/资产缺失报错
- [ ] Saved/Logs 无 Error 级日志
- [ ] 启动到可操控 ≤ 5s,包体大小已记录
能调试
- [ ] 控制台
stat unit/profilegpu可用 - [ ] VisLog 能录制并回放 CMC 状态
- [ ] 日志按 Category 分级,打包版仅 Warning 及以上
工程健康
- [ ] C++ 编译 0 Error,Warning 逐条有结论
- [ ] 移动参数集中在角色基类,蓝图无逻辑硬编码
- [ ] 尺寸表与性能基线已入库,README 可查
这份清单同时是卷一的结业证书和卷二的入场券。把它存成工程的 CHECKLIST.md,每次里程碑照着过一遍——第 18 章和第 28 章你还会见到它的升级版。
红灯项的处理也有章法:凡是"能跑、能跳"两项挂了,回第 5 章和本章 9.3;"能看"挂了,回第 6、7 章查预算;"能打包"挂了,回 9.5 的坑位表;"能调试"挂了,说明 9.4 的封装没做扎实。技术债滚进卷二不会消失,只会在美术资产的重压下利滚利。
小结
竖切是曳光弹,不是样品间:纵向切穿所有系统,每层只切最薄一片,先难看,先便宜。尺寸即玩法:门 120cm、走廊 240cm、跳高 100cm,这些数字在灰盒阶段锁定,因为下游的动画、相机、光照、导航全都按尺寸生产。手感等于延迟加曲线:CMC 的每个参数拧大拧小都有明确的主观后果,调校的目标是象限图上的一个点,不是"越灵敏越好"。调试三件套各司其职:控制台测当下,VisLog 倒带偶发,日志分级守发行版;共同纪律是先测量、别猜。PIE 说的不算,安装包说的才算:打包、记基线、照单验收,这是进卷二前的最后一步。
上手任务
- 灰盒一小时挑战:用 BSP/建模模式搭出「回响峡谷」谷口段——一扇 120cm 门、一段 240cm 走廊、一个 100cm 台沿。验收:尺寸表随关卡提交;角色不加速、不助跑能完成全部动线。
- 手感 A/B 实验:AirControl 分别设 0.05 / 0.25 / 0.5,GravityScale 设 1.0 / 1.6,凑 6 组组合,找 5 个人盲测打分。验收:每组记录跳跃成功率与主观分;最终选定组合写进角色基类 C++ 默认值,并附 50 字选择理由。
- 打包与基线:按 9.5 的命令打出 Development 包,把
stat unit四个数字与包体大小记进 README。验收:安装包 1080p 稳定 60fps;启动到可操控 ≤ 5s;日志无 Error。
下一章
竖切能玩了,但它还丑得理直气壮。卷二第一课,让光进来——《最后生还者》的氛围有七成是光给的,下一章拆开看:三点布光、色温叙事,以及「回响峡谷」三个标杆场景的布光实操。
延伸阅读
- 官方文档:Unreal Engine → Programming → Character Movement Component(CMC 参数全表)
- 官方文档:Unreal Engine → Testing and Debugging → Visual Logger 与 Gameplay Debugger 词条
- 官方文档:Unreal Engine → Packaging and Cooking Games(BuildCookRun 参数)
- 源码:Runtime/Engine/Private/Components/CharacterMovementComponent.cpp(Falling 与 Walking 的求解都在这里)
- GDC Vault:Kyle Pittman《Building a Better Jump》(跳跃曲线的数学调优);Nicolae Berbece《Game Feel: Why Your Death Animation Sucks》(手感系统工程)