🎮

Godot4 アニメーションの実装

2023/10/05に公開

見栄えを良くする

今画面が味気ないので少し見栄えを良くします
背景/床のテクスチャを用意します
自分はambientCGで用意しました
用意した画像はエディタの左下のファイルシステムにドラッグするか
直にプロジェクトのフォルダに入れてください
Godotが自動的に読み込んでくれます

背景、ライトの設定

次にノードを作成します
WorldEmvironmentとDirectionalLight3Dを追加します

WorldEmvironmentで背景の設定をします
Panoramaからクイックロードを選ぶとプロジェクトに入れた画像が一覧に出てくるので背景画像を設定します

DirectionalLight3Dで影の設定をします
光の向きはDirectionalLight3Dを回転していい感じにしてください

床のテクスチャの設定

設定場所は床のMeshInstance3Dから以下の場所
textureからクイックロードで床の画像を選択します

少し見栄えが良くなりました

アニメーションつきキャラクタの準備

前回この辺りが適当だったので少し丁寧に説明します
mixamoを使い場合の手順は次の通り

  • キャラクタを選ぶ
  • アニメーション付きモデルをダウンロードする
  • Blenderでアニメーションを1つにまとめる

mixamoのサイトに行きます
Charactersのタブから好きなキャラを選びます
Animationsのタブから待機/歩き/走り/ジャンプ/攻撃の5つのアニメーションを選んでダウンロードします
これ以外にも必要そうなものがあればダウンロードしておくとよいです
形式はFBXでダウンロードします

アニメーションのまとめ方

Blenderを使ってアニメーションをまとめます

  • 「ファイル>インポート>FBX」から全部のFBXファイルを選択
  • Armatureがたくさん出てくるので、1つを除いて全部選択
  • 右クリックから「階層の削除」を選ぶ
  • アニメーションの名前を変更する(やり方は次に記します)
  • 「ファイル>エクスポート>glTF」でエクスポートする
    player.glbという名前で出力し、Godotのプロジェクトフォルダに入れておきます

アニメーションの名前の変更方法

画面右上のアウトライナーでArmatureを選択しておきます
次に画面下部のタイムラインのウィンドウで名前を変更していきます

  1. ドープシートを選択する
  2. アクションを選択する
  3. 名前を変更したいアニメーションを選択する
  4. 変更したい名前を入力する

AnimationTreeを使ったアニメーションの制御

前回作ったplayerシーンからArmatureとAnimationPlayerを削除します
代わりに今回作ったキャラクターのモデルを追加します
player.glbをplayerシーンに追加してノードを右クリックから「ローカルにする」を選びます
ローカルにするとplayerシーンからArmatureやAnimationを操作できるようになります
AnimationTreeノードも追加しておきます
下はplayer.glbのノード名をPlayerModelに変更してます

StateMachineの設定

今回はStateMachineを使ってアニメーションの制御をします

  • AnimationTreeのTreeRootに「AnimationNodeStateMashine」を設定
  • AnimPlayerにPlayerModel下のAnimationPlayerを設定
    AnimationTreeではAnimPlayerに設定されたアニメーションが使えるようになります


シーンからAnimationTreeを選択すると画面下のアニメーションのタブから編集ができます

今回作るStateMachineは以下のようになります

  • 右クリックからStateMashin/Animation/OneShotを追加して以下のように繋ぐ
  • StateMashinの名前をLocomotionに設定
  • Animationに攻撃用のアニメーションを設定
  • OneShotの名前をAttack、Fadein/Fadeoutの時間を0.2sくらいにする
    Fadein/Fadeoutはアニメーションの遷移にかかる時間で、0にすると瞬時に切り替わります

Locomotionの設定

  • StateMashinとジャンプのアニメーションを追加し以下のように繋ぐ
  • StateMashinの名前をLandにする
  • LandとJump間の矢印はXfade Timeを0.2sに、Advance>Conditionにlanded/fallingを設定

Advance>Conditionは遷移の条件で、スクリプトで値の設定を行います

Landの設定

  • 待機/歩き/走りのAnimationを追加し以下のように繋ぐ
  • それぞれの矢印はXfade Timeを0.2sにする
  • Idleにつながる矢印はAdvance>Conditionにidelを設定
  • Runにつながる矢印はAdvance>Conditionにrunを設定
  • Walkにつながる矢印はAdvance>Conditionにwalkを設定

Startから出ている矢印はいじらなくてよいです

スクリプトでAnimationTreeの制御

player.gd
extends CharacterBody3D

@export var JUMP_VELOCITY = 4.5
@export var WALK_SPEED = 5.0
@export var CROUCH_SPEED = 2.0
@export var SPRINT_SPEED = 10.0
@export var LERP_SPEED = 10
var current_speed
var direction = Vector3.ZERO

# Get the gravity from the project settings to be synced with RigidBody nodes.
var gravity = ProjectSettings.get_setting("physics/3d/default_gravity")
var camera_controller: Node3D
@onready var animation_tree = $AnimationTree
@export var is_attack = false


func _ready():
	Input.set_mouse_mode(Input.MOUSE_MODE_CAPTURED)
	camera_controller = get_tree().get_nodes_in_group("camera_controller")[0]

func _physics_process(delta):

	if Input.is_action_pressed("sprint"):
		current_speed = SPRINT_SPEED
	else:
		current_speed = WALK_SPEED

	# Add the gravity.
	if not is_on_floor():
		velocity.y -= gravity * delta

	# Handle Jump.
	if Input.is_action_just_pressed("jump") and is_on_floor():
		velocity.y = JUMP_VELOCITY

	if Input.is_action_just_pressed("attack") and is_on_floor() and !is_attack:
		animation_tree.set("parameters/Attack/request",AnimationNodeOneShot.ONE_SHOT_REQUEST_FIRE)
		is_attack=true

	# Get the input direction and handle the movement/deceleration.
	# As good practice, you should replace UI actions with custom gameplay actions.
	var input_dir = Vector2.ZERO if is_attack else Input.get_vector("left", "right", "up", "down")
	direction = lerp( direction, (camera_controller.transform.basis * Vector3(input_dir.x, 0, input_dir.y)).normalized(), delta * LERP_SPEED)

	if direction:
		look_at(global_position + direction)
		velocity.x = direction.x * current_speed
		velocity.z = direction.z * current_speed
	else:
		velocity.x = move_toward(velocity.x, 0, current_speed)
		velocity.z = move_toward(velocity.z, 0, current_speed)

	move_and_slide()

	var is_falling = !is_on_floor()
	var is_landing = !is_falling
	var is_idle = input_dir==Vector2.ZERO
	var is_walk = !is_idle && current_speed == WALK_SPEED
	var is_run = !is_idle && !is_walk;
	animation_tree.set("parameters/Locomotion/conditions/falling",is_falling)
	animation_tree.set("parameters/Locomotion/conditions/landed",is_landing)
	animation_tree.set("parameters/Locomotion/Land/conditions/idle",is_idle)
	animation_tree.set("parameters/Locomotion/Land/conditions/walk",is_walk)
	animation_tree.set("parameters/Locomotion/Land/conditions/run",is_run)

アニメーションに関係する部分のみ解説します

AnimationTreeにアクセスするための変数と攻撃中のフラグを用意します
攻撃中のフラグは外からアクセスできるように@exportします

@onready var animation_tree = $AnimationTree
@export var is_attack = false

攻撃ボタンが押されたときにOneShotで設定した攻撃アニメーションを再生します
animation_tree.setのパスは補完がききますが、ただの文字列なのでAnimationTreeを編集してパスが変わった時には注意してください
AnimationTreeのインスペクタからパスをコピーすることもできます

	if Input.is_action_just_pressed("attack") and is_on_floor() and !is_attack:
		animation_tree.set("parameters/Attack/request",AnimationNodeOneShot.ONE_SHOT_REQUEST_FIRE)
		is_attack=true

攻撃中は移動ができないようにinput_dirをVector2.ZEROにします

	var input_dir = Vector2.ZERO if is_attack else Input.get_vector("left", "right", "up", "down")
	direction = lerp( direction, (camera_controller.transform.basis * Vector3(input_dir.x, 0, input_dir.y)).normalized(), delta * LERP_SPEED)

StateMashineの遷移の条件を設定します
指定のStateにいるときに値がtrueになればアニメーションが遷移します

	var is_falling = !is_on_floor()
	var is_landing = !is_falling
	var is_idle = input_dir==Vector2.ZERO
	var is_walk = !is_idle && current_speed == WALK_SPEED
	var is_run = !is_idle && !is_walk;
	animation_tree.set("parameters/Locomotion/conditions/falling",is_falling)
	animation_tree.set("parameters/Locomotion/conditions/landed",is_landing)
	animation_tree.set("parameters/Locomotion/Land/conditions/idle",is_idle)
	animation_tree.set("parameters/Locomotion/Land/conditions/walk",is_walk)
	animation_tree.set("parameters/Locomotion/Land/conditions/run",is_run)

アニメーションからプロパティを変更

このままでは攻撃しっぱなしなのでアニメーションが終わったら攻撃中のフラグをオフにします

  • playerシーンからAnimationPlayerを選択しアニメーションの画面で攻撃のアニメーションを選択
  • playerシーンからルートノード(player.gdがアタッチされているノード)を選択
  • インスペクタからIs Attackの右にある鍵のアイコンを押す
  • 確認のダイアログが出るので作成を押す
  • AnimationTrackにキーが追加されているので攻撃の終わりの位置に移動する

キーを選択するとインスペクタから値の設定ができます
AnimationTreeのActiveをオフにするとAnimationPlayerが有効になって位置の確認ができます
後でオンにするのを忘れないでください
ゲーム開始時にオンにするスクリプトを書いてもいいかもしれないです

武器を持たせる

キャラクターの手の位置に武器を持たせるスロットを作成します
武器を切り替えるような処理で使います

武器スロットの作成

Armatureのボーンの位置を参照するにはBoneAttachment3Dノードを使います
Armatureの下に以下のようにノードを追加します
Node3DをWeaponSlotに名前を変えています

ボーンがきちんと認識されていればBoneNameの設定でボーンの一覧が出てくるので右手を選びます

武器の作成

sketchfabBOOTH等で武器のモデルを探してきます
自分はここからピコピコハンマーをダウンロードしBlenderでglbファイルに変換しました
変換したファイルをGodotのプロジェクトフォルダに入れます
武器のモデルを右クリックから「新しい継承シーン」でシーンを作成します
名前をweaponシーンとしておきます
weaponシーンをドラッグしてWeaponSlotの下に追加します

weaponシーンでは武器の大きさと、持ち手の位置が原点に来るように調整します

BoneAttachment3Dは手首の位置になっているのでWeaponSlotで手の位置/向きを調節します

これでアニメーションの完成です
OneShotだと攻撃と攻撃のつなぎ目がうまく出来なさそうなので、StateMashineで切り替えするのがいかもしれないです
https://youtu.be/C_jJ5NLqfPM

Discussion