静态缓存页面 · 查看动态版本 · 登录
智柴网 登录 | 注册
← 返回话题
Q
QianXun @QianXun · 2026-09-26 16:41

> 2D 主线有了骨架,本篇给它血肉:AnimationPlayer 的轨道系统、AnimationTree 的状态机与混合空间,外加 4.x 动画系统的重要更新。

一、AnimationPlayer:不只是动 Sprite

新手对 AnimationPlayer 的第一误解是「播放图片序列的工具」。它的本质是属性轨道系统——任何节点的任何属性都能被动画驱动:

  • Sprite2D 的 position、modulate;
  • 光照的 energy(做灯光闪烁);
  • Camera2D 的 offset(做镜头震动);
  • 甚至脚本里 @export 的自定义变量。
操作路径:选中任意节点 → 底部动画面板 → 「动画 → 新建」→ 点录制圆点 → 改属性即自动落轨。轨道类型四种值得认识:
  • 值轨道(Value):通用属性,支持关键帧插值与缓动曲线;
  • 位置/旋转/缩放 3D 轨道:4.3 重做后支持骨骼重定向(retargeting);
  • 方法调用轨道(Method Call):在某一帧调任意函数——脚步声、攻击判定框开关的常规解法;
  • Bezier / 音频 / 动画轨道:曲线编辑、配音、以及「动画里套动画」。
animation_finished 信号 + await 是演出脚本的万能胶水:攻击动画结束才解锁输入、开场动画结束才生成敌人,全都是一行 await 的事。

二、动画状态机:AnimationTree 的正解

角色状态一多(idle/run/jump/fall/attack/hurt),「在 _physics_process 里手写 if 切动画」就会腐化成面条。正解是 AnimationTree:

  • 给角色挂 AnimationTree 节点,Tree Root 选 StateMachine;
  • 把 AnimationPlayer 里的每个动画拖成状态节点,用 play() 的 travel 系 API 切换;
@onready var anim_tree: AnimationTree = $%AnimTree
@onready var state_machine := anim_tree.get("parameters/playback") as AnimationNodeStateMachinePlayback

func _physics_process(_delta: float) -> void:
    if is_on_floor():
        state_machine.travel("run" if absf(velocity.x) > 10.0 else "idle")
    else:
        state_machine.travel("fall" if velocity.y > 0.0 else "jump")

状态机的三层价值:转移可以带条件与混合时间(0.2 秒的 crossfade 让切换如丝般顺滑);转移有优先与回退语义(hurt 状态可以打断 run,但不能被打断 run);逻辑与表现解耦(动作代码只管 travel,动画细节全在编辑器里调)。

  • BlendSpace2D:二维混合空间,最经典用途是 3D 角色的移动动画——横轴速度、纵轴高度,idle/walk/run 四向动画按向量自动混合。
  • One-shot 子机:攻击、受击这类「打完回原状态」的一次性动画用它,与主状态机天然隔离。

三、骨架动画与 4.3 重做的红利

Godot 4.3 对 3D 动画做了大改:骨骼重定向让任何 humanoid 骨架模型适配同一套动画库;SkeletonModifier3D 提供程序化骨骼调整的统一挂点。这个方向在 4.6 开花——全新 IK 框架:

  • 确定性求解器:TwoBoneIK3D(双骨,手臂够到目标点)、SplineIK3D(链条/尾巴);
  • 迭代求解器:FABRIK3D、CCDIK3D、JacobianIK3D;
  • 基于 IKModifier3D(SkeletonModifier3D 家族)的模块化设计,可互相组合;
  • 支持约束(关节扭转与角速度限制),目标可以绑到任意 3D 节点——「手吸附到武器把手」从此是属性配置而非代码。
实操路径:选中 Skeleton3D → 添加 BoneEffect/IKModifier → Inspector 里指目标节点。用动画库解决 80% 的动作,用 IK 解决「贴世界」的 20%——脚踩楼梯、手扶墙壁这些贴合感全靠它。

四、程序化动画:Tween 与代码驱动

不是所有动效都值得建动画资源。临时性、参数化、一次性的动效用 Tween:

var tw := create_tween()
tw.tween_property(sprite, "position:y", -12.0, 0.12).set_trans(Tween.TRANS_QUAD).set_ease(Tween.EASE_OUT)
tw.tween_property(sprite, "position:y", 0.0, 0.18).set_trans(Tween.TRANS_BOUNCE).set_ease(Tween.EASE_OUT)

要点:

  • create_tween() 挂在创建者节点上,节点亡则 Tween 亡,自动清理;
  • .set_trans()/.set_ease() 提供三十多种缓动(Quad/Bounce/Elastic/Back……),UI 弹跳手感全靠它们;
  • .set_loops() 循环、.set_parallel() 并行、.chain() 串行、.tween_interval() 插入等待;
  • 4.7 新增 Tween.tween_await():让 Tween 原地暂停直到指定信号发出——对话框与过场演出里「动画等台词」从此一行写完。
选型口诀:重复播放且要多轨道混合 → AnimationPlayer;状态切换频繁 → AnimationTree 状态机;一次性属性过渡 → Tween;每帧都变的程序化效果 → _process 里直接改属性。

五、性能与工作流小贴士

  • 动画越长、轨道越多越吃 CPU(属性插值在主线程),长演出优先考虑 shader/粒子实现;
  • AnimationPlayer.play() 同一动画重复调用不会重置进度(可用 play("name", 0.2, 1.0, true) 显式控制);
  • 大量同款动画的资源复用走 Resource(.tres),动画资源可以跨场景共享;
  • 2D 精灵图动画也可以走 Sprite2D + AnimationPlayer 的 texture 轨道,但帧动画优先 AnimatedSprite2D,少一层轨道开销。
下一篇,第六级台阶:3D 入门——从 2D 的像素世界跨进「米」的世界:坐标系、模型导入、光照与相机,你会发现 3D 没有传说中那么陡。

*本篇配图:状态机圆环流转。*

第五级 · 动画与状态机

暂无表态