godot-animation-player by thedivergentai/gd-agentic-skills
npx skills add https://github.com/thedivergentai/gd-agentic-skills --skill godot-animation-playerGodot 基于时间线的关键帧动画系统专家指南。
Animation.CALL_MODE_CONTINUOUS — 这会在关键帧期间每帧都调用方法。使用 CALL_MODE_DISCRETE(只调用一次)以避免逻辑泛滥 [13, 77]。material.albedo_color 会创建嵌入式资源,导致文件体积膨胀。应将材质存储在变量中或改用 instance uniform [14]。animation_finished — 该信号在动画循环时不会触发。应使用 animation_looped 或在 _process() 中检查 。广告位招租
在这里展示您的产品或服务
触达数万 AI 开发者,精准高效
current_animationupdate=true 的情况下使用 seek() 进行同帧逻辑处理 — 如果需要属性立即更新(例如,用于物理检查),必须将 update 参数设置为 true。active — 如果实体在屏幕外且其动画纯粹是视觉性的(无逻辑轨道),请设置 active = false 以节省大量 CPU/GPU 处理资源 [317]。AnimationLibrary 内容 — 这会导致立即崩溃或未定义的变换状态。在交换库之前,请停止播放器或等待 finished 信号。speed_scale 进行长期同步 — 对于多人游戏或节奏游戏,应使用 seek() 配合全局时间参考,以防止帧漂移。强制要求:在实现相应模式之前,请阅读对应的脚本。
使用 CALL_MODE_DISCRETE 进行高精度碰撞盒和状态管理的专家逻辑触发器。
在单个 AnimationPlayer 上管理多个 AnimationLibrary 资源(姿态、武器)。
与时间线关键帧同步动画化着色器 Uniform(例如,溶解、发光)。
运行时修改现有轨道(例如,调整跳跃高度),而无需创建新的 Animation 资源。
跨复杂多轨道节点设置的强制、即时状态重置模式。
在运行时从贝塞尔曲线轨道提取数值数据,以驱动程序化 VFX 或物理效果。
性能优化:使用 VisibleOnScreenNotifier 来禁用 AnimationPlayer.active。
使用 get_root_motion_position 进行专家级 3D CharacterBody 运动提取。
完全通过动画时间线轨道管理的角色自定义(装备/插槽)。
使用 TYPE_AUDIO 轨道进行完美定时的音效播放,并控制音量、音调和起始偏移。
# 动画化任意属性:位置、颜色、音量、自定义变量
var anim := Animation.new()
anim.length = 2.0
# 位置轨道
var pos_track := anim.add_track(Animation.TYPE_VALUE)
anim.track_set_path(pos_track, ".:position")
anim.track_insert_key(pos_track, 0.0, Vector2(0, 0))
anim.track_insert_key(pos_track, 1.0, Vector2(100, 0))
anim.track_set_interpolation_type(pos_track, Animation.INTERPOLATION_CUBIC)
# 颜色轨道 (modulate)
var color_track := anim.add_track(Animation.TYPE_VALUE)
anim.track_set_path(color_track, "Sprite2D:modulate")
anim.track_insert_key(color_track, 0.0, Color.WHITE)
anim.track_insert_key(color_track, 2.0, Color.TRANSPARENT)
$AnimationPlayer.add_animation("fade_move", anim)
$AnimationPlayer.play("fade_move")
# 在特定时间戳调用函数
var method_track := anim.add_track(Animation.TYPE_METHOD)
anim.track_set_path(method_track, ".") # 节点路径
# 插入方法调用
anim.track_insert_key(method_track, 0.5, {
"method": "spawn_particle",
"args": [Vector2(50, 50)]
})
anim.track_insert_key(method_track, 1.5, {
"method": "play_sound",
"args": ["res://sounds/explosion.ogg"]
})
# 关键:将调用模式设置为 DISCRETE
anim.track_set_call_mode(method_track, Animation.CALL_MODE_DISCRETE)
# 目标节点上必须存在这些方法:
func spawn_particle(pos: Vector2) -> void:
# 在指定位置生成粒子
pass
func play_sound(sound_path: String) -> void:
$AudioStreamPlayer.stream = load(sound_path)
$AudioStreamPlayer.play()
# 将音频与动画同步
var audio_track := anim.add_track(Animation.TYPE_AUDIO)
anim.track_set_path(audio_track, "AudioStreamPlayer")
# 插入音频播放
var audio_stream := load("res://sounds/footstep.ogg")
anim.audio_track_insert_key(audio_track, 0.3, audio_stream)
anim.audio_track_insert_key(audio_track, 0.6, audio_stream) # 第二个脚步声
# 为特定关键帧设置音量
anim.audio_track_set_key_volume(audio_track, 0, 1.0) # 最大音量
anim.audio_track_set_key_volume(audio_track, 1, 0.7) # 较低音量
# 用于平滑、自定义的插值曲线
var bezier_track := anim.add_track(Animation.TYPE_BEZIER)
anim.track_set_path(bezier_track, ".:custom_value")
# 插入带控制柄的贝塞尔点
anim.bezier_track_insert_key(bezier_track, 0.0, 0.0)
anim.bezier_track_insert_key(bezier_track, 1.0, 100.0,
Vector2(0.5, 0), # 输入控制柄
Vector2(-0.5, 0)) # 输出控制柄
# 在 _process 中读取值
func _process(delta: float) -> void:
var value := $AnimationPlayer.get_bezier_value("custom_value")
# 将值用于自定义效果
# 角色在动画中行走,但世界中的位置没有改变
# 动画修改的是骨骼,而不是 CharacterBody3D 根节点
# 场景结构:
# CharacterBody3D (根节点)
# ├─ MeshInstance3D
# │ └─ Skeleton3D
# └─ AnimationPlayer
# AnimationPlayer 设置:
@onready var anim_player: AnimationPlayer = $AnimationPlayer
func _ready() -> void:
# 启用根运动(指向根骨骼)
anim_player.root_motion_track = NodePath("MeshInstance3D/Skeleton3D:root")
anim_player.play("walk")
func _physics_process(delta: float) -> void:
# 提取根运动
var root_motion_pos := anim_player.get_root_motion_position()
var root_motion_rot := anim_player.get_root_motion_rotation()
var root_motion_scale := anim_player.get_root_motion_scale()
# 应用到 CharacterBody3D
var transform := Transform3D(basis.rotated(basis.y, root_motion_rot.y), Vector3.ZERO)
transform.origin = root_motion_pos
global_transform *= transform
# 根据根运动计算速度
velocity = root_motion_pos / delta
move_and_slide()
# 按顺序播放动画
@onready var anim: AnimationPlayer = $AnimationPlayer
func play_attack_combo() -> void:
anim.play("attack_1")
await anim.animation_finished
anim.play("attack_2")
await anim.animation_finished
anim.play("idle")
# 或者使用队列:
func play_with_queue() -> void:
anim.play("attack_1")
anim.queue("attack_2")
anim.queue("idle") # 在 attack_2 后自动播放
# 动画之间的平滑过渡
anim.play("walk")
# 从 walk → run 进行 0.5 秒的混合
anim.play("run", -1, 1.0, 0.5) # custom_blend = 0.5
# 或者设置默认混合时间
anim.set_default_blend_time(0.3) # 所有过渡使用 0.3 秒
anim.play("idle")
# 将精灵位置从 (0,0) 动画化到 (100, 0)
# 切换场景后,精灵仍停留在 (100, 0)!
# 创建包含默认值的 RESET 动画
var reset_anim := Animation.new()
reset_anim.length = 0.01 # 非常短
var track := reset_anim.add_track(Animation.TYPE_VALUE)
reset_anim.track_set_path(track, "Sprite2D:position")
reset_anim.track_insert_key(track, 0.0, Vector2(0, 0)) # 默认位置
track = reset_anim.add_track(Animation.TYPE_VALUE)
reset_anim.track_set_path(track, "Sprite2D:modulate")
reset_anim.track_insert_key(track, 0.0, Color.WHITE) # 默认颜色
anim_player.add_animation("RESET", reset_anim)
# 如果 AnimationPlayer 设置中启用了 "Reset on Save",则场景加载时 AnimationPlayer 会自动播放 RESET
# 通过程序创建弹跳动画
func create_bounce_animation() -> void:
var anim := Animation.new()
anim.length = 1.0
anim.loop_mode = Animation.LOOP_LINEAR
# 位置轨道 (Y 轴弹跳)
var track := anim.add_track(Animation.TYPE_VALUE)
anim.track_set_path(track, ".:position:y")
# 生成正弦波关键帧
for i in range(10):
var time := float(i) / 9.0 # 0.0 到 1.0
var value := sin(time * TAU) * 50.0 # 弹跳高度 50 像素
anim.track_insert_key(track, time, value)
anim.track_set_interpolation_type(track, Animation.INTERPOLATION_CUBIC)
$AnimationPlayer.add_animation("bounce", anim)
$AnimationPlayer.play("bounce")
# 反向播放动画(适用于关门等场景)
anim.play("door_open", -1, -1.0) # speed = -1.0 = 反向
# 暂停并反向播放
anim.pause()
anim.play("current_animation", -1, -1.0, false) # from_end = false
# 在特定帧发出自定义信号
func _ready() -> void:
$AnimationPlayer.animation_finished.connect(_on_anim_finished)
func _on_anim_finished(anim_name: String) -> void:
match anim_name:
"attack":
deal_damage()
"die":
queue_free()
# 跳转到动画的 50% 处
anim.seek(anim.current_animation_length * 0.5)
# 在动画中擦洗(过场动画编辑器)
func _input(event: InputEvent) -> void:
if event is InputEventMouseMotion and scrubbing:
var normalized_pos := event.position.x / get_viewport_rect().size.x
anim.seek(anim.current_animation_length * normalized_pos)
extends VisibleOnScreenNotifier2D
func _ready() -> void:
screen_exited.connect(_on_screen_exited)
screen_entered.connect(_on_screen_entered)
func _on_screen_exited() -> void:
$AnimationPlayer.pause()
func _on_screen_entered() -> void:
$AnimationPlayer.play()
# 问题:忘记将动画添加到播放器
# 解决方案:检查动画是否存在
if anim.has_animation("walk"):
anim.play("walk")
else:
push_error("未找到动画 'walk'!")
# 更好的做法:使用常量
const ANIM_WALK = "walk"
const ANIM_IDLE = "idle"
if anim.has_animation(ANIM_WALK):
anim.play(ANIM_WALK)
# 问题:调用模式是 CONTINUOUS
# 解决方案:设置为 DISCRETE
var method_track_idx := anim.find_track(".:method_name", Animation.TYPE_METHOD)
anim.track_set_call_mode(method_track_idx, Animation.CALL_MODE_DISCRETE)
| 功能 | AnimationPlayer | Tween |
|---|---|---|
| 时间线编辑 | ✅ 可视化编辑器 | ❌ 仅代码 |
| 多属性 | ✅ 多轨道 | ❌ 单一属性 |
| 可重用性 | ✅ 保存为资源 | ❌ 每次创建 |
| 动态运行时 | ❌ 静态 | ✅ 完全动态 |
| 方法调用 | ✅ 方法轨道 | ❌ 使用回调 |
| 性能 | ✅ 优化 | ❌ 稍慢 |
使用 AnimationPlayer 适用于:过场动画、角色动画、复杂 UI 使用 Tween 适用于:简单的运行时效果、一次性过渡
每周安装量
78
仓库
GitHub 星标数
59
首次出现
2026年2月10日
安全审计
安装于
opencode77
gemini-cli75
codex75
kimi-cli74
amp74
github-copilot74
Expert guidance for Godot's timeline-based keyframe animation system.
Animation.CALL_MODE_CONTINUOUS for function calls — This calls the method EVERY frame during the keyframe. Use CALL_MODE_DISCRETE (calls once) to avoid logic spam [13, 77].material.albedo_color creates embedded resources that bloat file size. Store the material in a variable or use instance uniform instead [14].animation_finished for looping animations — This signal doesn't fire for looped animations. Use animation_looped or check current_animation in _process().seek() without update=true for same-frame logic — If you need properties to update immediately (e.g., for physics checks), you MUST set the update parameter to true.active — If an entity is off-screen and its animation is purely visual (no logic tracks), set active = false to save significant CPU/GPU processing [317].AnimationLibrary content while it is playing — This causes immediate crashes or undefined transform states. Stop the player or wait for the finished signal before swapping libraries.speed_scale for long-term synchronization — For multiplayer or rhythm games, use seek() with a global time reference to prevent frame-drift.MANDATORY : Read the appropriate script before implementing the corresponding pattern.
Expert logic triggers using CALL_MODE_DISCRETE for high-precision hitbox and state management.
Managing multiple AnimationLibrary resources (Stances, Weapons) on a single AnimationPlayer.
Animating shader uniforms (e.g., dissolve, glow) in sync with timeline keyframes.
Runtime modification of existing tracks (e.g., jump height tweaking) without creating new Animation resources.
Pattern for forced, immediate state resets across complex multi-track node setups.
Extracting numeric data from Bezier tracks at runtime to drive procedural VFX or physics.
Performance optimization: using VisibleOnScreenNotifier to disable AnimationPlayer.active.
Expert 3D CharacterBody motion extraction using get_root_motion_position.
Character customization (equipment/slots) managed entirely through Animation timeline tracks.
Perfectly timed SFX using TYPE_AUDIO tracks with volume, pitch, and start-offset control.
# Animate ANY property: position, color, volume, custom variables
var anim := Animation.new()
anim.length = 2.0
# Position track
var pos_track := anim.add_track(Animation.TYPE_VALUE)
anim.track_set_path(pos_track, ".:position")
anim.track_insert_key(pos_track, 0.0, Vector2(0, 0))
anim.track_insert_key(pos_track, 1.0, Vector2(100, 0))
anim.track_set_interpolation_type(pos_track, Animation.INTERPOLATION_CUBIC)
# Color track (modulate)
var color_track := anim.add_track(Animation.TYPE_VALUE)
anim.track_set_path(color_track, "Sprite2D:modulate")
anim.track_insert_key(color_track, 0.0, Color.WHITE)
anim.track_insert_key(color_track, 2.0, Color.TRANSPARENT)
$AnimationPlayer.add_animation("fade_move", anim)
$AnimationPlayer.play("fade_move")
# Call functions at specific timestamps
var method_track := anim.add_track(Animation.TYPE_METHOD)
anim.track_set_path(method_track, ".") # Path to node
# Insert method calls
anim.track_insert_key(method_track, 0.5, {
"method": "spawn_particle",
"args": [Vector2(50, 50)]
})
anim.track_insert_key(method_track, 1.5, {
"method": "play_sound",
"args": ["res://sounds/explosion.ogg"]
})
# CRITICAL: Set call mode to DISCRETE
anim.track_set_call_mode(method_track, Animation.CALL_MODE_DISCRETE)
# Methods must exist on target node:
func spawn_particle(pos: Vector2) -> void:
# Spawn particle at position
pass
func play_sound(sound_path: String) -> void:
$AudioStreamPlayer.stream = load(sound_path)
$AudioStreamPlayer.play()
# Synchronize audio with animation
var audio_track := anim.add_track(Animation.TYPE_AUDIO)
anim.track_set_path(audio_track, "AudioStreamPlayer")
# Insert audio playback
var audio_stream := load("res://sounds/footstep.ogg")
anim.audio_track_insert_key(audio_track, 0.3, audio_stream)
anim.audio_track_insert_key(audio_track, 0.6, audio_stream) # Second footstep
# Set volume for specific key
anim.audio_track_set_key_volume(audio_track, 0, 1.0) # Full volume
anim.audio_track_set_key_volume(audio_track, 1, 0.7) # Quieter
# For smooth, custom interpolation curves
var bezier_track := anim.add_track(Animation.TYPE_BEZIER)
anim.track_set_path(bezier_track, ".:custom_value")
# Insert bezier points with handles
anim.bezier_track_insert_key(bezier_track, 0.0, 0.0)
anim.bezier_track_insert_key(bezier_track, 1.0, 100.0,
Vector2(0.5, 0), # In-handle
Vector2(-0.5, 0)) # Out-handle
# Read value in _process
func _process(delta: float) -> void:
var value := $AnimationPlayer.get_bezier_value("custom_value")
# Use value for custom effects
# Character walks in animation, but position doesn't change in world
# Animation modifies Skeleton bone, not CharacterBody3D root
# Scene structure:
# CharacterBody3D (root)
# ├─ MeshInstance3D
# │ └─ Skeleton3D
# └─ AnimationPlayer
# AnimationPlayer setup:
@onready var anim_player: AnimationPlayer = $AnimationPlayer
func _ready() -> void:
# Enable root motion (point to root bone)
anim_player.root_motion_track = NodePath("MeshInstance3D/Skeleton3D:root")
anim_player.play("walk")
func _physics_process(delta: float) -> void:
# Extract root motion
var root_motion_pos := anim_player.get_root_motion_position()
var root_motion_rot := anim_player.get_root_motion_rotation()
var root_motion_scale := anim_player.get_root_motion_scale()
# Apply to CharacterBody3D
var transform := Transform3D(basis.rotated(basis.y, root_motion_rot.y), Vector3.ZERO)
transform.origin = root_motion_pos
global_transform *= transform
# Velocity from root motion
velocity = root_motion_pos / delta
move_and_slide()
# Play animations in sequence
@onready var anim: AnimationPlayer = $AnimationPlayer
func play_attack_combo() -> void:
anim.play("attack_1")
await anim.animation_finished
anim.play("attack_2")
await anim.animation_finished
anim.play("idle")
# Or use queue:
func play_with_queue() -> void:
anim.play("attack_1")
anim.queue("attack_2")
anim.queue("idle") # Auto-plays after attack_2
# Smooth transitions between animations
anim.play("walk")
# 0.5s blend from walk → run
anim.play("run", -1, 1.0, 0.5) # custom_blend = 0.5
# Or set default blend
anim.set_default_blend_time(0.3) # 0.3s for all transitions
anim.play("idle")
# Animate sprite position from (0,0) → (100, 0)
# Change scene, sprite stays at (100, 0)!
# Create RESET animation with default values
var reset_anim := Animation.new()
reset_anim.length = 0.01 # Very short
var track := reset_anim.add_track(Animation.TYPE_VALUE)
reset_anim.track_set_path(track, "Sprite2D:position")
reset_anim.track_insert_key(track, 0.0, Vector2(0, 0)) # Default position
track = reset_anim.add_track(Animation.TYPE_VALUE)
reset_anim.track_set_path(track, "Sprite2D:modulate")
reset_anim.track_insert_key(track, 0.0, Color.WHITE) # Default color
anim_player.add_animation("RESET", reset_anim)
# AnimationPlayer automatically plays RESET when scene loads
# IF "Reset on Save" is enabled in AnimationPlayer settings
# Create bounce animation programmatically
func create_bounce_animation() -> void:
var anim := Animation.new()
anim.length = 1.0
anim.loop_mode = Animation.LOOP_LINEAR
# Position track (Y bounce)
var track := anim.add_track(Animation.TYPE_VALUE)
anim.track_set_path(track, ".:position:y")
# Generate sine wave keyframes
for i in range(10):
var time := float(i) / 9.0 # 0.0 to 1.0
var value := sin(time * TAU) * 50.0 # Bounce height 50px
anim.track_insert_key(track, time, value)
anim.track_set_interpolation_type(track, Animation.INTERPOLATION_CUBIC)
$AnimationPlayer.add_animation("bounce", anim)
$AnimationPlayer.play("bounce")
# Play animation in reverse (useful for closing doors, etc.)
anim.play("door_open", -1, -1.0) # speed = -1.0 = reverse
# Pause and reverse
anim.pause()
anim.play("current_animation", -1, -1.0, false) # from_end = false
# Emit custom signal at specific frame
func _ready() -> void:
$AnimationPlayer.animation_finished.connect(_on_anim_finished)
func _on_anim_finished(anim_name: String) -> void:
match anim_name:
"attack":
deal_damage()
"die":
queue_free()
# Jump to 50% through animation
anim.seek(anim.current_animation_length * 0.5)
# Scrub through animation (cutscene editor)
func _input(event: InputEvent) -> void:
if event is InputEventMouseMotion and scrubbing:
var normalized_pos := event.position.x / get_viewport_rect().size.x
anim.seek(anim.current_animation_length * normalized_pos)
extends VisibleOnScreenNotifier2D
func _ready() -> void:
screen_exited.connect(_on_screen_exited)
screen_entered.connect(_on_screen_entered)
func _on_screen_exited() -> void:
$AnimationPlayer.pause()
func _on_screen_entered() -> void:
$AnimationPlayer.play()
# Problem: Forgot to add animation to player
# Solution: Check if animation exists
if anim.has_animation("walk"):
anim.play("walk")
else:
push_error("Animation 'walk' not found!")
# Better: Use constants
const ANIM_WALK = "walk"
const ANIM_IDLE = "idle"
if anim.has_animation(ANIM_WALK):
anim.play(ANIM_WALK)
# Problem: Call mode is CONTINUOUS
# Solution: Set to DISCRETE
var method_track_idx := anim.find_track(".:method_name", Animation.TYPE_METHOD)
anim.track_set_call_mode(method_track_idx, Animation.CALL_MODE_DISCRETE)
| Feature | AnimationPlayer | Tween |
|---|---|---|
| Timeline editing | ✅ Visual editor | ❌ Code only |
| Multiple properties | ✅ Many tracks | ❌ One property |
| Reusable | ✅ Save as resource | ❌ Create each time |
| Dynamic runtime | ❌ Static | ✅ Fully dynamic |
| Method calls | ✅ Method tracks | ❌ Use callbacks |
| Performance | ✅ Optimized | ❌ Slightly slower |
Use AnimationPlayer for : Cutscenes, character animations, complex UI Use Tween for : Simple runtime effects, one-off transitions
Weekly Installs
78
Repository
GitHub Stars
59
First Seen
Feb 10, 2026
Security Audits
Gen Agent Trust HubPassSocketPassSnykPass
Installed on
opencode77
gemini-cli75
codex75
kimi-cli74
amp74
github-copilot74
Swift Actor 线程安全持久化:构建离线优先应用的编译器强制安全数据层
1,700 周安装
LobeHub Modal命令式API指南:React模态框实现与最佳实践
544 周安装
Accord Project 智能合同模板生成器 - 自动化创建法律合同与协议模板
558 周安装
GitHub Release自动化发布工具 - 安全检查与版本发布工作流
546 周安装
ess-dev技能:MCP Hub代理技能目录,每周安装量69+,支持多平台开发工具
69 周安装
remind-me 自然语言提醒工具 - 使用 cron 和 Markdown 设置自动化提醒
542 周安装
GitHub PR讲解视频生成工具 - 为拉取请求自动创建代码变更讲解视频
69 周安装