VCIのUIを考える！

# はじめに
VRのアイテムを作りこんでいくと、そのアイテムを操作するコントローラーが必要となる場面が発生します。
例えば、下記のような筆のアイテムであれば、筆をUSEして、エフェクトのON／OFFするだけですが、
![2023041900120608_#1 ミクランドファンマーケット会場 _小山内シノブ.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2985893/2cb8e5f9-5e32-ae79-b63b-83b10a5999c0.jpeg)
下記のような、ステージアイテムですと、エフェクトのON／OFF、スポットライトのON／OFF、スポットライトの点滅速度の変更、などなど、とても単体アイテムのON／OFFだけでは制御できなくなり、各種ON／OFFを制御するためのメニューなどが必要となります。
![2023041922490971_#1 ミクランドファンマーケット会場 _小山内シノブ.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2985893/a9c209ef-f808-be0f-b8e4-c32be7e9fcda.jpeg)
さらに、麻雀の操作用コントローラーのUIとなると、打牌をしたり、鳴いたり、鳴き牌を選んだり、上がったり、と色々な操作が必要となり、かなり汎用性の高いUIを考える必要があります。
ここでは、麻雀のコントローラーUIを例に、どのようにUIを考えていったのかと、考えたコントローラーUIをどのような構成でVCIとして実現したのか、をご紹介していきたいと思います。
ちなみに、今回作成した、Quatanionなんて難しいスクリプトはご使用しておりませんので、ご安心ください。（ｸｫｰﾀﾆｵﾝ､ﾅﾝﾓﾜｶﾗﾝ）
# 楽しい、VキャスのUI
VRのUIを考える際、平面をベースに考えていくと、色々参考にできるものがたくさんありますので、自然と使う側も慣れているものになりやすくなります。
したがって、操作のわかりやすさで言えば、断然平面ベースのUIを選択すべきかと思います。
ただし、楽しさをベースで考えるのならば、断然3Dベースで考えたほうがよいかと個人的には思っています。
Vキャスのメニューを例に見てみましょう。
VキャスのメニューのUIは、平面を曲げて3DにしているUIで、横方向に回転するようにスクロールしたり、項目を選択すると縦方向にスクロールしたりして、それだけでも他のVRSNSと比べて、ワクワクするUIですよね！
自分は、このUIが大好きなので、今回はこちら参考にUIを考えていきました。
![2023121610032249.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2985893/a8127b2e-1680-0b27-3a57-88d7bfdfa5e8.jpeg)

# 考えることが多い、麻雀のコントローラー
1. 他のプレイヤーから選んだ手牌をわかりにくくすること
3. 打牌、鳴き、和了、立直などができること
4. 鳴く場合、鳴く牌を選べること
4. コントローラーを見なくても操作できること

VRでの麻雀をコントローラーに必要なことを整理すると、このようになります。
1～3は通常のネット麻雀でも考慮されていることですが、4はVRならではの考慮すべき点となります。
これだけでも悩ましいのですが、さらに、VCIで選択できるようにコライダーをつけてUSEなどをできるようにすると、SubItemをつける必要があります。
SubItemを増やしていくと、Transformの同期共有が発生してしまい、FPSを落とす要因となってしまうので、SubItemをなるべく最小限で構成する必要があります。
こうなると、「打牌」も「鳴き」も「鳴き牌の選択」も共通のボタンでできたほうがSubItemの節約になるので、ある程度汎用性があるボタン構成のコントローラーを考える必要が出てきます。
実は色々と他のコントローラーの候補も考えましたが、最終的には、VキャスのUIを参考にする形で、手首の周りに牌や鳴き操作ボタンを表示する方式を採用しました。（下の写真は、デバッグ用なため、すべての牌がイーピンとなっています。）
![2023121610055253.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2985893/0e696ab2-5029-e331-6d39-5a1e841bb238.jpeg)

**作製したコントローラーの操作例**
- コントローラーを回転させることによる牌の選択
![ダウンロード (6).gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2985893/401013a3-32f1-6329-6948-e5e76d997254.gif)

- コントローラーをUSEすることによるカーソル移動
![ダウンロード (7).gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2985893/8feea1b9-3298-8c33-66ae-5b7c3924cd89.gif)

このUIデザインにすることで、上記の麻雀のコントローラーに必要なことも含めて色々と実装することできました。
デザインとして実装した細かいことは、下に畳んでいますので、気になる方は見てください。
<details><summary>デザインに関するetc.</summary><div>

- 円形のデザインに関して
円形のデザインにすることで、コントローラーの操作を読まれにくくしています。
コントローラーはローカルでしか見えていないので、操作していることはわかっても、どこへ回転させたか、他の方にはわからないようになっています。
- 手牌との連動に関して
コントローラーは手牌と連動しています。
これによって、コントローラーを見なくても、牌の選択操作や鳴き操作などができるようになっています。
- キャンセルに位置に関して
キャンセルの位置は、手牌の左端で固定しています。
これと、後述するコントローラーの振動によって、キャンセルは手牌上に表示されていませんが、コントローラーを見なくてもキャンセル操作ができるようになっています。
- コントローラーの振動に関して
牌や鳴きボタンなどが選択されると、コントローラーが振動するようになっています。
これによって、コントローラーを見なくても操作しやすくなっています。
</div></details>

# コントローラーの構成
コントローラーの構成とスクリプトは意外とシンプルです。
クォータニオンとは和解できていないため、クォータニオンの計算は一切使いません。
牌の選択機能は、Hinge JointとTriggerEnterで実装し、必要なSubItemはupdateAllで「～Pos」に移動させています。
Hinge Jointを、CtrlOriginを介して、アバターの左手の位置に追従するようにし、Hinge JointのAxisで回転軸を固定して、掴んで回せばクルクルコントローラーが回るようにしています。
また、SelectTriggerには小さなコライダーが設定してあるため、CtrlOriginを介して手の角度に追従して、選択する牌が変更できるようになっています。
牌の選択をTriggerEnterで行っているため、SubItemを比較的多く使用してしまうという欠点があるものの、仕組み自体はシンプルとなりました。
また、牌をボタンに変えれば他のコントローラーにも流用できますし、ボタンの数も増減も比較的簡単に変更できるため、かなり汎用性が高いVR上のコントローラーではないかと思います。
あとは、これをベースに、手牌と連動したり、チーやリーチ後の牌の選択フェーズを作ったりと、色々と拡張していくことも可能です。

![スクリーンショット (164).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2985893/cb903e4c-2d2c-eac8-0cf5-281c05be3ae9.png)
```lua:main.lua
local selHai = 1
local triggerHaiNo = 1

function updateAll()
    local myAvatar
    if vci.vc.GetSpaceType() == ExportVcSpaceType.studio then
        myAvatar = vci.studio.GetLocalAvatar()
    else
        myAvatar = vci.vc.room.GetLocalPlayer().Character
    end
    --コントローラーの移動
    local gameCtrlOri = vci.assets.GetTransform("CtrlOrigin")
    if myAvatar.GetBoneTransform("LeftHand") ~= nil then--アバターチェンジ対策
        gameCtrlOri.SetPosition(myAvatar.GetBoneTransform("LeftHand").position)
        gameCtrlOri.SetRotation(myAvatar.GetBoneTransform("LeftHand").rotation)
    end
    --コントローラーの牌の移動
    local gameCtrlHai = {}
    local gameCtrlHaiPos = {}
    for i = 1, 18 do
        gameCtrlHai[i] = vci.assets.GetTransform("CtrlHai"..i)
        gameCtrlHaiPos[i] = vci.assets.GetTransform("CtrlHaiPos"..i)
        gameCtrlHai[i].SetPosition(gameCtrlHaiPos[i].GetPosition())
        gameCtrlHai[i].SetRotation(gameCtrlHaiPos[i].GetRotation())
    end
    --TriggerEnter、TriggerExit用のコライダーの移動
    local gameSelTrig = vci.assets.GetTransform("SelectTrigger")
    local gameTrigPos = vci.assets.GetTransform("TriggerPos")
    gameSelTrig.SetPosition(gameTrigPos.GetPosition())
    gameSelTrig.SetRotation(gameTrigPos.GetRotation())
    --カーソルの移動
    local gameCtrlSel = vci.assets.GetTransform("ControllerSelect")
    gameCtrlSel.SetPosition(gameCtrlHaiPos[selHai].GetPosition())
    gameCtrlSel.SetRotation(gameCtrlHaiPos[selHai].GetRotation())
    gameCtrlSel.SetLocalScale(gameCtrlHai[selHai].GetLocalScale())
end

function onUse(use)
    if use == "Controller" then
        selHai = triggerHaiNo
    end
end

function onTriggerEnter(item, hit)
    if string.find(item, "SelectTrigger") and string.find(hit, "CtrlHai") then
        vci.assets.GetTransform(hit).SetLocalScale(Vector3.one*200)
        vci.assets.HapticPulseOnTouchingController("Controller", 200, 0.1)
        triggerHaiNo = tonumber(string.sub(hit, 8))
    end
end

function onTriggerExit(item, hit)
    if string.find(item, "SelectTrigger") and string.find(hit, "CtrlHai") then
        vci.assets.GetTransform(hit).SetLocalScale(Vector3.one*100)
    end
end
```

実際に、どのような動きになっているかは、こちらの有償アイテム、もしくは、そのアイテムが置いてあるルームにてご確認頂くことが可能です。
※4人で遊ぶアイテムとなりますので、正しい挙動をご確認したい方は4人集めてください。

https://virtualcast.jp/products/79eb631d463dd30f2c51d8add2a34b27a468fc6feef734acaa747562e113cf0a

https://virtualcast.jp/rooms/80jcyxztgENe7HBl

# おわりに
この麻雀VCIは、色々と工夫しながら実装して、工夫した点はいくつもありますが、このコントローラーUIは、満足いく出来になった一つでした！
色々と汎用が効きそうなUIだと思うので、このUIをヒントにアイテムに組み込んだ際には、Vキャスでお会いした際によろしければ作ったアイテムを見せて頂けると嬉しいです！（自分も参考にして、今後のアイテム作りに活かしたいので！）
![2023121700412697.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2985893/35b79be4-0d87-274e-1d15-04fbab26d0dc.jpeg)