待機、歩き、走り、ジャンプ、攻撃——キャラクターにできることが増えるほど、_physics_process の中は「速度がこれ以上なら走りアニメ、床にいなければジャンプアニメ…」という if-elif の山になっていきます。どのアニメがいつ再生されるのか、自分でも追えなくなってきた、という経験はないでしょうか。
この「アニメの切り替えロジ ック」を、コードから引きはがして 見えるグラフ にできるのが、Godotの AnimationTree と、その中の ステートマシン(AnimationNodeStateMachine)です。コード側は「今こういう状態だよ」と伝えるだけ。どのアニメへどう繋ぐかは、グラフが面倒を見てくれます。
この記事でわかること
if-elifの山を、見えるステートグラフに置き換える考え方AnimationPlayer(倉庫)とAnimationTree(司令塔)の役割分担travel()で「今の状態」を伝えてアニメを切り替える基本実装- X-Fadeによる滑らかな遷移と、コードだけで組む場合との使い分け
なぜAnimationTreeとステートマシンが必要なのか
キャラクターの状態が「待機・歩行」くらいなら、if 文でアニメを切り替えても困りません。しかし「走行・ジャンプ・落下・攻撃・被弾…」と増えると、_physics_process の中に if-elif-else の巨大なネスト が育ちます。条件が絡み合い、「このアニメはどの条件で再生されるのか」を追うのが一苦労になります。

ステートマシンを使うと、この切り替えを 状態(ノード)と遷移(矢印)のグラフ に描き替えられます。ロジックがコードから図に移るので、全体像がひと目で分かり、状態を1つ足すのもノードと矢印を追加するだけで済みます。
AnimationPlayerとAnimationTreeの役割分担
まず、登場する3つの役者を整理します。
AnimationPlayer(アニメの倉庫) :idle・walk・run・jumpといった個々のアニメーションクリップを保管する場所です。作り込みは AnimationPlayerの高度な活用 を参照してください。AnimationTree(司令塔) :倉庫からクリップを取り出し、どれをどの順で・どう滑らかに繋ぐかを制御するエンジンです。AnimationNodeStateMachinePlayback(コードの窓口) :GDScriptから「今この状態へ」と指示するためのオブジェクトです。

ポイントは、 コードから AnimationPlayer を直接叩かず、AnimationTree を通して状態を管理する ことです。こうしておくと、アニメの繋ぎ方を変えたくなってもグラフ側をいじるだけで済み、ゲームロジックのコードはそのままにできます。
ステートマシンの考え方:状態と遷移
ステートマシンは、 状態(State) と、状態をつなぐ 遷移(Transition) でできたグラフです。
- 状態(State) :
idle・walk・runのように、その瞬間に再生されるアニメーション1つ。 - 遷移(Transition) :状態から状態へ移る矢印。「速度が180を超えたら
walkからrunへ」のように、移る向きと条件を決めます。

エディタ下部の AnimationTree パネルで、Animation ノードを置いて idle・walk・run・jump と名付け、矢印でつなぐだけでグラフができます。あとはコードから「今の状態」を伝えれば、AnimationTree が矢印に沿ってアニメを切り替えます。
実践:待機・歩行・走行・ジャンプを実装する
横スクロールアクションのプレイヤーを例に、実際に組んでみます。トップダウンARPGでもメトロイドヴァニアでも、「移動速度と接地状態からアニメを決める」という骨組みは同じです。
ノードとアニメーションの準備
次のノード構成にして、AnimationPlayer に idle・walk・run・jump の4つのアニメーションを作っておきます。
- CharacterBody2D
- Sprite2D
- CollisionShape2D
- AnimationPlayer
- AnimationTree
AnimationTree の Anim Player に AnimationPlayer を割り当て、Tree Root を New AnimationNodeStateMachine にして、前の章のグラフを組みます。
コードから状態を伝える
コード側の仕事は「速度と接地から今の状態を決めて、AnimationTree に伝える」だけです。

extends CharacterBody2D
const WALK_SPEED := 100.0
const RUN_SPEED := 250.0
const JUMP_VELOCITY := -400.0
@onready var animation_tree: AnimationTree = $AnimationTree
# ステートマシンをコードから操作するための窓口
@onready var state_machine: AnimationNodeStateMachinePlayback = animation_tree.get("parameters/playback")
var gravity: float = ProjectSettings.get_setting("physics/2d/default_gravity")
func _ready() -> void:
animation_tree.active = true
func _physics_process(delta: float) -> void:
if not is_on_floor():
velocity.y += gravity * delta
if Input.is_action_just_pressed("ui_accept") and is_on_floor():
velocity.y = JUMP_VELOCITY
var direction := Input.get_axis("ui_left", "ui_right")
var target_speed := 0.0
if direction:
target_speed = RUN_SPEED if Input.is_action_pressed("ui_sprint") else WALK_SPEED
velocity.x = move_toward(velocity.x, direction * target_speed, 20.0)
move_and_slide()
_update_animation()
func _update_animation() -> void:
var target_state := "jump"
if is_on_floor():
var speed := absf(velocity.x)
if speed > 180.0:
target_state = "run"
elif speed > 10.0:
target_state = "walk"
else:
target_state = "idle"
# 状態が変わったときだけ travel() する(毎フレーム呼ばない)
if state_machine.get_current_node() != target_state:
state_machine.travel(target_state)
_update_animation() は「速度と接地」から状態名を1つ決め、それが今の状態と違うときだけ travel() します。アニメの実体(何コマ目で足が地面に着くか等)は AnimationPlayer 側に任せ、コードは状態名を渡すだけ——この分担が、状態が増えても破綻しない秘訣です。
よくある間違いとベストプラクティス
| よくある間違い | ベストプラクティス |
|---|---|
毎フレーム無条件に travel() を呼ぶ | 今の状態と違うときだけ travel() する(上の get_current_node() チェック) |
| 巨大な単一ステートマシン | 関連する状態をまとめ、ネストしたサブステートマシン(地上/空中など)に分ける |
| 速度などをマジックナンバーで直書き | const RUN_SPEED := 250.0 のように定数や @export にして調整しやすくする |
| 遷移の X-Fade Time が0 | 0.1〜0.3秒のクロスフェードを設定し、アニメ同士を滑らかに補間する |
とくに効果が大きいのが、遷移の X-Fade Time です。0のままだとアニメがパチッと切り替わりますが、少しフェードを入れるだけで、歩き→走りの繋ぎが見違えるほど自然になります。

コードだけで組む場合との使い分け
AnimationTree を使わず、AnimationPlayer を直接 play() してもアニメは切り替えられます。
func _physics_process(delta: float) -> void:
if not is_on_floor():
$AnimationPlayer.play("jump")
elif absf(velocity.x) > 180.0:
$AnimationPlayer.play("run")
elif absf(velocity.x) > 10.0:
$AnimationPlayer.play("walk")
else:
$AnimationPlayer.play("idle")

状態が3〜4個の単純なキャラなら、これで十分わかりやすいこともあります。ただし状態が増えると if がネストし、遷移条件やフェードの管理も手書きになってスパゲッティ化しがちです。 状態が5つ以上になる、または滑らかな遷移が欲しくなったら AnimationTree へ 、と覚えておくとよいでしょう。「状態そのものの管理」の考え方は ステートマシンで管理するAIとプレイヤーの状態 も参考になります。
おまけ:次に知っておくと良いこと
AnimationTree に慣れてきたら、次のノードが表現の幅を広げてくれます。今は「そういう手がある」と知っておくだけで十分です。
BlendSpace2D/BlendSpace1D:速度や方向のベクトルに応じて、複数アニメを滑らかにブレンドします。8方向移動の歩きアニメなどに最適です。AnimationNodeOneShot:攻撃やアイテム使用のように「一度だけ再生して元の状態へ戻る」動きに便利です。移動アニメの上に攻撃を重ねられます。- ネストしたステートマシン :状態が増えたら「地上」「空中」で大きく分け、その中に待機・歩行や、ジャンプ・落下のサブ状態を作ると整理できます。
- 単発の色演出は Tween で :被弾フラッシュのような単発演出は、ステートマシンに載せず Tween で足すのが手軽です。
まとめ
AnimationTree:AnimationPlayer(アニメの倉庫)からクリップを取り出し、遷移を制御する司令塔- ステートマシン :状態 (ノード)と遷移(矢印)で、アニメの切り替えを見えるグラフにする
- コードの仕事 :速度や接地から「今の状態」を決め、
travel()で伝えるだけ。アニメの中身は倉庫に任せる - 使い分け :状態が少なければコード直書き、増えたら
AnimationTree。滑らかさは X-Fade で足す
if-elif の山をグラフに置き換えると、キャラクターの動きを足したり直したりが一気に楽になります。まずは待機・歩行・走行・ジャンプの4状態から組んでみて、慣れたら BlendSpace や OneShot で方向ブレンドや攻撃を重ねていきましょう。関連して AnimatedSprite2DとAnimationPlayerの使い分け も押さえると、2Dアニメの選択肢が整理できます。