学习进度
0/9 已勾只保存在这台浏览器;勾满 9 项即视为本题完成。
先把题目说成人话
想象摄影机沿一条弯弯的轨道滑过三个取景点:美术要能拖动轨道改变形状,关掉编辑器再打开,轨道也不能走样;播放时还希望它像沿地面刻度匀速前进,而不是在直处猛冲、弯处磨蹭。这种由控制点拉出平滑路径的方法叫贝塞尔曲线(Bezier);把它做成可靠的游戏引擎工具,还要同时处理保存、撤销、朝向和真实路程。
这是虚构教学故事,不是个人项目经历。
场景:先让问题变得看得见
一台轨道相机要在 4 秒内经过三个构图点。美术拖动端点旁边的拉杆,把两段轨道拗成一个 S 形;现在接缝像折了一下,而且播放条明明均匀前进,相机却在直处猛冲、弯处放慢。关闭编辑器再打开,轨道形状、接缝规则和播放结果还必须保持一致。
先不要给这些部件起名字。这里其实有三件独立的事:拉杆决定路长什么样;接缝决定两段路能否顺着接上;沿路插下的等距离小旗决定相机每秒真正走多远。把三件事混成一个滑块,正是“看起来平滑却走不匀”的来源。
(右脑)画面: 一辆玩具车沿着弯曲铁轨前进。轨道旁原本只有一排刻度牌,弯道里的牌挤得很密,直路上的牌隔得很远;车若每次都从一块牌跳到下一块,就会在弯道慢下来、到了直路又突然窜出去。两段轨道的端头虽然碰在一起,出口若朝错了方向,车经过接缝时还是会猛地甩一下车头。
(左脑)对表: 现在把画面逐项落回实现。每段的两个端点和两只拉手依次记为四个控制点,统一保存在轨道物体的局部空间;曲线函数根据一个无量纲的段内进度返回局部位置,一阶导数返回前进方向。编辑时修改的仍是这同一组序列化数据,运行时先把需要的点和方向变换到同一空间,再求值。另一条缓存按项目世界单位累计相邻样点距离,把“目标路程”反查为“段号和段内进度”;只有项目明确约定 1 Unity unit=1 米时,这个长度才可直接标成米。证据分别来自接缝两侧方向、等距离小旗、瞬时速度和保存重开结果;开闭环、零长度方向和近似误差是必须单独处理的边界。
小明:先形成一个完整猜测
小明先把每段采样数从 32 提到 256。曲线的显示折线确实更细了,但相机仍然忽快忽慢。这次尝试没有白做:它排除了“预览线太粗导致视觉跳动”,也说明增加均匀 t 采样只能减小曲线近似误差,不能把 t 变成路程。
他又把接缝两端的位置重合,断口消失,方向却仍突然改变。证据表明这里只满足位置连续 C0;如果只让两边手柄共线同向,得到的是几何上的切向连续 G1。若还要求相同参数尺度下的一阶导数相等,手柄方向和对应长度关系都要匹配,才是 C1。
小红:把猜测放回现实检查
小红先固定控制点,只改播放方式。编辑结束后,她按小段采样曲线,把相邻样点距离累加成一张“累计路程 → 段号与 t”的查找表。运行时根据速度累计目标路程,用二分查找找到路程所在区间,再在两个样点之间插值得到近似 t。相机改为按路程前进后,速度曲线稳定了;控制点没有变化,因此变化只能来自重参数化。
随后她固定播放逻辑,只拖动一个控制点。若 Scene 视图通过 SerializedObject 与 SerializedProperty 修改,就在 ApplyModifiedProperties 时让 Unity 记录撤销并标记改动;若脚本直接改对象字段,则先 Undo.RecordObject,修改 Prefab 实例属性后再调用 PrefabUtility.RecordPrefabInstancePropertyModifications 记录覆盖,非场景资产按保存路径需要标记为 dirty。两条路径选一条走完整,不把它们当成必须叠加的仪式。撤销、重做、保存并重新打开场景后,控制点仍与画面一致,这才证明“可编辑”不是只在当前一帧里能拖动。
我:用检查结果修正模型
我把实现拆成四层:
- 数据层:可序列化节点数组,明确局部空间、开闭环和手柄约束模式。
- 求值层:三次 Bezier 公式求位置,一阶导数求切线;切线长度接近零时沿用上一帧合法方向,避免朝向突然失效。
- 编辑层:CustomEditor 或 EditorTool 通过 SerializedObject、Handles 和 Unity Undo 修改同一份资产。
- 播放层:缓存弧长表,按路程查 t;控制点改变时重建,而不是每帧重复计算整张表。
弧长表是数值近似,不是精确解析解。采样越密,速度误差通常越小,表也越大;急弯应自适应加密,直段可以稀疏。朝向也不能只拿切线就结束:轨道相机还需要稳定的 up 参考或平行移动框架,否则接近竖直方向时可能翻滚。
本页不能代替本人的真实经历;若把这套方法当作项目案例,还必须换成真实工具的代码、编辑器截图、使用者反馈和失败记录。
(右脑)画面修正: 小明只是把轨道旁的牌插得更多,车走过的路线看起来更细了,忽快忽慢却没有消失;他又把两段轨道的端头碰在一起,车轮不再跨过缺口,车头仍会突然转向。小红改用卷尺沿轨道每隔相同距离插一面旗,车才稳定下来;她还把接缝两侧的朝向调顺,让车能够自然驶过。最后把轨道保存、关闭再打开,车仍沿同一条路行驶,这次修改才算真正留下来。
(左脑)严格对表: 实现顺序是:用三次 Bezier 公式和一阶导数在统一空间求位置与切线;对相邻段分别验 P3=Q0、切线同向以及需要时的一阶导数相等;按误差阈值采样并构建单调的累计长度表。开放路径令 L 为总长、s=clamp(v·elapsed,0,L);本题要求 4 秒走完时取 v=L/4。闭环路径则令 s=(v·elapsed) mod L。运行时二分定位 s 所在区间并插值得到段号与 t,控制点变化时重建缓存。验收条件是速度误差、接缝角度、零切线回退、Undo/Redo 和保存重开均在项目阈值内,而不是“看起来顺”。
老师解惑
- 三次曲线位置为 P(t)=(1-t)³P0+3(1-t)²tP1+3(1-t)t²P2+t³P3。
- 一阶导数给出切线方向;t 均匀只表示参数均匀,不保证走过的距离均匀。
- 弧长表把“走了多远”近似映射回“该取哪个 t”。它的误差必须用实际速度曲线或等距离标记验证。
- Scene 视图若经 SerializedObject 修改,就让 ApplyModifiedProperties 负责撤销记录与脏状态;若直接改字段,则走 Undo.RecordObject,Prefab 实例再记录 property modifications,非场景资产按保存路径处理 dirty。不要把两套编辑路径混成一套固定步骤。
工具分工
- Unity Scene 视图与 Handles:编辑节点、手柄并显示切线、接缝和等距离刻度。
- SerializedObject、Undo、ScriptableObject:负责可靠编辑、撤销与持久化。
- 运行时调试图:同时画 t、累计路程和瞬时速度,用来验证“看起来顺”是否真的接近匀速。
- bezier-ik Lab:观察控制点、求值、切线与弧长采样之间的关系。
记忆钩子
Bezier 负责画路,Unity 编辑链负责保存路,弧长表才负责按真实路程走路。
这是教学场景,不是目标项目的真实捕获或个人经历。
下一步:验证这条因果
控制手柄、t 采样、弧长近似和等距旗标如何组成一条可编辑 Unity 路径证据。
教学边界:网页不会写入 Unity 资产;完整工具仍要验证节点增删、序列化、Scene View、Undo/Redo 与三维朝向。