[Vキャス]企画中のカメラ操作は参加者にやってもえたらいいのに

# 運営がカメラ操作までやるのは大変
最近Vキャスで「夜でもそれ正解！」(リンカーンの朝までそれ正解のパロディ)という、参加者がボードに書いたものを順番に見せていくという形式の企画を行いました。
それに併せて、座談会系の企画で使えそうな以下のようなテーブルを作りました。
![2021121601160667.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/64877/e3c661a6-4c1e-5c5c-f52d-7b1ce8f7f3a4.jpeg)

使い方としては上記のテーブルの周りに参加者に立ってもらい、
　・司会を進行しているときや皆で話しているときは引きの映像
　・特定の誰かが話しているときはその人のアップの映像
を写すような仕組みにしました。

はじめは運営側が話している人にカメラを向けるようと思ってましたが、以下のような課題が出てきました。
　・他の作業もしているためカメラ操作まで手が回らない
　・誰が喋っているかを判別してカメラを切り替えなければならない
　　(声が似てたり初めて合う人だと切り替え間違える)
　・参加者分のボタンを用意しなきゃいけなくから作るのが大変

そこで何かいい方法はないかを考えていたのですが、そもそも座談会とか大喜利とかの形式って一人ずつ話すことが多いので、カメラ操作は参加者が話すときに参加者自身にやって貰えばいい！と思い、そのような形式のVCIを作成しました。


# しゃべる人が自分でカメラを向ける仕組みを作ろう
今というわけで今回は上記のテーブルのVCIに対し、参加者が自分でカメラ操作を行えるようにしていこうと思います。
### 想定状況(制限事項)
- 参加者はテーブルを取り囲んで立っている
- 人数は不明。何人でも対応可能にする
- テーブルにボタンなどの余計なものは置きたくない

### 上記を踏まえ作成する機能
- カメラの操作はボタンなどではなくテーブルのグリップで行う(可変の人数に対応するため)
- テーブル掴むとその人のアップが映る。
- 他の人が押すと同様にその人の方を向く。
- 自分に向いてる状態で再度押すと引きに戻る。

完成品は以下のようなイメージです。
<img width=60% src="https://cdn.discordapp.com/attachments/407897870876934147/921417638599594004/2021121523230371.gif">

## unity
unityの構成は以下の感じです。
　・ArcTable：テーブル。これをグリップするとカメラの切り替えができる
　・Ref_Camera_UP(青い球の部分)：アップ時のカメラの移動先
　・Ref_Camera_All(赤い球の部分)：引きのときのカメラの移動先
(※わかりやすくするために青や赤の球を置きましたが、実際のVCIには球はありません)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/64877/5cb3380f-c3ef-8881-268b-15ad77000837.png)

## コード(lua)
以下の順で説明していきます。
① 特定の人をアップにする処理
② カメラを引きにする処理
③ グリップでカメラを切り替える処理

(※説明外で定義している変数をコード内で使用してます。適宜いい感じに読み替えて下さい)

### ①特定の人をアップにする処理
主に以下の3つの処理を行います
　・画角の変更(値は適当な固定値)
　・カメラを引数で渡された座標に向ける(Y座標は平行のまま)
　・カメラをRef_Camera_UPに移動
実際には移したいユーザーのポジションを引数に渡す形になります

``` lua
function SetCameraUp(targetPos)
  local camera = vci.studio.GetHandiCamera()
  if camera then

    -- カメラの画角設定(値は適当)。システム設定のGetMinFieldOfViewが変わるとちゃんと動かなくなるかもしれないので一応maxとってる
    local fov = math.max(38,camera.GetMinFieldOfView())
    camera.SetFieldOfView(fov)
    
    if targetPos then
      local direction = targetPos - ITEM.RefCameraUp.GetPosition()

      --カメラの向く向きを計算。y軸の傾きは0にする
      local up = Vector3.__new(direction.x,0,direction.z)
      local lookAt = Quaternion.LookRotation(up,Vector3.up) 
      camera.SetRotation(lookAt)
    end
    -- カメラを移動する
    camera.SetPosition(ITEM.RefCameraUp.GetPosition())
  end
end
```
### ②カメラを引きにする処理
主に以下の3つの処理を行います
　・画角の変更(値は適当な固定値)
　・カメラをテーブルの座標に向ける(Y座標は平行のまま)
　・カメラをRef_Camera_Allに移動
Up用の関数をまとめられそうですが一旦分けてます。

``` lua
function SetCameraAll()
  local camera = vci.studio.GetHandiCamera()
  local targetPos = ITEM.DonutsTable.GetPosition()

  if camera then
    -- カメラの画角設定。システム設定のGetMinFieldOfViewが変わるとちゃんと動かなくなるかもしれないので一応maxとってる
    local fov = math.max(40,camera.GetMinFieldOfView())
    camera.SetFieldOfView(fov)

    local direction = targetPos - ITEM.RefCameraAll.GetPosition()
    if targetPos then
      local direction = targetPos - ITEM.RefCameraAll.GetPosition()

      --カメラの向く向きを計算
      local up = Vector3.__new(direction.x,0,direction.z)
      local lookAt = Quaternion.LookRotation(up,Vector3.up) 
      camera.SetRotation(lookAt)
    end
    -- カメラを移動する
    camera.SetPosition(ITEM.RefCameraAll.GetPosition())
  end
end
```
### ③グリップでカメラを切り替える処理
主に以下のような方式で実現します。カメラの操作はオーナーしかできないためメッセージを使用しています
　・今誰を向いているかはユーザーのidを使用して管理する(オーナーの変数で管理)
　・テーブルをUseしたらメッセージでユーザーのIDと座標を贈る
　・メッセージを受け取ったらid情報を見て、今向いているユーザーかどうかを判断し、引きにするかアップにするかを切り替える

``` lua
function onUse(use)
  -- テーブルuse
  if use == ITEM.DonutsTable.GetName() then
    local avatar = vci.studio.GetLocalAvatar()
    if avatar then
      -- メッセージで押した人の情報変換してを送る。変換関数は後述
      vci.message.Emit(MESSAGE.USE_CAMERA_SWITCHER,convertAvatarToJson(avatar))
    end
  end
end

function MS_UseCameraSwitcher(sender, name, data)
　-- 受け取ったデータをアバター情報に変換(後述)
  local avatar= convertJsonToAvatar(data)

  ---今誰を向いているかはtargetNoにIDを入れることで管理
  if Owner.targetNo == avatar.id then
    SetCameraAll(avatar.pos)
    Owner.targetNo = nil
  else
    SetCameraUp(avatar.pos)
    Owner.targetNo = avatar.id
  end
end
vci.message.On(MESSAGE.USE_CAMERA_SWITCHER, MS_UseCameraSwitcher)

```

メッセージはアバター情報をそのままは送れないため、以下のように送信・受信時に変換しています。

``` lua
function convertAvatarToJson(avatar)
  local targetPos = avatar.GetLocalPosition()
  return {
    id = avatar.GetId(),
    pos = {
      x = targetPos.x,
      y = targetPos.y,
      z = targetPos.z
    }
  }
end

function convertJsonToAvatar(json)
  return {
    id = json.id,
    pos = Vector3.__new(json.pos.x,json.pos.y,json.pos.z)
  }
end
```

# 終わりに 
今回は紹介しませんでしたが運営のタイミングで引きに戻したいことがあったため、テーブルとは別に司会者が自由に引きカメラに戻せるようにするボタンだけ別に作りました。
今回作ったVCIは公開予定ですので、参加者の方にも協力してもらい良い企画を実現しましょう！！