1. ゲームのアニメーション(3Dゲーム)
作るには主に以下の2つの方法があると思います。
1.1 アニメーションのファイルを作成する
普通はこの方法で作ります。オブジェクトをモデリングするときにアニメーションも一緒に作成してゲームに読み込みます。ゲームでは、キャラごとにジャンプや走るなどのアニメーションを作成し、それをプログラムで制御するという方法で実行します。
1.2 ハードコーディングする
プログラマーの意匠を物体の動きに盛り込みたい時にこちらの方法を使います。例えば草の揺れ動きとか水の流れなど、動きが一定ではなくランダムに動くアニメーションはコードで作成する方がより自然な動きを実現できます。。
2. ハードコーディングするメリット・デメリット
2.1 メリット
- ランダム性のある動きを表現しやすい
- データの容量節約になる
- かなりプログラマーの自由が効く
- 楽しい(重要)
2.2 デメリット
- 人の動きなどの複雑な動きは難しい
- 微調整が面倒
- コードが大きくなりがち
3. Rustでアニメーション作成
ようやく本題です。Rustでアニメーションを作るときに私はよくステートマシーンを用います。このときEnumを用いて状態を管理します。基本的な作り方は、前フレームからの時間経過を取得し、現在の状態を実行し、終了したら次の状態に移るという形になります。
3.1 状態の定義
例えば走る→ジャンプ→攻撃というアニメーションを作成したい時はまず以下のように4つの状態をEnumを用いて定義します。かっこの中の数字は各状態のアニメーションの経過時間を保持します。
最初の状態は"走る"なので、現在の状態にRunを設定します。
/// アニメーションの状態の種類
enum AnimationState {
Run(f32), // かっこの中の数字は経過時間
Jump(f32),
Attack(f32),
Still // 何もしない状態
}
let current_state = AnimationState::Run(0.);
3.2 前フレームからの時間経過を取得
前フレームから経過した時間を取得し、現在状態が保持している経過時間に足し合わせます。このとき、tickという関数をAnimationStateに実装します。このtickという関数で状態遷移も定義するとスッキリしたコードになります。
/* 前フレームからの時間経過を取得するシステム */
let dt: f32 = time.delta_seconds(); // Amethystはこう書きます
current_state.tick(dt);
impl AnimationState {
/// 状態が保持している経過時間に足し合わせる
fn tick(&mut self, dt: f32) {
*self = match self {
AnimationState::Run(t) => {
// tは状態の経過時間
if *t >= RUN_DELAY {
// 走る状態が終了、ジャンプに移行
AnimationState::Jump(0.0)
} else {
// 走る状態が継続中
AnimationState::Run(*t + dt)
}
}
AnimationState::Jump(t) => {
if *t >= JUMP_DELAY {
// ジャンプ状態が終了、攻撃に移行
AnimationState::Attack(0.0)
} else {
// ジャンプ状態が継続中
AnimationState::Jump(*t + dt)
}
}
AnimationState::Attack(t) => {
if *t >= Attack_DELAY {
// 攻撃状態が終了、何もしない状態に移行
AnimationState::Still
} else {
// 攻撃状態が継続中
AnimationState::Attack(*t + dt)
}
}
AnimationState::Still => AnimationState::Still,
}
}
}
3.3 現在の状態を実行
現在の状態は経過時間を保持しているのでそれを元に進行状況(progress)を計算しオブジェクトを移動させます。
match current_state {
AnimationState::Run(t) => {
let progress = t / RUN_DELAY;
/* progressを元にxy座標を移動 */
}
AnimationState::Jump(t) => {
let progress = t / JUMP_DELAY;
/* progressを元にz座標を更新 */
}
AnimationState::Attack(t) => {
let progress = t / ATTACK_DELAY;
/* progressを元に攻撃させる */
}
_ => ()
}
3.4 次の状態へ移行
AnimationStateのtickが自動で状態を遷移してくれます。
3.5 アニメーションの終了
状態がAnimationState::Stillまで到達したらアニメーションは終了です。同様のアニメーションをもう一度実行したいときはcurrent_stateにもう一度"走る"状態をセットするだけです。
4. まとめ
以上、ステートマシーンを活用したアニメーションのハードコーディングの方法の一例でした。