はじめに
このVCIのスクリプトはバーチャルキャスト上のルームで1つのアイテムを
複数出せる機能を利用し、1種類のアイテムを複数出して
同じアイテム内でネットワークを作り連携した動作を目的としています
題材
アイテムと通信をするのに必要な条件
- 出現時にVCIアイテムIDの他にナンバーリストを作成する
- 扉を開ける時に接続先の扉がある場合のみ扉をつなげて開ける
- 接続先が被った場合はすでに接続されている扉を切断処理をし、
接続先の扉の状態を接続元と同期させる - 扉を操作していない人にも接続された扉を共有する
- 後から来た人や入室時に終了時の状態を保持する
同期に使用する機能
- ExportState (アイテム内同期変数)
vci.state
で共有出来るデータ量は最大16KBまで - ExportMessage (VCI間の通信)
vci.message
で送信出来る文字量は UTF-8 換算で 4000 byte まで - Transform (Vector3)
SubItemによる位置の自動同期 (プラットフォーム側の処理)
全体的な通信の動き
まず制作前に基本的なネットワーク通信仕様を考えます
最小構成として2個のVCI「VCI-001
」と「VCI-002
」で表してみましょう
下記図では中央にVCI本体の動作左右とvci.state
を表しています
赤枠:VCI-001の動作
青枠:VCI-002の動作
黒枠:ユーザーの操作
矢印:通信の方向と上に予定する関数名と送信情報
通信コストを削減と動作改善
vci.state
で非同期ドアの開閉を連続的に枚フレーム同期更新した際に
vci.state
が値が不定になりやすく値が喧嘩したり値が戻る事があるため
扉が動きが不自然になってしまいこの方式を辞めて同期用のSubItem
フラグ用に
オブジェクトを用意しTransform
の自動同期に変更を行う
下記図では中央にVCI本体の動作左右とSubItem
を表しています
赤枠:VCI-001の動作
青枠:VCI-002の動作
黒枠:ユーザーの操作
矢印:通信の方向と上に予定する関数名と送信情報
共通で使用するグローバル変数
この配列変数をメッセージ機能を使い送受信を行います
--通信配列:{Time(New),ID,Name,Mode,Message}
local SysMode = {}
--通信配列:{Time(New),ID,Name,Mode,Message}
SysMode[2] = vci.assets.GetInstanceId() --VCI_ID
SysMode[3] = Owner.GetName()
--初期化待ち用
local Reset_iniFlag = false
配列展開用の関数
受信後に文字列を配列に展開していきます
----------------------------------------
--受信関数(らーめんs)
----------------------------------------
function Explode(explodeCode, str)
local result = {}
--区切りがない場合は、新たに配列を作成して返す
if(string.find(str,explodeCode) == nil) then
result[1] = str
return result
end
local maxIndex = #str
local index=1
local resultID = 1
while (index<=maxIndex) do
local findIndex = string.find(str,explodeCode,index)
if(findIndex~=nil) then
result[resultID] = string.sub(str,index,findIndex-1)
resultID = resultID + 1
index = findIndex + 1
else
result[resultID] = string.sub(str,index)
break
end
end
return result
end
送信コード全体
まずは送信コードの全体を見てみます
local NotIdFlag = {}
NotIdFlag[1] = true
NotIdFlag[2] = ""
----------------------------------------
--ゲット機能メッセージ機能
----------------------------------------
local Get = {}
function Get.ID()
--IDをリクエスト
local NewTimeUser = os.date("%Y,%m,%d,%H,%M,%S")
SysMode[1] = NewTimeUser
SysMode[4] = "SyncRequest"
SysMode[5] = vci.assets.GetInstanceId()
SysMode[6] = "PortalDoor"
vci.message.Emit("PortalDoor_Controller", SysMode[1] .. "™" .. SysMode[2] .. "™" .. SysMode[3] .. "™" .. SysMode[4] .. "™" .. SysMode[5] .. "™" .. SysMode[6])
end--END_IDをリクエスト
function Get.CheckID(IdNo)
--ID有るか確認
if IdNo == 0 then
return
end
if IdNo >= #DoorList +1 then
return
end
print("Get.CheckID : " .. IdNo .. " to DoorList : " .. #DoorList)
local NewTimeUser = os.date("%Y,%m,%d,%H,%M,%S")
SysMode[1] = NewTimeUser
SysMode[4] = "CheckID"
SysMode[5] = vci.assets.GetInstanceId()
SysMode[6] = "PortalDoor"
SysMode[7] = DoorList[IdNo][1]
vci.message.Emit("PortalDoor_Controller", SysMode[1] .. "™" .. SysMode[2] .. "™" .. SysMode[3] .. "™" .. SysMode[4] .. "™" .. SysMode[5] .. "™" .. SysMode[6] .. "™" .. SysMode[7])
end--ID有るか確認
function Get.DoorPos(IdNo)
--IDをリクエスト
--生存Flag
NotIdFlag[1] = true
NotIdFlag[2] = IdNo
vci.state.Set("NotIdFlag", NotIdFlag)
local player_Rot = Portal_obj.GetRotation()
local player_Pos = GetPortal.GetPosition()
--時間取得
local NewTimeUser = os.date("%Y,%m,%d,%H,%M,%S")
--送信時間
SysMode[1] = NewTimeUser
--送信コード
SysMode[4] = "GetPortalPos"
--送信先のID
SysMode[5] = IdNo
--VCI識別ID
SysMode[6] = "PortalDoor"
--自分のID
SysMode[7] = vci.assets.GetInstanceId()
--自分のカメラセット用位置
SysMode[8] = tostring(GetCam_Pos.GetPosition().x) .. "," .. tostring(GetCam_Pos.GetPosition().y) .. "," .. tostring(GetCam_Pos.GetPosition().z)
--自分のカメラセット用回転
SysMode[9] = tostring(GetCam_Pos.GetRotation().x) .. "," .. tostring(GetCam_Pos.GetRotation().y) .. "," .. tostring(GetCam_Pos.GetRotation().z .. "," .. tostring(GetCam_Pos.GetRotation().w))
--自分のPortal出口位置
SysMode[10] = tostring(player_Pos.x) .. "," .. tostring(player_Pos.y) .. "," .. tostring(player_Pos.z)
--自分のPortal出口回転
SysMode[11] = tostring(player_Rot.x) .. "," .. tostring(player_Rot.y) .. "," .. tostring((player_Rot.z)) .. "," .. tostring((player_Rot.w))
--送信
vci.message.Emit("PortalDoor_Controller", SysMode[1] .. "™" .. SysMode[2] .. "™" .. SysMode[3] .. "™" .. SysMode[4] .. "™" .. SysMode[5] .. "™" .. SysMode[6] .. "™" .. SysMode[7] .. "™" .. SysMode[8] .. "™" .. SysMode[9] .. "™" .. SysMode[10] .. "™" .. SysMode[11])
end--END_IDをリクエスト
送信するコードは後から送信用として関数がの役割が分かりやすいように
構造体にしておきます
上記の送信関数と役割を表にしてみます
関数 | 役割 | 引数 | 送信 |
---|---|---|---|
Get.ID() | ドアの全数確認(存在する扉全てにSyncRequest に送信) |
無し | 無し |
Get.CheckID(IdNo) | IDをリクエスト(同期先のCheckID に送信) |
同期元のID | 「PortalDoor 」要求 |
Get.DoorPos(IdNo) | 同期元の座標を送信 同期先座標を要求 相手の GetPortalPos に送信 |
同期先のID | 「GetPortalPos 」要求 |
ここでは、必要な情報を収集し情報を要求しています
受信コード全体
まずは要求されたときの処理全体を見てみましょう
----------------------------------------
--リクエスト受信メッセージ機能
----------------------------------------
---{Time(New),ID,Name,Mode,VCI_ID,Message}
function PortalDoor_Controller(sender, name, message)
--受信処理
local RxMessage = Explode("™", message)
if Debug == true then
--Debugモード
print(SysMode[2])
print(RxMessage[1])
print(RxMessage[2])
print(RxMessage[3])
print(RxMessage[4])
print(RxMessage[5])
end--END_Debugモード
if RxMessage[6] == "PortalDoor" then
if RxMessage[4] =="SyncRequest" then
--同期リクエスト
--同期リクエスト
print("Send Sync")
--時間取得
local NewTimeUser = os.date("%Y,%m,%d,%H,%M,%S")
SysMode[1] = NewTimeUser
SysMode[4] = "SyncID"
SysMode[5] = vci.assets.GetInstanceId()
SysMode[6] = "PortalDoor"
SysMode[7] = MyDoorList[1][2]
vci.message.Emit("PortalDoor_Controller", SysMode[1] .. "™" .. SysMode[2] .. "™" .. SysMode[3] .. "™" .. SysMode[4] .. "™" .. SysMode[5] .. "™" .. SysMode[6] .. "™" .. SysMode[7])
elseif RxMessage[4] =="SyncID" then
if RxMessage[5] ~= vci.assets.GetInstanceId() then
--リスト追加
local Index = 0
for IndexCount = 1 , #DoorList do
if DoorList[IndexCount][1] == RxMessage[5] then
return
end
Index = Index + 1
end
table.insert(DoorList, {RxMessage[5],RxMessage[7]})
table.sort(DoorList,
function(a, b)
return (a[2] < b[2]) --小さい順に並べ替える
end
)
print("ID list")
for IndexCount = 1 , #DoorList do
print(DoorList[IndexCount][1])
if DoorList[IndexCount][1] == vci.assets.GetInstanceId() then
GetMyNumber.SetPosition(Vector3.__new(0, IndexCount, 0))
if GetID == vci.assets.GetInstanceId() then
GetlistNumberCount.SetPosition(Vector3.__new(0, IndexCount, 0))
end
end
end
if GetKnob.IsMine then
print("Set SyncID")
vci.state.Set("DoorList", DoorList)
end
end
elseif RxMessage[4] =="ReSync" then
--再同期
if RxMessage[5] == vci.assets.GetInstanceId() then
print("Set ReSync")
if GetKnob.IsMine then
--自分のVCIかチェック
SYS_ReSync()
else
SyncState()
end --自分のVCIかチェック
end
elseif RxMessage[4] =="CheckID" then
if RxMessage[7] == vci.assets.GetInstanceId() then
--自分のIDが来たか確認する
print("Emit CheckID")
--時間取得
local NewTimeUser = os.date("%Y,%m,%d,%H,%M,%S")
SysMode[1] = NewTimeUser
SysMode[4] = "GetCheckID"
SysMode[5] = RxMessage[5]--送信元ID
SysMode[6] = "PortalDoor"
SysMode[7] = RxMessage[7]
vci.message.Emit("PortalDoor_Controller", SysMode[1] .. "™" .. SysMode[2] .. "™" .. SysMode[3] .. "™" .. SysMode[4] .. "™" .. SysMode[5] .. "™" .. SysMode[6] .. "™" .. SysMode[7])
end--END_自分のIDが来たか確認する
elseif RxMessage[4] =="GetCheckID" then
if RxMessage[5] == vci.assets.GetInstanceId() then
--通信開始元か確認
print("Get to CheckID")
--IDセット
GetID = RxMessage[7]
Get.DoorPos(GetID)
end--通信開始元か確認
elseif RxMessage[4] =="DelID" then
--バッティング処理
if GetKnob.IsMine then
if RxMessage[7] == nil then
return
end
print("Del Request : " .. RxMessage[5])
print("DelID : " .. RxMessage[7])
if RxMessage[7] == vci.assets.GetInstanceId() then
--連携番号表示
for IndexCount = 1 , #DoorList do
if DoorList[IndexCount][1] == RxMessage[5] then
GetlistNumberCount.SetPosition(Vector3.__new(0, IndexCount, 0))
ReSync()
return
end
end
end--END_連携番号表示
if RxMessage[5] ~= vci.assets.GetInstanceId() then
if GetID == RxMessage[7] then
--連携先がバッティングしていた
print("Conflict ID : " .. RxMessage[7])
GetID = vci.assets.GetInstanceId()
Get.DoorPos(GetID)
deg = 5
DegValue.SetPosition(Vector3.__new(0, deg, 0))
ReSync()
end--END_連携先がバッティングしていた
end
end
elseif RxMessage[4] =="NotID" then
local My = 0
local You = 0
if nil ~= vci.state.Get("DoorList") then
DoorList = vci.state.Get("DoorList")
table.sort(DoorList,
function(a, b)
return (a[2] < b[2]) --小さい順に並べ替える
end
)
end
for IndexCount = 1 , #DoorList do
if DoorList[IndexCount][1] == RxMessage[5] then
My = IndexCount
end
if DoorList[IndexCount][1] == RxMessage[7] then
You = IndexCount
end
end
print("NotRequest : " .. You .. " => " .. My)
print("NotID : " .. RxMessage[4])
local DecCount = #DoorList
for IndexCount = 1 , #DoorList - 1 do
print("decCount")
print(DecCount)
if DoorList[DecCount][1] ~= nil then
if DoorList[DecCount][1] == RxMessage[7] then
table.remove(DoorList, DecCount)
break
end
end
DecCount = DecCount -1
end
table.sort(DoorList,
function(a, b)
return (a[2] < b[2]) --小さい順に並べ替える
end
)
for IndexCount = 1 , #DoorList do
if DoorList[IndexCount][1] == vci.assets.GetInstanceId() then
GetMyNumber.SetPosition(Vector3.__new(0, IndexCount, 0))
print(DoorList[IndexCount][1])
end
end
if GetKnob.IsMine then
if RxMessage[3] == SysMode[3] then
--自分のVCIかチェック
print("Set SyncID")
vci.state.Set("DoorList", DoorList)
end --自分のVCIかチェック
end
elseif RxMessage[4] =="GetPortalDeg" then
--ドア開閉同期
if GetKnob.IsMine then
if RxMessage[7] == vci.assets.GetInstanceId() then
--IDセット
DegValue.SetPosition(Vector3.__new(0, tonumber(RxMessage[8]), 0))
deg = tonumber(RxMessage[8])
local Rxbool = nil
if RxMessage[9] == "true" then
Rxbool = 1
else
Rxbool = 0
end
GetControlFlag.SetPosition(Vector3.__new(0, Rxbool, 0))
end
end
elseif RxMessage[4] =="SetUngrab" then
--ドアアングラブ処理
if GetKnob.IsMine then
--IDセット
if RxMessage[8] == vci.assets.GetInstanceId() then
local Rxbool = nil
if RxMessage[9] == "true" then
Rxbool = 1
else
Rxbool = 0
end
GetControlFlag.SetPosition(Vector3.__new(0, Rxbool, 0))
end
end
elseif RxMessage[4] =="GetPortalPos" then
--Portal情報受信送信
if RxMessage[5] == vci.assets.GetInstanceId() then
GetID = RxMessage[7]
local My = 0
local You = 0
for IndexCount = 1 , #DoorList do
if DoorList[IndexCount][1] == RxMessage[5] then
My = IndexCount
end
if DoorList[IndexCount][1] == RxMessage[7] then
You = IndexCount
end
end
print("Control target : " .. You .. " => " .. My)
--print(vci.assets.GetInstanceId())
--print(GetID)
local GetCamPos_Pos = Explode(",", RxMessage[8])
local GetCamPos_Rot = Explode(",", RxMessage[9])
local GetPlayer_Pos = Explode(",", RxMessage[10])
local GetPlayer_Rot = Explode(",", RxMessage[11])
Cam_Pos.SetPosition(Vector3.__new(tonumber(GetCamPos_Pos[1]), tonumber(GetCamPos_Pos[2]), tonumber(GetCamPos_Pos[3])))
Cam_Pos.SetRotation(Quaternion.__new(tonumber(GetCamPos_Rot[1]), tonumber(GetCamPos_Rot[2]), tonumber(GetCamPos_Rot[3]), tonumber(GetCamPos_Rot[4])))
SetPlayer_Pos = Vector3.__new(tonumber(GetPlayer_Pos[1]), tonumber(GetPlayer_Pos[2]), tonumber(GetPlayer_Pos[3]))
SetPlayer_Rot = Quaternion.__new(Quaternion.__new(tonumber(GetPlayer_Rot[1]), tonumber(GetPlayer_Rot[2]), tonumber(GetPlayer_Rot[3]), tonumber(GetPlayer_Rot[4])))
local player_Rot = GetPortal.GetRotation()-- * OffsetPortalRota
local player_Pos = GetPortal.GetPosition()-- + (player_Rot * Vector3.__new(0, 2.2, 0))
--時間取得
local NewTimeUser = os.date("%Y,%m,%d,%H,%M,%S")
SysMode[1] = NewTimeUser
SysMode[4] = "SetPortalPos"
--送信元のID
SysMode[5] = RxMessage[7]
SysMode[6] = "PortalDoor"
--自分のID
SysMode[7] = vci.assets.GetInstanceId()
--自分のカメラセット用位置
SysMode[8] = tostring(GetCam_Pos.GetPosition().x) .. "," .. tostring(GetCam_Pos.GetPosition().y) .. "," .. tostring(GetCam_Pos.GetPosition().z)
--自分のカメラセット用回転
SysMode[9] = tostring(GetCam_Pos.GetRotation().x) .. "," .. tostring(GetCam_Pos.GetRotation().y) .. "," .. tostring(GetCam_Pos.GetRotation().z .. "," .. tostring(GetCam_Pos.GetRotation().w))
--自分のPortal出口位置
SysMode[10] = tostring(player_Pos.x) .. "," .. tostring(player_Pos.y) .. "," .. tostring(player_Pos.z)
--自分のPortal出口回転
SysMode[11] = tostring(player_Rot.x) .. "," .. tostring(player_Rot.y) .. "," .. tostring((player_Rot.z)) .. "," .. tostring((player_Rot.w))
--送信
vci.message.Emit("PortalDoor_Controller", SysMode[1] .. "™" .. SysMode[2] .. "™" .. SysMode[3] .. "™" .. SysMode[4] .. "™" .. SysMode[5] .. "™" .. SysMode[6] .. "™" .. SysMode[7] .. "™" .. SysMode[8] .. "™" .. SysMode[9] .. "™" .. SysMode[10] .. "™" .. SysMode[11])
end
elseif RxMessage[4] =="SetPortalPos" then
--Portal情報をセット
if RxMessage[5] == vci.assets.GetInstanceId() then
local My = 0
local You = 0
for IndexCount = 1 , #DoorList do
--相手が明けたらここで止まった
if DoorList[IndexCount][1] == RxMessage[5] then
My = IndexCount
end
if DoorList[IndexCount][1] == RxMessage[7] then
You = IndexCount
end
end
print("Set Pos My id: " .. My .. ", Get id=> " .. You)
GetID = RxMessage[7]
--print(vci.assets.GetInstanceId())
--print(GetID)
local GetCamPos_Pos = Explode(",", RxMessage[8])
local GetCamPos_Rot = Explode(",", RxMessage[9])
local GetPlayer_Pos = Explode(",", RxMessage[10])
local GetPlayer_Rot = Explode(",", RxMessage[11])
Cam_Pos.SetPosition(Vector3.__new(tonumber(GetCamPos_Pos[1]), tonumber(GetCamPos_Pos[2]), tonumber(GetCamPos_Pos[3])))
Cam_Pos.SetRotation(Quaternion.__new(tonumber(GetCamPos_Rot[1]), tonumber(GetCamPos_Rot[2]), tonumber(GetCamPos_Rot[3]), tonumber(GetCamPos_Rot[4])))
SetPlayer_Pos = Vector3.__new(tonumber(GetPlayer_Pos[1]), tonumber(GetPlayer_Pos[2]), tonumber(GetPlayer_Pos[3]))
SetPlayer_Rot = Quaternion.__new(Quaternion.__new(tonumber(GetPlayer_Rot[1]), tonumber(GetPlayer_Rot[2]), tonumber(GetPlayer_Rot[3]), tonumber(GetPlayer_Rot[4])))
end
end--END_同期リクエスト
end
if RxMessage[3] == SysMode[3] then
--自分のVCIかチェック
end --自分のVCIかチェック
end --END_受信処理
vci.message.On("PortalDoor_Controller", PortalDoor_Controller)
上記の受信の役割を表にしてみます
コマンド | 内容 |
---|---|
PortalDoor | VCIコマンド受付 |
SyncRequest | 同期リクエスト |
SyncID | 要求の返答で返ってきたIDをリストに追加 |
ReSync | vci.state再同期 |
CheckID | 送信元の「GetCheckID」に返事する |
DelID | 同期先に新規同期が来ていれば同期を切断する |
NotID | 同期のリクエストが返って来なかったらリストから削除する |
GetPortalDeg | 同期元のドア開閉と同期をとる |
SetUngrab | ドアアングラブ処理 |
GetPortalPos | 同期元の座標を受け取り同期元に自分の座標を送信を行う |
SetPortalPos | 同期先の座標を保存する |
以外とvci.message
の受信コマンドが多くなります
実際のデバックウィンドの動き
下記画像は1つ目「VCI-001
」を出した時にリストは自分のIDしか存在しません
下記画像は2つ目「VCI-002
」を出した時に「ID list
」という行の下にVCIのIDがお互いに取得できています
次は扉のIDを選択し掴んだ時に同期先のIDをチェック後、
リストIDでホスト「VCI-001
」でどのVCI「VCI-002
」の操作を行うのかを最後にログを出してます
次は接続されている状態から「VCI-002
」を操作し「VCI-001
」の操作を行った時のログです
一度「VCI-001
」に他のVCIが接続していた場合のために「VCI-002
」以外の切断リクエストを送信し
「VCI-002
」がホストになって「VCI-001
」を操作します
以上がVCIがお互いにTCPのように対話しながら処理を行っていく内容になります
通信としては成立出来ました!
現在の課題
- 初期化時に同期が上手く行く時と行かない時がある
- 同じ人が複数出して出した人が操作し第三者が同期は確認されたが、
最初に相手に触られると動作しない時がある - ステートにドアのリストIDを
vci.state
からSubItemu
のTransform
で
座標にを入れて自動同期に変更をしたらIDの共有が不安定で共有が
出来ていない時がある - 第三者が出した場合の日時ベースでリスト化しているのに
IDがずれて同期が上手くいっていない
まとめ
-
1種類のVCIで役割を分けて操作元をホストとすることで、
指定した対話通信は実現できた -
1種類のVCIだと1種類のVCIの特性上自分のメッセージを受け取り、
誤動作の可能性があるため、どのVCIで誰が送信したのかが鍵になってくる -
別のVCI同士ではないので、通信の流れを書き出して整理する必要が
出てきた -
TCP通信としては上手く言っているが、データ状態の共有と
いう場面で課題が出たため全体公開ができない状態が続いている -
プレリリースまでは言ったが同期相手が同じVCIを出して協力する人が
居ないと困難になってきた -
PCを3台使い一人でデバックしていても操作する人が一人だと限界がある
-
デバックする際の人出が必要な場合頼める人が居なかったりすると辛い
結論
VCIなんもわからん