概要
動作確認環境: Godot 4.3+
3Dキャラクターに歩く・走る・攻撃するなどの動きをつけたいが、「アニメーションの切り替えがカクカクする」「状態管理が複雑になってしまう」といった問題に悩んでいませんか?
この記事では、Godot 4のAnimationPlayerによる基本的なアニメーション再生、AnimationTreeによる状態管理とブレンド、SkeletonIK3Dによる動的ポーズ調整を実例で解説します。
Skeleton3Dとボーン階層
アニメーションを扱う前に、まずは骨格の仕組みを理解しておきましょう。すべてのスケルタルアニメーションはこのボーン階層の上に成り立っています。
Skeleton3D は3Dキャラクターの骨格構造を表現するノードです。人間の骨格のように階層的に配置されたボーンを、時間経過で動かすことでアニメーションが実現されます。
基本構造
CharacterBody3D (ルート)
├─ MeshInstance3D (見た目)
│ └─ Skeleton3D (骨格)
│ ├─ BoneAttachment3D (武器などをアタッチ)
│ └─ ...
└─ AnimationPlayer (アニメーション再生)
ボーン情報の取得
インポートしたモデルにどんなボーンがあるか確認したいときは、以下のようにスクリプトからボーン一覧を取得できます。
var skeleton = $MeshInstance3D.find_child("Skeleton3D", true, false)
print("ボーン数: ", skeleton.get_bone_count())
print("ボーン名一覧:")
for i in skeleton.get_bone_count():
print(" ", skeleton.get_bone_name(i))
# 特定のボーンのインデックスを取得
var head_bone_idx = skeleton.find_bone("Head")
if head_bone_idx != -1:
var pose = skeleton.get_bone_pose(head_bone_idx)
print("頭の位置: ", pose.origin)
AnimationPlayerによる再生
骨格が理解できたら、次はアニメーションを再生してキャラクターを動かしてみましょう。
AnimationPlayer は最も基本的なアニメーション再生ノードです。3Dモデルをインポートすると自動的に作成され、すぐに使える状態になっています。
基本再生
@onready var anim_player = $AnimationPlayer
func _ready():
# アニメーション一覧を確認
print("登録されているアニメーション:")
for anim_name in anim_player.get_animation_list():
print(" ", anim_name)
# アニメーションを再生
if anim_player.has_animation("idle"):
anim_player.play("idle")
func walk():
anim_player.play("walk")
func run():
anim_player.play("run")
ブレンド時間の設定
アニメーションを切り替えたときにカクッと見えるのは、ブレンドなしで瞬時に切り替わっているためです。set_blend_time()で滑らかな遷移を実現できます。
# アニメーション間のブレンド時間を設定(秒)
anim_player.set_blend_time("idle", "walk", 0.2)
anim_player.set_blend_time("walk", "run", 0.3)
anim_player.set_blend_time("run", "idle", 0.4)
func change_to_walk():
anim_player.play("walk") # 0.2秒かけてidleからwalkへ遷移
再生速度の調整
# 通常速度
anim_player.play("walk", -1, 1.0)
# 2倍速
anim_player.play("walk", -1, 2.0)
# 0.5倍速(スローモーション)
anim_player.play("walk", -1, 0.5)
# 逆再生
anim_player.play_backwards("walk")
シグナルを使った制御
攻撃モーションが終わったら待機状態に戻す、といった制御にはシグナルが便利です。
func _ready():
anim_player.animation_finished.connect(_on_animation_finished)
anim_player.play("attack")
func _on_animation_finished(anim_name: String):
if anim_name == "attack":
print("攻撃アニメーション完了")
anim_player.play("idle") # 待機状態に戻る
AnimationTreeによる状態管理
AnimationPlayerだけ でも基本的な再生はできますが、歩き・走り・ジャンプ・攻撃と状態が増えてくると管理が大変になります。そこで登場するのがAnimationTreeです。
AnimationTree は複数のアニメーションを状態マシンやブレンドで管理する高度なシステムです。歩き・走り・攻撃などの複雑な遷移を、コードを最小限にして実装できます。
基本セットアップ
@onready var anim_tree = $AnimationTree
@onready var state_machine = anim_tree.get("parameters/playback")
func _ready():
anim_tree.active = true # AnimationTreeを有効化
func _process(delta):
var velocity = get_velocity()
if velocity.length() < 0.1:
state_machine.travel("idle")
elif Input.is_action_pressed("sprint"):
state_machine.travel("run")
else:
state_machine.travel("walk")
状態マシンの構築(エディタ)
エディタで視覚的に状態遷移を設計できます。
- AnimationTree ノードを追加
- インスペクタで Tree Root > AnimationNodeStateMachine を選択
- AnimationTree パネル下部で以下を作成:
- 「Add Animation」で
idle、walk、runノードを追加 - 各ノードを右クリック→「Connect to...」で遷移を作成
- 遷移線を選択し、「Xfade Time」を0.2に設定
- 「Add Animation」で
プログラムからの状態遷移
# 状態を強制的に変更
state_machine.travel("attack")
# 現在の状態を取得
var current_state = state_machine.get_current_node()
print("現在の状態: ", current_state)
# パラメータを設定(BlendSpace用)
anim_tree.set("parameters/movement/blend_position", velocity.length())
BlendSpaceによる方向制御
状態マシンでは離散的な状態を切り替えますが、移動速度や方向のように連続的に変化する値にはBlendSpaceが適しています。
BlendSpace1D/2D は入力値に応じて複数のアニメーションをブレンドする機能です。速度0なら待機、速度5なら歩き、速度10なら走りのように、滑らかに遷移させられます。
BlendSpace1Dの例(移動速度)
# エディタでBlendSpace1Dノードを作成し、以下を設定:
# - Min/Max: 0.0 / 10.0
# - Add Blend Point:
# - 0.0 = idle
# - 3.0 = walk
# - 10.0 = run
# コードから制御
var speed = velocity.length()
anim_tree.set("parameters/movement_blend/blend_position", speed)
BlendSpace2Dの例(8方向移動)
アクションRPGのようにキャラクターが全方向に動くゲームでは、2Dのブレンドスペースが活躍します。
# エディタでBlendSpace2Dノードを作成し、以下を設定:
# - X/Y軸: -1.0 / 1.0
# - Add Blend Point:
# - (0, 1) = walk_forward
# - (0, -1) = walk_backward
# - (1, 0) = walk_right
# - (-1, 0) = walk_left
# - (1, 1) = walk_forward_right
# # ...その他の方向
# コードから制御
var direction = Vector2(
Input.get_axis("move_left", "move_right"),
Input.get_axis("move_back", "move_forward")
).normalized()
anim_tree.set("parameters/locomotion/blend_position", direction)
SkeletonIK3Dによる動的調整
アニメーションデータだけでは、地形の凹凸に足を合わせたり、動いている物体に手を伸ばしたりといった動的な調整ができません。ここではIK(逆運動学)を使ったリアルタイムのポーズ調整を見ていきます。
tips: SkeletonIK3D はGodot 4.xで非推奨の方向にあり、SkeletonModifier3D 系への移行が進んでいます。新規プロジェクトでは SkeletonModifier3D の利用を検討してください。既存プロジェクトでは SkeletonIK3D は引き続き動作しますが、将来のバージョンで削除される可能性があります。
SkeletonIK3D は Inverse Kinematics(逆運動学)により、足が地面に接地する、手が目標を掴むなどの動的ポーズ調整を実現します。アニメーションだけでは対応できない、地形の凹凸への対応などに使用します。
基本セットアップ
# シーン構造
# Skeleton3D
# └─ SkeletonIK3D (足のIK)
# - Root Bone: "Hips"
# - Tip Bone: "FootL"
# - Target: Node3Dノード(地面の位置)
@onready var foot_ik = $Skeleton3D/FootIK_L
@onready var ground_target = $GroundTarget_L
func _ready():
foot_ik.start() # IKを開始
func _process(delta):
# 地面の位置を検出
var space_state = get_world_3d().direct_space_state
var query = PhysicsRayQueryParameters3D.create(
global_position + Vector3.UP,
global_position + Vector3.DOWN * 10
)
var result = space_state.intersect_ray(query)
if result:
ground_target.global_position = result.position
IKの制御
# IKの影響度を調整(0.0=無効, 1.0=完全適用)
foot_ik.interpolation = 1.0 # 完全適用
foot_ik.interpolation = 0.5 # 50%ブレンド
# IKを一時的に無効化
foot_ik.stop()
# ...
foot_ik.start()
パフォー マンス最適化
ここまでの機能を使えばリッチなアニメーションが実装できますが、大量のキャラクターがいるシーンでは処理負荷が問題になります。距離に応じて処理を軽減しましょう。
LODによる更新頻度制御
画面の端に小さく映っているキャラクターに、プレイヤーキャラと同じ精度でアニメーションを更新する必要はありません。カメラからの距離で処理を段階的に減らしていきます。
# 距離に応じてアニメーション更新を最適化
func _process(delta):
var distance = global_position.distance_to(camera.global_position)
if distance > 50.0:
# 遠距離: AnimationTree自体を無効化
anim_tree.active = false
elif distance > 20.0:
# 中距離: activeのまま、フレームスキップで負荷軽減
anim_tree.active = true
if Engine.get_process_frames() % 2 != 0:
return # 1フレームおきにスキップ
else:
# 近距離: 通常更新
anim_tree.active = true
tips:
anim_tree.active = falseにするとアニメーション処理が完全に停止します。advance()はactive = trueの状態で呼ぶのが前提です。遠距離では active を false にし、中距離では active を true のまま処理をスキップする方法が安全です。
不要なIKの無効化
# プレイヤーキャラ以外はIKを無効化
if not is_player_character:
foot_ik.stop()
hand_ik.stop()
アニメーション圧縮
# インポート設定で圧縮を有効化
# .glbファイルを選択 > Import > Animation:
# - Compression: Lossy
# - Optimize: true
# - Position Error: 0.01
# - Rotation Error: 0.01
まとめ
- AnimationPlayer は基本的なアニメーション再生に使用し、
play()とset_blend_time()で制御する - AnimationTree は状態マシンで複雑なアニメーション遷移を管理し、
travel()で状態を切り替える - BlendSpace1D/2D は速度や方向に応じてアニメーションをブレンドし、
blend_positionで制御する - SkeletonIK3D は足の接地や手の到達などの動的ポーズ調整を実現する(非推奨化が進行中、SkeletonModifier3D への移行を推奨)
- パフォーマンス向上には距離に応じたLOD、不要なIKの無効化、アニメーション圧縮が有効
animation_finishedシグナルでアニメーション完了を検知し、次の動作に遷移する