持ち物の概念をつくる　その2

「VキャスのVCIで◯ine◯raftっぽいもの作る Advent Calendar 2024」10日目

Vキャス内で、穴を掘って、石を砕き、木を切って、組み合わせて武器を作って、何なら出てくる敵を倒して・・・
そんなことをやりたいなぁっていう試みを12月でできるところまで行こうというカレンダーの10日目です！

昨日は情報表示板のオブジェクト部分を一旦作りました。
今日はコードを書いていこうと思います。

# 持ち物の概念のコードを書いていく

今日は記録みたいな仕組みまではいかず、表示時の板と非表示時のアクセサリーを動かすと他の部品も一緒に動くというのを実現しようと思います。
そこで、作業手順は以下の通り。

① 動かさないオブジェクトの設定を変更する
② デバッグできる状態にする
③ VirtualCastを起動し、VR内で情報表示板のVCIを取り出す
④ 移動時の追従コードを書く

では進めていきます。

# 手順

## ① 動かさないオブジェクトの設定を変更する
CubeとCylinderがこの情報表示板を動かすときに持つ対象になります。
そこで、それ以外のオブジェクトから、Grabbableのチェックを解除します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/222644/088fa592-0058-0ff2-51f0-13861e66d43b.png)

また逆に持つ対象の2つはGrabbableにチェックが付いていることは確認します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/222644/64baaa3b-f28a-e408-2a1b-e1204c119dfa.png)


## ② デバッグできる状態にする
コードデバッグをこれからしていくので、VCIObjectを選択して、Script Enable Debugging二チェックが付いていることを確認します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/222644/203c15f0-0847-3429-2c1b-0c8e1299a0de.png)

確認ができたら、VirtualCastにVCIを再投稿（更新）します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/222644/86922a63-82cd-bc7e-0be3-312edc872d31.png)

## ③ VirtualCastを起動し、VR内で情報表示板のVCIを取り出す
VirtualCastを起動して、ルームに入ったら、情報表示板のVCIを取り出します。
Bボタンを押し、リングメニューを展開してマイアイテムを選び、作成したVCIを選んで新規作成します。
![20241210223943_1.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/222644/4f3d1e60-19c8-d355-ad86-35759de0a507.jpeg)
![20241210223948_1.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/222644/2480e967-2286-d593-b080-60ba2488ad6c.jpeg)
![20241210224004_1.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/222644/31b4dd9d-b6a9-1360-7abc-4db8b0e2c3c8.jpeg)

出てくるVCIはUnity上で作成した形で出てきます。
![20241210224013_1.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/222644/15ad9aa1-ff25-f7d2-f54b-476b705cada2.jpeg)

しかし、例えば板を持って動かすとほかが取り残されることがわかると思います。
![20241210224022_1.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/222644/45337c22-ebaa-6015-a3a7-2d218018e693.jpeg)

この取り残されるオブジェクトたちを追従するようにコードを書いていきましょう。


## ④移動時の追従コードを書く
Visual Studio Codeを開き、EmbeddeedScriptWorkspace階層を開きます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/222644/3c03a8e1-fe64-54b2-8079-00bb12322c57.png)

Visual Studio Codeの左側に、今回作成したVCIのフォルダが存在するはずなので、その中にmain.luaファイルを作りましょう。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/222644/2fa0a0fb-7e74-0a0f-4586-7002835f067e.png)

main.luaを開き、コードを書いていきます。
まず、基準（手で動かすオブジェクト2つ）を変数にして、お互いの距離と角度も変数に格納しておきます。
```lua
mode=true

Cube = vci.assets.GetTransform("Cube")
Cylinder = vci.assets.GetTransform("Cylinder")
cubedist = Cylinder.GetPosition() - Cube.GetPosition()
cuberoat = Quaternion.Inverse(Cylinder.GetRotation()) * Cube.GetRotation()
cylinderdist = Cube.GetPosition() - Cylinder.GetPosition()
cylinderroat = Quaternion.Inverse(Cube.GetRotation()) * Cylinder.GetRotation()

roat_Cube = Quaternion.Inverse(Cube.GetRotation())
roat_Cylinder = Quaternion.Inverse(Cylinder.GetRotation())
```
今日の段階では、表示非表示の切り替えは用意しませんが、動かしうるオブジェクトが二つあるので、このように基準のオブジェクトは明示的に書きました。
また最初の`mode=true`は、表示非表示の切り替えフラグとして定義しているものです。

次に、基準となるオブジェクトに対して動かされるオブジェクトの基準オブジェクトとの距離や角度も変数に格納します。
```lua
SubItems = {
    slot1 = {item=vci.assets.GetTransform("slot1")},
    slot2 = {item=vci.assets.GetTransform("slot2")},
    slot3 = {item=vci.assets.GetTransform("slot3")},
    slot4 = {item=vci.assets.GetTransform("slot4")},
    slot5 = {item=vci.assets.GetTransform("slot5")},
    slot6 = {item=vci.assets.GetTransform("slot6")},
    slot7 = {item=vci.assets.GetTransform("slot7")},
    slot8 = {item=vci.assets.GetTransform("slot8")},
    select = {item=vci.assets.GetTransform("select")},
    l_v = {item=vci.assets.GetTransform("l_v")},
    Sphere = {item=vci.assets.GetTransform("Sphere")}
}
for key, value in pairs(SubItems) do
    SubItems[key].cubedist = SubItems[key].item.GetPosition() - Cube.GetPosition()
    SubItems[key].cuberoat = Quaternion.Inverse(SubItems[key].item.GetRotation()) * Cube.GetRotation()
    SubItems[key].cylinderdist = SubItems[key].item.GetPosition() - Cylinder.GetPosition()
    SubItems[key].cylinderroat = Quaternion.Inverse(SubItems[key].item.GetRotation()) * Cylinder.GetRotation()
end
```

そして、実際に動く処理を一旦updateAllで書いてみます。
※処理の効率からホントはupdateAllで一律やるのではなく、特定の節目タイミングでAllをしたほうがいいが、一旦今はこの書き方をしている。
```lua
function updateAll()
    if mode then
        local pos = Cube.GetPosition()
        local roat = Cube.GetRotation()
        for key, value in pairs(SubItems) do
            local offset = roat * roat_Cube * SubItems[key].cubedist
            local pos_d = pos + offset
            SubItems[key].item.SetPosition(pos_d)
            SubItems[key].item.SetRotation(roat * Quaternion.Inverse(SubItems[key].cuberoat))
        end
        local offset = roat * roat_Cube * cubedist
        local pos_d = pos + offset
        Cylinder.SetPosition(pos_d)
        Cylinder.SetRotation(roat * Quaternion.Inverse(cuberoat))
    else
        local pos = Cylinder.GetPosition()
        local roat = Cylinder.GetRotation()
        for key, value in pairs(SubItems) do
            local offset = roat * roat_Cylinder * SubItems[key].cylinderdist
            local pos_d = pos + offset
            SubItems[key].item.SetPosition(pos_d)
            SubItems[key].item.SetRotation(roat * Quaternion.Inverse(SubItems[key].cylinderroat))
        end
        local offset = roat * roat_Cylinder * cylinderdist
        local pos_d = pos + offset
        Cube.SetPosition(pos_d)
        Cube.SetRotation(roat * Quaternion.Inverse(cylinderroat))
    end
end
```
modeでの分岐は、今はtrueしかないですが、後々表示ON/OFFが入ったときのために書いています。
記述の意味としては、基準となるオブジェクト（Cube）の位置を確認し、その位置から最初に保存しておいた距離と角度の位置に他のオブジェクトを動かすという処理を書いています。

これで手で持って動かしても追従してくれるようになりました。
![20241210233457_1.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/222644/51b0e20c-21e9-5239-d7a0-46c0aed67eb4.jpeg)


コードが長いので全文も乗せておきます。
```lua
mode = true

Cube = vci.assets.GetTransform("Cube")
Cylinder = vci.assets.GetTransform("Cylinder")
cubedist = Cylinder.GetPosition() - Cube.GetPosition()
cuberoat = Quaternion.Inverse(Cylinder.GetRotation()) * Cube.GetRotation()
cylinderdist = Cube.GetPosition() - Cylinder.GetPosition()
cylinderroat = Quaternion.Inverse(Cube.GetRotation()) * Cylinder.GetRotation()

roat_Cube = Quaternion.Inverse(Cube.GetRotation())
roat_Cylinder = Quaternion.Inverse(Cylinder.GetRotation())

SubItems = {
    slot1 = {item=vci.assets.GetTransform("slot1")},
    slot2 = {item=vci.assets.GetTransform("slot2")},
    slot3 = {item=vci.assets.GetTransform("slot3")},
    slot4 = {item=vci.assets.GetTransform("slot4")},
    slot5 = {item=vci.assets.GetTransform("slot5")},
    slot6 = {item=vci.assets.GetTransform("slot6")},
    slot7 = {item=vci.assets.GetTransform("slot7")},
    slot8 = {item=vci.assets.GetTransform("slot8")},
    select = {item=vci.assets.GetTransform("select")},
    l_v = {item=vci.assets.GetTransform("l_v")},
    Sphere = {item=vci.assets.GetTransform("Sphere")}
}
for key, value in pairs(SubItems) do
    SubItems[key].cubedist = SubItems[key].item.GetPosition() - Cube.GetPosition()
    SubItems[key].cuberoat = Quaternion.Inverse(SubItems[key].item.GetRotation()) * Cube.GetRotation()
    SubItems[key].cylinderdist = SubItems[key].item.GetPosition() - Cylinder.GetPosition()
    SubItems[key].cylinderroat = Quaternion.Inverse(SubItems[key].item.GetRotation()) * Cylinder.GetRotation()
end

function updateAll()
    if mode then
        local pos = Cube.GetPosition()
        local roat = Cube.GetRotation()
        for key, value in pairs(SubItems) do
            local offset = roat * roat_Cube * SubItems[key].cubedist
            local pos_d = pos + offset
            SubItems[key].item.SetPosition(pos_d)
            SubItems[key].item.SetRotation(roat * Quaternion.Inverse(SubItems[key].cuberoat))
        end
        local offset = roat * roat_Cube * cubedist
        local pos_d = pos + offset
        Cylinder.SetPosition(pos_d)
        Cylinder.SetRotation(roat * Quaternion.Inverse(cuberoat))
    else
        local pos = Cylinder.GetPosition()
        local roat = Cylinder.GetRotation()
        for key, value in pairs(SubItems) do
            local offset = roat * roat_Cylinder * SubItems[key].cylinderdist
            local pos_d = pos + offset
            SubItems[key].item.SetPosition(pos_d)
            SubItems[key].item.SetRotation(roat * Quaternion.Inverse(SubItems[key].cylinderroat))
        end
        local offset = roat * roat_Cylinder * cylinderdist
        local pos_d = pos + offset
        Cube.SetPosition(pos_d)
        Cube.SetRotation(roat * Quaternion.Inverse(cylinderroat))
    end
end
```


# おわりに
今日は、手に持つ部分を動かしたら、持っていない他の部分も追従するというコードを書きました。
明日は、ON/OFFの切り替えを書いていこうと思います。