godot-animation-player

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

AnimationPlayer

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
    material.albedo_color
    creates embedded resources, bloating file size. Store material in variable, animate variable's properties.
  • NEVER use animation_finished for looping animations — Signal doesn't fire for looped animations. Use
    animation_looped
    or check
    current_animation
    in _process().
  • 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信号 — 循环动画不会触发该信号。请使用
    animation_looped
    信号或在_process()中检查
    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
undefined
gdscript
undefined

Animate 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")
undefined
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")
undefined

Method Tracks (Function Calls)

Method轨道(函数调用)

gdscript
undefined
gdscript
undefined

Call 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()
undefined
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()
undefined

Audio Tracks

Audio轨道

gdscript
undefined
gdscript
undefined

Synchronize 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
undefined
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
undefined

Bezier Tracks (Custom Curves)

Bezier轨道(自定义曲线)

gdscript
undefined
gdscript
undefined

For 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
undefined
gdscript
undefined

Character 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

undefined
undefined

Solution: Root Motion

解决方案:根运动

gdscript
undefined
gdscript
undefined

Scene 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
undefined
gdscript
undefined

Play 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
undefined
func play_with_queue() -> void: anim.play("attack_1") anim.queue("attack_2") anim.queue("idle") # Auto-plays after attack_2
undefined

Blend Times

混合时间

gdscript
undefined
gdscript
undefined

Smooth 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
undefined
gdscript
undefined

Animate 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)!

undefined
undefined

Solution: RESET Animation

解决方案:RESET动画

gdscript
undefined
gdscript
undefined

Create 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
undefined
gdscript
undefined

Create 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
undefined
gdscript
undefined

Play 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
undefined
anim.pause() anim.play("current_animation", -1, -1.0, false) # from_end = false
undefined

Animation Callbacks (Signal-Based)

动画回调(基于信号)

gdscript
undefined
gdscript
undefined

Emit 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()
undefined
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()
undefined

Seek to Specific Time

跳转到特定时间点

gdscript
undefined
gdscript
undefined

Jump 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
undefined
gdscript
undefined

Problem: 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)
undefined
const ANIM_WALK = "walk" const ANIM_IDLE = "idle"
if anim.has_animation(ANIM_WALK): anim.play(ANIM_WALK)
undefined

Method Track Not Firing

Method轨道未触发

gdscript
undefined
gdscript
undefined

Problem: 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

FeatureAnimationPlayerTween
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
特性AnimationPlayerTween
时间轴编辑✅ 可视化编辑器❌ 仅支持代码
多属性动画✅ 多轨道支持❌ 单属性
可复用性✅ 保存为资源❌ 每次都要创建
动态运行时❌ 静态✅ 完全动态
函数调用✅ Method轨道❌ 使用回调
性能✅ 已优化❌ 略慢
使用AnimationPlayer的场景:过场动画、角色动画、复杂UI动画 使用Tween的场景:简单运行时效果、一次性过渡

Reference

参考

  • Master Skill: godot-master
  • 核心技能:godot-master