【Rive実践編】ステートマシンでインタラクティブアニメーションを実装
モーショングラフィックスの作成にとても便利なRiveですが、今回は実践編です。
Riveそのものについての概要はこちらに書きましたのでまたご覧ください。
前に書いた通り、Riveには丁寧に使い方が解説されたYoutube動画があります。
Rive 101
Rive Editorの操作方法や、ステートマシンの使い方もこの動画で紹介されています。
アニメーションの作り方は他のソフトの知見があればなんとなくで触ることもできますが、ステートマシンのような機能はインタラクティブなアニメーションならではの機能で、あまり他のソフトで見たことがありませんでした。
今回は「インタラクティブな動作をどう実装するか?」についてを中心に考え触ってみました。
デモと、今回の解説範囲
デモで実装してみたのは
- ローディング後の木のパスアニメーション
- 木のアニメーションの後に妖精が降りてくる(タイムラインの接続)
- 妖精はアイドル状態ではふわふわしている
- HTMLのButtonを押すと木がアニメーションする
- 雨のボタンで雨のアニメーション
- マウスに妖精がついてくる
その中でも今回は、「HTMLのButtonを押すと木がアニメーションする」部分についてやり方を書いていきます。
※背景黒なのでいつも夜みたいですが…木の変化がわかる背景色にした結果こうなりました。。
主に、Riveのアニメーションを外部から制御する方法が伝わればと思います。
ちなみに、HTML要素ではなくcanvas内にあるオブジェクトをクリックして何かイベントを起こす場合は
今回紹介する方法のほかに「リスナー」を使った方法があります。
リスナーを使った方法の方が簡単ですが、今回は「HTML要素」+「inputs(という機能)」を使った汎用的な方法を紹介します。
必要なタイムラインを用意する
朝・昼・夕・夜のボタンを押すと、木の状態が変化します。
これを実装するために、Riveの方でそれぞれのタイムラインを用意します。
タイムラインは複数作成することができ、後にそれをステートマシンで組み合わせることができます。
まず、「morning」というタイムラインを用意し、0sのところに朝の状態のアニメーションを作成します。
lightというシェイプのPositionとFillを変化させているので、そのキーフレームをうち、それで朝のタイムラインは完成です。
タイムラインの長さもデフォルトのままでOKです。
ボタンを押した時に、初期状態からさらにアニメーションをする場合は、このタイムラインにアニメーションを作成しますが、今回は状態が変わっておしまいなので、キーフレームは0sのところに一つうつだけとなります。
同じようにしてday、sunset、nightのタイムラインも作成します。
ステートマシンの設定をする
ステートマシンは、デフォルトで「State Machine 1」が作られています。
inputsを作成する
まず、ステートマシンにinputsを作成します。
inputsは、外部からステートマシンに値を渡すためのものです。
https://help.rive.app/editor/state-machine/inputs
入力タイプは、Boolean、Trigger、Numberの3種類です。
アニメーション界隈は、数値さえ渡せれば大抵のことができます。
Numberを渡すことができれば、プログレスバーを作ったり、スクロール位置を渡したり、あらゆる動作を制御できます。
Triggerは、短期間だけtrueになる入力タイプです。ボタンを押した時に一度だけアニメーションを再生するような時に使います。
今回は、朝・昼・夕・夜のそれぞれを、0〜3の状態として扱いたいので、Numberを選択します。
Inputsの名前を「timeValue」にします。この名前は、後にJSから参照できます。
JSで数字をわたすための受け口のようなものを作成したということです。
レイヤーを作成し、タイムラインをつなげる
レイヤーでは、再生した時にどのタイムラインをどんなタイミングで再生するかを設定することができ、それらをチャートのようなビジュアルで管理することができます。
レイヤーは複数作ることができ、左上にある再生ボタンですべてのレイヤーが同時再生されるという仕組みになっているので、アニメーションパーツを便利に管理できるのはもちろん、プレビューもその場で確認できます。
デフォルトでは「レイヤー1」が作成されています。
今回は、timeValueを切り替えた時にそれぞれのタイムラインを再生するための「time」というレイヤーを作りました。
それぞれのタイムラインをレイヤーにドラッグ&ドロップします。
もしくは右クリックで「Add state」を選択して、右のインスペクターに出てきた「Timeline」でタイムラインを選択します。
それらをそれぞれ「Any State」につなぎます。
ステートにはいろんな種類があります。
「Any state」は、ステートマシンがどのステートにあるか関係なく、いつでも再生できます。
今回は、timeValueが0〜3のどれかになった時に、いつでもアクティブ化できるようにAny stateにつなぎます。
トランジションの設定をする
ステートを繋いでる線と矢印はトランジションと呼ばれ、ステート間の遷移を制御します。
トランジションという名の通り、デフォルトでこのステート間はトランジションが設定できるようになっています。
矢印アイコンをクリックすると、右のインスペクターに「Duration」の項目が表示されるので、ここでミリ秒を入力すると、その時間をかけてステートが変化します。
今回は1秒かけて変化させたいので、1000を入力します。
また、Conditionsという項目もあります。
これは、次のアニメーションへ進むための条件を設定できます。
inputsの項目が○○ならば、というような条件を設定できます。
今回は、inputsのtimeValue
が0ならmorningのステートに切り替わって欲しいので、morningへのトランジションには「timeValue == 0」という条件を設定します。
同様に昼・夕・夜のステートにも、それぞれのtimeValue(1,2,3)の値に合わせた条件を設定します。
これで、左上の再生ボタンを押して「Inputs」のtimeValueの数値を変えると、それに合わせてステートが切り替わるというプレビューを確認できます。
以上でRive側の設定はおわりです。
ランタイム
HTML
HTMLは、canvas
タグとbutton
を用意しておきます。
<main class="main">
<div class="camvasWrap">
<canvas id="canvas" />
</div>
<div class="btns">
<button class="js-btn-morning">朝</button>
<button class="js-btn-day">昼</button>
<button class="js-btn-sunset">夕</button>
<button class="js-btn-night">夜</button>
</div>
</main>
Riveインスタンスの作成
※以下、ボタンクリックアニメーションに絞って記載していますが、コードの全文はこちらです。
@rive-app/canvas
をインストールし、importします。
パラメーターを設定して、インスタンスを作成します。(パラメーターは公式を参照)
import { Rive } from '@rive-app/canvas';
const canvas = document.getElementById('canvas') as HTMLCanvasElement;
const riveAnime = new Rive({
src: '/public/animation/tree.riv',
canvas: canvas,
autoplay: false,
useOffscreenRenderer: true,
stateMachines: 'State Machine 1',
onLoad: () => {
riveAnime.resizeDrawingSurfaceToCanvas();
initAnimation();
},
});
onLoad
でinitAnimation
を実行します。
ボタンのイベントを作成 / ステートマシンinputsの更新
initAnimation
でボタンにイベントリスナーを付与します。
const RIVE_TIMEVALUE_INPUTS = {
morning: {
name: 'morning',
value: 0,
},
day: {
name: 'day',
value: 1,
},
sunset: {
name: 'sunset',
value: 2,
},
night: {
name: 'night',
value: 3,
},
};
const morningBtn = document.querySelector('.js-btn-morning') as HTMLButtonElement;
const dayBtn = document.querySelector('.js-btn-day') as HTMLButtonElement;
const sunsetBtn = document.querySelector('.js-btn-sunset') as HTMLButtonElement;
const nightBtn = document.querySelector('.js-btn-night') as HTMLButtonElement;
function initAnimation() {
const inputs = riveAnime.stateMachineInputs('State Machine 1');
const timeValueInput = inputs.find((input) => input.name === 'timeValue');
timeValueInput.value = RIVE_TIMEVALUE_INPUTS.morning.value;
riveAnime.play();
morningBtn.addEventListener('click', () => {
timeValueInput.value = RIVE_TIMEVALUE_INPUTS.morning.value;
});
dayBtn.addEventListener('click', () => {
timeValueInput.value = RIVE_TIMEVALUE_INPUTS.day.value;
});
sunsetBtn.addEventListener('click', () => {
timeValueInput.value = RIVE_TIMEVALUE_INPUTS.sunset.value;
});
nightBtn.addEventListener('click', () => {
timeValueInput.value = RIVE_TIMEVALUE_INPUTS.night.value;
});
}
ポイントは下記です。
ステートマシンのinputsを取得するには、stateMachineInputs
メソッドを使います。
const inputs = riveAnime.stateMachineInputs('State Machine 1');
これで全てのinputesが取得できるので、Rive側でつけたnameを頼りにtimeValueのinputsを取得します。
const timeValueInput = inputs.find((input) => input.name === 'timeValue');
取得したinputsのvalueプロパティの値を変更すれば、ステートマシンのinputsを更新できます。
timeValueInput.value = RIVE_TIMEVALUE_INPUTS.morning.value;
まとめ
ランタイムの記述に関しては、すごく直感的だと感じました。
Riveの方で値を受け取るよう設定する方法さえ覚えたら、Riveでキーフレームを打てる部分に関してはどの箇所もアニメーション可能ということになります。
また時間があればいろいろやってみたいと思います。