Open49

Lightship VPSを触る

FumiyaHrFumiyaHr

クアルコムの展開するXRプラットフォーム「Snapdragon Spaces」ユーザーは、NianticのARプラットフォーム「Lightship VPS」の機能を簡単に追加できるようになる見通しです。

https://www.moguravr.com/niantic-qualcomm-linked-xr-platform/
記事の中で上記のように記載があり、今後 Lightship VPS を使える範囲が広くなる可能性があるため触ってみる。

FumiyaHrFumiyaHr

ARDKを使い始める の通りに実施して、Android(Pixel 6a Android 13)上でカメラが起動することを確認。

ARDKを使い始める ドキュメントを進める中でのメモ

  • アウェアネス機能を使用するためにARSessionを追加する は Assets/ARDK/Extensions/Prefabs/ARSceneManager プレハブを利用した
    • ドキュメントは ARSession を独自にスクリプトで制御するように記載があるが簡略化のため
  • Androidのカメラパーミッション取得は ARSceneManager に Android Permission Requester コンポーネントをアタッチして対応
  • Android向けARDKアプリをビルドする の下記の2つは実施
    • Android 11(APIレベル30)以降のデバイスでビルドする
    • OpenCL準拠のAndroid APIレベル31を対象としたビルド
FumiyaHrFumiyaHr

アプリユーザーのユーザーIDを作成し設定する に関しては、ARDKユーザーIDの使用 に詳細がある。
API リクエスト時のユーザIDの使用 にあるようにアプリをリリースする時は以下のようにユーザIDの対応が必要そう。

using Niantic.ARDK.Configuration;

// Set the user id associated with the current user.
ArdkGlobalConfig.SetUserIdOnLogin(your_generated_user_ID);

お試しで実行する場合は、ユーザーIDを指定できない場合の対処方法 にあるように自動的に生成してくれるようなので今回は省略。

FumiyaHrFumiyaHr

VPS Coverage APIを使用して、VPSが作動するWayspotを検出する に関しては、今回カバレッジエリアの検出等は行わず、プライベートVPSで決まった場所を検出するので不要と思われる。

メモ

カバレッジエリアとは、ユーザーがローカライズターゲットを使用してローカライズできる地理的なエリアを表します。ローカライズターゲットとは、VPSが作動したWayspotを使ってローカライズする際に、ユーザーがデバイスを向けるべき方向を示すものです。

FumiyaHrFumiyaHr

WayspotAnchorServiceを使用する に WayspotAnchorService の作成方法は書いてあるが、作成するタイミングがわからないため調査。


開発者のダッシュボードにダウンロードがあり、ここに ARDK Sample Scenes があるのでダウンロードして確認。


Assets/ARDKExamples/WayspotAnchors/WayspotAnchorExampleManager.cs を確認すると、ARSessionの初期化時に呼ばれるイベント(SessionInitialized)で取得した、IARSessionのセッションが実行された時に呼ばれるイベント(Ran)の時に実行すれば良さそう。

...
private void OnEnable()
{
  ARSessionFactory.SessionInitialized += HandleSessionInitialized;
}

private void OnDisable()
{
  ARSessionFactory.SessionInitialized -= HandleSessionInitialized;
}
...
private void HandleSessionInitialized(AnyARSessionInitializedArgs args)
{
  _statusLog.text = "Session initialized";
  _arSession = args.Session;
  _arSession.Ran += HandleSessionRan;
}

private void HandleSessionRan(ARSessionRanArgs args)
{
  _arSession.Ran -= HandleSessionRan;
  WayspotAnchorService = CreateWayspotAnchorService();
  WayspotAnchorService.LocalizationStateUpdated += OnLocalizationStateUpdated;
  _statusLog.text = "Session running";
}
...

ちなみにこの ARSessionFactory.SessionInitialized はドキュメントを検索していたら下記のようなものらしい。

非同期でサブスクライブする

ARDKのコアオブジェクトは1つのクラスで作成して管理されますが、そのオブジェクトへの参照は別のクラスで必要になることがあります。参照渡し(そしてそれによって生じる可能性のある2つのクラス間の不要な依存関係)を防ぐために、インスタンス構築時にファクトリで発生するイベントをサブスクライブできます。

FumiyaHrFumiyaHr

WayspotAnchorServiceを使用する を実装するとこんな感じかな?

using System;
using System.Collections;
using System.Collections.Generic;
using Niantic.ARDK.AR;
using Niantic.ARDK.AR.ARSessionEventArgs;
using Niantic.ARDK.AR.WayspotAnchors;
using Niantic.ARDK.LocationService;
using UnityEngine;

public class WayspotAnchorManager : MonoBehaviour
{
    private static readonly float desiredAccuracyInMeters = 0.01f;
    private static readonly float updateDistanceInMeters = 0.01f;

    private IARSession _arSession;
    private WayspotAnchorService _wayspotAnchorService;
    private bool _isLocalized;

    private void OnEnable()
    {
        ARSessionFactory.SessionInitialized += HandleSessionInitialized;
    }

    private void OnDisable()
    {
        ARSessionFactory.SessionInitialized -= HandleSessionInitialized;
    }

    private void HandleSessionInitialized(AnyARSessionInitializedArgs args)
    {
        _arSession = args.Session;
        _arSession.Ran += HandleSessionRan;
    }

    private void HandleSessionRan(ARSessionRanArgs args)
    {
        var wayspotAnchorsConfiguration = WayspotAnchorsConfigurationFactory.Create();
        var locationService = LocationServiceFactory.Create(_arSession.RuntimeEnvironment);
        locationService.Start(desiredAccuracyInMeters, updateDistanceInMeters);
        _wayspotAnchorService = new WayspotAnchorService(_arSession, locationService, wayspotAnchorsConfiguration);

        _wayspotAnchorService.LocalizationStateUpdated += LocalizationStateUpdated;
    }

    private void LocalizationStateUpdated(LocalizationStateUpdatedArgs args)
    {
        if (LocalizationState.Localized.Equals(args.State) && !_isLocalized)
        {
            _isLocalized = true;
            // ToDo: Create anchor?
	    }
    }
}

_isLocalized は以下のような記載があり、何度も呼び出される可能性がありそうだったのでとりあえず一度だけ実行されるようにした。

セッションがローカライズされていても、VPSで断続的なネットワーク接続が発生している可能性があることが検出されると、 LocalizationStateUpdated イベントが発生します。ステータスは Localized のままですが、 FailureReason は CannotConnectToServer に設定されます。このシナリオについて検討し、ネットワーク環境に不具合が検出されたことをアプリのユーザーに通知する必要があります。

FumiyaHrFumiyaHr

一点書き忘れ。
Wayspot Anchor APIを使ってローカライズする に下記のような記載がある。今回は WayspotAnchorService を使用する。

Wayspot Anchor APIは、VPSを初期化するときに、 WayspotAnchorService インスタンスを新規に作成するか、 WayspotAnchorController.StartVps() を使って、VPSが有効なWayspotでローカライズを試みます。 WayspotAnchorService には、Wayspot Anchorsを作成して管理するためのシンプルな「高レベル」API、 WayspotAnchorController には、よりきめ細かい制御が必要な場合に使用できる「低レベル」APIが付属しています。

FumiyaHrFumiyaHr

連続ローカライゼーションを行う は役立ちそう。

Lightship VPSは、VPS 起動時に WayspotAnchorsConfiguration.ContinuousLocalizationEnabled をTrueに設定することで、連続ローカライゼーションを行うように設定できます。連続ローカライゼーションを有効にすると、VPSは最初にローカライズを行った後、ユーザーの現在の環境に合わせて連続ローカライズを行います。

FumiyaHrFumiyaHr

VPSを再起動する もよく探しそうなのでメモ

アプリでVPSセッションをゼロから再起動する必要がある場合は、 WayspotAnchorService.Restart() を使用できます。この操作は、アプリがデバイスの位置情報サービスやネットワーク接続に問題が発生した場合(ユーザーが機内モードを有効または無効にした場合など)に必要になることがあります。

FumiyaHrFumiyaHr

モッキングローカライゼーション は自宅デバッグで役立ちそう

Unityエディターでモックモードを実行すると、VPSでモックのローカライズが行われ、300ミリ秒後に自動的に Localized ステータスに移行して、ローカライゼーションプロセスをシミュレートします。その後、モックモードでWayspot Anchorsを配置できるようになります。

FumiyaHrFumiyaHr

アンカートラッキングの更新を処理する を確認すると、WayspotAnchorTracker を用いればアンカーの位置や向きの更新を自動的に処理できるらしい。

WayspotAnchorTracker コンポーネントをアタッチしたPrefabを準備しておいて、Instantiate 後に WayspotAnchorTracker.AttachAnchor に IWayspotAnchor をアタッチすれば良いらしい。

using Niantic.ARDK.Extensions;
using Niantic.ARDK.AR.WayspotAnchors;

private GameObject CreateWayspotAnchorGameObject(IWayspotAnchor anchor, Vector3 position, Quaternion rotation, bool startActive)
{
  var go = Instantiate(_anchorPrefab, position, rotation);

  var tracker = go.GetComponent<WayspotAnchorTracker>();
  if (tracker == null)
  {
    Debug.Log("Anchor prefab was missing WayspotAnchorTracker, so one will be added.");
    tracker = go.AddComponent<WayspotAnchorTracker>();
  }

  tracker.gameObject.SetActive(startActive);
  tracker.AttachAnchor(anchor);
  // ...Add to a local cache of WayspotAnchorTrackers if needed...

  return go;
}
FumiyaHrFumiyaHr

Wayspot Anchorsの永続化と共有 にあるようにWayspot Anchorのペイロードを保持するには、WayspotAnchorPayload の Serialize() および Deserialize() メソッドを利用してBase64文字列表現に変換してから保存すればいいらしい。

FumiyaHrFumiyaHr

Wayspot Anchorsのベストプラクティス も重要ですね。

Wayspot Anchorのデータの有効期限は現在6か月です。アンカーを最初に作成したときの時間を記録し、アンカーを復元するときにこの時間を確認する必要があります。6か月以上経過している場合は、アンカーの再作成を検討してください。

6か月らしい。

FumiyaHrFumiyaHr

Wayspot Anchorを作成する を参考にコードを追加するとこれで良さそう。

[SerializeField] private GameObject _anchorPrefab;
...
private void LocalizationStateUpdated(LocalizationStateUpdatedArgs args)
{
    if (LocalizationState.Localized.Equals(args.State) && !_isLocalized)
    {
        _isLocalized = true;
        PlaceAnchor(Matrix4x4.zero);
    }
}

private void PlaceAnchor(Matrix4x4 localPose)
{
    var anchors = _wayspotAnchorService.CreateWayspotAnchors(localPose);
    if (anchors.Length == 0)
    {
        return; // error raised in CreateWayspotAnchors
    }

    CreateWayspotAnchorGameObject(
        anchors[0],
        localPose.ToPosition(),
        localPose.ToRotation(),
        true);
}

private GameObject CreateWayspotAnchorGameObject
(
    IWayspotAnchor anchor,
    Vector3 position,
    Quaternion rotation,
    bool startActive
)
{
    var go = Instantiate(_anchorPrefab, position, rotation);

    var tracker = go.GetComponent<WayspotAnchorTracker>();
    if (tracker == null)
    {
        tracker = go.AddComponent<WayspotAnchorTracker>();
    }

    tracker.gameObject.SetActive(startActive);
    tracker.AttachAnchor(anchor);

    return go;
}
  • とりあえずアンカーの位置は Matrix4x4.zero を指定
  • 作成ステータスの追跡やペイロードの保存はとりあえず後回し
  • アンカーの位置更新は WayspotAnchorTracker が対応してくれるはず
FumiyaHrFumiyaHr

とりあえず動いて豆腐が表示された

Matrix4x4.zero の位置がかなり上の方で、最初動いているのがわからなかった。
下記2点の調査が必要そう

  • どのように位置合わせをするのか
  • どのようにWayspotを指定するのか
    • 現状どのWayspotでもコンテンツが表示される
FumiyaHrFumiyaHr

開発者ポータルでプライベートVPSのロケーションを管理する を確認すると、メッシュをダウンロードする にプライベートVPSロケーションのメッシュと原点アンカーの文字列があるらしい。
この原点アンカーをペイロードとして復元し、他のオブジェクトはこの原点から相対的に配置すればいいらしい。原点アンカーをParentとしてInstantiateすれば良さそう。

これによってプライベートVPSのロケーションは「どのように位置合わせをするのか」が解決できそう。

FumiyaHrFumiyaHr

My MeshesからプライベートVPSロケーションのダウンロードをしたところ下記3つが入っていた。

  1. fbx
  • スキャンしたロケーションのfbx
  1. jpeg
  • fbxのテクスチャ
  1. json
  • 下記のようなjson
{
  "AnchorPayload": "*********",
  "AnchorName": "*********",
  "NodeIdentifier": "*********",
  "LocalizationTargetName": "Private Mesh - *********"
}

fbxとjpegをUnity上で配置すると下図のようになる。
このMeshの原点がオブジェクトを配置する時の原点となりそう。

RotationのXは-180になっていた。

jsonのAnchorPayloadの文字列が原点アンカーの文字列かな?
ドキュメントにあるメッシュの原点アンカーを含む .b64 ファイルは見つからず。

FumiyaHrFumiyaHr

RotationのXに関しては メッシュをダウンロードする に下記のように記載があったので対応したところ直った。
(自分の置き方が悪かっただけかも)

When importing the FBX mesh in Unity, check Bake Axis Conversion in the Model section of the import settings in the Unity inspector. Baking the transform into the mesh ensures the mesh transform won't get overwritten on the GameObject when the anchor position is updated.

FumiyaHrFumiyaHr

こんな感じでしょうか?
_anchorPrefab に指定するPrefabは原点位置に空オブジェクトを配置し、その子オブジェクトにコンテンツを配置しました。

[Header("Origin")]
[SerializeField] private string _payload;

...

private void LoadOriginAnchor()
{
    var payload = WayspotAnchorPayload.Deserialize(_payload);
    if (payload == null)
    {
        return; 
    }

    var anchors = _wayspotAnchorService.RestoreWayspotAnchors(payload);
    if (anchors.Length == 0)
    {
        return;
    }

    var anchor = CreateWayspotAnchorGameObject(
        anchors[0],
        Vector3.zero,
        Quaternion.identity,
        true);
}
FumiyaHrFumiyaHr

_anchorPrefab に指定するPrefabは原点位置に空オブジェクトを配置し、その子オブジェクトにコンテンツを配置しました。

よくよく考えたらPrefabは原点において、子オブジェクトを相対的に作ればいいだけ。
つまりこんな感じ

FumiyaHrFumiyaHr

「どのようにWayspotを指定するのか」については、WayspotAnchorService のインスタンス生成時に指定する LocationService かな?
LocationInfo LastData; がそれっぽい。

struct LocationInfo {
    // fields

    readonly double Altitude;
    readonly LatLng Coordinates;
    readonly double HorizontalAccuracy;
    readonly double Timestamp;
    readonly double VerticalAccuracy;

    // methods

    bool Equals(LocationInfo other);
    override bool Equals(object obj);
    override int GetHashCode();

    LocationInfo(
        double latitude,
        double longitude,
        double altitude = double.NaN,
        double horizontalAccuracy = double.NaN,
        double verticalAccuracy = double.NaN,
        double timestamp = double.NaN
    );

    LocationInfo(LatLng coordinates);
    LocationInfo(UnityEngine.LocationInfo info);
    override string ToString();
    static bool operator != (LocationInfo l1, LocationInfo l2);
    static bool operator == (LocationInfo l1, LocationInfo l2);
};
FumiyaHrFumiyaHr

「Wayspot は指定できない」が正しいのかな。
ただし VPS Coverage API を使用して、ARコンテンツを表示する Wayspot の近くに移動したかどうかは判定できるという感じみたい(リンク)。

FumiyaHrFumiyaHr

位置合わせに失敗した状態

再度確認でSceneにMeshとコンテンツを配置したところ、失敗した状態と同じ状態の位置にコンテンツが表示されていた。
何かをミスったのか・・・

FumiyaHrFumiyaHr

Unity上で調整し直したところVPSでも位置合わせが成功した。
対象物の周りを一周してみたが大きくずれることはなさそう。

FumiyaHrFumiyaHr

今回使用したスキャンデータは12時半くらいにスキャンしたデータ。今回実験したのは11時くらいだったのが原因か12時半くらいに比べると認識がなかなかしなかった。
Wayspotを確認すると3時間単位でスキャンデータが蓄積されているみたい。

FumiyaHrFumiyaHr

Wayspotを確認すると3時間単位でスキャンデータが蓄積されているみたい。

アプリを動かした時の時間を使ってどのスキャンデータを使用するか特定しているのかな?この辺りはわからないな。
またスキャンデータが複数あった時に原点は毎回同じなのだろうか?同じではない場合は原点からの相対位置にコンテンツを出すと毎回ずれることになる気がする。

FumiyaHrFumiyaHr

2つの時間のMeshを比較してみた。

  • 青が11時くらいのMesh
  • 緑が12時半くらいのMesh
  • スフィアが(0,0,0)で各Meshの原点

今回位置合わせした対象物はそれぞれのMeshで原点からの相対位置が異なるみたい。
こうなるとどのように位置合わせをするのかわからない。。。

FumiyaHrFumiyaHr

2つの時間のMesh(上記の青と緑のメッシュ)のそれぞれにコンテンツを置いてみた。
Unity Editor上でコンテンツを置く時は、それぞれのMeshはズレているけど、位置合わせすると位置合わせされているみたい。

動作確認している時に、最初は片方のMeshのコンテンツのみ位置があって、もう片方のコンテンツは位置がずれていた気がした。その後、両方の位置があった感じがした。
このため両方のMeshの位置合わせができたということかもしれない。
やはりどのMeshで位置合わせをするかによって位置がズレるとなるとコンテンツ作成が難しい気がする。

FumiyaHrFumiyaHr

今の所わかってきた特徴

  • マップ
    • 各地にあるスポット、または自分でスキャン
    • GPSでマップが自動的に選択(マップを指定できない)
  • ローカライズ
    • 同じスポットに複数マップがあるといずれかでローカライズされる
  • アンカー
    • Lightshipのサーバに保存(Payloadで復元)
    • 位置合わせはマップの原点基準の相対位置で配置
FumiyaHrFumiyaHr

GPSのマップがダウンロードできるらしい。
ダウンロードできるのであれば周囲の環境をアプリに入れておく必要がなさそう。

XRKaigi 2022 - AR戦国時代の幕開け!~ARアプリ開発で押さえておきたい"ARデバイス"×"VPS"を解説~ より

FumiyaHrFumiyaHr

プライベートVPSロケーションのため省いていた VPS Coverage APIを使用する も確認していく

VPS Coverage APIでは、指定した地理的なロケーションの周辺にあるVPSカバレッジエリアを検出することができます。
ARDKのバックエンドによって、ユーザーがVPSを使用してローカライズできる地理的なカバレッジエリアのリストが提供されます。カバレッジエリアには、VPSが作動したWayspotによって動作するVPSローカライゼーションターゲット一式が含まれるため、ユーザーはエリア内でローカライズできるようになります。VPS Coverage APIを使用して、ターゲット名やターゲットの緯度と経度、ターゲットの「ヒント画像」など、ローカライゼーションターゲットの詳細を取得することができます。

FumiyaHrFumiyaHr

カバレッジエリアと Wayspot の違いがわかりづらいため用語を確認すると以下の通りらしい。

Lightship VPSのコンセプトと用語

Lightship VPSには、ユーザーがVPSを使用してローカライズし、永続するARコンテンツとインタラクトできる地理的な領域である VPS Coverage Areas (VPSカバレッジエリア) が提供されています。VPSカバレッジエリアは、ユーザーがローカライズできる、VPS有効なWayspotの周辺地域を示します。

カバレッジエリアの境界内には通常、1つまたは複数の VPS Localization Targets があります。VPS Localization Targets(ローカライゼーションターゲット)は、ユーザーがデバイスを使ってローカライズする現実世界の特徴的な地点です。現在、ローカライゼーションターゲットは、VPS有効のWayspotに対応しています。VPS Coverage APIは今後、Wayspot以外の現実世界のロケーションにも対応する予定です。

FumiyaHrFumiyaHr

VPS Coverage APIを使用する際の代表的なユーザーフロー によると VPS Coverage API を使用すると以下の情報が取れるらしい。

  • 周辺のカバレッジエリア
  • 特定のカバレッジエリアにあるロケーションターゲットのリスト
  • カバレッジエリアまでの距離
  • ターゲットの名前やGPS位置情報、「ヒント画像」の写真など、ターゲットの詳細
FumiyaHrFumiyaHr

https://lightship.dev/ja/docs/ardk/vps/using_the_coverage_api.html#discover-vps-coverage-areas-and-localization-targets

ILocationService インスタンスを作成し、 OnLocationUpdated イベントをサブスクライブして、デバイスの現在の位置情報の更新を取得します。Unityエディターで実行している場合は、 SpoofLocationService を使って、偽装した位置情報を設定することができます。

Unity エディターの時は、ILocationService を SpoofLocationService にキャストすると、偽装した位置情報でテストできるらしい。

using Niantic.ARDK.LocationService;

private ILocationService _locationService;

// Default is the Ferry Building in San Francisco
private LatLng _spoofLocation = new LatLng(37.79531921750984, -122.39360429639748);

// Start is called before the first frame update
void Awake()
{

    // ...

    _locationService = LocationServiceFactory.Create();

#if UNITY_EDITOR
    var spoofService = (SpoofLocationService) _locationService;

    // In editor, the specified spoof location will be used.
    spoofService.SetLocation(_spoofLocation);
#endif

    _locationService.LocationUpdated += OnLocationUpdated;
    _locationService.Start();
}
FumiyaHrFumiyaHr

VPS Coverage API から取得できる「ターゲットの名前やGPS位置情報、「ヒント画像」の写真など、ターゲットの詳細」と Geospatial API を組み合わせると面白そう

FumiyaHrFumiyaHr

「VPS Coverage APIリクエストを行うために ICoverageClient インスタンスを作成します。」にある

using Niantic.ARDK;
using Niantic.ARDK.VPSCoverage;
using Niantic.ARDK.VirtualStudio.VpsCoverage;

private RuntimeEnvironment _coverageClientRuntime = RuntimeEnvironment.Default;
private ICoverageClient _coverageClient;
private VpsCoverageResponses _mockResponses;

void Awake()
{

    // ...

    // The mockResponses object is a ScriptableObject containing the data that a Mock
    // implementation of the ICoverageClient will return. This is a required argument for using
    // the mock client on a mobile device. It is optional in the Unity Editor; the mock client
    // will simply use the data provided in the ARDK/VirtualStudio/VpsCoverage/VPS Coverage Responses.asset file.
    _coverageClient = CoverageClientFactory.Create(_coverageClientRuntime, _mockResponses);

    // ...
}

のコードにある CoverageClientFactory.Create の引数が気になったので調査。

API Reference のページはここ
https://lightship.dev/ja/docs/ardk/api-documentation/class_Niantic_ARDK_VPSCoverage_CoverageClientFactory.html#methods

第一引数の RuntimeEnvironment env で実行環境が指定できるらしく、API Reference を確認すると下記の実行環境があるらしい。Remote と Playback が気になる。

Value Detail(DeepL翻訳)
Default モバイル端末では、LiveDeviceの値に相当します。Unity EditorのVirtual StudioウィンドウでRemotingが有効になっている場合はRemoteの値、そうでない場合はMockの値と同等になります。
LiveDevice ARデータは「ライブ」(実際のカメラなどが使用されている)で取得され、ネットワークはライブサーバーに接続されます。
Remote ARデータやネットワークのレスポンスは、遠隔地から送られてきます。
Mock ARデータやネットワークのレスポンスは、完全にコードベースで、Unity Editorに収められています。
Playback ARデータはPlaybackデータセットから供給されます。このモードでは、ネットワークはサポートされていません。

第二引数の VpsCoverageResponses mockResponses には下記のような記載がある。env に Mock を指定した時のレスポンスを指定できるらしい。
ARDK の SDK に VPS Coverage Responses.asset が含まれるため、これを利用または参考にすればいいみたい。

ICoverageClientのモック実装が返すデータを含むScriptableObject。これはモバイルデバイスでモッククライアントを使用する際に必要な引数です。Unity Editorではオプションです。モッククライアントは、ARDK/VirtualStudio/VpsCoverage/VPS Coverage Responses.assetファイルで提供されるデータをそのまま使用します。(DeepL翻訳)

FumiyaHrFumiyaHr

VPSのカバレッジエリアとローカライゼーションターゲットを検出する に書いてある通りに実装するとこんな感じ。

public class VPSCoverageManager : MonoBehaviour
{
    [SerializeField]
    private LatLng _spoofLocation = new LatLng(37.79531921750984, -122.39360429639748);

    [SerializeField]
    private RuntimeEnvironment _coverageClientRuntime = RuntimeEnvironment.Default;

    [SerializeField]
    private VpsCoverageResponses _mockResponses;

    [SerializeField]
    [Range(0, 2000)]
    private int _queryRadius = 250;

    private ILocationService _locationService;
    private ICoverageClient _coverageClient;

    private void Awake()
    {
        _locationService = LocationServiceFactory.Create();
        Debug.Log("LocationService create.");

        _coverageClient = CoverageClientFactory.Create(_coverageClientRuntime, _mockResponses);

#if UNITY_EDITOR
        var spoofService = (SpoofLocationService)_locationService;
        spoofService.SetLocation(_spoofLocation);
#endif

        _locationService.LocationUpdated += OnLocationUpdated;
        _locationService.Start();
        Debug.Log("LocationService start.");
    }

    private void OnLocationUpdated(LocationUpdatedArgs args)
    {
        Debug.Log("OnLocationUpdated.");

        _locationService.LocationUpdated -= OnLocationUpdated;
        _coverageClient.RequestCoverageAreas(args.LocationInfo, _queryRadius, ProcessAreasResult);
    }

    private void ProcessAreasResult(CoverageAreasResult result)
    {
        Debug.Log("ProcessAreasResult.");

        if (result.Status != ResponseStatus.Success)
        {
            return;
        }

        foreach (var area in result.Areas)
        {
            Debug.Log(
		        "--------------\n" +
		        $"LocalizationTargetIdentifiers : {string.Join(", ", area.LocalizationTargetIdentifiers)}\n" +
                $"LocalizabilityQuality : {area.LocalizabilityQuality}\n" +
                $"Centroid : {area.Centroid}"
		    );
        }
    }
}

結果はこんな結果

LocalizationTargetIdentifiers : target1
LocalizabilityQuality : PRODUCTION
Centroid : [Latitude: 37.796106500212, Longitude: -122.394184]

LocalizationTargetIdentifiers : target2, target3
LocalizabilityQuality : EXPERIMENTAL
Centroid : [Latitude: 37.7953915002879, Longitude: -122.393594]

_mockResponses に指定した ARDK/VirtualStudio/VpsCoverage/VPS Coverage Responses.assetファイルで提供されるデータの値がレスポンスされている。
指定した範囲にある CoverageArea がまとめてレスポンスされるのかと思ったが、別々にレスポンスされるみたい。
試しに実際の緯度経度を指定すると各 LocalizationTargetIdentifiers 毎にレスポンスされていた。
ここはまとめてくるのではなく都度レスポンスされる前提の実装をする必要がありそう。

FumiyaHrFumiyaHr

VPSローカライゼーションターゲットの詳細情報を取得する の通りに実装するとこんな感じ。

public class VPSCoverageManager : MonoBehaviour
{
...
    [SerializeField]
    private RawImage _targetImage;
...
 private void ProcessAreasResult(CoverageAreasResult result)
    {
        Debug.Log("ProcessAreasResult.");

        if (result.Status != ResponseStatus.Success)
        {
            return;
        }

        var allTargets = new List<string>();
        foreach (var area in result.Areas)
        {
            Debug.Log(
                "--------------\n" +
                $"LocalizationTargetIdentifiers : {string.Join(", ", area.LocalizationTargetIdentifiers)}\n" +
                $"LocalizabilityQuality : {area.LocalizabilityQuality}\n" +
                $"Centroid : {area.Centroid}"
            );

            allTargets.AddRange(area.LocalizationTargetIdentifiers);
        }

        _coverageClient.RequestLocalizationTargets(allTargets.ToArray(), ProcessTargetsResult);
    }

    private void ProcessTargetsResult(LocalizationTargetsResult result)
    {
        Debug.Log("ProcessTargetsResult.");

        if (result.Status != ResponseStatus.Success)
        {
            return;
        }

        if (result.ActivationTargets.Count > 0)
        {
            var imageSize = _targetImage.rectTransform.sizeDelta;
            var firstActivationTarget = result.ActivationTargets.FirstOrDefault();

            Debug.Log(
                "--------------\n" +
                $"Key : {firstActivationTarget.Key}\n" +
                $"Identifier : {firstActivationTarget.Value.Identifier}\n" +
                $"Center: {firstActivationTarget.Value.Center}\n" +
                $"Name : {firstActivationTarget.Value.Name}\n" +
                $"ImageURL : {firstActivationTarget.Value.ImageURL}"
            );

            firstActivationTarget.Value.DownloadImage((int)imageSize.x, (int)imageSize.y, args => _targetImage.texture = args);
        }
    }
}

結果はこんな感じ。

Key : target1
Identifier : target1
Center: [Latitude: 37.7961, Longitude: -122.39425]
Name : acme bread
ImageURL : https://lh3.googleusercontent.com/sZl-o9TGNYX.......

LocalizabilityQuality が PRODUCTION の target1 のみ通知されるらしい。
DownloadImage メソッドも準備されているので簡単にヒント画像を利用できる。