Godot 动画变速:一次踩坑讲清 speed_scale、custom_speed 与标记事件

发布时间:2026/8/29 9:10:25
Godot 动画变速:一次踩坑讲清 speed_scale、custom_speed 与标记事件 Godot 动画变速:一次踩坑讲清 speed_scale、custom_speed 与标记事件【免费下载链接】godotGodot Engine – Multi-platform 2D and 3D game engine项目地址: https://gitcode.com/GitHub_Trending/go/godot角色挥剑动画播到一半你调了一次速度,命中判定帧突然对不上;把speed_scale设成 0 想停住,is_playing()却还返回 true。这两个坑的根子都在同一处:Godot 的AnimationPlayer把速度拆成了全局一份、单次一份,而位置推进只认一个乘法结果。读完本文,你能说清speed_scale与custom_speed各自影响范围、反向播放的正确写法、变速后事件为什么漂移,以及该用哪几把工具组合。速度是怎么算进每一帧的推进逻辑在 scene/animation/ 里,每帧对每个正在播放的PlaybackData做一次乘法:void AnimationPlayer::_process_playback_data(PlaybackData cd, double p_delta, ...) { double speed speed_scale * cd.speed_scale; // 全局 × 单次 bool backwards std::signbit(speed); // 负零也算反向 double delta p_started ? 0 : p_delta * speed; double next_pos cd.pos delta; }三个结论直接由这段代码得出:最终速度 speed_scale× 该次播放的speed_scale。speed_scale是节点属性,改它影响之后所有播放;play的custom_speed参数(绑定名就叫custom_speed)只写进当前这一次PlaybackData,动画播完自动失效。负号决定方向,绝对值决定快慢。signbit只看符号位,所以-0.5就是半速倒放。速度为 0 时delta恒为 0,位置不再推进——这就是设 0 等于暂停但is_playing()仍为 true的原因:playing标志只在stop()/播完时才清。想确认当前实际生效的速度的话,用get_playing_speed(),它返回的就是上面那个乘积(未播放时返回 0)。全局变速 vs 单次变速:该改哪个同一个慢放需求,两种路径,影响范围完全不同:方式写法影响范围适用全局$AP.speed_scale 0.5之后所有播放,直到你再改重伤减速、Boss 狂暴这种状态级变速单次$AP.play(hit, -1, 0.5)仅本次播放一次性特效、某段演出单次路径有个容易忽略的细节:参数顺序是play(名字, custom_blend, custom_speed, from_end),-1是混合时长,不是速度。想倒着播一段,用play_backwards(),它就是play(..., -1, true)的封装(scene/animation/animation_player.cpp 第 404 行):# 命中特写:倒放 0.6 秒的受击反馈,不影响后续 walk 速度 $AP.play(hit_stun, -1, -1.0) # 等价于 play_backwards(hit_stun)换路径的判据:如果你发现自己在每帧里反复改speed_scale,说明你要的不是变速而是状态机,继续用全局属性会更省事;只有明确这一次特殊才用custom_speed。变速后事件为什么提前/延后,标记怎么保标记(m marker)在Animation资源里存的是时间线上的绝对秒数,不存比例。播速变 0.5 倍,1.2 秒的hit标记实际在第 2.4 秒(墙钟)才触发——事件相对动画内容的位置没变,变的是相对现实时间的早晚。所以:需要和游戏逻辑同步的事件(受击、攻击判定)绑标记,不要绑await get_tree().create_timer()之类的墙钟计时,后者不随speed_scale缩放。监听animation_finished信号拿播完时机,它是按动画时间轴收尾的,变速后依然准确。真正想要无论多快慢,第 N 帧逻辑必触发的效果,把判定逻辑放进标记对应的 track 里(方法调用轨道),让动画自己驱动,而不是脚本计时。反向播放会让标记从尾向头依次经过,顺序敏感的事件要自己处理方向,或直接避免倒放带逻辑的动画。平滑变速:用 Tween 插值而不是突变直接赋值speed_scale是阶跃,视觉上会有顿挫。用 Tween 对属性插值即可:var tw : create_tween() tw.tween_property($AP, speed_scale, $AP.speed_scale * 0.4, 0.6) tw.set_trans(Tween.TRANS_QUAD).set_ease(Tween.EASE_OUT)注意插值跨 0 会出问题:从 1.0 插到 -1.0 会经过 0,中间那帧动画停住、且方向在过零点翻转。想减速后倒放要分两段:先降到接近 0,再切负值,或直接seek到目标位置后倒放。另外Engine.time_scale是更上层的时间缩放,作用于整个主循环,AnimationPlayer读到的 delta 已经被它压过,所以全局慢动作(子弹时间)优先改time_scale,别动speed_scale——两者相乘,容易双重减速。落地清单状态级变速改speed_scale,一次性变速用play的第三个参数custom_speed,别互相污染。反向播放用play_backwards()或负数custom_speed,不要用seek硬模拟。逻辑事件一律绑动画标记,不绑墙钟 timer;变速后检查一遍触发顺序。停住用pause()/stop(),不要把速度设 0 后还以为is_playing()会变 false。变速用 Tween 平滑过渡,跨 0 要拆两段;全局慢动作改Engine.time_scale。【免费下载链接】godotGodot Engine – Multi-platform 2D and 3D game engine项目地址: https://gitcode.com/GitHub_Trending/go/godot创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考