ExportStudioを使用した、スタジオでアバターを基準に制御するサンプルVCIです。
操作のために使うGrab可能なSubitemを3つ用意してあるだけで、VCI本体は特別な事をしてありません。
ただし、ONになってる時に緑、OFFになってる時に赤とマテリアルを操作するために、Subitem毎にマテリアルを割り当てて、Subitemの名前とマテリアルの名前を統一しておくと便利です。
---------------------- ホストのみBonePositionを管理する ---------------------- local _ownerAvater -- 初期化 (ホスト実行) if vci.assets.IsMine then vci.studio.shared.Set('CameraMode', "STOP") _ownerAvater = vci.studio.GetOwner() print("CameraSwitcher Owner : ".._ownerAvater.GetName()) end -- リモートの確認 if vci.assets.IsMine == false then print("あなたはCameraSwitcherのOwnerではりません。") end ---------------------- モーションのプロパティ ----------------------- -- スイッチング時のNeckの位置・回転 (ホスト管理) local _fixNeckPosition = Vector3.zero local _fixNeckRotation = Quaternion.identity local _fixHipsPosition = Vector3.zero local _fixHipsRotation = Quaternion.identity -- LookAtの補正 -- +で上方向 -で下方向 を注視する local _LookAtoffset = -2.0 -- カメラ位置のランダム設定 -- Min ~ Max の範囲でランダムな値になります local _randomPosition = Vector3.zero local _posXmax = 1.5 -- X軸(左右) local _posXmin = -1.5 local _posYmax = 1.5 -- Y軸(上下) local _posYmin = 0.5 local _posZmax = 2.0 -- Z軸(前後) local _posZmin = 0.8 -- カメラの位置をランダムで決定 function randomPosition() local x = 0.1 * math.random(10.0 * _posXmin, 10.0 * _posXmax) local y = 0.1 * math.random(10.0 * _posYmin, 10.0 * _posYmax) local z = 0.1 * math.random(10.0 * _posZmin, 10.0 * _posZmax) _randomPosition = Vector3.__new(x, y, z) end -- Lookat Lerpで追従する (ホスト実行) function LookAtLerp() local cam = vci.studio.GetHandiCamera() local forward = _ownerAvater.GetBoneTransform("Neck").position - cam.GetPosition() forward = forward.normalized local target = Quaternion.LookRotation(forward, Vector3.up) target = target * Quaternion.AngleAxis(_LookAtoffset, Vector3.left) --向き補正 local rot = Quaternion.Lerp(cam.GetRotation(), target, 0.02) cam.SetRotation(rot) end -- Lookat 切り替え時に注視するが、固定 (ホスト実行) function LookAtFix() local cam = vci.studio.GetHandiCamera() local forward = _fixNeckPosition - cam.GetPosition() forward = forward.normalized local rot = Quaternion.LookRotation(forward, Vector3.up) rot = rot * Quaternion.AngleAxis(_LookAtoffset, Vector3.left) --向き補正 cam.SetRotation(rot) end -- LookATカメラ切り替え時、即座に正面に移動 (ホスト実行) function fixPosition() local cam = vci.studio.GetHandiCamera() --回転 local forward = _ownerAvater.GetBoneTransform("Head").position - cam.GetPosition() forward = forward.normalized local rot = Quaternion.LookRotation(forward) cam.SetRotation(rot) --移動 local pos = _ownerAvater.GetBoneTransform("Head").rotation * Vector3.__new(0, 0, 1.0) pos = pos + _ownerAvater.GetBoneTransform("Head").position cam.SetPosition(pos) end function updateAll() CameraMotion() end -- カメラモーションの監視 (ホスト実行) function CameraMotion() --リモートの場合は終了 if vci.assets.IsMine == false then return end --ハンディカメラが存在しないので終了 if vci.studio.HasHandiCamera() == false then print("スタジオにハンディカメラが存在しません") return end -- mode が STOP なので終了 local mode = vci.studio.shared.Get('CameraMode') if mode == "STOP" then return end -- バストアップ if mode == "LOOKAT" then modeLOOKAT() end -- 固定位置ランダム if mode == "RANDOM" then modeFixpos() end end -- バストアップモーション (ホスト実行) function modeLOOKAT() -- Neckからの相対距離 local distance = 0.8 --ターゲットとカメラの間隔 local height = 0.2 --カメラの高さ local pos = _ownerAvater.GetBoneTransform("Neck").rotation * Vector3.__new(0, height, 1.0 * distance) -- 原点をNeckにオフセット pos = pos + _ownerAvater.GetBoneTransform("Neck").position -- カメラに位置を適用 local cam = vci.studio.GetHandiCamera() local fixpos = Vector3.Lerp(cam.GetPosition(), pos, 0.02) cam.SetPosition(fixpos) LookAtLerp() end -- ランダム位置に出現 (ホスト実行) function modeFixpos() -- 原点をHipsにオフセット local pos = _fixNeckRotation * _randomPosition pos = pos + _fixNeckPosition -- カメラに位置を適用 local cam = vci.studio.GetHandiCamera() cam.SetPosition(pos) LookAtLerp() end function onUse(switch) -- スイッチの色変更(リモート/ホスト) ColorChange(switch) -- カメラモードの切り替え(リモート/ホスト) vci.studio.shared.Set('CameraMode', switch) -- 各クライアントのパラメータ更新(リモート/ホスト) vci.message.Emit("onUseFireOnce", tostring(switch)) end -- Useした時に1回だけ実行する関数 function onUseFireOnce(sender, name, message) print("message : "..tostring(message)) -- 各クライアントでランダム値を更新 randomPosition() -- ホストのみ実行 if vci.assets.IsMine then --オーナーのみがボーンの情報を取得する GetBoneTransform() print("ボーン情報を更新") --バストアップの場合、食座に位置を切り替え if message == "LOOKAT" then fixPosition() end end -- リモートで確認 if vci.assets.IsMine == false then print("ホストがボーン情報を更新しました") end end -- Emit "onUseFireOnce" で onUseFireOnce()を実行する vci.message.On("onUseFireOnce", onUseFireOnce) -- スイッチング時のボーン情報の保存 (ホスト実行) function GetBoneTransform() --切り替え時の Neck Hips の位置・回転を保存 _fixNeckPosition = _ownerAvater.GetBoneTransform("Neck").position _fixNeckRotation = _ownerAvater.GetBoneTransform("Neck").rotation _fixHipsPosition = _ownerAvater.GetBoneTransform("Hips").position _fixHipsRotation = _ownerAvater.GetBoneTransform("Hips").rotation end -- スイッチの色変更 (リモートorホストで実行可能) function ColorChange(switch) --リセット vci.assets.material._ALL_SetColor("STOP", Color.red) vci.assets.material._ALL_SetColor("LOOKAT", Color.red) vci.assets.material._ALL_SetColor("RANDOM", Color.red) -- オンのスイッチを緑色に if switch == "STOP" then vci.assets.material._ALL_SetColor("STOP", Color.green) end if switch == "LOOKAT" then vci.assets.material._ALL_SetColor("LOOKAT", Color.green) end if switch == "RANDOM" then vci.assets.material._ALL_SetColor("RANDOM", Color.green) end end
基本的に vci.assets.IsMine
が true になるクライアント(VCIを出したクライアント)が代表して処理を行います。
ただし、他のクライアントからでも操作可能にしたい為…
同期変数で状態の共有、メッセージを使ってどのクライアントから実行しても、全体のクライアントで実行するように工夫します。
_ALL_で同期が可能な処理については、複雑な事を行わず_ALL_で同期させます。
local cam = vci.studio.GetHandiCamera() local forward = _ownerAvatar.GetBoneTransform("Neck").position - cam.GetPosition() forward = forward.normalized local target = Quaternion.LookRotation(forward, Vector3.up) target = target * Quaternion.AngleAxis(_LookAtoffset, Vector3.left) --向き補正 local rot = Quaternion.Lerp(cam.GetRotation(), target, 0.02) cam.SetRotation(rot)
ターゲットの座標(頭のボーン)-カメラの座標 を計算した結果を normalized
すると、カメラからターゲットの方向へ向かう単位ベクトルが得られます。
Quaternion.LookRotation()
の第一引数に上記で求めたベクトルを入れると、ターゲットの方向へ向くクォータニオンが得られます。
この結果をそのまま SetRotation()
してもいいですが、なめらかに補間させるために Quaternion.Lerp()
を実行する事で、ゆっくりカメラが振り向くようになります。