【VCIアドカレ2023】立体四目ＶＣＩを作ってみた

どうも！「てすてゃ～」といいます！

普段は好きにVCI作ったり、たまに配信したりしてるバチャキャス(Ｖキャス)一般人です。

![2023050100311147_#5 広場【MIKU LAND 2023 New Beginning】_てすてゃ〜.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3076559/c29d381c-a630-6bb7-f917-c3989a055fef.jpeg)


VCIを作る時によくアドカレの記事を見る事もあり、何となく自分も投稿してみました！
今回は私が最近作った「立体四目ＶＣＩ」について工夫した点などを書いてみたいと思います。

:::note warn
記事内容は ≪VCI作った事ある人向け～中級者向け≫ ぐらいになるかと思います。
:::

👇 作ったアイテムは、ここから取得できるよ！

https://virtualcast.jp/products/f9b2a1e25ac4c3f4355464de2b04c229deccef85f9ec67992ef8e4e5499e6677


## １．作ろうと思ったきっかけ🐓
<details><summary>詳細</summary>

・卓上ゲーム系VCIって今まで作った事がない。せっかく作るならブラウザゲーで表現が難しそうな３Ｄ空間を活かせるものにしたい。立体の四目並べなら遊んだことあるし、それにしよう！

・立体四目なら自分で揃ったと宣言しないと勝ちにならないので、処理判定が省ける！
（…って思ったけど普通のオンラインゲーなら判定いりますよね。でもそこは相手が目の前にいるVRだからこそリアルと同じく言葉や仕草で相手にどこがそろったのか伝えらえるので、、楽ができるところは全力で省きます！）

・負けた時に「こんなゲームやってられるか～！」と星一徹返しのごとく全部ひっくり返してみたい！

などです。

![chabudai_kaeshi.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3076559/b2a4461b-19e5-ef61-09b5-6c69d433c46d.png)


</details>

## ２．モデルについて🐖
<details><summary>詳細</summary>

・blenderが使いこなせないので基本はUnityで用意されているプリミティブモデルで構成しています。（隠しモードのみ今回はbooth販売モデルも使用）

![_てすてゃ～.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3076559/e6758d5c-9dd2-87a0-5101-3084abd471f9.jpeg)

</details>

## ３．Unity上での構成について🐄
<details><summary>詳細</summary>

・今回は星一徹返しをしたいので、珠に関しては普段使わないUse Gravityの☑を入れて、重力付きの設定にしてみました。（いつもはIs Kinematicのみ☑して、空中に留まるタイプのアイテムを作ることが多い）
・デフォルトだと結構落ちる速度が速いのでDragで空気抵抗も少し与えています。

![スクリーンショット 2023-11-26 204234.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3076559/58002cff-6ee9-8ce4-ce00-5fce424a9fcc.png)



・盤上の１６本の棒に４個ずつ球を刺したいので、キューブコライダを格子状に設置して、そこに珠が入って積み上がるような方法にしました。

![スクリーンショット 2023-11-26 204428a.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3076559/02180f1a-2926-83f4-fe73-56ecc2ab7f2c.png)



</details>

## ４．工夫した点🦙（クリックすると詳細が見れます）
<details><summary>①土台や器を一定範囲内の角度で離すと、必ず垂直になるように。</summary>
（範囲外の角度であれば自由に置けるようになっています）

　リジットボディのフリーズで常に水平にする方法もありますが、今回は持ってる時は自由に運びたい＆裏返して星一徹返しをしたかったため、スクリプトで実装。卓上ゲーム系VCIでは結構使いそうな機能なので今後も使えそうです。

```lua
    --土台のUpベクトルと、上方向のベクトルが成す角度を取得  
    local rot = Vector3.Angle(dodai.GetUp(), Vector3.up)

    --30度以内であれば土台を水平にする
    if rot <= 30 then
        local dodai_rot = dodai.GetRotation() --土台の角度を取得
        dodai.SetRotation(Quaternion.Euler(0, dodai_rot.eulerAngles.y, 0))
    end
```
　こういうスクリプト例の記事って「これ一発で書いてるのか。すげー」って思いながらいつも見てたんですが、私の場合はあーでもないこうでもないって、特にこれは他の方法も色々試しに試しまくって、やっとの思いで出来た「５行」って感じでした。。みんなもこうやってスクリプト作ってるのかなー。
- - -

</details>


<details><summary>②球が床に落ちてしまった時に自動で器に戻る。※条件付き</summary>

　単純にonCollisionEnterで床と珠の接触を感知して、球を器の上に飛ばしてるだけですが、先ほどの「器が垂直の状態」時のみ作動するようにしています。
 　これは器を裏返しに設置した際に、珠が床に落ちて・器に戻って・また落ちて…を延々と繰り返してしまうのを防ぐための処理です。
  
```lua
    function onCollisionEnter(item, hit)
        --------中略--------
        if hit == "RoomTransparentPlane" or hit == "TransparentPlane" then
            --print("ルームかスタジオの床にぶつかった")
            
            local kazu = tonumber(string.sub(item, 8)) --珠の名前を「Sphere_0～Sphere_63」とつけているので、そこから番号を取得。           
            
            if kazu <= 63 then --ぶつかったのが珠なら　※64番以降は別サブアイテム
                local rot = Vector3.Angle(huta.GetUp(), Vector3.up)
                if rot <= 5 then --器の角度がほぼ水平なら
                    sphere[kazu].SetPosition(hukki.GetPosition()) --該当する球を復帰ポジションに移動
                end
            end
        end
    end
```

:::note warn
　器をわざと裏返して、滝のようにアイテムを降らす使い方も嫌いではないですが、今回は珠の数が４×４×４＝６４個もあり、裏向きでも作動するような状態でルームに放置すると「何も知らないプレイヤーがルームに入った際も常に負荷をかけ続ける」というような状況が生まれるしまうため、それを防いでいます。
:::
 
 - - -
 
 </details>


<details><summary>③一定距離を超えると球が見えなくなる。</summary>

・先ほどと同じく負荷軽減のための処理です。
　複数人数のいるルーム内で誰かと対戦した場合、遠くにいる人たちにも64個の珠の動きを同期させる必要性は無いので、土台から一定以上離れている人には珠の代わりに以下のような表示が出るようにしています。

![2023101902274463_てすちゃんとのお部屋_てすてゃ～.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3076559/b28ecdce-7cc6-8d7d-e8bf-69878cb60a48.jpeg)

```lua
    function updateAll()
        --------中略--------
        count = count + 1
        if count >= 90 then --90フレームごとに実行
            count = 0
            local avatar = vci.vc.studio.GetLocalAvatar()
            local pos = avatar.GetPosition()
            if pos then --アバターの位置情報が取得できたら
                local kyori = Vector3.Distance(pos, dodai.GetPosition()) --土台までの距離を計算
                local sss = 3
                --既に球が表示されてる場合の処理
                if sphere[0].ActiveSelf then
                    if kyori >= sss then --土台より３ｍ以上離れたら
                        for i = 0, 63 do
                            sphere[i].SetActive(false) --球を全て非表示にする
                        end
                        oto.SetActive(false) --SEをいれてるオブジェも非表示
                        keiryou_UI.SetActive(true) --「軽量表示中」のテキストを表示
                        SubitemAnime._ALL_Play(true) --テキストの回転アニメーションを再生
                    end
                --球が表示されてない場合の処理
                else
                    if kyori <= sss then
                        for i = 0, 63 do
                            sphere[i].SetActive(true)
                        end
                        oto.SetActive(true)
                        keiryou_UI.SetActive(false)
                    end
                end
            end
        end
    end
```
:::note warn
　またここで、以下のようなこのVCIに関する音が入ってるアセットを非表示にして、離れた人には効果音も聞こえないようにしています。
:::
![スクリーンショット 2023-11-30 100846.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3076559/c9c0c801-7243-27ec-48cb-720bc727e99f.png)

- - -

</details>
 
<details><summary>④土台と器を持つと、全ての球の所有権が得られる。</summary>

　まずはVCIを作った人なら一度は直面する「所有権」という存在についてです。
　作ったことがない人もお皿のようなアイテムを持った時に「上の食べ物が上手く動かせない！」といったような経験はありませんか？あれが所有権です。今回はここをクリアしないと星一徹返しも出来ないわけです。（所有権の無い球が土台や器に乗っていると上手くひっくり返せない。。）
 
:::note info
 【所有権の詳しい仕様はこちら】
 https://wiki.virtualcast.jp/wiki/vci/component/sdk/subitem/owned
:::

　基本的にサブアイテムの１つ１つは最後に持った人が所有権を持ち、それ以外の人は自由に動かす事が出来ません。
　そこでサブアイテムには「グループID」というものがあって、それを使えば１つを掴んだだけで同じIDのもの全ての所有権を得ることができます。

![スクリーンショット 2023-11-27 184916.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3076559/8751b28d-73c3-c46f-2de2-87c8c31c9945.png)


　ということは全ての珠のグループIDを一緒にしてしまえばいいんじゃないか？という話になりますが、例えば相手が黒い球を持っている間に、自分が白い球を持った瞬間に所有権が移るので、結果的に相手の手から球がポロリと落ちてしまいます。

　かといってゲームの進行上、待ってる間は珠を手に持っておきたいのも実情。。
そこで今回はグループIDを２つ用意して以下のように割り振りました。

**グループID：１　黒珠×32、土台
グループID：２　白珠×32、器**

　これで互いに黒と白の珠は自由なタイミングで持ちつつ、【土台と器】の両方を掴めば全ての球の所有権が得られるので、星一徹返しが出来ます！

　ただ、毎回両方持たないといけないのも面倒ですね！！（面倒というか実際負けた時にやろうと思ったらそんなこと忘れて土台だけ持っちゃう）

　なので、次のセクションでもうひと工夫してみます。

- - -
</details>

 <details><summary>⑤土台か器かどちらかを持つと、全ての球の所有権が得られる。</summary>
 
 　先ほどの所有権が得られる条件ですが「アイテムを持った人」以外にも条件があり、それが「アイテムが**装着**された人」というものです。
　つまり土台を持った際に別のグループＩＤのアイテムが自動装着されれば、全ての所有権が得られる！というわけです。
　よって今回は透明な**装着クン**という所有権を移すためだけの目に見えないサブアイテムも追加しました。

**グループID：１　黒珠×32、土台、装着クン１
グループID：２　白珠×32、器、装着クン２**

![スクリーンショット 2023-11-27 191846.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3076559/9310b818-d2d5-56ee-9923-af1001323834.png)


 土台を掴んだ際、この装着クンが自動で装着されて、所有権を移動してくれる感じです。
 (ただし、装着完了後に所有権が移るので多少ラグがあります。。)

:::note warn
基本的に所有権がないアイテムを動かす場合、所有権の持ってる人から命令が出るようなスクリプトを組むのが一般的かなと思っています。
ただし特定の人のみで処理を行いたい場合は、こういう手法をとるのも手かと個人的に思っています。私が昔作っていたコメント落下ＶＣＩも同じ仕組みで、文字の所有権を統一してから同じ速度で落下させたりしていました。
あの時はTakiさんと、まといちかさんに色々ご助言いただき大変参考になりました。。
:::
</details>





## ５．おわりに🍖
<details><summary>詳細</summary>

記事書くのも大変だったけど、ここまで読むのも大変だったかと思います。
読んでくれてありがとー！＼(＾o＾)／

Qiitaのアカウント持ってる人は、左にあるお気に入りとか
Twitterのアカウントある人は、左にあるXボタンから「全部読んだ」って良かったらポストしてね♡

他にも
「珠をuseしてから落とすとカッコいい音が鳴るようにしました！」
「土台と器の大きさに合わせて珠も自動でサイズが変わります！」
「隠しコマンドで見た目が変わります！」
とか自分なりに工夫した点がありましたが、そちらは実際のVCIで試してみて下さいね～

![2023101902264764_てすちゃんとのお部屋_てすてゃ～ (2).jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3076559/f248de04-0751-5124-3eb6-778e0bdd5ced.jpeg)

バイバイ(＊´ω｀*)ﾉｼ
</details>