はじめに
別のアドベントカレンダー【VCI】テーマパークを作ろう Advent Calendar 2020 にて
「永続的に再生されるアニメーションをあとから来た人と同期させる」という点で少し悩んだので、
あとから来た凸者に対するアニメーション再生についてパターン別に実装方法をまとめたいと思います。
作りたいもの
現在、以下のような永続的に回転するアニメーションを作成しています。
見て!観覧車が回っているよ
— #らいと(きょろ、ちょべりば→ぐ) (@lightjug) December 24, 2020
かわいいね pic.twitter.com/VYeuSn8fL4
今回は、上記のアニメーションが後から凸してきた人にも同期されるようにする方法を検討します。
なお、移動対象であるゴンドラの数を今後増やす予定のため、
むやみにSubItemを増やさないようにゴンドラは子アイテムとして作成しています。
上記制限がなく移動対象をSubItemにできる場合は、SubItem自体が同期機能を持っているため改めて同期を気にする必要は無いはずです。
アニメーション関数の特徴
アニメーション関数については以下の公式ページに詳細が書かれています。
・vci.assets(ExportAnimation)
上記ページには乗ってないですが、アニメーションには以下のような法則があります。
・再生中に同じアニメーションを再度再生しても最初から再生にはならない。(再生中のアニメーションがそのまま続く)
・再生中に別のアニメーションを再生すると再生中アニメーションを停止して別のアニメーションを上書きで再生する
(BlendやCrossFadeを使えば違うかもしれませんが。今回は時間の関係上BlendやCrossFadeには触れません)
今回は上記の法則がポイントとなるので覚えておいてください。
[NG]アニメーションをloopで再生
まずは単純に後から来た人が初期処理でアニメーションをローカルでループ再生するようにしてみます。
VCIを出した人(Owner)とあとから来た凸者(Guset)のアニメーション再生タイミングは以下の様なイメージです。
(縦長の長方形がアニメーション1回分と思ってください。)
コードは以下のようになります。
local animationLoop = vci.assets.GetTransform("AnimationLoop").GetAnimation()
animationLoop.PlayFromName("Rotate",true)
こちらの方法ではVCI出したときからいる人は同期して再生されますが、後から凸してきた人は同期はされません。
特に位置の同期をする必要のないものであれば上記でも問題ありませんが、
今回はゴンドラの位置をある程度同期したいため上記方法では要件を満たしません。
ちなみに確かめてはいませんが、ALL付きで再生しても以下のようになるだけで動作的にはほぼ同様になると思います。
[NG]onceで再生し、終了後に再度再生
次に、アニメーションをonceで再生し、終了したら再度再生する方法で作成してみます。
この方法ではあとから来た凸者ははじめはアニメーションが再生されませんが、Ownerが再度再生をするタイミングで同期されます。(通信の影響で多少誤差は生まれます。)
コードは以下のようになります。
local animationOnce = vci.assets.GetTransform("AnimationOnce").GetAnimation()
if vci.assets.IsMine then
animationOnce._ALL_PlayFromName("Rotate",false)
end
function update()
--終了してたら再実行する
if not animationOnce.IsPlaying() then
animationOnce._ALL_PlayFromName("Rotate",false)
end
end
一見この方法で問題ないように動く用に見えます。
しかし、この方法を実際に試したところ、再再生のタイミングで何回かに1回がアニメーションが再生されないことがありました。
おそらく回線速度などのブレによって以下のような現象が起きているのではないかと思います
[OK?]onceで再生し、終了後に停止&再生
上記の方法では再生中に次の再生が書かてしまううまく行かなかったため、明示的に停止させてから再生するように変更します。
アニメーション再生タイミングは以下の様なイメージです。
コードは以下のとおりです
local animationOnce = vci.assets.GetTransform("AnimationOnce").GetAnimation()
if vci.assets.IsMine then
animationOnce._ALL_PlayFromName("Rotate",false)
end
function update()
--終了してたら再実行する
if not animationOnce.IsPlaying() then
animationOnce._ALL_Stop()
animationOnce._ALL_PlayFromName("Rotate",false)
end
end
通信の影響でstopとplayが前後することもあるかと思いましたが、今の所上記コードで正しく動いていそうです。
補足
もしかしたら私の確認時にstopとplayがたまたま入れ替わってなかっただけで、もしかしたら順番が入れ替わってうまく行かない場合があるかもしれません。
その場合はアニメーションを2分割または同じアニメーションを複製して交互に再生するようにすると、終了後再生の方法で再生中に次の再生がかかったとしても、違うアニメーションは上書きされるルールにより正しく動くようになると思います。
コードは以下のとおりです
local animationDivede = vci.assets.GetTransform("AnimationDivide").GetAnimation()
if vci.assets.IsMine then
vci.state.Set("rotateIndex",1)
animationDivede._ALL_PlayFromName("Rotate_1",false)
end
function update()
--終了してたら次のアニメーションを実行する
if not animationDivede.IsPlaying() then
local index = vci.state.Get("rotateIndex")
if index then
index = index%2+1
animationDivede._ALL_PlayFromName("Rotate_"..index,false)
vci.state.Set("rotateIndex",index)
end
end
end
(正直停止&再生の方法でうまくいくと思ってなかったので最初はこちらの方法で記事を書く予定でした。)
終わりに
いかがでしたでしょうか。
今回は実験的な確認で理論的には不明な部分が多いです。
もしこの方法でうまく行かなかったケースなどがアレば教えて頂けるとありがたいです。
、
また、今回紹介した観覧車を含まれている「VCIでテーマパークを作ろう」のアドカレも書いているので是非見ていってください。
【VCI】テーマパークを作ろう Advent Calendar 2020