見出し画像

Godot GDでポーズ操作

Godotエンジンで、スクリプトを使ってSkeleton3Dを直接動かす記事です。

AnimationPlayerって、体の部位ごとに違うアニメーションを再生する機能が無いんですね。AnimationTreeなら、Filterという機能でできるみたい。でもTree使いたくない~!
ならAnimationPlayer使わず、自分(GDScript)で動かすしかないよね。
自分で動かせば、表情を別アニメにしたり、首や腰を注視対象に向けたり、色々できます。諸々うれしい。


準備

Skeleton3Dノードの参照が必要です。あと、Blenderで作成したアニメーションが使いたければAnimationリソースもロードしておきます。これだけです。Skeleton3Dを操作することで、キャラクターを動かします。

忘れそうになりますが、Skeleton3Dはリソースではなく、ノードです。なので、複数同じアーマチュアのキャラクターがいても、コピーする必要はありません。

ポーズ

Animationを使わず、関節を曲げてポーズを取る例です。

# メンバ変数
var _skeleton: Skeleton3D = null

# ボーン名からBone Index取得
var bone_idx: int = _skeleton.find_bone("Arm.L")

# Rest Transform、いわゆるバインドポーズ
var rest: Transform3D = _skeleton.get_bone_rest(bone_idx)

# 特定部位を回転
# Restに差分を適用する
var rot_quat_rest: Quaternion = rest.basis.get_rotation_quaternion()
var rot_euler_sub: Vector3 = Vector3(deg_to_rad(-45.0), 0.0, 0.0)
var rot_quat_sub: Quaternion = Quaternion.from_euler(rot_euler_sub)
var rot_quat: Quaternion = rot_quat_rest * rot_quat_sub
_skeleton.set_bn_ps_rot(bone_idx, rot_quat)

これを実行すると、↓画像の通り。

腕が-45度曲がった~

まずRestを取得します。これがいわゆるバインドポーズです。親ボーンから見た子ボーンの相対姿勢です。上記画像の場合、垂直に生えているChestボーンに対してArm.Lボーンは横に生えてます。これがRestです。
そして、Restをさらに回転させて、最終的な姿勢を作ります。Rest×Subです。
それをSkeleton3Dにセットします。

ポーズ(Positionを変える)

回転ではなく、Translationを変える場合も同様の処理でいけます。

var bone_idx: int = _skeleton.find_bone("Hand.R")

var rest: Transform3D = _skeleton.get_bone_rest(bone_idx)
var pos_rest: Vector3 = rest.origin
var pos_sub: Vector3 = Vector3(0.0, 0.5, 0.0)
var pos: Vector3 = pos_rest + pos_sub

_skeleton.set_bn_ps_pos(bone_idx, pos)

実行すると↓の通り。

腕が伸びた~~~~~~

Restを取得して、差分を適用するのは回転と同じです。掛け算ではなく足し算で、Rest+Subですね。
Restは親ボーン(Arm.R)に対する子ボーン(Hand.R)の姿勢なので、+Y方向が伸びる方向(グローバルだと-X)になってます。これは回転でも同様に注意する必要があります。

Animation再生

AnimatinPlayerノードを使わないAnimationリソースの再生です。

# 再生する例

# メンバ変数
var _time: float = 0.0
var _skeleton: Skeleton3D = null
var _animation: Animation = null

# 時間を管理
# 仮にアニメの定義範囲をオーバーしてもAnimationの補間値取得関数はエラーなく動く
_time += sub_time
while _time > _animation.len:
	_time -= _animation.len

for track_idx: int in range(0, _animation.get_track_count()):
	# トラックに対応するBone Indexを探す
	var node_path: NodePath = _animation.track_get_path(track_idx)
	var bone_name: StringName = node_path.get_subname(0) # "Arma/Skeleton3D:Arm.L"の"Arm.L"を取り出す
	var bone_idx: int = _skeleton.find_bone(bone_name)

	# キーのタイプ(Pos/Rot/Scale)に応じて、
	# 補間値をゲットしてSkeletonにセット
	var track_type: Animation.TrackType = _animation.trk_get_type(track_idx)
	if track_type == Animation.TYPE_POSITION_3D:
		var val_pos: Vector3 = _animation.pos_trk_interp(track_idx, _time)
		_skeleton.set_bn_ps_pos(bone_idx, val_pos)
	if track_type == Animation.TYPE_ROTATION_3D:
		var val_rot: Quaternion = _animation.rot_trk_interp(track_idx, _time)
		_skeleton.set_bn_ps_rot(bone_idx, val_rot)
	if track_type == Animation.TYPE_SCALE_3D:
		var val_scale: Vector3 = _animation.scale_trk_interp(track_idx, _time)
		_skeleton.set_bn_ps_scale(bone_idx, val_scale)

実行結果

手を振った! お~い

本来一番面倒くさいであろう、
・現在時間から補間元のキー2つを割り出す
・Valueを補間する
というパートを、Animationの「何々_track_interpolate」関数がやってくれるので、案外らくちんです。

Animationに記録されているキーフレーム値は、Rest(バインドポーズ)からの差分ではなく、親ボーンに対する子ボーンの姿勢そのものです。なので、Restを取得する必要はありません。

単に再生するだけなら結構簡単ですね。クロスフェードとかしたくなったら、もうちょっと頑張りが必要なんでしょうけど。
AnimationPlayerの動作で悩むくらいなら自作のプレイヤーでもいいかもしれません。GDで動かす以上、処理は重たいはずなので、大量のキャラクターを出すには向いてないでしょうけど。そういうシーンの無いゲームなら十分いけそう。


以上です~~

この記事が気に入ったらサポートをしてみませんか?