DXライブラリとC言語でゲーム制作。今回は3Dモデルのアニメーションを再生する方法について。
アニメーション再生の基本
DXライブラリで3Dモデルのアニメーションを再生する方法は基本以下の通り。
- 再生したいアニメーションを MV1AttachAnim でアタッチする
- 再生したいアニメーションの総再生時間を MV1GetAttachAnimTotalTime で取得する
- 再生時間を進める。総再生時間より大きくなった場合は0にする。
- 再生時間を MV1SetAttachAnimTime セットする
- 別のアニメーションに切り替える場合は MV1DetachAnim で現在のアニメーションをデタッチして1に戻る
アニメーションのアタッチ・デタッチについて、各アニメーションには番号が割り当てられているので任意の番号を指定する。アニメーション名や番号は DxlibModelViewer のアニメーションタブから確認できる。No.*が番号で Armature|*** がアニメーション名。横のCountは総再生時間。
アニメーション名について、MV1形式の場合Blenderなどで作成した名前に Armature| といった文字列が基本追加される。またDXライブラリには自動でアニメーションを進める機能がないので自分で再生時間を設定する必要がある。
実装
では実装。今回は例として3Dモデルに「Armature|Idle」「Armature|T-Pose」「Armature|Walk」の3つのアニメーションがあり、決定ボタンでそれらのアニメーションを切り替えていく内容を実装する。
こちらの記事で60fps環境に固定し、モデルのアニメーションもBlenderなどで60fps設定で作成している前提で進めます。
Player.cpp/hにアニメーション関連の変数や関数を追加する。
Player.h
//以下の関数を追加 void Player_PlayAnimation();
Player.cpp
//中略
//Playerの構造体を以下のように変更
typedef struct {
int ModelHandle; //モデルハンドル
VECTOR Position; //座標
int AnimIndex; //任意のアニメーションの番号を取得するときに使う
int AttachIndex; //アタッチするアニメーションの番号
float TotalTime; //アニメーションの総再生時間
float PlayTime; //アニメーションの再生時間
}player_t;
const static int AllPlayerAnimationNum = 2; //プレイヤーのアニメーション総数
//中略
static int test_count; //アニメーション切り替え用変数(今回限り)
//初期化
void Player_Initialize() {
//中略
//アニメーション関連の初期化
//Idle という名前のアニメーションの番号を取得する
player.AnimIndex = MV1GetAnimIndex(player.ModelHandle, "Armature|Idle");
//取得したアニメーション番号のアニメーションをアタッチする
player.AttachIndex = MV1AttachAnim(player.ModelHandle, player.AnimIndex, -1, FALSE);
//アタッチしたアニメーションの総再生時間を取得する
player.TotalTime = MV1GetAttachAnimTotalTime(player.ModelHandle, player.AttachIndex);
//再生時間の初期化
player.PlayTime = 0.0f;
test_count = 0;
}
//更新
void Player_Update() {
//アニメーション処理
Player_PlayAnimation();
}
//中略
//プレイヤーのアニメーション処理
void Player_PlayAnimation() {
//再生時間を進める
player.PlayTime += 1.0f;
//再生時間がアニメーションの総再生時間に達したら再生時間を0に戻す
if (player.PlayTime >= player.TotalTime) player.PlayTime = 0.0f;
//決定ボタンが押されたら再生するアニメーションを変更する
if (Input_GetGamepadDown(config.confirm)) {
test_count++;
if (test_count > AllPlayerAnimationNum) test_count = 0;
//今までアタッチしていたアニメーションのデタッチ
MV1DetachAnim(player.ModelHandle, player.AttachIndex);
if (test_count == 0) {
//Idle という名前のアニメーションの番号を取得する
player.AnimIndex = MV1GetAnimIndex(player.ModelHandle, "Armature|Idle");
//取得したアニメーション番号のアニメーションをアタッチする
player.AttachIndex = MV1AttachAnim(player.ModelHandle, player.AnimIndex, -1, FALSE);
//アタッチしたアニメーションの総再生時間を取得する
player.TotalTime = MV1GetAttachAnimTotalTime(player.ModelHandle, player.AttachIndex);
}
else if (test_count == 1) {
//T-Pose という名前のアニメーションの番号を取得する
player.AnimIndex = MV1GetAnimIndex(player.ModelHandle, "Armature|T-Pose");
//取得したアニメーション番号のアニメーションをアタッチする
player.AttachIndex = MV1AttachAnim(player.ModelHandle, player.AnimIndex, -1, FALSE);
//アタッチしたアニメーションの総再生時間を取得する
player.TotalTime = MV1GetAttachAnimTotalTime(player.ModelHandle, player.AttachIndex);
}
else if (test_count == 2) {
//Walk という名前のアニメーションの番号を取得する
player.AnimIndex = MV1GetAnimIndex(player.ModelHandle, "Armature|Walk");
//取得したアニメーション番号のアニメーションをアタッチする
player.AttachIndex = MV1AttachAnim(player.ModelHandle, player.AnimIndex, -1, FALSE);
//アタッチしたアニメーションの総再生時間を取得する
player.TotalTime = MV1GetAttachAnimTotalTime(player.ModelHandle, player.AttachIndex);
}
//再生時間の初期化
player.PlayTime = 0.0f;
}
//再生時間をセットする
MV1SetAttachAnimTime(player.ModelHandle, player.AttachIndex, player.PlayTime);
}
アニメーションのアタッチ・デタッチについて、今回は MV1GetAnimIndex でアニメーション名を検索して番号を取得する方法を採用。アニメーションを追加すると各アニメーションの番号がずれるため名前で検索して取得する方がずれを気にしなくてすむ。
アニメーションを切り替える際にデタッチを忘れると他のアニメーションに影響が出るため注意。
最後に MV1SetAttachAnimTime で再生時間を設定するのを忘れずに。
再生するアニメーションによってはポーズの切り替わるがはっきりとわかって違和感を覚えるかもしれない。その場合は MV1SetAttachAnimBlendRate を使って2つのポーズをブレンドしてあげると違和感が減る。詳しい内容についてはこちら。