AnimationTree 스테이트 머신
Godot 4

스테이트 머신, 트랜지션, 블렌드 트리, OneShot, travel() 대 조건 비교의 완벽 가이드 — 실제 GDScript 코드 예제와 함께.

1. 소개

AnimationTree는 복잡한 애니메이션 블렌딩과 상태 전환을 위한 Godot 4의 시스템입니다. 코드 곳곳에 흩어진 AnimationPlayer.play() 호출로 여러 애니메이션을 관리하려고 시도해 본 적이 있다면, 그것이 얼마나 빠르게 감당할 수 없게 되는지 잘 아실 겁니다. AnimationTree는 상태와 트랜지션으로 이루어진 시각적 그래프로 이 문제를 해결합니다.

AnimationTree는 놀랍도록 강력하지만, 공식 문서는 빈약해서 개발자들이 감으로 헤매게 되는 경우가 많습니다. 이 가이드는 기본 설정부터 고급 블렌드 트리까지, 프로젝트에 그대로 복사해 넣을 수 있는 실제 GDScript 코드와 함께 모든 것을 차근차근 설명합니다.

이 가이드에서 배우는 것

스테이트 머신, 트랜지션, 블렌드 스페이스(1D & 2D), 공격용 OneShot, travel() 대 조건 비교, 그리고 완전한 캐릭터 컨트롤러 패턴.

2. 사전 준비

AnimationTree를 설정하기 전에 다음이 필요합니다:

  • Idle, Walk, Run, Jump 애니메이션이 최소한 이미 만들어져 있는 AnimationPlayer 노드
  • AnimationPlayer는 AnimationTree를 추가하는 노드의 형제 또는 자식이어야 합니다(보통 둘 다 캐릭터 루트 노드의 자식입니다)
  • Godot 4.x (이 가이드는 Godot 4 API를 사용합니다 — AnimationTree API는 Godot 3에서 크게 바뀌었습니다)
Godot 3 대 Godot 4

Godot 4에서는 AnimationTree.animation_playerAnimationTree.anim_player로 대체되었습니다. 재생 파라미터 경로도 바뀌었습니다. Godot 3에서 마이그레이션하는 경우 공식 마이그레이션 가이드를 확인하세요.

3. 기본 설정

AnimationTree 설정은 네 단계로 이루어집니다:

  1. AnimationTree 노드 추가 — AnimationPlayer 옆에, 캐릭터(예: CharacterBody2D 또는 CharacterBody3D)의 자식으로 추가합니다.
  2. anim_player 설정 — 인스펙터에서 "Anim Player" 속성이 AnimationPlayer 노드를 가리키도록 지정합니다.
  3. tree_root 설정 — 인스펙터에서 "Tree Root" 속성을 클릭하고 새 AnimationNodeStateMachine을 생성합니다.
  4. active = true 설정 — 인스펙터에서 "Active" 체크박스를 켜거나 코드에서 설정합니다.

씬 트리는 다음과 같은 모습이어야 합니다:

CharacterBody2D (or CharacterBody3D)
  +-- Sprite2D (or Sprite3D)
  +-- CollisionShape2D
  +-- AnimationPlayer      <-- has Idle, Walk, Run, Jump animations
  +-- AnimationTree         <-- points to AnimationPlayer above
GDScript
# Minimal code setup (usually done via the editor instead):
@onready var anim_tree: AnimationTree = $AnimationTree

func _ready() -> void:
    # If you set these in the Inspector, you don't need this code
    anim_tree.anim_player = ^"../AnimationPlayer"
    anim_tree.tree_root = AnimationNodeStateMachine.new()
    anim_tree.active = true
에디터 대 코드

실제로는 거의 항상 에디터에서 AnimationTree를 구성합니다. 위 코드는 완전성을 위해 보여드리는 것이며, 보통 스크립트에서는 anim_tree.active = true만 있으면 충분합니다(이마저도 에디터에서 설정할 수 있습니다).

4. 스테이트 머신 기초

AnimationTree의 스테이트 머신은 단순한 개념으로 동작합니다: 상태(state)는 애니메이션을 나타내고, 트랜지션은 상태 사이를 전환하는 조건을 정의합니다.

상태 추가하기

AnimationTree의 tree_root가 AnimationNodeStateMachine으로 설정되면, 인스펙터에서 더블 클릭해 스테이트 머신 에디터를 엽니다:

  1. 그래프 영역에서 마우스 오른쪽 버튼을 클릭하고 Add Animation을 선택합니다
  2. AnimationPlayer에서 애니메이션을 선택합니다(Idle, Walk, Run, Jump 등)
  3. 필요한 각 애니메이션 상태에 대해 반복합니다
특수 상태

Start는 진입점입니다 — 첫 번째 트랜지션은 항상 여기에서 시작합니다. End는 선택 사항이며 스테이트 머신이 완료되었음을 알립니다(중첩 스테이트 머신에서 유용합니다).

트랜지션 추가하기

두 상태 사이에 트랜지션을 만들려면:

  1. 출발 상태 노드를 클릭합니다
  2. 목적지 상태로 드래그하여 트랜지션 화살표를 만듭니다
  3. 트랜지션 화살표를 클릭해 인스펙터에서 구성합니다

트랜지션 속성

속성 설명
advance_mode Auto — 조건이 참일 때 발동합니다. Enabled — travel()에 항상 사용 가능합니다. Disabled — 차단됩니다.
advance_condition 불리언 파라미터의 이름(예: is_moving). 참일 때 트랜지션이 자동으로 발동합니다.
xfade_time 크로스페이드 지속 시간(초). 애니메이션 사이를 부드럽게 블렌딩합니다. 일반적으로 0.1 – 0.3초.
switch_mode Immediate — 즉시 전환합니다. Sync — 재생 위치를 맞춥니다. AtEnd — 현재 애니메이션이 끝날 때까지 기다립니다.

플랫포머 캐릭터를 위한 전형적인 설정:

Start --> Idle
Idle  --> Walk   (condition: is_moving)
Walk  --> Idle   (condition: is_idle)
Idle  --> Jump   (condition: is_jumping)
Walk  --> Jump   (condition: is_jumping)
Jump  --> Fall   (condition: is_falling)
Fall  --> Idle   (condition: is_on_floor, switch_mode: Immediate)

5. 코드에서 스테이트 머신 제어하기

스테이트 머신을 구동하는 주요 방법은 두 가지입니다: 조건 파라미터 설정(자동 트랜지션)과 travel() 호출(수동 트랜지션). 두 방식을 함께 섞어 사용할 수도 있습니다.

GDScript
extends CharacterBody2D

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

func _physics_process(delta: float) -> void:
    # ... movement logic here ...
    move_and_slide()
    _update_animation_parameters()

func _update_animation_parameters() -> void:
    # Approach 1: Set condition parameters — transitions fire automatically
    anim_tree.set("parameters/conditions/is_moving", velocity.length() > 10.0)
    anim_tree.set("parameters/conditions/is_idle", velocity.length() <= 10.0)
    anim_tree.set("parameters/conditions/is_on_floor", is_on_floor())
    anim_tree.set("parameters/conditions/is_jumping", velocity.y < 0 and not is_on_floor())
    anim_tree.set("parameters/conditions/is_falling", velocity.y > 0 and not is_on_floor())

    # Approach 2: Use travel() for direct control
    # if velocity.length() > 10.0:
    #     state_machine.travel("Walk")
    # else:
    #     state_machine.travel("Idle")
파라미터 경로

트랜지션에 설정된 조건의 경우 파라미터는 parameters/conditions/<condition_name> 패턴을 따릅니다. 조건 이름은 에디터에서 트랜지션에 설정한 advance_condition과 정확히 일치해야 합니다.

6. travel() 대 조건

조건 기반 (이동에 권장)

매 프레임 불리언 파라미터를 설정하고 트랜지션이 자동으로 발동하도록 두면 됩니다. 이 방식은 더 선언적이며 코드를 깔끔하게 유지합니다. 스테이트 머신이 트랜지션 로직, 크로스페이드, 예외 상황을 대신 처리해 줍니다.

# Declarative: just describe the current state of the world
anim_tree.set("parameters/conditions/is_moving", velocity.length() > 10.0)
anim_tree.set("parameters/conditions/is_on_floor", is_on_floor())

travel() (일회성 동작에 권장)

travel()은 상태 전환을 요청합니다. 트랜지션 규칙을 준수하므로 — 현재 상태에서 목표까지 유효한 경로가 없으면 호출은 무시됩니다. 덕분에 반복적으로 호출해도 안전합니다. 공격, 이모트, 컷신 애니메이션 같은 일회성 트리거에 사용하세요.

# travel() — requests a transition (respects transition rules)
state_machine.travel("Jump")

# Get current state name
var current: StringName = state_machine.get_current_node()
print(current)  # "Idle", "Walk", etc.

# Check if travel is possible
var is_playing: bool = state_machine.is_playing()
print(is_playing)
언제 무엇을 사용할까?

조건은 연속적인 상태(대기, 걷기, 달리기, 낙하)에. travel()은 이벤트로 유발되는 상태(공격, 회피, 상호작용)에. 많은 프로젝트가 둘 다 사용합니다: 이동에는 조건, 전투 동작에는 travel().

7. 블렌드 트리

블렌드 트리를 사용하면 이산적인 상태 사이를 딱딱하게 전환하는 대신, 연속적인 값을 기준으로 여러 애니메이션 사이를 부드럽게 보간할 수 있습니다. 걷기/달리기 속도 블렌딩과 방향성 이동에 안성맞춤입니다.

BlendSpace1D

단일 축을 따라 두 개 이상의 애니메이션 사이를 1D로 블렌딩합니다. 흔한 사용 예: 이동 속도를 기준으로 Walk와 Run을 블렌딩.

에디터에서 스테이트 머신 안에(또는 독립적인 트리 루트로) BlendSpace1D 노드를 만듭니다. 애니메이션 포인트를 추가합니다:

# BlendSpace1D setup (in editor):
# Point 0.0 = Walk animation
# Point 1.0 = Run animation

# Control from code:
var speed_factor: float = clamp(velocity.length() / max_speed, 0.0, 1.0)
anim_tree.set("parameters/WalkRun/blend_position", speed_factor)

BlendSpace2D

두 축을 사용하는 2D 블렌드. 8방향 이동이나 캐릭터가 어느 방향으로든 움직일 수 있는 탑다운 게임에 완벽합니다.

# BlendSpace2D setup (in editor):
# Place animations at positions:
#   Idle at (0, 0)
#   WalkRight at (1, 0), WalkLeft at (-1, 0)
#   WalkUp at (0, -1), WalkDown at (0, 1)
#   Diagonals at corners

# Control from code:
var input_dir := Input.get_vector("move_left", "move_right", "move_up", "move_down")
anim_tree.set("parameters/Movement/blend_position", input_dir)
블렌드 모드

BlendSpace2D는 여러 블렌드 모드를 지원합니다: 기본 삼각 분할(triangulation)은 대부분의 경우 잘 동작합니다. 보간 없는 픽셀 아트 스타일 애니메이션을 원한다면 이산 모드(가장 가까운 포인트로 스냅)를 선택할 수도 있습니다.

8. 자주 쓰는 노드 타입

AnimationTree는 여러 노드 타입을 지원하며, 이들을 조합해 복잡한 애니메이션 동작을 만들 수 있습니다:

노드 타입 사용 사례
AnimationNodeStateMachine 트랜지션이 있는 스테이트 머신. 가장 흔한 루트 노드. 상태는 애니메이션이거나 중첩된 스테이트 머신일 수 있습니다.
AnimationNodeBlendSpace1D 한 축을 따르는 1D 블렌드. 걷기/달리기 속도, 조준 각도 등.
AnimationNodeBlendSpace2D 두 축을 사용하는 2D 블렌드. 방향성 이동, 스트레이프 블렌딩.
AnimationNodeBlendTree 블렌드 연산의 그래프. 여러 블렌드 노드를 사용자 정의 로직으로 조합합니다.
AnimationNodeAdd2 가산 블렌딩. 한 애니메이션 위에 다른 애니메이션을 겹칩니다(예: 걷기 위에 조준 오프셋).
AnimationNodeTimeScale 속도 제어. 런타임에 애니메이션을 더 빠르거나 느리게 재생합니다.
AnimationNodeOneShot 일회성 애니메이션 오버레이. 공격, 이모트, 피격 반응에 완벽합니다.
AnimationNodeTransition 크로스페이드로 여러 입력 사이를 전환합니다. 더 단순한 설정에서 스테이트 머신의 대안입니다.

9. OneShot 패턴 (공격, 이모트)

OneShot 노드는 AnimationTree에서 가장 유용한 패턴 중 하나입니다. 기본 애니메이션 위에 일회성 애니메이션을 재생하고(예: 걷는 도중 공격 스윙 재생), 이후 자동으로 기본 애니메이션으로 되돌아갑니다.

BlendTree에서 설정

OneShot을 사용하려면 AnimationTree의 루트(또는 그 안의 상태)가 BlendTree여야 합니다:

# BlendTree graph setup:
#
#   [StateMachine] ---> [OneShot "AttackOneShot"] ---> [Output]
#   (base locomotion)      ^
#                          |
#                   [Animation "Attack"]
#                   (shot input)
#
# The StateMachine provides the base (idle/walk/run).
# The Attack animation is connected to the OneShot's "shot" input.

코드에서 트리거하기

GDScript
extends CharacterBody2D

@onready var anim_tree: AnimationTree = $AnimationTree

func _input(event: InputEvent) -> void:
    if event.is_action_pressed("attack"):
        _play_attack()

func _play_attack() -> void:
    # Fire the one-shot animation
    anim_tree.set(
        "parameters/AttackOneShot/request",
        AnimationNodeOneShot.ONE_SHOT_REQUEST_FIRE
    )

func _process(delta: float) -> void:
    # Check if the one-shot is currently active
    var is_attacking: bool = anim_tree.get("parameters/AttackOneShot/active")
    if is_attacking:
        # Optionally disable movement during attack
        pass

OneShot 리퀘스트 상수

상수 효과
ONE_SHOT_REQUEST_FIRE OneShot 애니메이션 재생 시작
ONE_SHOT_REQUEST_ABORT OneShot을 취소하고 즉시 기본으로 되돌아감
ONE_SHOT_REQUEST_FADE_OUT OneShot을 페이드 아웃(fadeout_time 속성 사용)
다중 공격

콤보 시스템의 경우, 여러 OneShot 노드를 순차적으로 사용하거나 OneShot의 shot 입력 안에 Attack1 → Attack2 → Attack3 상태를 가진 중첩 스테이트 머신을 사용하세요.

10. 실전 예제: 완전한 캐릭터 컨트롤러

다음은 스테이트 머신 이동과 OneShot 공격을 결합한 완전한 2D 플랫포머 캐릭터 스크립트입니다. 여러분의 프로젝트에 맞게 응용할 수 있는 프로덕션 수준의 패턴입니다.

GDScript — player.gd
extends CharacterBody2D

const SPEED := 200.0
const JUMP_VELOCITY := -350.0
const SPRINT_MULTIPLIER := 1.6

@onready var anim_tree: AnimationTree = $AnimationTree
@onready var playback: AnimationNodeStateMachinePlayback = anim_tree.get("parameters/playback")
@onready var sprite: Sprite2D = $Sprite2D

var gravity: float = ProjectSettings.get_setting("physics/2d/default_gravity")

func _ready() -> void:
    anim_tree.active = true

func _physics_process(delta: float) -> void:
    _apply_gravity(delta)
    _handle_jump()
    _handle_movement()
    move_and_slide()
    _update_animation()

func _apply_gravity(delta: float) -> void:
    if not is_on_floor():
        velocity.y += gravity * delta

func _handle_jump() -> void:
    if Input.is_action_just_pressed("jump") and is_on_floor():
        velocity.y = JUMP_VELOCITY

func _handle_movement() -> void:
    var direction := Input.get_axis("move_left", "move_right")
    var is_sprinting := Input.is_action_pressed("sprint")
    var current_speed := SPEED * (SPRINT_MULTIPLIER if is_sprinting else 1.0)

    if direction != 0.0:
        velocity.x = direction * current_speed
        sprite.flip_h = direction < 0.0
    else:
        velocity.x = move_toward(velocity.x, 0.0, SPEED)

func _update_animation() -> void:
    # Skip animation updates during attack
    var is_attacking: bool = anim_tree.get("parameters/AttackOneShot/active")
    if is_attacking:
        return

    if not is_on_floor():
        if velocity.y < 0:
            playback.travel("Jump")
        else:
            playback.travel("Fall")
    elif abs(velocity.x) > 10.0:
        if Input.is_action_pressed("sprint"):
            playback.travel("Run")
        else:
            playback.travel("Walk")
    else:
        playback.travel("Idle")

func _input(event: InputEvent) -> void:
    if event.is_action_pressed("attack") and is_on_floor():
        anim_tree.set(
            "parameters/AttackOneShot/request",
            AnimationNodeOneShot.ONE_SHOT_REQUEST_FIRE
        )
이 예제의 AnimationTree 구조

Root = BlendTree. 내부: StateMachine 노드(Idle/Walk/Run/Jump/Fall 상태)가 OneShot 노드("AttackOneShot")에 연결되고, 이것이 다시 Output에 연결됩니다. 공격 애니메이션은 OneShot의 "shot" 입력에 연결됩니다.

11. 문제 해결

"애니메이션이 재생되지 않음"

AnimationTree에 active = true가 설정되어 있고 anim_player 속성이 유효한 AnimationPlayer를 가리키는지 확인하세요. 또한 참조하는 이름의 애니메이션이 AnimationPlayer에 실제로 존재하는지 확인하세요.

"travel()이 아무 일도 하지 않음"

현재 상태와 목표 상태 사이에 유효한 트랜지션 경로가 존재하는지 확인하세요. 경로가 없으면 travel()은 조용히 실패합니다. state_machine.get_current_node()로 실제로 어떤 상태에 있는지 디버깅하세요.

"블렌드가 동작하지 않음"

blend_position 값이 블렌드 스페이스 포인트 범위 안에 들어가는지 확인하세요. 포인트가 0.0과 1.0에 있다면 5.0 값은 기대대로 동작하지 않습니다. clamp()를 사용하세요.

"Warning: AnimationTree is not active"

에디터(인스펙터 체크박스)나 _ready() 함수에서 active = true를 설정하세요. AnimationTree는 활성화되기 전까지 아무것도 하지 않습니다.

"조건 기반 트랜지션이 발동하지 않음"

다음을 다시 한 번 확인하세요: (1) 트랜지션의 advance_modeAuto로 설정되어 있는지, (2) advance_condition 이름이 코드에서 설정한 것과 정확히 일치하는지(대소문자 구분), (3) _physics_process()에서 매 프레임 파라미터를 설정하고 있는지.

"애니메이션은 재생되지만 캐릭터가 움직이지 않음"

AnimationTree는 애니메이션 재생만 처리합니다. 이동 로직(velocity, move_and_slide())은 별개이며 스크립트의 _physics_process()에서 구현해야 합니다.

AI가 AnimationTree를 만들게 하고 싶으신가요?

Godot MCP Pro는 스테이트 머신 생성, 상태와 트랜지션 추가, 블렌드 트리 구성, 파라미터 설정을 — 모두 단 하나의 프롬프트로 처리할 수 있습니다. 원하는 애니메이션 동작을 AI 어시스턴트에게 말하기만 하면, 전체 AnimationTree를 대신 구축해 줍니다.

  • create_animation_tree
  • add_state_machine_state
  • add_state_machine_transition
  • set_blend_tree_node
  • set_tree_parameter
  • get_animation_tree_structure
Godot MCP Pro 구매하기 — $15