====== アバターを基準に制御するVCI(CameraSwitcher) ======
[[https://developer.virtualcast.jp/vci-docs/api/classes/ExportStudio/index.html|ExportStudio]]を使用した、スタジオでアバターを基準に制御するサンプルVCIです。\\
=== サンプルデータ ===
https://virtualcast.jp/products/feb68b3db0ef71fc80576cf96212fa586004ac425ff30ac041a5bc7b5e0de017
{{ :vci:sample:avatar:cameraswitcher.zip |}}
==== コンポーネント設定 ====
{{:vci:sample:avatar:cameraswitcher.png?direct&600|}}
操作のために使うGrab可能なSubitemを3つ用意してあるだけで、VCI本体は特別な事をしてありません。\\
ただし、ONになってる時に緑、OFFになってる時に赤とマテリアルを操作するために、Subitem毎にマテリアルを割り当てて、Subitemの名前とマテリアルの名前を統一しておくと便利です。
===== VCIスクリプト =====
---------------------- ホストのみ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スクリプト(解説) ====
=== vci.assets.IsMine を使ってホストで行う処理・リモートで行う処理を分ける ===
基本的に ''vci.assets.IsMine'' が true になるクライアント(VCIを出したクライアント)が代表して処理を行います。\\
ただし、他のクライアントからでも操作可能にしたい為…\\
[[vci/script/reference/syncvariable|同期変数]]で状態の共有、メッセージを使ってどのクライアントから実行しても、全体のクライアントで実行するように工夫します。\\
_ALL_で同期が可能な処理については、複雑な事を行わず_ALL_で同期させます。
=== LookAtの処理について ===
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()'' を実行する事で、ゆっくりカメラが振り向くようになります。