19日目：園内マップで学ぶ凸者位置情報の取得

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

# 今回の内容
今回は以下のように自分の位置がマーカーで表示されるマップを作成していきたいと思います。
![1225.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/64877/23f2cc36-cfc0-6a7e-9f7c-2592bc399259.jpeg)



今回は学ぶ技術は以下となります。
　・Room内のアバター(凸者)の位置情報の取得
　・SetPositionとSetLocalPositionの違い(絶対座標と相対座標)


# 主な処理の流れ
以下の様な流れでアバター位置をマップ上に反映しています。
　・パーク内に基準点2つ(左手前と右奥)を設置
　　(この基準点から作成された四角いエリアがマップに表示されるエリアに対応します。)
　・アバターの位置情報を取得
　・基準点からアバターの相対位置を算出
　・相対位置をマップ上の座標に変換してマーカーを表示
![advent-ページ6.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/64877/848f7c46-1f4d-9a98-f15c-17d4b9b56ed6.jpeg)


# unity設定
avatar情報を取得自体にはUnityの設定は必要ありません。
ここでは座標系の計算と表示のためのオブジェクトのみ設定します。

設置するのは基準点2つ、マップ、表示用のマーカー16個です。
今回は以下の様な構成で作成しました。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/64877/19f4f4dc-5218-1722-c554-9046e4c951af.png)

マーカーオブジェクトはRootに直下に設置するのではなく、Mapの子以下に設置しています。
後述しますが、こうしておくことで座標の変換が少し楽になります。

また、マーカーオブジェクトの親オブジェクトをX軸方向に90度傾けています。
こうしておくことでマーカーオブジェクトのz座標軸(奥行き)がマップのY座標(高さ)に相当するようになるため、Z軸→Y軸への変換がいらずわかりやすくなります。

# コードの作成
アバターの位置情報をマップに反映するコードを作成します。
アバター系の関数については以下の公式ページを御覧ください
　・[ExportAvatar(アバター情報)](https://virtualcast.jp/wiki/vci/script/reference/exportavatar)

実際に作成したコードは以下のとおりです。

``` lua
function updateAll()
    --ルーム内のアバター情報を取得
    local avatars = vci.studio.GetAvatars()
    
    --パークの基準点取得＆中央座標を算出
    local parkMarker1 = vci.assets.GetTransform("ParkAreaMarker 1").GetPosition()
    local parkMarker2 = vci.assets.GetTransform("ParkAreaMarker 2").GetPosition()
    local parkCenter = (parkMarker1+parkMarker2)/2

    --マーカーを一旦全て隠す
    for i=1,16 do
      local marker = vci.assets.GetTransform("AvatarMarker "..i)
      marker.SetLocalPosition(Vector3.__new(0, 0,-500))  
    end

    for index,avatar in pairs(avatars) do
      --markerとboneの情報の読み込み
      local marker = vci.assets.GetTransform("AvatarMarker "..index)
      local bone = avatar.GetBoneTransform("spine")
      if bone and marker then
        --名前の先頭を取得して表示
        local namelead = string.sub(avatar.GetName(),1,1)
        vci.assets.SetText("AvatarInitialName "..index,namelead)

        --アバターの位置情報の取得＆相対位置を計算(相対位置はエリア内にいればー0.5~0.5)となる
        local avater_x = (bone.position.x-parkCenter.x)/(parkMarker2.x-parkMarker1.x)
        local avater_z = (bone.position.z-parkCenter.z)/(parkMarker2.z-parkMarker1.z)
        
        --mapの端より外にいる場合は端に表示するように調整する
        local avaterMarker_x = math.min(math.max(avater_x,-0.5),0.5)
        local avaterMarker_z = math.min(math.max(avater_z,-0.5),0.5)

        --マーカーを設置する
        marker.SetLocalPosition(Vector3.__new(avaterMarker_x, 0.0, avaterMarker_z))  
      end
    end
end
```

最後の部分をSetPositionではなくSetLocalPositionにしています。
SetPositionはRoomの**原点からの絶対座標**なのに対し、SetLocalPositionは**親からの相対座標**となっています。
相対座標では親オブジェクトの左端が-0.5、右端が0.5になるようになっています。
そのため、求めた相対位置をそのままSetLocalPositionで設定することができます。
(Unity側でマーカーを子オブジェクトにしたのもマーカーの親オブジェクトを回転させたのもここを楽にするためです)

実際に動かすと以下のようになります。
<blockquote class="twitter-tweet"><p lang="ja" dir="ltr">人のいる位置を表示するマーカーの安定版でのテスト。<br>誰が誰だかわかるかな？ <a href="https://t.co/yQ4kUPuref">pic.twitter.com/yQ4kUPuref</a></p>&mdash; ＃らいと（きょろ、ちょべりば→ぐ） (@lightjug) <a href="https://twitter.com/lightjug/status/1342314644233355264?ref_src=twsrc%5Etfw">December 25, 2020</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>

### 補足
今回のコードでは毎フレーム表示反映の処理をしているため重いかもしれません。
その場合は処理するフレームを間引くか、1フレームに処理するアバター数を決めて処理を数フレームにバラすかすると軽くなるかと思います。

# おまけ
上記だけではアトラクションの位置がわからなかったためアトラクションの位置も読み込んで表示するようにしました。
アトラクションが移動することは基本ないため、処理は読み込み時の1回のみとしています。

``` lua
--アトラクション一覧。Unityのオブジェクトの名前を入れる
local attractionList={
  "FerrisWheel",
  "Carousel",
  "ChurrosShop",
  "FlightTower",
  "HotAirBalloon"
}

--パークの基準点取得＆中央座標を算出
local parkMarker1 = vci.assets.GetTransform("ParkAreaMarker 1").GetPosition()
local parkMarker2 = vci.assets.GetTransform("ParkAreaMarker 2").GetPosition()
local parkCenter = (parkMarker1+parkMarker2)/2

for index,attractionName in pairs(attractionList) do
  local marker = vci.assets.GetTransform("AttractionMarker "..index)
  local attraction = vci.assets.GetTransform(attractionName)
  if attraction and marker then
    --名前を表示
    vci.assets.SetText("AttractionName "..index,attractionName)

    --アトラクションの位置情報の取得＆計算
    local attraction_x = (attraction.GetPosition().x-parkCenter.x)/(parkMarker2.x-parkMarker1.x)
    local attraction_z = (attraction.GetPosition().z-parkCenter.z)/(parkMarker2.z-parkMarker1.z)
    
    --mapの端より外にある場合は端に表示するように調整する
    local marker_x = math.min(math.max(attraction_x,-0.5),0.5)
    local marker_z = math.min(math.max(attraction_z,-0.5),0.5)

    --マーカーを設置する
    marker.SetLocalPosition(Vector3.__new(marker_x, 0.0, marker_z))  
  end
end
```
(アバターとほぼ同じ処理なので関数にまとめるべきですが、今回はわかりやすさのためにそのまま書いてます)

実際に読み込むと以下のようになります。
![2020122501210361.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/64877/0727fb23-9ba8-5acd-0aaa-e6349849d36e.jpeg)


# 終わりに 
いかがでしたでしょうか？
今回は実施しませんでしたが、アバター取得時に自分のアバターかどうかを見極めることで、自分のマーカーだけ色を変えることも可能だと思います。
次回の内容は未定です。