VirtualCast公式わんコメOSCプラグインでYouTube・ニコニコ生放送のコメントを受信してアクションを起こすVCIのサンプルです。
local debagFlag = false local roomFlag local myId local myPlayer local camera local monitor local uploadNum = 0 local queueTbl = {} local supportUnit = {["¥"] = {{5000, 3}, {10000, 6}, {50001, 18}}} local performTbl = {} local timingTbl = {4.7, 5.7, 6.7, 7.7, 8.7, 9.7, 11.7} local timingFlag = {} local timingEffectFlag = {} for i = 1, 9 do timingFlag[i] = false timingEffectFlag[i] = false end local objPassTbl = {"/CamFrame/DummyFrame/Effect","/MonFrame/DummyFrame/Effect"} local camActiveFlag = false local switchAnimeFlag = false local swInterval = 0.2 local swPreTime = 0 local camDummy = vci.assets.GetTransform("CamDummyCollider") local monFrame = vci.assets.GetTransform("MonFrame") local camFrame = vci.assets.GetTransform("CamFrame") --local sound = vci.assets.GetTransform("Sound").GetAudioSources() local camEffect = vci.assets.GetEffekseerEmitters("CamEffect") local monEffect = vci.assets.GetEffekseerEmitters("MonEffect") --カメラの生成・削除のチェック処理 --演出中に生成・削除された場合、後の処理で、演出がアクティブなカメラのほうに切り替わる local function camSwitchCheck() camera = vci.vc.room.streamCamera.GetLocalStreamCameraByPlayerId(vci.state.Get("OwnerID")) monitor = vci.vc.room.monitorCamera if camActiveFlag and not(camera.IsAvailable()) then switchAnimeFlag = true camActiveFlag = false for i = 1, 9 do timingFlag[i] = false end end if not(camActiveFlag) and camera.IsAvailable() then switchAnimeFlag = true camActiveFlag = true for i = 1, 9 do timingFlag[i] = false end end end --追従処理 --カメラの有無により、エフェクトの追従処理が変わる ----カメラ有の場合 ----演出処理ではない際は、追従用ダミーコライダーは、非アクティブにし、カメラに追従しておく ----演出処理の際には、カメラとエフェクトをダミーコライダーに追従させ、カメラとエフェクトがブレないようにする ----カメラ無の場合 ----モニターにエフェクトを追従させる --エフェクトアイテムは、非SubItemであるため、ローカルでそれぞれカメラやモニタに追従させる local function performTransform() if camera.IsAvailable() then if #performTbl == 0 then if camDummy.ActiveSelf then camDummy.SetActive(false) camera.SetGrabbable(true) end if camDummy.IsMine then camDummy.SetPosition(camera.GetPosition()) camDummy.SetRotation(camera.GetRotation()) end else if camera.IsMine() then camera.SetPositionImmediately(camDummy.GetPosition()) camera.SetRotationImmediately(camDummy.GetRotation()) end if not(camDummy.ActiveSelf) then camDummy.SetActive(true) camera.SetGrabbable(false) end camFrame.SetPosition(camDummy.GetPosition()) camFrame.SetRotation(camDummy.GetRotation()) local scl = Vector3.one*(math.tan(camera.GetFov()/2/180*math.pi))/(math.tan(22.5/180*math.pi)) vci.assets.GetTransform("CamEffect").SetLocalScale(scl*0.07) vci.assets.GetTransform(objPassTbl[1]).SetLocalScale(scl*0.07) vci.assets.GetTransform("CamEff_thnder").SetLocalScale(scl*2) vci.assets.GetTransform("CamEff_star").SetLocalScale(scl*1) vci.assets.GetTransform("CamEff_leaf2").SetLocalScale(scl*1) vci.assets.GetTransform("CamEff_cloud1").SetLocalScale(scl*0.1) vci.assets.GetTransform("CamEff_cloud2").SetLocalScale(scl*0.1) end else if monitor.GetPosition() ~= nil and #performTbl ~= 0 then monFrame.SetPosition(monitor.GetPosition()) monFrame.SetRotation(monitor.GetRotation()) local scl = monitor.GetScale()*1.11 vci.assets.GetTransform("MonEffect").SetLocalScale(scl*4) vci.assets.GetTransform(objPassTbl[2]).SetLocalScale(scl*0.8) vci.assets.GetTransform("MonEff_thnder").SetLocalScale(scl*100) vci.assets.GetTransform("MonEff_star").SetLocalScale(scl*50) vci.assets.GetTransform("MonEff_leaf2").SetLocalScale(scl*50) vci.assets.GetTransform("MonEff_cloud1").SetLocalScale(scl*5) vci.assets.GetTransform("MonEff_cloud2").SetLocalScale(scl*5) end end end --スパチャ・ギフトの演出の管理 --下記の4条件がそろった際に実行される --⓵VCIオーナー --⓶キューがあり、アバターが読み込まれている(この後、アバター情報を読み込む場合があるので) --⓷カメラ、もしくは、モニタカメラがある(モニタカメラのリスポーン対策) --⓸オーナーで前のスパチャ・ギフト演出が終了している -- local function negesenStateUpdate() if vci.state.Get("OwnerID") == myId then if #queueTbl ~= 0 and myPlayer.Character.IsAvailable() then if camera.IsAvailable() or monitor.GetPosition() ~= nil then if #performTbl == 0 then local uploadTbl = {} for i = 1, #queueTbl[1] do table.insert(uploadTbl, queueTbl[1][i]) end table.remove(queueTbl, 1) local num = uploadNum + 1 if num > 10 then num = 1 end vci.state.Set("UploadTbl", uploadTbl) vci.state.Set("UploadNum", num) end end end end end --演出発生時、最初に処理されるエフェクト処理 local function initializePerform() if vci.state.Get("UploadNum") ~= uploadNum then uploadNum = vci.state.Get("UploadNum") local tbl = vci.state.Get("UploadTbl") performTbl = {} for i = 1, #tbl do table.insert(performTbl, tbl[i]) end table.insert(performTbl, 2, os.time(sec)) local num = performTbl[1] camEffect[num].PlayOneShot() monEffect[num].PlayOneShot() if num == 3 then for i = 1, 9 do timingFlag[i] = false timingEffectFlag[i] = false end for i = 1, 2 do vci.assets.GetTransform(objPassTbl[i]).SetActive(true) vci.assets.GetTransform(objPassTbl[i].."/bg").SetActive(false) vci.assets.GetTransform(objPassTbl[i].."/Price").SetActive(false) vci.assets.GetTransform(objPassTbl[i].."/NumPerform").SetActive(false) vci.assets.GetTransform(objPassTbl[i].."/WhiteOut").SetActive(false) end vci.assets.material.SetColor("Mat_WhiteOut", Color.clear) end --sound[num].PlayOneShot(0.1) print("演出SE"..num) if camera.IsAvailable() then monEffect[num].SetShow(false) if vci.state.Get("CameraWork") then local ownerPlayer = vci.vc.room.GetPlayerById(vci.state.Get("OwnerID")) local pos = ownerPlayer.Character.GetBoneTransform("Head").position + ownerPlayer.GetForward() local rot = Quaternion.LookRotation(-1*ownerPlayer.GetForward(), Vector3.up) if camDummy.IsMine then camDummy.SetPosition(pos) camDummy.SetRotation(rot) end if camera.IsMine() then camera.SetPositionImmediately(pos) camera.SetRotationImmediately(rot) camera.SetFovImmediately(30) end end else camEffect[num].SetShow(false) end end end ---------------------------------------------------------------- ---高額のスパチャ・ギフトの演出処理(いわゆる、赤スパ以上) ---演出は、最初のカットインフレーム、¥を含めた6つの数字の強調アニメーション、最後のフレームアニメーション、の3つから構成される ---最初のカットインフレームは、別の処理で行われている ---6つの数字強調アニメーションは、オブジェクトを使いまわし、数字のアクティブ処理で切り替える -----オブジェクトのアクティブ化、アニメーション処理、エフェクト処理、マテリアル処理、(ホワイトアウト処理)で構成 ---最後のフレームアニメーションは、アクティブ化処理、アニメーション処理、エフェクト処理、で構成 ---カメラの生成、もしくは、削除があった場合、カメラ、もしくは、モニタに切り替わり、演出途中の場合、続きから再生される ------------------------------------------------------------------- local function highPricePerform() if performTbl[1] == 3 then local activeNum if camera.IsAvailable() then activeNum = 1 else activeNum = 2 end local deltaTime = os.time(sec) - performTbl[2] if switchAnimeFlag then if deltaTime >= timingTbl[1] and deltaTime < timingTbl[7] then vci.assets.GetTransform(objPassTbl[1].."/NumPerform").SetActive(camActiveFlag) vci.assets.GetTransform(objPassTbl[2].."/NumPerform").SetActive(not(camActiveFlag)) end if deltaTime >= 12.2 then vci.assets.GetTransform(objPassTbl[1].."/bg").SetActive(camActiveFlag) vci.assets.GetTransform(objPassTbl[2].."/bg").SetActive(not(camActiveFlag)) end if deltaTime >= 12.7 then vci.assets.GetTransform(objPassTbl[1].."/Price").SetActive(camActiveFlag) vci.assets.GetTransform(objPassTbl[2].."/Price").SetActive(not(camActiveFlag)) end end ---------------------------------------------------------------- --最初のカットインフレーム後の、6つの金額強調アニメーション処理 ---------------------------------------------------------------- for i = 1, 6 do --金額数字の処理 if deltaTime >= timingTbl[i] and deltaTime < timingTbl[i+1] and not(timingFlag[i]) then vci.assets.GetTransform(objPassTbl[activeNum].."/NumPerform").SetActive(true) --金額数字のアクティブ・非アクティブ処理 if i == 1 then for j = 0, 10 do if j ~= 10 then vci.assets.GetTransform(objPassTbl[activeNum].."/NumPerform/Num/fm_"..j).SetActive(false) else vci.assets.GetTransform(objPassTbl[activeNum].."/NumPerform/Num/fm_"..j).SetActive(true) end end else local num = tonumber(string.sub(tostring(performTbl[4]), i-1, i-1)) for j = 0, 10 do if j ~= num then vci.assets.GetTransform(objPassTbl[activeNum].."/NumPerform/Num/fm_"..j).SetActive(false) else vci.assets.GetTransform(objPassTbl[activeNum].."/NumPerform/Num/fm_"..j).SetActive(true) end end end --アニメーション再生処理 local stateTable if i ~= 6 then stateTable = {speed=2, weight=1, wrap_mode="clamp_forever"} else stateTable = {speed=1.5, weight=1, wrap_mode="clamp_forever"} end local stateJson = json.serialize(stateTable) vci.assets.GetTransform(objPassTbl[activeNum].."/NumPerform").GetAnimation().Stop() vci.assets.GetTransform(objPassTbl[activeNum].."/NumPerform").GetAnimation().PlayWithState("NumAnime1", stateJson) if switchAnimeFlag then vci.assets.GetTransform(objPassTbl[activeNum].."/NumPerform").GetAnimation().SetTime("NumAnime1", deltaTime - timingTbl[i]) end timingFlag[i] = true end --エフェクトの処理 if deltaTime >= timingTbl[i]+0.5 and deltaTime < timingTbl[i+1]+0.5 and not(timingEffectFlag[i]) then if i ~= 6 then vci.assets.GetEffekseerEmitters("CamEff_thnder")[1].PlayOneShot() vci.assets.GetEffekseerEmitters("MonEff_thnder")[1].PlayOneShot() else vci.assets.GetEffekseerEmitters("CamEff_thnder")[2].PlayOneShot() vci.assets.GetEffekseerEmitters("MonEff_thnder")[2].PlayOneShot() end --sound[4].PlayOneShot(0.2) print("演出SE4") timingEffectFlag[i] = true end for i = 1, 2 do vci.assets.GetEffekseerEmitters("CamEff_thnder")[i].SetShow(camActiveFlag) vci.assets.GetEffekseerEmitters("MonEff_thnder")[i].SetShow(not(camActiveFlag)) end --ホワイトアウト処理(最後の数字~最後のフレームアニメーション、の切り替え処理) if i == 6 then if deltaTime <= timingTbl[i]+1.5 then elseif deltaTime <= timingTbl[i]+2.5 then vci.assets.GetTransform(objPassTbl[1].."/WhiteOut").SetActive(camActiveFlag) vci.assets.GetTransform(objPassTbl[2].."/WhiteOut").SetActive(not(camActiveFlag)) vci.assets.material.SetColor("Mat_WhiteOut", Color.__new(1,1,1,((deltaTime-(timingTbl[i]+1.5))/1)^2)) elseif deltaTime <= timingTbl[i]+2.8 then vci.assets.material.SetColor("Mat_WhiteOut", Color.white) else vci.assets.GetTransform(objPassTbl[1].."/WhiteOut").SetActive(false) vci.assets.GetTransform(objPassTbl[2].."/WhiteOut").SetActive(false) end end end --マテリアル処理 --4.7秒までは、アニメーション演出が始まらないので、透明 --それ以降は、各数字アニメーションの最後0.3秒でフェードアウトするように --最後の数字だけ、約倍の時間でフェードアウトする if deltaTime <= 4.7 then vci.assets.material.SetColor("font_10000_red", Color.clear) vci.assets.material.SetColor("font_10000_gold", Color.clear) vci.assets.material.SetColor("font_10000_blackFrame", Color.clear) elseif deltaTime <= 9.7 then if (deltaTime-2.7)%1 < 0.7 then vci.assets.material.Reset("font_10000_red") vci.assets.material.Reset("font_10000_gold") vci.assets.material.Reset("font_10000_blackFrame") else vci.assets.material.SetColor("font_10000_red", Color.__new(1,1,1,(1- (deltaTime-2.7)%1)/0.3)) vci.assets.material.SetColor("font_10000_gold", Color.__new(1,1,1,(1- (deltaTime-2.7)%1)/0.3)) vci.assets.material.SetColor("font_10000_blackFrame", Color.__new(24/255,19/255,35/255,(1- (deltaTime-2.7)%1)/0.3)) end elseif deltaTime <= 11.7 then if deltaTime - 9.7 < 1.3 then vci.assets.material.Reset("font_10000_red") vci.assets.material.Reset("font_10000_gold") vci.assets.material.Reset("font_10000_blackFrame") else vci.assets.material.SetColor("font_10000_red", Color.__new(1,1,1,(11.7-deltaTime)/0.7)) vci.assets.material.SetColor("font_10000_gold", Color.__new(1,1,1,(11.7-deltaTime)/0.7)) vci.assets.material.SetColor("font_10000_blackFrame", Color.__new(24/255,19/255,35/255,(11.7-deltaTime)/0.7)) end end ---------------------------------------------------------------- --最後のフレームアニメーション処理 ---------------------------------------------------------------- if deltaTime >= 12.2 and not(timingFlag[7]) then vci.assets.GetTransform(objPassTbl[activeNum].."/NumPerform").SetActive(false) vci.assets.GetTransform(objPassTbl[activeNum].."/bg").SetActive(true) local stateTable = {speed=1, weight=1, wrap_mode="clamp_forever"} local stateJson = json.serialize(stateTable) vci.assets.GetTransform(objPassTbl[activeNum].."/bg").GetAnimation().PlayWithState("bg", stateJson) if switchAnimeFlag then local limitTime = vci.assets.GetTransform(objPassTbl[activeNum].."/bg").GetAnimation().GetDuration("bg") if deltaTime - 12.2 < limitTime then vci.assets.GetTransform(objPassTbl[activeNum].."/bg").GetAnimation().SetTime("bg", deltaTime-12.2) else vci.assets.GetTransform(objPassTbl[activeNum].."/bg").GetAnimation().SetTime("bg", limitTime) end end timingFlag[7] = true end --最後のスパチャ・ギフト価格のアニメーション if deltaTime >= 12.7 and not(timingFlag[8]) then vci.assets.material.Reset("font_10000_red") vci.assets.material.Reset("font_10000_gold") vci.assets.material.Reset("font_10000_blackFrame") vci.assets.GetTransform(objPassTbl[activeNum].."/Price").SetActive(true) --万~一の位の数字のアクティブ・非アクティブ処理 for i = 1, 5 do local num = tonumber(string.sub(tostring(performTbl[4]), i, i)) for j = 0, 9 do if j ~= num then vci.assets.GetTransform(objPassTbl[activeNum].."/Price/Num"..i.."/fm_"..j).SetActive(false) else vci.assets.GetTransform(objPassTbl[activeNum].."/Price/Num"..i.."/fm_"..j).SetActive(true) end end end --アニメーション処理 local stateTable = {speed=1, weight=1, wrap_mode="clamp_forever"} local stateJson = json.serialize(stateTable) vci.assets.GetTransform(objPassTbl[activeNum].."/Price").GetAnimation().PlayWithState("PriceAnime", stateJson) if switchAnimeFlag then local limitTime = vci.assets.GetTransform(objPassTbl[activeNum].."/Price").GetAnimation().GetDuration("PriceAnime") if deltaTime - 12.7 < limitTime then vci.assets.GetTransform(objPassTbl[activeNum].."/Price").GetAnimation().SetTime("PriceAnime", deltaTime-12.7) else vci.assets.GetTransform(objPassTbl[activeNum].."/Price").GetAnimation().SetTime("PriceAnime", limitTime) end end --エフェクト処理 vci.assets.GetEffekseerEmitter("CamEff_star").Play() vci.assets.GetEffekseerEmitter("CamEff_leaf2").Play() vci.assets.GetEffekseerEmitter("CamEff_cloud1").Play() vci.assets.GetEffekseerEmitter("CamEff_cloud2").Play() vci.assets.GetEffekseerEmitter("MonEff_star").Play() vci.assets.GetEffekseerEmitter("MonEff_leaf2").Play() vci.assets.GetEffekseerEmitter("MonEff_cloud1").Play() vci.assets.GetEffekseerEmitter("MonEff_cloud2").Play() timingFlag[8] = true end vci.assets.GetEffekseerEmitter("CamEff_star").SetShow(camActiveFlag) vci.assets.GetEffekseerEmitter("CamEff_leaf2").SetShow(camActiveFlag) vci.assets.GetEffekseerEmitter("CamEff_cloud1").SetShow(camActiveFlag) vci.assets.GetEffekseerEmitter("CamEff_cloud2").SetShow(camActiveFlag) vci.assets.GetEffekseerEmitter("MonEff_star").SetShow(not(camActiveFlag)) vci.assets.GetEffekseerEmitter("MonEff_leaf2").SetShow(not(camActiveFlag)) vci.assets.GetEffekseerEmitter("MonEff_cloud1").SetShow(not(camActiveFlag)) vci.assets.GetEffekseerEmitter("MonEff_cloud2").SetShow(not(camActiveFlag)) --演出終了処理 if deltaTime >= 17.7 and not(timingFlag[9]) then vci.assets.GetTransform(objPassTbl[1]).SetActive(false) vci.assets.GetTransform(objPassTbl[2]).SetActive(false) vci.assets.GetTransform(objPassTbl[activeNum].."/bg").SetActive(false) vci.assets.GetTransform(objPassTbl[activeNum].."/Price").SetActive(false) vci.assets.GetEffekseerEmitter("CamEff_star").Stop() vci.assets.GetEffekseerEmitter("CamEff_leaf2").Stop() vci.assets.GetEffekseerEmitter("CamEff_cloud1").Stop() vci.assets.GetEffekseerEmitter("CamEff_cloud2").Stop() vci.assets.GetEffekseerEmitter("MonEff_star").Stop() vci.assets.GetEffekseerEmitter("MonEff_leaf2").Stop() vci.assets.GetEffekseerEmitter("MonEff_cloud1").Stop() vci.assets.GetEffekseerEmitter("MonEff_cloud2").Stop() timingFlag[9] = true end switchAnimeFlag = false end end --投げ銭演出の管理関数 --initializePerformにて演出の更新チェックを行い、更新があった場合、まず、カットインフレームエフェクトを実行 --ローカルストリーミングカメラの生成・削除があった場合、エフェクトの表示を切り替える --赤スパ(相当)があった場合、highPricePerformを実行 --演出時間が過ぎたら、演出をリセット local function nagesenPerformance() initializePerform() if #performTbl ~= 0 then local num = performTbl[1] if camera.IsAvailable() then camEffect[num].SetShow(true) monEffect[num].SetShow(false) else camEffect[num].SetShow(false) monEffect[num].SetShow(true) end highPricePerform() if os.time(sec) - performTbl[2] > performTbl[3] then performTbl = {} end end end local youtubeAdress2 = "/vc-official/onecomme/youtube/super" local niconicoAdress2 = "/vc-official/onecomme/niconico/gift" local function superChatPerformance(content) if roomFlag then if vci.state.Get("OwnerID") == myId then local oscData = json.parse(content) if supportUnit[oscData.unit] ~= nil then for j = 1, #supportUnit[oscData.unit] do if oscData.price < supportUnit[oscData.unit][j][1] then table.insert(queueTbl, {j, supportUnit[oscData.unit][j][2], oscData.price, oscData.unit}) break end end end end end end vci.osc.RegisterMethod(youtubeAdress2, superChatPerformance, {ExportOscType.BlobAsUtf8}) local function giftPerformance(content) if roomFlag then if vci.state.Get("OwnerID") == myId then local oscData = json.parse(content) for j = 1, #supportUnit["¥"] do if tonumber(oscData.price) < supportUnit["¥"][j][1] then table.insert(queueTbl, {j, supportUnit["¥"][j][2], oscData.price, "¥"}) break end end end end end vci.osc.RegisterMethod(niconicoAdress2, giftPerformance, {ExportOscType.BlobAsUtf8}) --初期化処理 local function initialize() --ルームの識別 if vci.vc.GetSpaceType() == ExportVcSpaceType.studio then roomFlag = false else roomFlag = true end --ルームの時のみ処理 if roomFlag then myPlayer = vci.vc.room.GetLocalPlayer() myId = myPlayer.GetId() --stateの初期化 if vci.state.Get("OwnerID") == nil and vci.assets.IsMine then vci.state.Set("OwnerID", myId)--VCIのオーナー判別用 end vci.state.Set("CameraWork", true)--VCIオーナーにカメラをフォーカスするかどうかフラグ管理 vci.state.Set("UploadNum", 0)--演出の更新が入ったかどうかのチェック用 --オブジェクトの非アクティブ化 vci.assets.GetTransform(objPassTbl[1]).SetActive(false) vci.assets.GetTransform(objPassTbl[2]).SetActive(false) --カメラフォーカススイッチの表示更新 local tempFlag if vci.state.Get("CameraWork") ~= nil then tempFlag = vci.state.Get("CameraWork") else tempFlag = true--初回読み込み時のVCI読み込みラグ対策 end local tempText if tempFlag then tempText = "ON" else tempText = "OFF" end vci.assets.SetText("SwitchText1", tempText) vci.assets.SetText("SwitchText2", tempText) swPreTime = os.time(sec) --デバッグ時にローカルストリーミングカメラを自動生成 if debagFlag then if vci.state.Get("OwnerID") == myId then camera = vci.vc.room.streamCamera.GetMyLocalStreamCamera() if not camera.IsAvailable() then local pos = vci.vc.room.GetLocalPlayer().GetPosition() + vci.vc.room.GetLocalPlayer().GetForward() local headPos = vci.vc.room.GetLocalPlayer().Character.GetBoneTransform("Head").position pos.y = headPos.y local rot = Quaternion.LookRotation(headPos - pos, Vector3.up) vci.vc.room.streamCamera.CreateMyLocalStreamCamera(pos, rot, function() camera = vci.vc.room.streamCamera.GetMyLocalStreamCamera() camera.SetFovImmediately(10) camera.SetGrabbable(false) end) end end end end end initialize() function updateAll() if vci.state.Get("OwnerID") ~= nil then --ローカルストリーミングカメラが生成・削除した際のエフェクトの切り替えフラグ管理 camSwitchCheck() --エフェクトやアニメーションオブジェクト、ダミーコライダーの移動管理 performTransform() --スパチャ・ギフトの演出用変数の管理 negesenStateUpdate() --スパチャ・ギフトの演出処理 nagesenPerformance() end end --VCIオーナーにカメラをフォーカスするかどうかのスイッチ管理 function onUse(use) if use == "SwitchDummy" then if os.time(sec)-swPreTime >= swInterval then local tempFlag = not(vci.state.Get("CameraWork")) vci.state.Set("CameraWork", tempFlag) local tempText if tempFlag then tempText = "ON" else tempText = "OFF" end vci.assets._ALL_SetText("SwitchText1", tempText) vci.assets._ALL_SetText("SwitchText2", tempText) swPreTime = os.time(sec) end end end