概要
AnimationPlayerを「スプライトやUIを動かすノード」とだけ考えると、少しもったいないです。実制作では、見た目、判定、音、カメラ、UIを同じ時間軸へ並べるシーケンサーとして使う場面が多くなります。
たとえばアクションゲームの攻撃なら、見た目のフレームだけでなく、次の出来事が同じタイミングで起きます。
- 剣を振る見た目に変える
- 当たる瞬間だけ
Area2Dの判定を有効にする - 同じ瞬間に効果音を鳴らす
- ヒットストップやカメラ揺れを始める
- 終了後に通常状態へ戻す
これを全部_process()やTimerで合わせようとすると、秒数・フラグ・状態復帰が散らばります。AnimationPlayerへ寄せると、「どのタイミングで何が起きるか」を1本のタイムラインとして見られるようになります。
この記事でわかること
- トラック設計 ——位置・色・関数・音をどう分けるか
- メソッド呼び出しトラック ——判定・音・揺れをタイミングよく起こす考え方
- RESETアニメーション ——初期状態へ戻す基準を残す理由
AnimationPlayer/Tween/AnimationTreeの使い分け
トラックは役割ごとに分ける
AnimationPlayerのアニメーションは、複数のトラックで構成されます。トラックは「時間に沿って何を変えるか」を表します。

よく使うトラックは次の通りです。
| トラック | 何をするか | 実制作での例 |
|---|---|---|
| プロパティ | ノードの値を変える | position、scale、modulate、CollisionShape2D.disabled |
| メソッド呼び出し | 指定タイミングで関数を呼ぶ | 効果音再生、画面揺れ、弾の生成、状態変更 |
| オーディオ | 音をタイムラインへ配置する | 攻撃音、決定音、演出SE |
| アニメーション再生 | 別のアニメーションを呼ぶ | 子ノードの演出やUIパーツの連動 |
初心者がつまずきやすいのは、1つのアニメーションに「見た目の変更」と「ゲームロジック」を無秩序に詰め込むことです。トラック名と関数名から意図が読めるようにすると、後からタイミングを調整しやすくなります。
おすすめは、次のように責務を分けることです。
- 目で見える変化はプロパティトラック
- タイミングだけ合わせたい処理はメソッド呼び出しトラック
- 重い判断や計算はスクリプト側
- 何度も使うまとまりは別アニメーションや関数へ分ける
AnimationPlayerは便利ですが、万能のロジック置き場ではありません。タイムラインは「いつ起こすか」、スクリプトは「何を判断するか」と分けると、記事#23で扱った使い分けから一段進んだ設計になります。
メソッド呼び出しトラックでロジックと同期する
メソッド呼び出しトラックは、アニメーションの特定フレームでノードの関数を呼ぶための仕組みです。攻撃判定、効果音、カメラ揺れのように「この瞬間に起きてほしい」処理と相性が良いです。

たとえば攻撃演出なら、スクリプト側に小さな関数を用意します。
extends CharacterBody2D
@onready var attack_collision: CollisionShape2D = $AttackArea/AttackCollision
@onready var attack_sound: AudioStreamPlayer2D = $AttackSound
@onready var camera: Camera2D = $Camera2D
func enable_hitbox() -> void:
attack_collision.disabled = false
func disable_hitbox() -> void:
attack_collision.disabled = true
func play_attack_sound() -> void:
attack_sound.play()
func shake_camera() -> void:
# 実際の揺れ処理はCamera2D側の関数へ逃がしてもよい
camera.offset = Vector2(4, 0)
AnimationPlayerのattackアニメーションでは、剣が当たる瞬間にenable_hitbox()、play_attack_sound()、shake_camera()を呼び、振り終わりでdisable_hitbox()を呼びます。
ここで重要なのは、メソッド呼び出しトラックに大きな処理を詰め込みすぎないことです。
良い使い方:
- 判定をON/OFFする
- 効果音を鳴らす
- 画面揺れを開始する
- 弾を1発生成する
- 状態を
ATTACKINGからRECOVERへ変える
避けたい使い方:
- 敵を大量検索して複雑なダメージ計算を全部行う
- セーブデータ更新やロード処理を直接呼ぶ
- 重い生成処理を大量に同じフレームへ置く
- どの状態へ行くかの判断をタイムラインだけで隠す
呼び出す関数は小さくし、関数名から目的がわかるようにします。do_event()よりenable_hitbox()、play_attack_sound()、spawn_slash_effect()の方が、タイムラインを見たときに意味を追いやすくなります。
攻撃演出を1本のタイムラインにまとめる
実際の攻撃 アニメーションでは、次のようにトラックを分けると整理しやすくなります。
attack
├─ Sprite2D: frame
├─ AttackCollision: disabled
├─ Method: play_attack_sound()
├─ Method: shake_camera()
└─ Method: end_attack()
スクリプト側は、攻撃の開始と終了後の状態遷移を担当します。
extends CharacterBody2D
enum State { MOVE, ATTACK, RECOVER }
@onready var animation_player: AnimationPlayer = $AnimationPlayer
@onready var attack_collision: CollisionShape2D = $AttackArea/AttackCollision
var state: State = State.MOVE
func _ready() -> void:
attack_collision.disabled = true
animation_player.animation_finished.connect(_on_animation_finished)
func _unhandled_input(event: InputEvent) -> void:
if event.is_action_pressed("attack") and state == State.MOVE:
state = State.ATTACK
animation_player.play("attack")
func enable_hitbox() -> void:
attack_collision.disabled = false
func disable_hitbox() -> void:
attack_collision.disabled = true
func enter_recover() -> void:
# 攻撃直後の硬直へ移す。長さは別アニメーションで管理してもよい
state = State.RECOVER
func _on_animation_finished(anim_name: StringName) -> void:
if anim_name == "attack":
attack_collision.disabled = true
state = State.MOVE
このコードで大切なのは、AnimationPlayerがすべてを決めているわけではない点です。タイムラインは「攻撃の何フレーム目で判定を出すか」を担当し、状態遷移のルールはスクリプト側が持っています。
この分け方にすると、次の調整がしやすくなります。
- 攻撃の当たり始めを少し遅らせる
- 判定が出ている時間だけを短くする
- 効果音の位置を見た目に合わせる
- カメラ揺れを当たりフレームへ合わせる
- 攻撃後の硬直を別アニメーションとして伸ばす
RPGのスキル演出、タワーディフェンスの砲台発射、パズルゲームの連鎖演出でも考え方は同じです。処理の順序をスクリプトだけで読むのではなく、タイムラインとして見えるようにすることがAnimationPlayerの価値です。
RESETアニメーションで初期状態を守る
AnimationPlayerでプロパティを動かすほど、終了後の状態が残る問題が起きます。
- 攻撃判定をOFFに戻し忘れる
modulateで白くしたキャラが戻らない- UIの透明度が0のままになる
- カメラやパネルの位置がずれたままになる
- 途中でアニメーションを中断したら状態が半端に残る
この対策として、初期状態を記録したRESETアニメーションを用意しておくと安定します。RESETは、普段の表示、通常の色、判定OFF、UIの初期位置などを戻すための基準です。

たとえばプレイヤーなら、RESETに次の値を入れておきます。
| 対象 | 初期値の例 |
|---|---|
Sprite2D.modulate | Color.WHITE |
AttackCollision.disabled | true |
Camera2D.offset | Vector2.ZERO |
UIパネルのposition | 初期位置 |
UIパネルのmodulate.a | 1.0 |
ゲーム中に毎回RESETを再生する必要はありません。重要なのは、戻るべき基準をAnimationPlayer内に残しておくことです。アニメーションが増えてきたとき、「この演出が終わると何が初期値へ戻るべきか」を確認しやすくなります。
特に、複数の演出が同じプロパティを触る場合は、RESETの価値が上がります。攻撃で色を変え、被弾で点滅し、バフで発光するようなキャラクターでは、戻し忘れが見た目のバグとして出やすいからです。
AnimationPlayer、Tween、AnimationTreeの境界
アニメーション系の機能は複数あるため、AnimationPlayerに寄せすぎると逆に複雑になります。

目安は次の通りです。
| ツール | 向いていること | 例 |
|---|---|---|
AnimationPlayer | 事前に作った定型の時間制御 | 攻撃、カットシーン、UI表示、演出SE同期 |
Tween | コードから作る一回きりの動的補間 | ダメージ数字を上へ飛ばす、クリック位置へ移動する |
AnimationTree | 複数アニメーションの遷移・ブレンド | 待機、歩き、走り、ジャンプ、攻撃の状態管理 |
たとえば、宝箱を開ける演出はAnimationPlayerが向きます。フタの回転、光、効果音、アイテム表示のタイミングが決まっているからです。
一方で、敵が倒れた場所からランダムな位置へコインが飛ぶ演出はTweenが向きます。開始位置や終了位置が毎回変わるため、コードでその場の値から補間を作る方が自然です。
プレイヤーの移動アニメーションが、待機、歩き、走り、ジャンプ、落下、着地、回避へ増えてきたらAnimationTreeを考えます。これはAnimationPlayerが不要になるという意味ではありません。AnimationPlayerに個々のアニメーションを持たせ、AnimationTreeでどれを再生するかを制御する、という関係になります。
おまけ:先に知っておくと良いこと
AnimationLibraryで整理する
アニメーション数が増えてきたら、AnimationLibraryを使ってまとまりごとに整理できます。たとえば、move、attack、ui、cutsceneのように分けておくと、1つのAnimationPlayerに大量の名前が並んでも探しやすくなります。
ただし、最初から細かく分けすぎると管理が増えます。まずはアニメーション名をattack_light、attack_heavy、ui_open_menuのように揃え、数が増えてからライブラリ分割を考えるくらいで十分です。
物理判定と同期する処理は慎重に見る
攻撃判定のON/OFFをAnimationPlayerから行う場合、見た目のタイミングと物理判定のタイミングが少しずれて見えることがあります。特に、物理フレームで動くキャラクターと、表示フレームで更新されるアニメーションを混ぜる場合は注意が必要です。
まずは見た目として自然か、当たり判定が不公平に感じないかをゲーム内で確認します。厳密な同期が必要になったら、AnimationPlayerの処理コールバック設定や、判定を有効にする場所を見直します。早い段階で設定だけを細かく追うより、実際のプレイ感でズレを見つける方が判断しやすいです。
タイムラインに隠しすぎない
AnimationPlayerを使うと、コードから見えない場所で処理が起きます。これは便利ですが、デバッグ時に「どこで判定がONになったのか」が追いにくくなることもあります。
対策は、関数名とアニメーション名を具体的にすることです。
event_1()ではなくenable_hitbox()call_sound()ではなくplay_slash_sound()anim1で はなくattack_lightfinish()ではなくreturn_to_move_state()
タイムライン上のキーをクリックしたとき、関数名だけで意図がわかる状態にしておくと、未来の自分が助かります。
まとめ
AnimationPlayerは、単にものを動かすノードではなく、ゲーム内の出来事を時間軸で揃えるための道具です。
- トラックは役割ごとに分ける
- メソッド呼び出しトラックは小さな関数を呼ぶために使う
- 攻撃、UI、カットシーンのような定型シーケンスに向く
- RESETで戻るべき基準を残しておく
- 動的補間は
Tween、複雑な状態遷移はAnimationTreeも検討する
迷ったときは、最後にこの問いで判断します。
「これは毎回同じ流れで起きる演出か、それともその場の値から動的に作る動きか?」