LoginSignup
7
2

More than 3 years have passed since last update.

Rust + Entity Component System で仕様変更に強いゲーム設計 その4ー2 〜 アニメーションコンポーネント

Last updated at Posted at 2020-01-28

目次

その1 〜 序文
その2 〜 キャラの移動
その3−1 〜 コンポーネントの設計
その3−2 〜 システムの設計
その3−3 〜 メイン部分
その4−1 〜 剣を表示
【イマココ】その4−2 〜 アニメーションコンポーネント
その4-3 〜 アニメーションを動かす
その5-1~ あたり判定
その5-2~ やられアニメーション
その6 〜 これまでの振り返り

前回は、アニメーションを再生する準備のため、キャラの向きを示す線(=剣)を表示するところまで実装しました。

今回から、いよいよアニメーションの実装に入ります。

まず、アニメーションを制御するコンポーネントとしてAnimatorを作ります。

Animator

//K:アニメーションを識別するためのID(キー)
//V:1フレーム分のデータを示す型
#[derive(Default)]
pub(crate) struct Animator<K, V>
where
    K: Hash + Eq,
{
    playing_id: Option<K>,                   //現在再生中のアニメーションID 再生していないならNone
    current_frame: usize,                    //現在何フレーム目か
    animations: HashMap<K, Animation<V>>,    //アニメーションデータ
}

impl<K, V> Animator<K, V>
where
    K: Hash + Eq + Copy,
{
    //再生
    pub fn play(&mut self, animation_id: K) {
        if self.animations.contains_key(&animation_id) {
            self.playing_id = Some(animation_id);
            self.current_frame = 0;
        }
    }
    //アニメーションが終わっているか
    pub fn is_end(&self) -> bool {
        if let Some(id) = self.playing_id {
            let anim = self.animations.get(&id).unwrap();
            return !anim.looped && self.current_frame == anim.values.len();
        }
        return false;
    }
    //フレーム更新(毎フレーム呼ばれる想定)
    pub fn update(&mut self) {
        if let Some(id) = self.playing_id {
            if let Some(anim) = self.animations.get(&id) {
                self.current_frame += 1;
                if anim.values.len() <= self.current_frame && anim.looped {
                    self.current_frame = 0;
                }
            }
        }
    }
    //アニメーションの登録
    pub fn register(&mut self, id: K, anim: Animation<V>) {
        self.animations.insert(id, anim);
    }
    //現在のアニメーションの値
    pub fn value(&self) -> Option<&V> {
        let id = self.playing_id?;
        let anim = self.animations.get(&id)?;
        anim.values.get(self.current_frame)
    }
}

//アニメーション1つ分のデータ
//T: 1フレーム分のデータを示す型
#[derive(Default)]
pub(crate) struct Animation<T> {
    looped: bool,        //ループアニメかどうか
    values: Vec<T>,      //アニメーションデータ
}

impl<T> Animation<T> {
    pub fn new(looped: bool, values: Vec<T>) -> Self {
        Self { looped, values }
    }
}

Animator の中には、複数のアニメーションデータが入っており、それをIDで切り替えて再生する想定です。
今回(その4)では、待機アニメーションと攻撃アニメーションを登録しておいて、デフォルトでは待機アニメ、攻撃時は攻撃アニメを再生する、ということになります。

AnimatorはECSの「コンポーネント」として使います。
一般にコンポーネントは単なるデータで、機能を持たないということになっていると思いますが、コンポーネント内部だけで完結する機能は、コンポーネントのメソッドとして実装して良いと判断しました。

アニメーション1フレーム分のデータは、下記の様になります。

#[derive(Default, Clone)]
pub(crate) struct CharacterAnimFrame {
    pub radius_scale: f32,      //円のスケール
    pub weapon_direction: f32,  //武器の向き
}

この値が時間とともに変化し、実際の描画にアニメーションが適用されるわけですね。

アニメーションのキーとなる値は、enumにしました。

#[derive(Clone, Copy, Hash, PartialEq, Eq)]
pub(crate) enum CharacterAnimID {
    Wait,     //待機
    Attack,   //攻撃
}

今回使うAnimatorの別名も定義しておきます。

type CharacterAnimator = Animator<CharacterAnimID, CharacterAnimFrame>;

ここまでで、アニメーション周りのコンポーネントの準備は完了です。

続いて、Animator自体を更新するシステムを実装しましょう。

impl<K, V> SystemProcess for System<CContainer<Animator<K, V>>, ()>
where
    K: Hash + Eq + Copy,
{
    fn process(animators: &mut Self::Update, _: &Self::Refer) {
        animators.iter_mut().for_each(|(_, a)| a.update());
    }
}

単純にupdate()を呼ぶだけです。

次に、Animatorから現在の値を取り出し、CharacterViewに反映させるシステムです。

impl SystemProcess for System<CContainer<CharacterView>, CContainer<CharacterAnimator>> {
    fn process(views: &mut Self::Update, animators: &Self::Refer) {
        views
            .iter_mut()
            .zip_entity(animators)
            .for_each(|(view, animator)| {
                if let Some(val) = animator.value() {
                    view.radius_scale = val.radius_scale;
                    view.weapon_direction = val.weapon_direction;
                }
            });
    }
}

radius_scaleweapon_directionをコピーしているだけです。

これで、アニメーションを再生する仕組みができました。

さて、アニメーションを制御するのに、もう1つ、仕組みが必要です。
それは、「どういう条件でどのアニメーションを再生するか」を決める仕組みです。

これについては、また次の投稿で、説明します。

7
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
7
2