3Dキャラクターに歩く・走る・攻撃するといった動きを付けたい。でも「アニメの切り替えがカクつく」「坂道や段差で足が地面にめり込む」「状態管理が複雑になる」——3Dアニメには2Dにはない悩みがつきものです。
この記事では、3Dならではの部分——Skeleton3D(骨格)とボーン、AnimationPlayer での再生、BlendSpace による方向ブレンド、SkeletonIK3D による接地調整—— を中心に解説します。AnimationTree とステートマシンの基本的な考え方は2Dと共通なので、AnimationTreeとステートマシン と合わせて読むと理解が深まります。
この記事でわかること
Skeleton3Dのボーン階層と、スクリプトからのボーン取得- GLBインポートで自動生成される
AnimationPlayerでの再生・ブレンドBlendSpace1D/2Dで速度・方向に応じてアニメを混ぜる方法SkeletonIK3Dで足を地面に接地させる動的調整(と非推奨化の注意)
Skeleton3Dとボーン階層
アニメーションを扱う前に、まずは骨格の仕組みを押さえておきましょう。すべてのスケルタルアニメーションは、このボーン階層の上に成り立っています。
Skeleton3D は3Dキャラクターの骨格構造を表すノードです。人間の骨格のように階層的に配置されたボーンを、時間経過で動かすことでアニメーションが生まれます。

腰(Hips)のような根元のボーンを親に、背骨・頭・腕・脚が子としてぶら下がります。親を動かすと子もついてくる——この親子関係が、体全体をまとめて動かす土台になります。
インポートしたモデルにどんなボーンがあるかは、スクリプトから一覧できます。
var skeleton := $MeshInstance3D.find_child("Skeleton3D", true, false) as Skeleton3D
print("ボーン数: ", skeleton.get_bone_count())
for i in skeleton.get_bone_count():
print(" ", skeleton.get_bone_name(i))
# 特定のボーンのインデックスと現在のポーズを取得
var head_idx := skeleton.find_bone("Head")
if head_idx != -1:
var pose := skeleton.get_bone_pose(head_idx) # Transform3D
print("頭のローカル位置: ", pose.origin)
AnimationPlayerで3Dアニメを再生する
AnimationPlayer は最も基本的なアニメーション再生ノードです。3Dモデル(GLB)をインポートすると、MeshInstance3D・Skeleton3D と一緒に自動生成され、すぐ使える状態になっています。

基本再生
@onready var anim_player: AnimationPlayer = $AnimationPlayer
func _ready() -> void:
for anim_name in anim_player.get_animation_list():
print(" ", anim_name)
if anim_player.has_animation("idle"):
anim_player.play("idle")
ブレンド時間で切り替えを滑らかに
切り替えた瞬間にカクッと見えるのは、ブレンドなしで一瞬で切り替わっているためです。set_blend_time() で滑らかな遷移になります。
# アニメーション間のブレンド時間(秒)を設定しておく
anim_player.set_blend_time("idle", "walk", 0.2)
anim_player.set_blend_time("walk", "run", 0.3)
func change_to_walk() -> void:
anim_player.play("walk") # idleから0.2秒かけてwalkへ
再生速度とシグナル
anim_player.play("walk", -1, 2.0) # 2倍速
anim_player.play_backwards("walk") # 逆再生
func _ready() -> void:
# 攻撃モーションが終わったら待機へ戻す、といった制御に使う
anim_player.animation_finished.connect(_on_animation_finished)
func _on_animation_finished(anim_name: String) -> void:
if anim_name == "attack":
anim_player.play("idle")
AnimationPlayer単体の作り込み(メソッド呼び出しトラック、RESETなど)は AnimationPlayerの高度な活用 を参照してください。
AnimationTreeで状態を管理する
歩き・走り・ジャンプ・攻撃と状態が増えたら、AnimationTree のステートマシンでまとめます。 考え方は2Dとまったく同じ で、AnimationPlayer(アニメの倉庫)から AnimationTree(司令塔)がクリップを取り出し、コードは travel() で「今の状態」を伝えるだけです。
@onready var anim_tree: AnimationTree = $AnimationTree
@onready var state_machine: AnimationNodeStateMachinePlayback = anim_tree.get("parameters/playback")
func _ready() -> void:
anim_tree.active = true
func _physics_process(delta: float) -> void:
var speed := velocity.length()
var target := "idle"
if speed > 0.1:
target = "run" if Input.is_action_pressed("sprint") else "walk"
if state_machine.get_current_node() != target: # 変わったときだけ
state_machine.travel(target)
ステートマシンの設計(ノード追加・遷移・Xfade Time)や、コード側との分担の詳しい考え方は AnimationTreeとステートマシン で解説しています。ここからは、3Dで特に役立つ BlendSpace と IK に進みます。
BlendSpaceで速度・方向をブレンドする
ステートマシンが「歩き」「走り」のような離散的な状態を切り替えるのに対し、速度や方向のように 連続的に変化する値 には BlendSpace が向いています。
BlendSpace1D は1つの値(例:移動速度)でブレンドします。速度0で待機、3で歩き、10で走り、を滑らかに繋げます。
# エディタでBlendSpace1Dを作り、0.0=idle / 3.0=walk / 10.0=run を配置
var speed := velocity.length()
anim_tree.set("parameters/movement/blend_position", speed)
BlendSpace2D は2次元のベクトル(例:移動方向)でブレンドします。アクションRPGのように全方向へ動くキャラの、8方向歩きアニメに最適です。

# 四隅と上下左右に walk_forward / walk_right などを配置しておく
var dir := Vector2(
Input.get_axis("move_left", "move_right"),
Input.get_axis("move_back", "move_forward")
)
anim_tree.set("parameters/locomotion/blend_position", dir)
SkeletonIK3Dで足を地面に合わせる
アニメーションデータだけでは、地形の凹凸に足を合わせたり、動く物体に手を伸ばしたりといった その場の調整 ができません。ここで使うのが IK(Inverse Kinematics/逆運動学)です。

SkeletonIK3D は、足首から先を目標位置へ合わせるようにボーンを逆算し、足の接地や手の到達を実現します。平地用の歩きアニメのままでも、段差や坂で足が地面にぴたっと着くようになります。
tips:
SkeletonIK3DはGodot 4.xで非推奨の方向にあり、SkeletonModifier3D系への移行が進んでいます。新規プロジェクトではSkeletonModifier3Dの利用を検討してください。既存プロジェクトではSkeletonIK3Dも引き続き動作しますが、将来のバージョンで削除される可能性があります。
# Skeleton3D の下に SkeletonIK3D(Root Bone: Hips / Tip Bone: FootL)を置き、
# Target に地面の位置を表す Node3D を割り当てておく
@onready var foot_ik: SkeletonIK3D = $Skeleton3D/FootIK_L
@onready var foot_target: Node3D = $FootTarget_L
func _ready() -> void:
foot_ik.start() # IKを開始
func _process(delta: float) -> void:
# 足の下へレイを飛ばし、地面の高さへターゲットを合わせる
var space := get_world_3d().direct_space_state
var from := foot_target.global_position + Vector3.UP
var to := foot_target.global_position + Vector3.DOWN * 2.0
var hit := space.intersect_ray(PhysicsRayQueryParameters3D.create(from, to))
if hit:
foot_target.global_position = hit.position
IKの効き具合は interpolation(0.0=無効〜1.0=完全適用)で調整でき、stop() で一時的に切ることもできます。
実践:移動ブレンド+接地IKを組む
3Dアクションやオープンワールド、TPSなどで「移動速度に応じて歩き↔走りが混ざり、坂道や段差では足がちゃんと地面に着く」キャラを作ってみます。コツは、 アニメ(大まかな動き)とIK(接地の微調整)を別の役割として重ねる ことです。
- アニメ側 :
BlendSpaceやAnimationTreeが、速度から歩き/走りのポーズを作る - IK側 :足の下へレイキャストして地面を探し、
SkeletonIK3Dが足首をそこへ合わせる

@onready var anim_tree: AnimationTree = $AnimationTree
@onready var foot_ik: SkeletonIK3D = $Skeleton3D/FootIK_L
@onready var foot_target: Node3D = $FootTarget_L
func _ready() -> void:
anim_tree.active = true
foot_ik.start()
func _physics_process(delta: float) -> void:
# アニメ: 速度をブレンド位置に渡す(歩き↔走り)
anim_tree.set("parameters/locomotion/blend_position", velocity.length())
# IK: 足の下の地面の高さへターゲットを合わせる
var space := get_world_3d().direct_space_state
var from := foot_target.global_position + Vector3.UP
var to := foot_target.global_position + Vector3.DOWN * 2.0
var hit := space.intersect_ray(PhysicsRayQueryParameters3D.create(from, to))
if hit:
foot_target.global_position = hit.position
ポイントは2つです。
- アニメとIKは役割が違う :アニメは「歩いている」という大きな動きを、IKは「その足を、今いる地面の高さに合わせる」という微調整を担当します。分けておくと、同じ歩きアニメのまま平地でも坂でも自然に見えます。
- 重いのはIK :IKは毎フレーム計算します。次のように、遠くのキャラや画面外のキャラではIKを止めて負荷を下げます。
パフォーマンス最適化
リッチなアニメーションは魅力的ですが、大量のキャラがいるシーンでは処理負荷が問題になります。カメラからの距離に応じて、処理を段階的に減らしましょう。

画面の端に小さく映るキャラを、主人公と同じ精度で更新する必要はありません。
func _process(delta: float) -> void:
var distance := global_position.distance_to(camera.global_position)
if distance > 50.0:
anim_tree.active = false # 遠距離: 処理を停止
else:
anim_tree.active = true
if distance > 20.0 and Engine.get_process_frames() % 2 != 0:
return # 中距離: 1フレームおきにスキップ
# 近距離: 通常更新
tips:
anim_tree.active = falseにするとアニメーション 処理が完全に止まります。遠距離ではactiveを false にし、中距離ではactiveを true のまま更新をスキップするのが安全です。あわせて、プレイヤー以外のIK(foot_ik.stop())を切る、インポート時にアニメーション圧縮(Compression: Lossy / Optimize)を有効にする、といった手も効きます。
まとめ
Skeleton3D:ボーンの親子階層。親を動かすと子もついてくるのが土台AnimationPlayer:GLBインポートで自動生成。play()とset_blend_time()で再生・ブレンドAnimationTree:状態管理は2Dと同じ考え方(AnimationTreeとステートマシン)。3DではBlendSpaceの方向ブレンドが強力SkeletonIK3D:足の接地など動的なポーズ調整(非推奨化が進行中、SkeletonModifier3Dへの移行を推奨)- 最適化 :距離に応じたLOD、不要なIKの停止、アニメーション圧縮
まずはGLBを読み込んで AnimationPlayer で歩き・走りを再生し、慣れてきたら BlendSpace で方向ブレンド、SkeletonIK3D で接地、と一段ずつ重ねていきましょう。被弾フラッシュのような単発演出は Tween で足すのが手軽です。