godot-animation-player
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseAnimationPlayer
AnimationPlayer
Expert guidance for Godot's timeline-based keyframe animation system.
Godot基于时间轴的关键帧动画系统专家指南。
NEVER Do
绝对不要做的事
- NEVER forget RESET tracks — Without a RESET track, animated properties don't restore to initial values when changing scenes. Create RESET animation with all default states.
- NEVER use Animation.CALL_MODE_CONTINUOUS for function calls — Calls method EVERY frame during keyframe. Use CALL_MODE_DISCRETE (calls once). Continuous causes spam.
- NEVER animate resource properties directly — Animating creates embedded resources, bloating file size. Store material in variable, animate variable's properties.
material.albedo_color - NEVER use animation_finished for looping animations — Signal doesn't fire for looped animations. Use or check
animation_loopedin _process().current_animation - NEVER hardcode animation names as strings everywhere — Use constants or enums. Typos cause silent failures.
- 绝对不要忘记RESET轨道 — 没有RESET轨道,切换场景时动画属性不会恢复到初始值。创建包含所有默认状态的RESET动画。
- 绝对不要对函数调用使用Animation.CALL_MODE_CONTINUOUS — 该模式会在关键帧期间每帧调用方法。请使用CALL_MODE_DISCRETE(仅调用一次)。Continuous模式会导致频繁调用。
- 绝对不要直接动画化资源属性 — 动画化会创建嵌入资源,导致文件体积膨胀。将材质存储在变量中,动画化变量的属性。
material.albedo_color - 绝对不要对循环动画使用animation_finished信号 — 循环动画不会触发该信号。请使用信号或在_process()中检查
animation_looped。current_animation - 绝对不要在所有地方硬编码动画名称为字符串 — 使用常量或枚举。拼写错误会导致静默失败。
Available Scripts
可用脚本
MANDATORY: Read the appropriate script before implementing the corresponding pattern.
强制要求:在实现对应模式前,请阅读相应脚本。
audio_sync_tracks.gd
audio_sync_tracks.gd
Sub-frame audio synchronization via Animation.TYPE_AUDIO tracks. Footstep setup with automatic blend handling for cross-fades.
通过Animation.TYPE_AUDIO轨道实现子帧音频同步。脚步音效设置,自动处理交叉淡入淡出的混合。
programmatic_anim.gd
programmatic_anim.gd
Procedural animation generation: creates Animation resources via code with keyframes, easing, and transition curves for dynamic runtime animations.
程序化动画生成:通过代码创建包含关键帧、缓动和过渡曲线的Animation资源,用于动态运行时动画。
Track Types Deep Dive
轨道类型深入解析
Value Tracks (Property Animation)
Value轨道(属性动画)
gdscript
undefinedgdscript
undefinedAnimate ANY property: position, color, volume, custom variables
Animate ANY property: position, color, volume, custom variables
var anim := Animation.new()
anim.length = 2.0
var anim := Animation.new()
anim.length = 2.0
Position track
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)
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)
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")
undefinedvar 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")
undefinedMethod Tracks (Function Calls)
Method轨道(函数调用)
gdscript
undefinedgdscript
undefinedCall functions at specific timestamps
Call functions at specific timestamps
var method_track := anim.add_track(Animation.TYPE_METHOD)
anim.track_set_path(method_track, ".") # Path to node
var method_track := anim.add_track(Animation.TYPE_METHOD)
anim.track_set_path(method_track, ".") # Path to node
Insert method calls
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"]
})
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
CRITICAL: Set call mode to DISCRETE
anim.track_set_call_mode(method_track, Animation.CALL_MODE_DISCRETE)
anim.track_set_call_mode(method_track, Animation.CALL_MODE_DISCRETE)
Methods must exist on target node:
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()
undefinedfunc spawn_particle(pos: Vector2) -> void:
# Spawn particle at position
pass
func play_sound(sound_path: String) -> void:
$AudioStreamPlayer.stream = load(sound_path)
$AudioStreamPlayer.play()
undefinedAudio Tracks
Audio轨道
gdscript
undefinedgdscript
undefinedSynchronize audio with animation
Synchronize audio with animation
var audio_track := anim.add_track(Animation.TYPE_AUDIO)
anim.track_set_path(audio_track, "AudioStreamPlayer")
var audio_track := anim.add_track(Animation.TYPE_AUDIO)
anim.track_set_path(audio_track, "AudioStreamPlayer")
Insert audio playback
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
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
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
undefinedanim.audio_track_set_key_volume(audio_track, 0, 1.0) # Full volume
anim.audio_track_set_key_volume(audio_track, 1, 0.7) # Quieter
undefinedBezier Tracks (Custom Curves)
Bezier轨道(自定义曲线)
gdscript
undefinedgdscript
undefinedFor smooth, custom interpolation curves
For smooth, custom interpolation curves
var bezier_track := anim.add_track(Animation.TYPE_BEZIER)
anim.track_set_path(bezier_track, ".:custom_value")
var bezier_track := anim.add_track(Animation.TYPE_BEZIER)
anim.track_set_path(bezier_track, ".:custom_value")
Insert bezier points with handles
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
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
Read value in _process
func _process(delta: float) -> void:
var value := $AnimationPlayer.get_bezier_value("custom_value")
# Use value for custom effects
---func _process(delta: float) -> void:
var value := $AnimationPlayer.get_bezier_value("custom_value")
# Use value for custom effects
---Root Motion Extraction
根运动提取
Problem: Animated Movement Disconnected from Physics
问题:动画移动与物理系统脱节
gdscript
undefinedgdscript
undefinedCharacter walks in animation, but position doesn't change in world
Character walks in animation, but position doesn't change in world
Animation modifies Skeleton bone, not CharacterBody3D root
Animation modifies Skeleton bone, not CharacterBody3D root
undefinedundefinedSolution: Root Motion
解决方案:根运动
gdscript
undefinedgdscript
undefinedScene structure:
Scene structure:
CharacterBody3D (root)
CharacterBody3D (root)
├─ MeshInstance3D
├─ MeshInstance3D
│ └─ Skeleton3D
│ └─ Skeleton3D
└─ AnimationPlayer
└─ AnimationPlayer
AnimationPlayer setup:
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()
---@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()
---Animation Sequences & Queueing
动画序列与队列
Chaining Animations
链式动画
gdscript
undefinedgdscript
undefinedPlay animations in sequence
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")
@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:
Or use queue:
func play_with_queue() -> void:
anim.play("attack_1")
anim.queue("attack_2")
anim.queue("idle") # Auto-plays after attack_2
undefinedfunc play_with_queue() -> void:
anim.play("attack_1")
anim.queue("attack_2")
anim.queue("idle") # Auto-plays after attack_2
undefinedBlend Times
混合时间
gdscript
undefinedgdscript
undefinedSmooth transitions between animations
Smooth transitions between animations
anim.play("walk")
anim.play("walk")
0.5s blend from walk → run
0.5s blend from walk → run
anim.play("run", -1, 1.0, 0.5) # custom_blend = 0.5
anim.play("run", -1, 1.0, 0.5) # custom_blend = 0.5
Or set default blend
Or set default blend
anim.set_default_blend_time(0.3) # 0.3s for all transitions
anim.play("idle")
---anim.set_default_blend_time(0.3) # 0.3s for all transitions
anim.play("idle")
---RESET Track Pattern
RESET轨道模式
Problem: Properties Don't Reset
问题:属性无法重置
gdscript
undefinedgdscript
undefinedAnimate sprite position from (0,0) → (100, 0)
Animate sprite position from (0,0) → (100, 0)
Change scene, sprite stays at (100, 0)!
Change scene, sprite stays at (100, 0)!
undefinedundefinedSolution: RESET Animation
解决方案:RESET动画
gdscript
undefinedgdscript
undefinedCreate RESET animation with default values
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)
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
AnimationPlayer automatically plays RESET when scene loads
IF "Reset on Save" is enabled in AnimationPlayer settings
IF "Reset on Save" is enabled in AnimationPlayer settings
---
---Procedural Animation Generation
程序化动画生成
Generate Animation from Code
通过代码生成动画
gdscript
undefinedgdscript
undefinedCreate bounce animation programmatically
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")
---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")
---Advanced Patterns
高级模式
Play Animation Backwards
反向播放动画
gdscript
undefinedgdscript
undefinedPlay animation in reverse (useful for closing doors, etc.)
Play animation in reverse (useful for closing doors, etc.)
anim.play("door_open", -1, -1.0) # speed = -1.0 = reverse
anim.play("door_open", -1, -1.0) # speed = -1.0 = reverse
Pause and reverse
Pause and reverse
anim.pause()
anim.play("current_animation", -1, -1.0, false) # from_end = false
undefinedanim.pause()
anim.play("current_animation", -1, -1.0, false) # from_end = false
undefinedAnimation Callbacks (Signal-Based)
动画回调(基于信号)
gdscript
undefinedgdscript
undefinedEmit custom signal at specific frame
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()
undefinedfunc _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()
undefinedSeek to Specific Time
跳转到特定时间点
gdscript
undefinedgdscript
undefinedJump to 50% through animation
Jump to 50% through animation
anim.seek(anim.current_animation_length * 0.5)
anim.seek(anim.current_animation_length * 0.5)
Scrub through animation (cutscene editor)
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)
---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)
---Performance Optimization
性能优化
Disable When Off-Screen
离开屏幕时禁用动画
gdscript
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()gdscript
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()Edge Cases
边缘情况
Animation Not Playing
动画无法播放
gdscript
undefinedgdscript
undefinedProblem: Forgot to add animation to player
Problem: Forgot to add animation to player
Solution: Check if animation exists
Solution: Check if animation exists
if anim.has_animation("walk"):
anim.play("walk")
else:
push_error("Animation 'walk' not found!")
if anim.has_animation("walk"):
anim.play("walk")
else:
push_error("Animation 'walk' not found!")
Better: Use constants
Better: Use constants
const ANIM_WALK = "walk"
const ANIM_IDLE = "idle"
if anim.has_animation(ANIM_WALK):
anim.play(ANIM_WALK)
undefinedconst ANIM_WALK = "walk"
const ANIM_IDLE = "idle"
if anim.has_animation(ANIM_WALK):
anim.play(ANIM_WALK)
undefinedMethod Track Not Firing
Method轨道未触发
gdscript
undefinedgdscript
undefinedProblem: Call mode is CONTINUOUS
Problem: Call mode is CONTINUOUS
Solution: Set to DISCRETE
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)
---var method_track_idx := anim.find_track(".:method_name", Animation.TYPE_METHOD)
anim.track_set_call_mode(method_track_idx, Animation.CALL_MODE_DISCRETE)
---Decision Matrix: AnimationPlayer vs Tween
决策矩阵:AnimationPlayer 对比 Tween
| 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
| 特性 | AnimationPlayer | Tween |
|---|---|---|
| 时间轴编辑 | ✅ 可视化编辑器 | ❌ 仅支持代码 |
| 多属性动画 | ✅ 多轨道支持 | ❌ 单属性 |
| 可复用性 | ✅ 保存为资源 | ❌ 每次都要创建 |
| 动态运行时 | ❌ 静态 | ✅ 完全动态 |
| 函数调用 | ✅ Method轨道 | ❌ 使用回调 |
| 性能 | ✅ 已优化 | ❌ 略慢 |
使用AnimationPlayer的场景:过场动画、角色动画、复杂UI动画
使用Tween的场景:简单运行时效果、一次性过渡
Reference
参考
- Master Skill: godot-master
- 核心技能:godot-master