箱から取り出して食べる系VCIの作り方


# はじめに
この記事はある程度食べ物VCIの実現方法を知っている方がわかりやすいと思います。
知らなくても多分わかります。

# 今回作りたいもの
以下のような最初は箱に入っており、そこから取りだして食べるれるようなVCIを作ります。今回は例としてもみじ饅頭を作ります。
![momiji.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/64877/9bbabd92-dd42-3625-e8db-d18bb58fcc58.gif)


主な要件は以下です
　1. もみじ饅頭が箱に入っているときは箱が動くともみじ饅頭も一緒に動く
　2. もみじ饅頭を一度箱から取り出すと、箱が動いてももみじ饅頭は動かない
　3. もみじ饅頭を食べると手元から消失し、箱の中に戻る
　4. 箱を動かした際にもみじ饅頭が見た目上遅れてついてこない


# 実現における課題
ポイントとなるのは要件1の箱が動くともみじ饅頭も一緒に動く点です。
この点について細かく考えていきたいと思います。

## 子オブジェクトでの位置同期
単純に一緒に動かすだけならもみじ饅頭を箱の子オブジェクトにすれば可能です。
しかし、今回はもみじ饅頭を取り出せるようにするため、もみじ饅頭はSubItemにする必要がありますが、
SubItemはVCIObjectの直下にしか置けないため、箱の子オブジェクトにすることはできません、

## スクリプトでのSubItem位置同期
異なるSubItemの一緒に動かす方法としては、スクリプトで同期させる方法があります。箱ともみじ饅頭の所有権を同じ人が持っていればこれでほぼ問題ない動きになります。

しかし実際には取り出して食べた後箱に戻るため、食べた後のもみじ饅頭は食べた人が所有権を持っている状態になります。
この状態では同期のラグが発生し、もみじ饅頭が少し遅れてついてくるようになってしまい、要件4の見た目上遅れてついてこないが満たせません。

# 解決方法
上記の問題を解決するため、今回は子オブジェクトとスクリプト同期を両方使用するハイブリッドな方式を取りました。
箱の中の子オブジェクトにつかめないもみじ饅頭(Fakeもみじ）を設置し、通常のもみじ饅頭(Realもみじ)と可視状態を切り替えることにより、自然な動きに見えるようにします。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/64877/b9268758-8848-02d4-6742-5b9d449b829f.png)

具体的な流れは以下のとおりです。
　・箱の中に入っているときはFakeもみじが可視化、Realもみじが不可視化の状態。
　・RealもみじはFakeもみじと位置を同期させており、すこし遅れはするもののほぼ同じ位置にある。
　・Fakeもみじをつかもうとすると、同じ位置にあるRealもみじが掴まれる。
　・Realもみじを掴んだ瞬間に、Fakeもみじを不可視化・Realもみじを可視化する。
　・Realもみじを食べた瞬間に、Fakeもみじを可視化、Realもみじが不可視化する。

# コード
具体的なコードは以下のとおりです。
変数・関数の定義などかなり割愛してるので雰囲気で読んでください。コピペしても動きません。


###掴む処理

``` lua
function onGrab(target)
  if string.find( target,NAME.MOMIJI ) then
     local no = string.match( target,NAME.MOMIJI.." (.+)" )
     if no then
      changeFakeToReal(no)
     end
   end
end

function changeFakeToReal(no)
  --箱から出てるフラグをONにする
  vci.state.Set(STATE.IS_TAKE_OUT.." "..no,true)
  --Fake用のもみじ饅頭を透過して、実物のもみじ饅頭を可視化する。
  vci.assets._ALL_SetMaterialColorFromName(NAME.MOMIJI.." "..no,COLOR.SHOW)
  vci.assets._ALL_SetMaterialColorFromName(NAME.FAKE.." "..no,COLOR.HIDE)
end
```

### 食べる処理

``` lua
function onCollisionEnter(item, hit)
  if string.find(item,NAME.MOMIJI) and hit == "Head" then
    local momijiNo = string.match( item,NAME.MOMIJI.." (.+)" )
    if momijiNo then
      eatMomiji(momijiNo)
    end
  end
end

function eatMomiji(_no)
  local no = tonumber(_no)
  local item = ITEM.MOMIJI_LIST[no]

  if item and item.IsMine then
    --箱から出てるフラグをOFFにする
    vci.state.Set(STATE.IS_TAKE_OUT.." "..no,false)
    --Fake用のもみじ饅頭を可視化して、実物のもみじ饅頭を透過する。
    vci.assets._ALL_SetMaterialColorFromName(NAME.FAKE.." "..no,COLOR.SHOW)
    vci.assets._ALL_SetMaterialColorFromName(NAME.MOMIJI.." "..no,COLOR.HIDE)

    vci.assets._ALL_PlayAudioFromName(AUDIO.EAT)
  end
end
```

### 位置合わせ処理

``` lua
function updateAll()
  for i=1,#ITEM.MOMIJI_LIST do
    local item = ITEM.MOMIJI_LIST[i]

    --所有権がバラバラの可能性があるため各自所有権保持者が処理する
    if item.IsMine then
      -- 箱から出ていなかったら
      if vci.state.Get(STATE.IS_TAKE_OUT.." "..i) == false then
        --- Fakeもみじの位置にRealもみじを移動する。
        item.SetPosition(ITEM.FAKE_LIST[i].GetPosition())
        item.SetRotation(ITEM.FAKE_LIST[i].GetRotation())
      end
    end
  end
end
```
毎フレーム位置合わせする必要はないかもしれません。

以上です。


# 終わりに 
いかがでしたでしょうか。
今回の方法は食べ物VCI以外にも応用できると思います。
別でVCIでテーマパークを作るアドカレも書いているので是非見ていってください。
　[【VCI】テーマパークを作ろう Advent Calendar 2020](https://adventar.org/calendars/5855)