はじめに
バーチャルキャストでユーザーが自由に作成し、VR空間に持ち込むことが可能なアイテム、VCIアイテム
VCIアイテムはLua言語のスクリプトを組み込むことで、アイテムに機能を実装することが出来ます。
lua言語はスクリプト言語でかつ、動作も高速と非常によい言語ではあるのですが、swith-case文がない!!!
そのため、アイテムの名前やコメント内容で処理を分岐させる場合、以下のようにif文で処理を分岐させる必要があり、if-elseが大量になったり、ネストが深くなったり、複数箇所に同じ条件文を書く場合修正ミスが発生したりします。
function onMessage(sender, name, message)
if name == "comment" then
local str = nil
-- カメラを制御するコメントが受信したら、VCI内の特定アイテムを操作する
if message == "カメラ1" or message == "camera1" or message == "Camera1" or message == "Cam1" or message == "cam1" then
str = "camera1"
elseif message == "カメラ2" or message == "camera2" or message == "Camera2" or message == "Cam2" or message == "cam2" then
str = "camera2"
-- カメラ3,4,5についても同様のif文が必要
end
-- strがnilの場合(if文の条件にヒットしなかった)、処理しない
if str ~= nil then
src = cameraModule.GetPosition()
local item = vci.assets.GetSubItem(str)
dst = item.GetPosition()
start = vci.me.UnscaledTime
end
end
end
判定条件が少ない場合は問題ありませんが、条件が増えたりすると可読性が悪化し、条件追加時にミスをする恐れがありますし、なにより美しくありません。
そこで、先日公開したこちらのアイテムに組み込んだスクリプトをベースにテーブルを用いてswith-case文を疑似的に実現する手法を説明します。
テーブルを使って、変数に値をセットする
前述のスクリプトをテーブルを用いて、書いた場合以下のようになります。
-- テーブルを使用し、キーと対応した値を書く
local CameraTbl = {
["カメラ1"] = "camera1",
["Camera1"] = "camera1",
["camera1"] = "camera1",
["Cam1"] = "camera1",
["cam1"] = "camera1",
["カメラ2"] = "camera2",
["Camera2"] = "camera2",
["camera2"] = "camera2",
["Cam2"] = "camera2",
["cam2"] = "camera2",
-- カメラ3,4,5についても同様
}
function onMessage(sender, name, message)
if name == "comment" then
local str = CameraTbl[message]
-- テーブルに存在しないキーを指定した場合、nilが返る(defaultの動作に相当)
if str ~= nil then
src = cameraModule.GetPosition()
local item = vci.assets.GetSubItem(str)
dst = item.GetPosition()
start = vci.me.UnscaledTime
end
end
end
ほかの言語だとこんな感じ
String str
switch(message) {
case :
case "カメラ1" :
case "Camera1" :
case "camera1" :
case "Cam1" :
case "cam1" :
str = "camera1";
break;
case "カメラ2" :
case "Camera2" :
case "camera2" :
case "Cam2" :
case "cam2" :
str = "camera2";
break;
default :
break;
}
function onMessage(sender, name, message)の関数内のはかなりすっきりしました。
テーブルを1つ用意しておけば、それを複数箇所から参照して動作する場合でも、テーブルのみを修正することになるため、ミスの可能性が減るかと思います。
また、テーブルについてはモジュールで別のソースに分離することも可能ですので、テーブルは別ソースに記述し、メインのソースからはアクセッサを通して参照することも可能です。
テーブルを使って、関数を呼び分ける
lua言語の変数は関数型をサポートしており、関数型の変数についてもテーブルに格納することが出来ます。
そのため、テーブルに関数型の値、テーブルを用いて引数テーブルを持つことでテーブルを用いて処理を分岐させることが可能です。
-- 使用可能なアイテム毎に関数型の値と引数のテーブルを持つ
local itemTbl = {
["eye"] = {["function"] = switchVisiable, ["args"] = nil },
["move"] = {["function"] = sendMsg, ["args"] = {"move"} },
["panel1"] = {["function"] = panelChange, ["args"] = {1} },
["panel2"] = {["function"] = panelChange, ["args"] = {2} },
["panel3"] = {["function"] = panelChange, ["args"] = {3} },
["panel4"] = {["function"] = panelChange, ["args"] = {4} },
["panel5"] = {["function"] = panelChange, ["args"] = {5} },
["xMinus"] = {["function"] = sendMsg, ["args"] = {"xMinus"} },
["yMinus"] = {["function"] = sendMsg, ["args"] = {"yMinus"} },
["zMinus"] = {["function"] = sendMsg, ["args"] = {"zMinus"} },
["xPlus"] = {["function"] = sendMsg, ["args"] = {"xPlus"} },
["yPlus"] = {["function"] = sendMsg, ["args"] = {"yPlus"} },
["zPlus"] = {["function"] = sendMsg, ["args"] = {"zPlus"} },
}
---[SubItemの所有権&Use状態]アイテムをグラッブしてグリップボタンを押すと呼ばれる。
---@param use string @押されたアイテムのSubItem名
function onUse(use)
if itemTbl[use] then
local func = itemTbl[use]["function"]
local args = itemTbl[use]["args"]
-- funcがnil(テーブルにキーがない)場合、使用しても何も起こらないアイテム
if func ~= nil then
if args == nil then
func()
elseif #args == 1 then
func(args[1])
end
end
end
end
テーブルを使わない場合だとこんな感じ
---[SubItemの所有権&Use状態]アイテムをグラッブしてグリップボタンを押すと呼ばれる。
---@param use string @押されたアイテムのSubItem名
function onUse(use)
if use == "eye" then
switchVisiable()
elseif use == "panel1" or use == "panel2" or use == "panel3" or use == "panel4" or use == "panel5" then
local args
if use == "panel1" then
args = 1
elseif use == "panel2" then
args = 2
elseif use == "panel3" then
args = 3
elseif use == "panel4" then
args = 4
elseif use == "panel5" then
args = 5
end
panelChange(args)
elseif use == "move" or use == "xMinus" or use == "yMinus" or use == "zMinus" or use == "xPlus" or use == "yPlus" or use == "zPlus" then
local args
if use == "move" then
args = "move"
elseif use == "xMinus" then
args = "xMinus"
elseif use == "yMinus" then
args = "yMinus"
elseif use == "zMinus" then
args = "zMinus"
elseif use == "xPlus" then
args = "xPlus"
elseif use == "yPlus" then
args = "yPlus"
elseif use == "zPlus" then
args = "zPlus"
end
sendMsg(args)
end
end
複数の条件で呼び出す際のパラメータを変えつつ、呼び出す処理がかなり簡潔になりました。
最後に
今回の手法はpythonでswitch-case文を実現できないかと検索していた際に、こちらのページ(【Python入門】switch文の代わりに使える書き方)を見つけ、lua言語でも使用可能なtipsであると感じたのがきっかけです。
今回はコメントの内容やアイテム名を使用しての解説でしたが、時間をstring型に変換してタイムテーブルとして扱うなどluaのテーブル型は応用が利くため、色々トライしてみてください。
P.S.公開中のアイテムのスクリプトライセンスはMITですので、抜き出して改造を認めています。