强曰为道
与天地相似,故不违。知周乎万物,而道济天下,故不过。旁行而不流,乐天知命,故不忧.
文档目录

Godot 3 GDScript 教程 / Godot 3 GDScript 教程(十一):动画系统(AnimationPlayer)

动画系统(AnimationPlayer)

动画是游戏的灵魂。Godot 3 的 AnimationPlayer 节点提供了强大而灵活的动画系统,不仅能驱动属性变化,还能调用方法、触发事件,适用于 UI 过渡、角色动作、场景演出等几乎所有场景。


AnimationPlayer 基础

AnimationPlayer 是 Godot 动画系统的核心节点,可以对场景中的任意节点的任意属性进行动画控制。

基本控制

# 播放动画
$AnimationPlayer.play("idle")

# 播放并等待完成
yield($AnimationPlayer, "animation_finished")

# 停止动画
$AnimationPlayer.stop()

# 设置播放速度(负值为倒放)
$AnimationPlayer.playback_speed = 2.0

# 检查是否正在播放
if $AnimationPlayer.is_playing():
    print("动画播放中: ", $AnimationPlayer.current_animation)

AnimationPlayer 重要属性

属性/方法说明
play(name)播放指定名称的动画
stop(reset)停止动画,reset 决定是否回到起始
is_playing()判断当前是否在播放
current_animation当前动画名称
playback_speed播放速度倍率
animation_finished动画完成信号
get_animation_list()获取所有动画名称列表

💡 提示playback_speed = -1.0 可以实现动画倒放,适合实现"时间回溯"等特效。


关键帧动画

关键帧(Keyframe)是动画的基本单位。通过在不同时间点设置属性值,引擎自动插值过渡。

插值模式

插值模式说明
INTERPOLATION_NEAREST无插值,直接跳到下一帧
INTERPOLATION_LINEAR线性插值
INTERPOLATION_CUBIC三次插值,更平滑

代码动态创建关键帧

var animation = Animation.new()

# 创建位置属性轨道
var track_idx = animation.add_track(Animation.TYPE_VALUE)
animation.track_set_path(track_idx, "Sprite:position")

# 插入关键帧:时间、值
animation.track_insert_key(track_idx, 0.0, Vector2(0, 0))
animation.track_insert_key(track_idx, 0.5, Vector2(200, 0))
animation.track_insert_key(track_idx, 1.0, Vector2(200, 100))

animation.loop = true
$AnimationPlayer.add_animation("move_path", animation)
$AnimationPlayer.play("move_path")

属性动画

属性动画可以驱动节点的任何可序列化属性,如 positionmodulatescalerotation 等。

缩放动画(受击反馈)

# 创建缩放"弹跳"动画用于受击反馈
func create_hit_animation() -> Animation:
    var anim = Animation.new()
    var track = anim.add_track(Animation.TYPE_VALUE)
    anim.track_set_path(track, ".:scale")

    anim.track_insert_key(track, 0.0, Vector2(1, 1))
    anim.track_insert_key(track, 0.05, Vector2(1.3, 1.3))
    anim.track_insert_key(track, 0.1, Vector2(0.9, 0.9))
    anim.track_insert_key(track, 0.15, Vector2(1, 1))

    anim.length = 0.15
    return anim

⚠️ 注意:属性动画的 track_path 格式为 "NodeName:property"".:property"(当前节点),路径错误是最常见的动画不生效原因。


方法调用轨道

AnimationPlayer 不仅能动画属性,还能在指定时间点调用节点的方法。

var animation = Animation.new()

# 创建方法调用轨道
var track_idx = animation.add_track(Animation.TYPE_METHOD)
animation.track_set_path(track_idx, ".")

# 在 0.5 秒时调用 play_footstep_sound
animation.track_insert_key(track_idx, 0.5, {
    "method": "play_footstep_sound",
    "args": []
})

# 在 1.0 秒时调用带参数的方法
animation.track_insert_key(track_idx, 1.0, {
    "method": "spawn_dust_effect",
    "args": [Vector2(0, 32)]
})

$AnimationPlayer.add_animation("walk", animation)

func play_footstep_sound():
    $FootstepSound.play()

func spawn_dust_effect(pos: Vector2):
    var dust = dust_scene.instance()
    dust.position = pos
    add_child(dust)

💡 提示:方法调用轨道非常适合触发音效、粒子特效、伤害判定等事件,避免用代码手动计时。


动画过渡与混合

常见过渡策略

场景推荐过渡时间说明
站立 → 奔跑0.1 ~ 0.2s快速响应
攻击 → 站立0.2 ~ 0.3s略有收招感
受击 → 站立0.15s硬直后恢复
待机切换0.3 ~ 0.5s平滑自然
# 从当前动画平滑过渡到 "run"
$AnimationPlayer.play("run", -1, 1.0, false)

动画状态机

对于角色动画管理,状态机是最常用的模式。

代码控制状态切换

onready var anim_tree = $AnimationTree
onready var state_machine = anim_tree.get("parameters/playback")

func _physics_process(delta):
    var velocity = get_velocity()

    if not is_on_floor():
        state_machine.travel("jump")
    elif velocity.length() > 10:
        state_machine.travel("run")
    else:
        state_machine.travel("idle")

    # 攻击时直接切换
    if Input.is_action_just_pressed("attack"):
        state_machine.start("attack")

⚠️ 注意travel() 会寻找最短路径过渡,而 start() 会立即切换。攻击等需要立即响应的动作建议用 start()


AnimationTree(Godot 3.x)

AnimationTree 提供了比 AnimationPlayer 更高级的动画控制,支持状态机、混合树等。

BlendSpace2D 实现八方向移动

onready var anim_tree = $AnimationTree

func _ready():
    anim_tree.active = true

func update_blend_space(velocity: Vector2):
    var normalized = velocity.normalized()
    anim_tree.set("parameters/MoveBlend/blend_position", normalized)

动画混合树(BlendTree)

在编辑器中创建 BlendTree:
1. AnimationTree → Tree Root → AnimationNodeBlendTree
2. 添加 Animation 节点(idle, run)
3. 添加 Blend2 节点,连接动画
4. 代码控制混合比例:

anim_tree.set("parameters/Blend2/blend_amount", run_weight)

动画事件触发

利用方法调用轨道,可以在动画的特定时刻触发游戏逻辑。

实现攻击判定窗口

# 在攻击动画中使用方法调用轨道
# 帧 5: enable_hitbox()  帧 10: disable_hitbox()

func enable_hitbox():
    $AttackArea/CollisionShape2D.disabled = false
    $AttackEffect.emitting = true

func disable_hitbox():
    $AttackArea/CollisionShape2D.disabled = true

func _on_AttackArea_body_entered(body):
    if body.has_method("take_damage"):
        body.take_damage(attack_power)

骨骼动画

2D 骨骼(Bone2D)

# Godot 3 的 Skeleton2D + Bone2D 系统
# 代码控制骨骼
$Skeleton2D/Bone2D/UpperArm.rotation_degrees = 45
$Skeleton2D/Bone2D/UpperArm/LowerArm.rotation_degrees = -30

3D 骨骼动画

onready var skeleton = $Armature/Skeleton
onready var anim_player = $AnimationPlayer

func play_attack():
    anim_player.play("attack")
    yield(anim_player, "animation_finished")
    anim_player.play("idle")

动画复用

方法一:共享动画资源

# 多个同类型角色共享动画
var shared_animations = preload("res://animations/enemy_animations.tres")

func _ready():
    $AnimationPlayer.add_animation_library(shared_animations)

方法二:代码动画模板

# 创建可复用的动画工厂
class_name AnimationFactory

static func create_tween_bounce(node: Node, property: String, amount: float, duration: float = 0.15) -> Animation:
    var anim = Animation.new()
    var track = anim.add_track(Animation.TYPE_VALUE)
    anim.track_set_path(track, node.name + ":" + property)

    var original = node.get(property)
    anim.track_insert_key(track, 0.0, original)
    anim.track_insert_key(track, duration * 0.3, original * (1.0 + amount))
    anim.track_insert_key(track, duration, original)
    anim.length = duration
    return anim

⚠️ 注意:共享动画时,确保动画轨道的 track_path 与各角色的节点结构一致,否则动画将无法正确驱动目标属性。


游戏开发场景

场景一:UI 界面过渡动画

func show_menu():
    $AnimationPlayer.play("menu_fade_in")
    yield($AnimationPlayer, "animation_finished")
    $MenuContainer.visible = true

func hide_menu():
    $AnimationPlayer.play("menu_fade_out")
    yield($AnimationPlayer, "animation_finished")
    $MenuContainer.visible = false

场景二:过场演出(Cutscene)

func play_cutscene():
    get_tree().paused = true
    $CutscenePlayer.play("boss_intro")
    yield($CutscenePlayer, "animation_finished")
    get_tree().paused = false
    emit_signal("cutscene_finished")

扩展阅读

💡 总结:AnimationPlayer 是 Godot 动画系统的核心,善用属性动画 + 方法调用轨道可以覆盖绝大多数游戏动画需求。对于复杂的角色动画,推荐使用 AnimationTree + StateMachine 的组合。