11_3 Bird Monster

カスタムイージング関数を利用したWAAPIアニメーションです。鳥が羽ばたくアニメーションはCSS Animationsで作成し、左右と上下に移動するアニメーションをWAAPIで作成しています。アニメーションの切り替えには、Animation.effect.setKeyframes()メソッドを使っています。

左右と上下に移動するアニメーションに激しいカスタムイージング関数を適用しているので、鳥はまったく”おだやかでない”動きをしています。

HTML

<div class="bird-monster bird-monster-fly"></div>

CSS

/*  鳥のフレームサイズ*/

.bird-monster {
    width: 134px;
    height: 105px;
    margin: 10% auto;
}
/* 羽ばたいているフレームアニメーション */

.bird-monster-fly {
    /* https://opengameart.org/content/2d-monster-bat-enemy */
    
    background-image: url("images/monster_fly.png");
    animation: fly-anim 800ms infinite steps(8);
}

@keyframes fly-anim {
    0% {
        background-position: 0 0;
    }
    100% {
        background-position: -1078px 0;
    }
}
/* 攻撃するフレームアニメーション */

.bird-monster-attack {
    /* https://opengameart.org/content/2d-monster-bat-enemy */
    
    background-image: url("images/monster_attack.png");
    animation: attack-anim 700ms infinite steps(8);
}

@keyframes attack-anim {
    0% {
        background-position: 0 0;
    }
    100% {
        background-position: -1078px 0;
    }
}

JavaScript

// アニメーションタイマー
const timings = {
    duration: 5000
};
const effect = new KeyframeEffect(null, null, timings);
const animationTimer = new Animation(effect, document.timeline);

// ターゲットの鳥。
const birdMonster = document.querySelector('.bird-monster');
// 左右に移動するアニメーション、時間は1
const keyframesFly = {
    transform: ['translateX(0)', 'translateX(-100px)', 'translateX(100px)', 'translateX(0)']
};
const timingsBird = {
    duration: 1,
    fill: 'forwards'
};
const effectFly = new KeyframeEffect(birdMonster, keyframesFly, timingsBird);
const flyAnim = new Animation(effectFly, document.timeline);

// 上下に移動する攻撃時のキーフレームパラメータ
// アニメーションを切り替えるとき、setKeyframes()メソッドで使用する
const keyframesAttack = {
    transform: ['translateY(0)', 'translateY(500px)', 'translateY(0)']
};

// カスタムイージング関数
// ばね運動の公式を転用
const customEasingFly = (p) => {
        return (1 - Math.cos(p * 5 * Math.PI) * (1 - p) + p) - 1;

    }
    // イーズインアウトの公式の転用
const customEasingAttack = (p) => {
    return p - Math.sin(p * 2 * Math.PI) / (2 * Math.PI);
    // return 1-(1-p)*(1-p)*(1-p)*(1-p);
}

// 状態に応じて切り替えるキーフレームやイージング関数、CSSクラスを
// 定義したオブジェクト
// flyState[state].keyframesでアクセスできる
const flyState = {
    'fly': {
        keyframes: keyframesFly,
        customEasing: customEasingFly,
        classList: 'bird-monster-fly'
    },
    'attack': {
        keyframes: keyframesAttack,
        customEasing: customEasingAttack,
        classList: 'bird-monster-attack'
    }
};
// 初期状態は'fly'
let state = 'fly';

// 毎フレーム呼び出される
const update = () => {
    // アニメーションタイマーの進捗値を取得
    const progress = animationTimer.effect.getComputedTiming().progress;
    if (progress) {
        // 進捗値を、状態に応じたイージング関数に渡して計算結果を得る
        const r = flyState[state].customEasing(progress)
            // console.log(r);
            // 鳥のアニメーションの現在位置に設定する
        flyAnim.currentTime = r;
    }
}

const render = () => {
    update();
    window.requestAnimationFrame(render);
}

// アニメーションタイマーが終わったら、
animationTimer.addEventListener('finish', () => {
    // 鳥のクラスを調べて、それによって状態を切り替える
    if (birdMonster.classList.contains('bird-monster-fly')) {
        state = 'attack';
        animationTimer.playbackRate *= 10;
    }
    else {
        state = 'fly'
        animationTimer.playbackRate = 1;
    }
    // 鳥のクラスを一度全部削除する
    const classList = birdMonster.classList;
    while (classList.length > 0) {
        classList.remove(classList.item(0));
    }
    // 鳥の基本クラスとフレームアニメーションのクラスを追加
    birdMonster.classList.add('bird-monster');
    birdMonster.classList.add(flyState[state].classList);
    // setKeyframes()でキーフレームを切り替え
    flyAnim.effect.setKeyframes(flyState[state].keyframes);
    // アニメーションタイマーを再生。
    // これにより、鳥の別のアニメーションの再生が始まる
    animationTimer.play();
}, false);

// アニメーションタイマーをスタートし、JavaScriptアニメーションを開始する
flyAnim.ready.then(() => {
    animationTimer.play();
    render();
});

コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です

CAPTCHA