[VCI]後からきた凸者にも永続アニメーションを同期させる方法を考える

# はじめに
別のアドベントカレンダー[【VCI】テーマパークを作ろう Advent Calendar 2020](https://adventar.org/calendars/5855) にて
「永続的に再生されるアニメーションをあとから来た人と同期させる」という点で少し悩んだので、
あとから来た凸者に対するアニメーション再生についてパターン別に実装方法をまとめたいと思います。

# 作りたいもの
現在、以下のような永続的に回転するアニメーションを作成しています。
<blockquote class="twitter-tweet"><p lang="ja" dir="ltr">見て！観覧車が回っているよ<br>かわいいね <a href="https://t.co/VYeuSn8fL4">pic.twitter.com/VYeuSn8fL4</a></p>&mdash; ＃らいと（きょろ、ちょべりば→ぐ） (@lightjug) <a href="https://twitter.com/lightjug/status/1342038766035238912?ref_src=twsrc%5Etfw">December 24, 2020</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>

今回は、上記のアニメーションが後から凸してきた人にも同期されるようにする方法を検討します。

なお、移動対象であるゴンドラの数を今後増やす予定のため、
むやみにSubItemを増やさないようにゴンドラは子アイテムとして作成しています。
上記制限がなく移動対象をSubItemにできる場合は、SubItem自体が同期機能を持っているため改めて同期を気にする必要は無いはずです。

# アニメーション関数の特徴
アニメーション関数については以下の公式ページに詳細が書かれています。
　・[vci.assets(ExportAnimation)](https://virtualcast.jp/wiki/doku.php?id=vci:script:reference:exportanimation)

上記ページには乗ってないですが、アニメーションには以下のような法則があります。
　・再生中に**同じアニメーションを再度再生**しても**最初から再生にはならない**。(再生中のアニメーションがそのまま続く)
　・再生中に**別のアニメーションを再生**すると再生中アニメーションを停止して**別のアニメーションを上書きで再生**する
(BlendやCrossFadeを使えば違うかもしれませんが。今回は時間の関係上BlendやCrossFadeには触れません)

今回は上記の法則がポイントとなるので覚えておいてください。

# [NG]アニメーションをloopで再生
まずは単純に後から来た人が初期処理でアニメーションをローカルでループ再生するようにしてみます。

VCIを出した人(Owner)とあとから来た凸者(Guset)のアニメーション再生タイミングは以下の様なイメージです。
(縦長の長方形がアニメーション1回分と思ってください。)
![advent-Loop.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/64877/c35e5a9e-22ac-48a9-a714-206adddcbeb2.jpeg)

コードは以下のようになります。

``` lua
local animationLoop = vci.assets.GetTransform("AnimationLoop").GetAnimation()
animationLoop.PlayFromName("Rotate",true)
```

こちらの方法ではVCI出したときからいる人は同期して再生されますが、後から凸してきた人は同期はされません。

特に位置の同期をする必要のないものであれば上記でも問題ありませんが、
今回はゴンドラの位置をある程度同期したいため上記方法では要件を満たしません。

ちなみに確かめてはいませんが、_ALL_付きで再生しても以下のようになるだけで動作的にはほぼ同様になると思います。
![advent-LoopAll.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/64877/03358e6f-a1d0-17d3-2ce2-11dbf3883468.jpeg)


# [NG]onceで再生し、終了後に再度再生
次に、アニメーションをonceで再生し、終了したら再度再生する方法で作成してみます。
この方法ではあとから来た凸者ははじめはアニメーションが再生されませんが、Ownerが再度再生をするタイミングで同期されます。(通信の影響で多少誤差は生まれます。)

アニメーション再生タイミングは以下の様なイメージです。
![advent-Once.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/64877/2fe4abc8-66dc-d246-df93-fa971c1885e8.jpeg)


コードは以下のようになります。

``` lua
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回がアニメーションが再生されないことがありました。
おそらく回線速度などのブレによって以下のような現象が起きているのではないかと思います
![advent-Once_NG.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/64877/7148bd51-2284-4615-ec46-16897084aa14.jpeg)


# [OK?]onceで再生し、終了後に停止＆再生
上記の方法では再生中に次の再生が書かてしまううまく行かなかったため、明示的に停止させてから再生するように変更します。
アニメーション再生タイミングは以下の様なイメージです。
![advent-Stop&Play.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/64877/fd3ebf85-8abe-9390-8fa8-460e51ec291f.jpeg)


コードは以下のとおりです

``` lua
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分割または同じアニメーションを複製して交互に再生するようにすると、終了後再生の方法で再生中に次の再生がかかったとしても、違うアニメーションは上書きされるルールにより正しく動くようになると思います。

コードは以下のとおりです

``` lua
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](https://adventar.org/calendars/5855)