13日目：チュロスから学ぶオブジェクトの生成と削除(中編)：所有権

この記事はアドベントカレンダー「[【VCI】テーマパーク を作ろう](https://adventar.org/calendars/5855) 」の12日目の記事です。

# 今回の内容
今回はチュロスを作りながらオブジェクトの生成と削除について学んで行きます。
前後編の予定でしたが長くなりそうでしたので3回にわけました。今回は以下の内容を学びます。
　・所有権について
　・メッセージの使い方について
今回の内容はVCI制作の肝となる部分です。
**今回のコードはマルチ動作未確認です**

# 所有権への対応
## 所有権とは
VCIでSubItemのついているオブジェクトは他の人と位置や大きさが同期される性質があります。
この同期を行う際に部屋にいる誰のデータを同期元とするかを定める必要があります。そのためにSubItem付きオブジェクトには**所有権**という**だれがそのオブジェクトの同期元となっているか**を管理する仕組みがあります。
詳しくは以下を公式ページを御覧ください
　・[SubItemの所有権と同期処理の関係](https://virtualcast.jp/wiki/doku.php?id=vci:component:sdk:subitem:owned)

いろいろと書かれていますが、取り急ぎは以下の点をおさえて置けば今回は大丈夫です
　1. Grabすると所有権はその人へ移動する
　2. 自分が所有権を持っていないSubItemはスクリプトで移動できない。情報の取得のみなら可能。
　　 (移動したい場合は所有権を持ってる人が移動させなければならない)
　3. SubItemオブジェクトの子オブジェクトは同期されない。
　　 (SubItemオブジェクトを動かした場合は子オブジェクトも一緒に動くので問題ないが、
　　 子オブジェクトだけスクリプトなどで動かすとその動きは同期されない)

## チュロス出現コードを問題点
上記の所有権の特性をふまえ、前回作成したコードを見ていきます。

``` lua
function onUse(use)
  --今回作成したボタン「Button_Appear_Churros」が押されたときのみ実行される
  if use == "Button_Appear_Churros" then
    local churros = fetchChurros()
    if churros then
      --対象のオブジェクトを出現ポイントの位置に移動させる
      local appearPoint = vci.assets.GetTransform("Churros_AppearPoint")
      churros.SetPosition(appearPoint.GetPosition())
      churros.SetRotation(appearPoint.GetRotation())
    else
      --対象となるチュロスがなかった場合
      print("チュロスは完売しました。")
    end
  end
end
```

onUse関数はオブジェクトをgripした人のみコードが実行されますが、その中に「対象のオブジェクトを出現ポイントの位置に移動させる」というコードが入っています。
しかしここで対象となるオブジェクト(チュロス)は必ずしもボタンを押した人が所有権を持っているわけではありません。
(VCIを出したときは所有権はVCIを出した人が持っており、次回記載の削除処理後は削除をした人が所有権を持っています。)
そのため、所有権を持っていない場合はSubItemつきオブジェクトを移動することができません。(移動してもすぐ元の位置に戻されます。)


## メッセージによる所有権問題の解決
上記を解決するためにはmessageという機能を用います。
messageを使用すると他の人にもコードを実行してもらうことが可能になります。
messageの詳細は以下の公式ページを御覧ください。
　・[vci.message(VCI間の通信)](https://virtualcast.jp/wiki/doku.php?id=vci:script:reference:message)

簡単に言えば「○○○の処理をして！」というメッセージをみんな宛に送り、受け取った人は「○○○」という処理を各自実行するという様なイメージです。
どのように所有者だけに実行させるかはこの後説明します。


公式ページの内容を参考に、まずメッセージ送信側のであるonUseのコードを以下のように変更します。

``` lua
function onUse(use)
  --今回作成したボタン「Button_Appear_Churros」が押されたときのみ実行される
  if use == "Button_Appear_Churros" then
    local churros = fetchChurros()
    if churros then
      --オブジェクト表示用の関数を呼び出すためのメッセージを送信する。
      vci.message.Emit("appearChurros",churros.GetName())
    else
      --対象となるチュロスがなかった場合
      print("チュロスは完売しました。")
    end
  end
end
```
「対象のオブジェクトを出現ポイントの位置に移動させる」処理が以下の記述に変わっています。`vci.message.Emit("appearChurros",churros.GetName())`
という記述に変わっているのがわかるかと思います。この`vci.message.Emit`がメッセージを送信する関数です。
1つ目の引数はメッセージの名前です。今回はappearChurrosというメッセージ名でメッセージを送っています。
2つ目の引数にデータを付けることができます。今回はどのチュロスを表示するかという内容を送っています。

次にメッセージ受信側のコードを作成します。

``` lua
--メッセージが呼ばれると関数が実行される
function AppearChurros(sender, name, churrosName)
  local churros = vci.assets.GetTransform(churrosName)
  -- オブジェクトが取得できていて、かつ所有権を持っている場合のみ処理実行
  if churros and churros.IsMine then
    --対象のオブジェクトを出現ポイントの位置に移動させる
    local appearPoint = vci.assets.GetTransform("Churros_AppearPoint")
    churros.SetPosition(appearPoint.GetPosition())
    churros.SetRotation(appearPoint.GetRotation())
  end

end
vci.message.On("appearChurros", AppearChurros)
```

ここは新しい要素が多いため1つずつ見ていきます。
まずは1番下の記述です。

``` lua
vci.message.On("appearChurros", AppearChurros)
```
これはメッセージの受信部分の処理で、どのメッセージが来たら何をするかについて書いています。
今回は`appearChurros`というメッセージが来たら`AppearChurros`という関数を呼び出す、という内容になっています。
このとき、呼び出す対象の関数の定義は上記のメッセージ受信処理より前に書いてある必要があります。

次に関数の定義の部分です。

``` lua
　function AppearChurros(sender, name, churrosName)
``` 
メッセージで呼び出す関数には上記のように引数が3つ付きます。
1つ目は送信者情報、2つ目はメッセージの名前、3つ目はEmitで2つ目の引数に設定した値となります。
自分で作成したVCI内でメッセージの送受信を行う場合は基本的には3つ目しか使わないと思います。
今回は対象となるチュロスの名前を渡したため3つ目の変数名をchurrosNameにしています。

最後に以下の部分です。

``` lua
  if churros and churros.IsMine then
``` 

この`churros.IsMine`という部分のIsMineは、対象のオブジェクト(今回はchurros)の所有権を持っているかがわかる変数です。
所有権を持っている人はtrue、そうでない人はfalseを返すようになっています。
このIsMineを使うことにより所有権を持っている人だけオブジェクトを動かすことが可能となります。


以上で対応は完了です。
今回は「どのチュロスを出現させるか」という処理をボタンを押した人が行うようにしましたが、この処理もメッセージを受け取った人が行うようにしても問題はないと思います。

# 終わりに 
いかがでしたでしょうか？
所有権は複雑な仕組みですが、凝ったVCIを作ろうとすると避けては通れないないようなのでここでおさえておきましょう。
また、今回は同一VCIでmessage機能の送信・受信を行いましたが、VCIをまたいで送信・受信を行うこともできます。