はじめに
本記事はVCIアドベントカレンダーの16日目の記事です。
12/12に開催されたVMF(バーチャルミュージックフェスティバル)にAnd U'sの一員として参加し、バーチャルライブを作成しました。
バーチャルライブはメンバーで以下の通り役割を分担し作成しています。
_yukku : モデリング、ダンス
くろのす : エフェクト、アニメーション
八葉ユーマ : カメラワーク
ちんぐ : VCI化、スクリプト
今回は私が担当したVCI化、スクリプトの観点で主に表題の通り「タイムテーブルを用いた処理の実行」について記事にします。
VCI化
各種オブジェクトは以下のようにあらかじめその場に配置し、マテリアルをCutoutまたはTransparentに設定し透明化しています。
スクリプト
スクリプトでは以下のようなタイムテーブルを作成し、経過時間をキーにタイムテーブルの処理を実行します。
タイムテーブル
local ProductionTable = {
["ti"]={["title"] = "シャルル"},
["length"]={["time"] = "03:48.47"},
["00:00.3"]={["function"] = initMaterials },
["00:00.9"]={["function"] = onMaterials, ["val"] = { { "noise", "sprite" } } }, -- ノイズエフェクト再生(テクスチャスクロール)
["00:03.5"]={["function"] = onMaterials, ["val"] = { {"sorenanoni"} }}, --マテリアルON
["00:04.9"]={["function"] = startAnimationFromName, ["val"] = { "sorenanoni3", "sorenanoni6" }}, --アニメーション
["00:07.7"]={["function"] = offMaterials, ["val"] = { {"sorenanoni"} } },
["00:12.9"]={["function"] = offMaterials, ["val"] = { { "noise","sprite" } } },
["00:13.3"]={["function"] = onMaterials, ["val"] = { { "Charles_Kashi5", "光", "黒" } } }, --"笑って" 暗転
["00:13.4"]={["function"] = startAndStopEffect, ["val"] = { "smoke", 1 } },
["00:14.7"]={["function"] = offMaterials, ["val"] = { { "Charles_Kashi5", "光", "黒" } } }, -- 暗転終わり
["00:14.8"]={["function"] = changeColorMaterials, ["val"] = { {"cylinder_ao"}, Color.__new(211/255, 211/255, 211/255, 1)} }, --歌詞なし 建物マテリアル不透明化
["00:14.9"]={["function"] = startTitleEffect, ["val"] = { 11.7 } }, -- タイトル
["00:15.0"]={["function"] = onMaterials, ["val"] = { { "kaben", "kuki" } } }, -- クロッカス出現
["00:39.9"]={["function"] = changeColors2, ["val"] = { 500, "Moya", Color.__new(255/255, 255/255, 255/255, 0), Color.__new(255/255, 255/255, 255/255, 1) } }, -- "空っぽでいよう"
["00:40.8"]={["function"] = changeColorMaterials, ["val"] = { {"cylinder_ao"}, Color.gray} }, -- 建物マテリアル 灰色
["00:42.3"]={["function"] = changeColors2, ["val"] = { 500, "Moya", Color.__new(255/255, 255/255, 255/255, 1), Color.__new(255/255, 255/255, 255/255, 0) } }, -- 暗転終わり
["00:44.5"]={["function"] = onoffMaterials, ["val"] = {500, {"SINobject2", "SINobject1"} } }, -- "深い青で満たしたのなら" -- 複数マテリアル連続透明・不透明化
["00:44.8"]={["function"] = onMaterials, ["val"] = { {"bc"} } }, -- 水面不透明化
["00:44.9"]={["function"] = startAnimation, ["val"] = { "Water surface" } }, -- 水面上昇
["00:45.0"]={["function"] = startAndStopEffect, ["val"] = { "awa", 3.6 } }, -- "どうだろう" -- 泡エフェクト再生
["00:46.1"]={["function"] = startAndStopEffect, ["val"] = { "hukaiao", 2.5 } }, -- 泡エフェクト再生
["00:48.7"]={["function"] = offMaterials, ["val"] = { {"bc"} } }, -- 水面透明化
-- ["00:48.9"]={["lylic"] = "こんな風に悩めるのかな", ["function"] = changeColorMaterials, ["val"] = { {"cylinder_ao"}, Color.__new(28/255, 44/255, 82/255, 1) } }, -- 建物マテリアル 青色
["00:48.8"]={["function"] = changeColorMaterials, ["val"] = { {"cylinder_ao"}, Color.__new(159/255, 217/255, 246/255, 1) } }, --"こんな風に悩めるのかな" 建物マテリアル 青色
["00:50.5"]={["function"] = onoffMaterials, ["val"] = {500, {"SINobject4", "SINobject3"} } }, -- 複数マテリアル連続透明・不透明化
["00:52.9"]={["function"] = changeColorMaterials, ["val"] = { {"cylinder_ao"}, Color.__new(211/255, 211/255, 211/255, 1)} },
["00:53.8"]={["function"] = onMaterials, ["val"] = { {"utatte"} }},
["00:54.0"]={["function"] = startAndStopEffect, ["val"] = { "kumo", 3.5 } },
["00:54.1"]={["function"] = playKumonoUeAnime, ["val"] = { } },
["00:55.8"]={["function"] = offMaterials, ["val"] = { {"utatte"} }},
["00:59.4"]={["function"] = uvTexture, ["val"] = {500, "pannel4", Vector2.__new(0,0), Vector2.__new(3,0), Vector2.__new(4,5) } }, -- テクスチャUVoffset切り替え
["01:01.6"]={["function"] = startAndStopEffect, ["val"] = { "iya", 2.5 } },
["01:01.7"]={["function"] = onMaterials, ["val"] = { { "光", "黒" } } }, -- 暗転
["01:04.2"]={["function"] = offMaterials, ["val"] = { { "光", "黒" } } }, -- 暗転終わり
["01:04.3"]={["function"] = startAnimationFromName, ["val"] = { "Light", "lightBig" }}, --"遠く描いてた日々を" 街灯拡大
["01:07.1"]={["function"] = startAnimationFromName, ["val"] = { "Light", "lightSmall" } }, -- 街灯縮小
["01:07.2"]={["function"] = changeColor, ["val"] = {1000, "tenkyu", Color.__new(8/255, 35/255, 67/255, 1) } }, -- 夜
["01:07.3"]={["function"] = startAnimation, ["val"] = { "katattebone" }},
["01:07.4"]={["function"] = onMaterials, ["val"] = { {"katatte"} }},
["01:09.9"]={["function"] = offMaterials, ["val"] = { {"katatte"} }},
["01:08.3"]={["function"] = startAndStopEffect, ["val"] = { "yorunomure", 2.6 }}, -- effect
["01:12.7"]={["function"] = uvTexture, ["val"] = {500, "pannel4", Vector2.__new(0,4), Vector2.__new(3,4), Vector2.__new(4,5) }}, -- テクスチャUVoffset切り替え
["01:14.4"]={["function"] = changeColorMaterials, ["val"] = { {"tenkyu"}, Color.__new(0/255, 171/255, 202/255, 1) } }, -- 昼
["01:14.7"]={["function"] = startEffect, ["val"] = { "ina" }}, -- effect
["01:14.8"]={["function"] = onMaterials, ["val"] = { { "光", "黒" } } }, -- 暗転
["01:17.4"]={["function"] = startAndStopEffect, ["val"] = { "smoke", 3 } },
["01:17.9"]={["function"] = onMaterials, ["val"] = { { "Charles_Kashi26" } }}, --"笑い合ってさよなら"
["01:19.0"]={["function"] = changeColorMaterials, ["val"] = { {"tenkyu"}, Color.__new(0/255, 171/255, 202/255, 1) } }, -- 昼
-- 間奏
["01:19.9"]={["lylic"] = "Charles_Kashi66", ["function"] = changeColor, ["val"] = {6500, "tenkyu", Color.__new(255/255, 165/255, 0, 1) } }, -- 昼→夕
["01:20.5"]={["function"] = offMaterials, ["val"] = { { "Charles_Kashi26", "光", "黒" } } }, -- 暗転終わり
["01:21.0"]={["function"] = startAndStopEffect, ["val"] = { "particle", 12 }}, -- effect
["01:26.5"]={["function"] = changeColor, ["val"] = {6500, "tenkyu", Color.__new(8/255, 35/255, 67/255, 1) } }, -- 夕→夜
["01:33.3"]={["function"] = changeColorMaterials, ["val"] = { {"tenkyu"}, Color.__new(0/255, 171/255, 202/255, 1) } }, -- 昼
["01:45.9"]={["lylic"] = "Charles_Kashi32", ["function"] = changeColors2, ["val"] = { 500, "Moya", Color.__new(255/255, 255/255, 255/255, 0), Color.__new(255/255, 255/255, 255/255, 1) }}, --"黙っていよう それでいつか苛まれたとしても" 暗転
["01:48.6"]={["function"] = changeColors2, ["val"] = { 500, "Moya", Color.__new(255/255, 255/255, 255/255, 1), Color.__new(255/255, 255/255, 255/255, 0) } }, -- 暗転終わり
["01:48.9"]={["lylic"] = "Charles_Kashi33" }, -- 複数マテリアル連続透明・不透明化
["01:50.2"]={["function"] = onoffMaterials, ["val"] = {600, {"SINobject2", "SINobject1"} } }, -- 複数マテリアル連続透明・不透明化
["01:51.0"]={["function"] = changeColorMaterials, ["val"] = { {"cylinder_ao"}, Color.__new(207/255, 115/255, 122/255, 1)}}, -- 建物マテリアル 赤
["01:51.1"]={["function"] = startAndStopEffect, ["val"] = { "Sainamare", 2 }}, -- effect
["01:52.9"]={["function"] = changeColorMaterials, ["val"] = { {"cylinder_ao"}, Color.__new(211/255, 211/255, 211/255, 1)}},
["01:56.6"]={["function"] = onoffMaterials, ["val"] = {500, {"SINobject4", "SINobject3"} }}, -- 複数マテリアル連続透明・不透明化
["01:59.5"]={["function"] = onMaterials, ["val"] = { {"koito6"} }},
["02:00.0"]={["function"] = onMaterials, ["val"] = { {"koito"} }},
["02:00.3"]={["function"] = offMaterials, ["val"] = { { "koito6", "koito" } } }, -- 暗転終わり
["02:02.0"]={["lylic"] = "Charles_Kashi38"},
["02:03.8"]={["lylic"] = "Charles_Kashi39"}, --"汚れきった言葉を今"
["02:05.6"]={["function"] = uvTexture, ["val"] = {500, "pannel4", Vector2.__new(0,3), Vector2.__new(3,3), Vector2.__new(4,5) }}, -- テクスチャUVoffset切り替え
["02:08.0"]={["lylic"] = "Charles_Kashi40", ["function"] = startAndStopEffect, ["val"] = { "ima", 2.2 } }, --"今今" effect
["02:09.2"]={["function"] = startAnimationFromName, ["val"] = { "CrocusLarge", "default" }}, --アニメーション
["02:09.4"]={["function"] = onMaterials, ["val"] = { { "kaben1", "kuki1" } } },
["02:09.7"]={["function"] = offMaterials, ["val"] = { { "kaben1", "kuki1" } } },
["02:13.5"]={["function"] = onMaterials, ["val"] = { {"mazatte"} }},
["02:13.6"]={["function"] = startAnimationFromNameTransform, ["val"] = { "Armature(mazatte)", "mazatteanim" }}, --"混ざって混ざって二人の果て",
["02:15.6"]={["function"] = offMaterials, ["val"] = { {"mazatte"} }},
["02:18.9"]={["function"] = uvTexture, ["val"] = { 400, "parple5", Vector2.__new(0,0), Vector2.__new(4,0), Vector2.__new(5,1) }}, -- テクスチャUVoffset切り替え
["02:20.0"]={["function"] = changeColor, ["val"] = {3000, "tenkyu", Color.__new(255/255, 165/255, 0, 1) } }, -- 昼→夕
["02:20.9"]={["function"] = startAndStopEffect, ["val"] = { "ina10", 2.5 }}, -- effect
["02:22.0"]={["function"] = startAnimationFromName, ["val"] = { "CrocusLarge", "default" }}, --アニメーション
["02:22.3"]={["function"] = onMaterials, ["val"] = { { "kaben1", "kuki1" } } },
["02:22.6"]={["function"] = offMaterials, ["val"] = { { "kaben1", "kuki1" } } },
--skydomeをオレンジから夜へ
["02:26.5"]={["function"] = playKansouEffect, ["val"] = { } },
["02:27.6"]={["function"] = changeColor, ["val"] = {1000, "tenkyu", Color.__new(0/255, 171/255, 202/255, 1) } }, -- 夕→昼
["02:43.2"]={["function"] = onoffMaterials, ["val"] = {500, {"SINobject1", "SINobject2"} }}, -- 複数マテリアル連続透明・不透明化
["02:49.6"]={["function"] = onoffMaterials, ["val"] = {500, {"SINobject3", "SINobject4"} }}, -- 複数マテリアル連続透明・不透明化
["03:05.3"]={["function"] = uvTexture, ["val"] = {500, "kansou", Vector2.__new(0,1), Vector2.__new(3,1), Vector2.__new(4,5) }}, -- テクスチャUVoffset切り替え
["03:07.2"]={["function"] = startEffect, ["val"] = { "iya" } },
["03:07.4"]={["function"] = onMaterials, ["val"] = { { "光", "黒" } } }, -- 暗転
["03:10.4"]={["function"] = offMaterials, ["val"] = { { "光", "黒" } } }, -- 暗転終わり
["03:10.2"]={["function"] = startAndStopEffect, ["val"] = { "moya4", 3 }}, -- effect
["03:13.3"]={["function"] = changeColor, ["val"] = {1000, "tenkyu", Color.__new(8/255, 35/255, 67/255, 1) } }, -- 夜
["03:13.5"]={["function"] = onMaterials, ["val"] = { {"katatte"} }},
["03:13.6"]={["function"] = startAnimation, ["val"] = { "katattebone" }},
["03:14.5"]={["function"] = offMaterials, ["val"] = { {"katatte"} }},
["03:14.6"]={["function"] = startAndStopEffect, ["val"] = { "yorunomure", 2.4 }}, -- effect
["03:18.3"]={["function"] = uvTexture, ["val"] = {500, "pannel4", Vector2.__new(0,2), Vector2.__new(3,2), Vector2.__new(4,5) }}, -- テクスチャUVoffset切り替え
["03:20.3"]={["function"] = changeColorMaterials, ["val"] = { {"tenkyu"}, Color.__new(0/255, 171/255, 202/255, 1) } }, -- 昼
["03:20.7"]={["function"] = startAndStopEffect, ["val"] = { "ina10", 3 }}, -- effect
["03:21.5"]={["function"] = startAnimationFromName, ["val"] = { "CrocusLarge", "default" }}, --アニメーション
["03:22.0"]={["function"] = onMaterials, ["val"] = { { "kaben1", "kuki1" } } },
["03:22.4"]={["function"] = offMaterials, ["val"] = { { "kaben1", "kuki1" } } },
["03:23.2"]={["function"] = startAnimationFromName, ["val"] = { "CrocusLarge", "colorful" }}, --アニメーション
["03:23.4"]={["function"] = onMaterials, ["val"] = { { "kaben1", "kuki1" } } },
["03:23.5"]={["function"] = switchColor, ["val"] = { 300, {"kaben", "kaben1"} }}, -- effect
["03:24.8"]={["function"] = offMaterials, ["val"] = { { "kaben1", "kuki1" } } },
["03:25.6"]={["function"] = playMovie, ["val"] = { "nc79270_movie_halfclear", 15, 30 } }, --"愛を謳って謳って雲の上"
["03:26.1"]={["function"] = onMaterials, ["val"] = { {"utatte"} }},
["03:26.2"]={["function"] = startAnimation, ["val"] = { "utatteroot" }},
["03:26.7"]={["function"] = startEffect, ["val"] = { "utatteeffe" } },
["03:28.4"]={["function"] = offMaterials, ["val"] = { {"utatte"} }},
["03:33.2"]={["function"] = startAnimation, ["val"] = { "katattebone" }},
["03:33.3"]={["function"] = onMaterials, ["val"] = { {"katatte"} }},
["03:34.9"]={["function"] = offMaterials, ["val"] = { {"katatte"} }},
}
タイムテーブルの処理を実行する方法
タイムテーブルの処理は以下のシーケンス図のように実現しています。
その際、コルーチンはvci.StartCoroutineではなく、私の方で公開しているexpandedVCICoroutineモジュールを使用しています。
経過時間をキーにタイムテーブルから値を取り出す方法についてはLua言語でテーブルを用いてswith-case文を疑似的に実現するを参照ください。
タイムテーブル実行コルーチン
function timerCoroutine()
print("not all コルーチン開始")
vci.assets.audio.PlayOneShot("シャルル【ボカロ完成】", 0.3)
startAnimationFromName("Light", "lightSmall")
startAnimationFromName( "sorenanoni3", "sorenanonikoteiyou" )
initMaterials()
local colorWaku = vci.assets.material.GetColor("waku")
colorWaku.a = 0
vci.assets.material.SetColor("waku", colorWaku)
while isStart == true do
local curr = vci.me.UnscaledTime
local baseTime = os.time({["year"] = 1970, ["month"] = 1, ["day"] = 1, ["hour"] = 0, ["min"] = 0, ["sec"] = 0 })
-- local curr = TimeSpan.FromSeconds( os.difftime( os.time(), baseTime ) )
local tStr = getTimeStr(curr)
if lastTimeStr ~= tStr then
local lylic = getLylic(tStr)
if lylic ~= nil then
vci.assets.SetText("TimerText", tStr)
end
local func = getFunction(tStr)
local val = getValues(tStr)
if func == changeColorMaterials then
func(val[1], val[2] )
elseif func == onMaterials then
expandedVCICoroutine.startCoroutine(func, val[1])
elseif func == offMaterials then
expandedVCICoroutine.startCoroutine(func, val[1])
elseif func == changeColor then
expandedVCICoroutine.startCoroutine(func, val[1], val[2], val[3] )
elseif func == changeColors2 then
expandedVCICoroutine.startCoroutine(func, val[1], val[2], val[3], val[4])
elseif func == onoffMaterials then
expandedVCICoroutine.startCoroutine(func, val[1], val[2])
elseif func == uvTexture then
expandedVCICoroutine.startCoroutine(func, val[1], val[2], val[3], val[4], val[5])
elseif func == startEffect then
expandedVCICoroutine.startCoroutine(func, val[1])
elseif func == startAndStopEffect then
expandedVCICoroutine.startCoroutine(func, val[1], val[2])
elseif func == startAnimation then
expandedVCICoroutine.startCoroutine(func, val[1])
elseif func == startAnimationFromName then
expandedVCICoroutine.startCoroutine(func, val[1], val[2])
elseif func == startAnimationFromNameTransform then
expandedVCICoroutine.startCoroutine(func, val[1], val[2])
elseif func == switchColor then
expandedVCICoroutine.startCoroutine(func, val[1], val[2])
elseif func == playMovie then
expandedVCICoroutine.startCoroutine(func, val[1], val[2], val[3] )
elseif func == startTitleEffect then
expandedVCICoroutine.startCoroutine(func, val[1])
elseif func == playKumonoUeAnime then
expandedVCICoroutine.startCoroutine(func)
elseif func == playKansouEffect then
expandedVCICoroutine.startCoroutine(func, val[1])
elseif func == initMaterials then
initMaterials()
end
lastTimeStr = tStr
end
moveCameraModule()
if tStr > songLength then
break
end
sleepMs(20)
end
isStart = false
vci.assets.SetText("LylicText", ProductionTable["ti"]["title"])
vci.assets.audio.Stop("シャルル【ボカロ完成】")
colorWaku.a = 1
vci.assets.material.SetColor("waku", colorWaku)
print("not all コルーチン終了")
end
なぜvci.StartCoroutineを使用しないか
vci.StartCoroutineを使用しない理由としては以下の2点が挙げられます。
1.vci.StartCoroutineで実行したコルーチンの中でvci.StartCoroutineを実行すると、VCIシステムが死ぬ
以下の図のようにvci.StartCoroutineで実行したコルーチンの中でコルーチンを実行しようとするとその時点でVCIの大本が死に、スタジオに入りなおさないといけません。
2.コルーチンの実行時に引数が渡せない
例えば、徐々に色を変えるコルーチン
function changeColor(time, mat, color)
print("changeColor 開始")
print(time)
local start = vci.assets.material.GetColor(mat)
local t0 = vci.me.UnscaledTime.Add(TimeSpan.FromMilliseconds(time))
while TimeSpan.Compare(vci.me.UnscaledTime, t0) < 0 do
local t1 = TimeSpan.FromTicks((t0 - vci.me.UnscaledTime).Ticks)
local msec = (t1.Hours * 1000 * 60 * 60) + (t1.Minutes * 1000 * 60) + (t1.Seconds * 1000) + t1.Milliseconds
local c = Color.Lerp(color, start, (msec/time))
vci.assets.material.SetColor(mat, c)
sleepMs(5)
end
vci.assets.material.SetColor(mat, color)
print("changeColor 終了")
print(time)
end
引数を渡さない場合、専用のコルーチンを作成したり、グローバル変数に使いたい値を覚えることで実現することも出来ますが、同じコルーチンを複数実行する場合に煩雑になります。
そのため、自作のコルーチンモジュール(luaのコルーチンをモジュール化)を使用しています。
専用のコルーチンを作成する例
function changeColorFromNoonToEvening()
print("changeColor 開始")
local time = 6500
local mat = "tenkyu"
local color = Color.__new(255/255, 165/255, 0, 1)
local start = vci.assets.material.GetColor(mat)
local t0 = vci.me.UnscaledTime.Add(TimeSpan.FromMilliseconds(time))
while TimeSpan.Compare(vci.me.UnscaledTime, t0) < 0 do
local t1 = TimeSpan.FromTicks((t0 - vci.me.UnscaledTime).Ticks)
local msec = (t1.Hours * 1000 * 60 * 60) + (t1.Minutes * 1000 * 60) + (t1.Seconds * 1000) + t1.Milliseconds
local c = Color.Lerp(color, start, (msec/time))
vci.assets.material.SetColor(mat, c)
sleepMs(5)
end
vci.assets.material.SetColor(mat, color)
print("changeColor 終了")
print(time)
end
function changeColorFromEveningToNight()
print("changeColor 開始")
local time = 6500
local mat = "tenkyu"
local color = Color.__new(8/255, 35/255, 67/255, 1)
local start = vci.assets.material.GetColor(mat)
local t0 = vci.me.UnscaledTime.Add(TimeSpan.FromMilliseconds(time))
while TimeSpan.Compare(vci.me.UnscaledTime, t0) < 0 do
local t1 = TimeSpan.FromTicks((t0 - vci.me.UnscaledTime).Ticks)
local msec = (t1.Hours * 1000 * 60 * 60) + (t1.Minutes * 1000 * 60) + (t1.Seconds * 1000) + t1.Milliseconds
local c = Color.Lerp(color, start, (msec/time))
vci.assets.material.SetColor(mat, c)
sleepMs(5)
end
vci.assets.material.SetColor(mat, color)
print("changeColor 終了")
print(time)
end
vci.StartCoroutine( coroutine.create( changeColorFromNoonToEvening ) )
vci.StartCoroutine( coroutine.create( changeColorFromEveningToNight) )
グローバル変数に使いたい値を覚える例
local time
local mat
local color
function changeColor()
print("changeColor 開始")
local start = vci.assets.material.GetColor(mat)
local t0 = vci.me.UnscaledTime.Add(TimeSpan.FromMilliseconds(time))
while TimeSpan.Compare(vci.me.UnscaledTime, t0) < 0 do
local t1 = TimeSpan.FromTicks((t0 - vci.me.UnscaledTime).Ticks)
local msec = (t1.Hours * 1000 * 60 * 60) + (t1.Minutes * 1000 * 60) + (t1.Seconds * 1000) + t1.Milliseconds
local c = Color.Lerp(color, start, (msec/time))
vci.assets.material.SetColor(mat, c)
sleepMs(5)
end
vci.assets.material.SetColor(mat, color)
print("changeColor 終了")
print(time)
end
time = 6500
mat = "tenkyu"
color = Color.__new(255/255, 165/255, 0, 1)
vci.StartCoroutine( coroutine.create( changeColor) )
updateとVCIコルーチンでよくない?
前項の内容を以下のようにupdateとVCIコルーチンで実現することも可能です。
ただ、ダサいし、処理が分割してて管理が面倒なので自作のコルーチンモジュールを使用しています。
タイムテーブルの作成方法
タイムテーブルを作成する際にはカラオケ等で使用される同期歌詞が非常に役に立ちます。
曲名+LRCファイルで検索すると幸せになります。
ほぼほぼ、luaのテーブルに近い形なのでテキストエディタで少し置換するだけでVCIで使用できるタイムテーブルになります。
タイムテーブルにないタイミングで処理を実行したい場合は録画とにらめっこしながら頑張ってください
最後に
今回は実装方法の概要について記載しました。
Vキャスでもいわゆるパーティクルライブを作成することができますので、どんどん作ってください
VFMの放送画面に映らない部分も色々作り込んでいます。近々VR内での体験会を実施しますので、是非見に来てください
2020/12/17
シーケンス図にスリープが抜けていたので修正
「専用のコルーチンを作成する例」、「グローバル変数に使いたい値を覚える例」を追記