これちのPost-it

技術ネタをペラペラと

Unity の Relay と Lobby について調べたメモ

How to 記事ではありません。自分が Unity の Relay と Lobby で調べたことをメモ代わりに。

Relay

Unity Documentation (Unity Relay)

プレイヤー間で安全性の高いピアツーピアの、リッスンサーバーを使用した UDP 通信を簡単に実現(原文ママ

  • Relay servers という Unity が提供してくれている(のかな?)専用サーバを proxy のように介すことでプレイヤー間での接続を実現
    • 全プレイヤーが接続可能なエンドポイントとして Relay server は振る舞い、全プレイヤーは同じ IP アドレス、ポートに接続する
    • つまり実際にプレイヤー同士が直接コネクションを張る訳ではない
    • NAT 越えやファイヤーウォールなど一般的にネットワーク接続において課題となる問題を気にせずに済む

  • 最大プレイヤー数は allocation を作成するときに指定可能

  • プレイヤーが タイムアウト したら Relay server はそのプレイヤーを切断する

    • デフォルト TTL は 10秒
    • ただしホストが一人の場合は TTL は 60秒
    • タイムアウトを防ぐために PING メッセージ を Relay server に送信できる
      • もし Relay with NGO を使っているなら network manager が自動でコネクションを維持する
      • もし Relay with UTP を使っているなら手動でコネクションを維持する必要がある
        • が、そもそも UTP ではなくよりハイレベルな API を提供する NGO を使うことが現時点では推奨されている
    • CLOSE message を送信しても接続解除が可能

Allocations

Allocations

ゲームセッションのためにゲームサーバーを予約する機能

  • リクエストを受信したらそれに最適なサーバーを探し割り当てる

    • サーバーが見つかったらそれを利用可能なサーバープールから削除し、その情報を呼び出し元へ送信する
    • deallocates するまで残り続ける
    • ゲームサーバーの検索は主に以下の情報でもって行われる
      • サーバーのリージョン
      • サーバーとゲームクライアントの通信品質
      • サーバーのビルドとビルド設定
  • アロケーションのリクエスト方法は3つ

    1. Game Server Hosting API を使用
    2. Game Server Hosting SDK を使用
    3. Unity Cloud Dashboard を使用

Relay with Netcode for GameObjects (NGO)

Relay with Netcode for GameObjects (NGO)

  • NGO と Relay を使ってプレイヤー間で通信を行うサンプル(あまり NGO の実装方法は触れてない)

Get started with NGO

  • NGO 側のホスト、サーバー、クライアント間での Rpc 呼び出し方法などのサンプルがまとまっている

これらを読めば Relay と NGO を使ったマルチプレイの簡単な実装ができる

ただし別ネットワーク間で通信する場合は Network Manager の Unity Transport の Allow remote connection を有効にしないといけなさそうだった

Lobby

Unity Documentation (Lobby)

プレイヤーがマルチプレイゲームにおいて他のプレイヤーを探して接続するための機能を提供するサービス

  • サービス例

    • 利用可能なゲームセッションのリストを表示し、プレイヤーがセッションを選択して参加
    • フレンドと join code を共有して自分のゲームセッションにフレンドが直接接続
    • Quick Join を使って利用可能なマッチを探して参加
    • プライベートロビーを作りゲーム内フレンドリストのフレンドを招待
    • ゲームサーバからロビーをホストし、サーバーセッションへのアクセスを管理及び制限
    • ゲームモードやタイプなどといった要求をもとににロビーをクエリ
  • プレイヤーの既存のゲームセッションへの再ジョインを可能に

  • 予期しない切断後のホストの移行を容易に

ロビーの作成

Create a lobby

いくつかのプロパティをセットして create コールを行うことでロビーが作成できる

  • プロパティ

    • ロビー名(必須)
    • 公開、非公開
    • 最大プレイヤー数
    • パスワード設定
    • カスタムロビーデータ
      • マップ ID やゲームモードなど任意のデータを含めることが可能
      • string か数値データならインデックス化してクエリ用にフィルタリングや並び替えが可能
    • ホストのプレイヤーデータ
      • 例えば名前、スキル、キャラクターなど
    • それぞれの設定方法はドキュメントを参照
  • ロビーのハートビート

    • デフォルトで30秒ホストからハートビートリクエストがないロビーは inactive になり以降はクエリの検索結果に出なくなり自動で削除される
      • このタイムアウト時間は変更が可能
      • inactivate されたロビーはロビーが更新、またはハートビートリクエストを送信することで再度 activate 可能

ビーサンプル

Unity Documentation (Game lobby sample)

Github

まだ試せてないが、規模の大きいサンプルのようで既存のプロジェクトに入れて試すというよりはリファレンスとする方が良いみたい。

Relay vs Lobby

Relay vs Lobby

Relay

  • サーバー/クライアント環境を使用してプレイヤーを P2P 方式で接続するネットワーキングソリューション
  • 専用ゲームサーバーや P2P 接続の複雑さを無視してマルチプレイヤーゲームのセッションを容易に実現

Lobby

  • ゲームセッション前のプレイヤーのグループ化と構成設定を容易にする機能
  • Lobby は P2P を使う制約はなく、また Relay を使わないでも実現可能
    • その場合は専用ゲームサーバー(DGS) を使って Lobby を使うことが可能

Unity の Netcode for GameObjects について調べたメモ

Netcode for GameObjects で自分が気になって調べたことをメモ代わりにまとめておく。

Netcode for GameObjects が何かとか、How to Use 系の記事は他にたくさんあるのでそのあたりは触れません。

参考にしたサイト

About Netcode for GameObjects | Unity Multiplayer Networking

Unity Netcode for GameObjectsを使ったオンラインマルチプレイゲーム開発Tips【Advent Calendar 12/23】|Colorful Palette

ClientRPC

ClientRpc | Unity Multiplayer Networking

  • サーバーは ClientRPC を実行することで(デフォルトで)全クライアントに対して命令を行える

  • Host から ClientRPC を実行する場合は Host 自身もクライアントとして振る舞うため自身の ClientRPC も実行される

    • 同じスタックで呼ばれるため無限ループにならないよう注意
  • 宣言するには [ClientRPC] アトリビュートをつけるのと、メソッド名に ClientRpc プレフィックスをつけないといけない

    • それと前提として NetworkBehavior 継承のクラスにする必要がある
  • 特定のクライアントにのみ send したい場合は ClientRpcSendParameters を使う。以下公式からコピペ。

private void DoSomethingServerSide(int clientId)
{
    // If isn't the Server/Host then we should early return here!
    if (!IsServer) return;


    // NOTE! In case you know a list of ClientId's ahead of time, that does not need change,
    // Then please consider caching this (as a member variable), to avoid Allocating Memory every time you run this function
    ClientRpcParams clientRpcParams = new ClientRpcParams
    {
        Send = new ClientRpcSendParams
        {
            TargetClientIds = new ulong[]{clientId}
        }
    };

    // Let's imagine that you need to compute a Random integer and want to send that to a client
    const int maxValue = 4;
    int randomInteger = Random.Range(0, maxValue);
    DoSomethingClientRpc(randomInteger, clientRpcParams);
}

[ClientRpc]
private void DoSomethingClientRpc(int randomInteger, ClientRpcParams clientRpcParams = default)
{
    if (IsOwner) return;

    // Run your client-side logic here!!
    Debug.LogFormat("GameObject: {0} has received a randomInteger with value: {1}", gameObject.name, randomInteger);
}

ServerRPC

ServerRpc | Unity Multiplayer Networking

  • クライアントからサーバに情報を送信する際実行する RPC

    • クライアントからしか送信できない&サーバーかホストしか受信できない
  • 宣言には [ServerRpc] アトリビュートと ServerRpc プレフィックスをメソッドにつける必要がある

    • NetworkBehavior 継承のクラスである必要がある
  • 自身が NetworkObject のオーナーではない場合は ServerRpc の実行が許可されない

    • しかし [ServerRpc(RequireOwnership = false)] を宣言すれば任意のクライアントが ServerRpc を実行できるっぽい(以下公式をコピペ)
[ServerRpc(RequireOwnership = false)]
public void MyGlobalServerRpc(ServerRpcParams serverRpcParams = default)
{
    var clientId = serverRpcParams.Receive.SenderClientId;
    if (NetworkManager.ConnectedClients.ContainsKey(clientId))
    {
        var client = NetworkManager.ConnectedClients[clientId];
        // Do things for this client
    }
}

public override void OnNetworkSpawn()
{
    MyGlobalServerRpc(); // serverRpcParams will be filled in automatically
}
  • ServerRpcParams.Receive.SenderClientId で Rpc を実行したクライアントの特定が可能
    • 他の引数がある場合は引数の最後に ServerRpcParams を宣言する必要がある
    • 以下の例ではクライアントが銃を打った時にクライアントの PlayerObject から lookWorldPosition に向けて Ray を打つサンプル(以下公式をコピペ)
[ServerRpc(RequireOwnership = false)]
public void PlayerShootGunServerRpc(Vector3 lookWorldPosition, ServerRpcParams serverRpcParams = default)
{
    var clientId = serverRpcParams.Receive.SenderClientId;
    if (NetworkManager.ConnectedClients.ContainsKey(clientId))
    {
        var client = NetworkManager.ConnectedClients[clientId];
        var castRay = new Ray(client.PlayerObject.transform.position, lookWorldPosition);
        RaycastHit rayCastHit;
        if (Physics.Raycast(castRay, out rayCastHit, 100.0f))
        {
            // Handle shooting something
        }
    }
}

なお、RequireOwnership を false にしない場合は上の例で Vector3 引数だけ設定すれば良いらしい。

[ServerRpc]
public void PlayerOwnerShootGunServerRpc(Vector3 lookWorldPosition)
{
    if (NetworkManager.ConnectedClients.ContainsKey(OwnerClientId))
    {
        var client = NetworkManager.ConnectedClients[OwnerClientId];
        var castRay = new Ray(client.PlayerObject.transform.position, lookWorldPosition);
        RaycastHit rayCastHit;
        if (Physics.Raycast(castRay, out rayCastHit, 100.0f))
        {
            // Handle shooting something
        }
    }
}
  • ホストで ServerRpc を実行したら自身の ServerRpc が実行されるので無限ループにならないよう注意が必要

NetworkVariable

NetworkVariable | Unity Multiplayer Networking

  • サーバとクライアント間で RPC など使わずに値の同期ができる仕組み

  • テンプレート <T> の型の値のコンテナのラッパーなので、その値は NetworkVariable.Value で取得する

  • Rpc との使い分け

    • RPC vs NetworkVariable
    • Rpc はその瞬間にのみ有用な一時的なイベントや情報を送受信したい時に使う
      • 後から参加したプレイヤーにそれまで送られた Rpc が自動で送られることは無い
    • NetworkVariable はずっと必要な永続的なステートや情報の同期に使う

最後に

Unity の Relay についても今度調査したい。まだベータ版。ベータ中は無料みたい。

OpenCV plus Unity の顔検出を iOS で行う

OpenCV plus Unity(無料) は PC(自分は Mac) 上ではインポートしてそのまま iOS 向けに Build すると Build 時にエラーが出たのでその対処方法

環境

参考にしたリンク

エラー1

Exception: IL2CPP error (no further information about what managed code was being converted is available)
Mono.Cecil.AssemblyResolutionException: Failed to resolve assembly: 'netstandard, Version=2.0.0.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51'
   at Unity.Cecil.Awesome.AssemblyResolver.ResolveInternal(AssemblyNameReference name) in /Users/bokken/build/output/unity/il2cpp/Unity.Cecil.Awesome/AssemblyResolver.cs:line 153
   at Unity.Cecil.Awesome.AssemblyResolver.Resolve(AssemblyNameReference name) in /Users/bokken/build/output/unity/il2cpp/Unity.Cecil.Awesome/AssemblyResolver.cs:line 62
   at Unity.Cecil.Awesome.WindowsRuntimeAwareMetadataResolver.Resolve(TypeReference type) in /Users/bokken/build/output/unity/il2cpp/Unity.Cecil.Awesome/WindowsRuntimeAwareMetadataResolver.cs:line 99

エラー全文

Exception: IL2CPP error (no further information about what managed code was being converted is available)
Mono.Cecil.AssemblyResolutionException: Failed to resolve assembly: 'netstandard, Version=2.0.0.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51'
   at Unity.Cecil.Awesome.AssemblyResolver.ResolveInternal(AssemblyNameReference name) in /Users/bokken/build/output/unity/il2cpp/Unity.Cecil.Awesome/AssemblyResolver.cs:line 153
   at Unity.Cecil.Awesome.AssemblyResolver.Resolve(AssemblyNameReference name) in /Users/bokken/build/output/unity/il2cpp/Unity.Cecil.Awesome/AssemblyResolver.cs:line 62
   at Unity.Cecil.Awesome.WindowsRuntimeAwareMetadataResolver.Resolve(TypeReference type) in /Users/bokken/build/output/unity/il2cpp/Unity.Cecil.Awesome/WindowsRuntimeAwareMetadataResolver.cs:line 99
   at Mono.Cecil.Mixin.CheckedResolve(TypeReference self)
   at Mono.Cecil.SignatureReader.ReadCustomAttributeEnum(TypeReference enum_type)
   at Mono.Cecil.SignatureReader.ReadCustomAttributeElement(TypeReference type)
   at Mono.Cecil.SignatureReader.ReadCustomAttributeNamedArgument(Collection`1& fields, Collection`1& properties)
   at Mono.Cecil.SignatureReader.ReadCustomAttributeNamedArguments(UInt16 count, Collection`1& fields, Collection`1& properties)
   at Mono.Cecil.SignatureReader.ReadSecurityAttribute()
   at Mono.Cecil.MetadataReader.ReadSecurityDeclarationSignature(SecurityDeclaration declaration)
   at Mono.Cecil.SecurityDeclaration.<Resolve>b__19_0(SecurityDeclaration declaration, MetadataReader reader)
   at Mono.Cecil.ModuleDefinition.Read[TItem,TRet](TItem item, Func`3 read)
   at Mono.Cecil.SecurityDeclaration.Resolve()
   at Mono.Cecil.SecurityDeclaration.get_SecurityAttributes()
   at Mono.Cecil.ImmediateModuleReader.ReadSecurityDeclarations(ISecurityDeclarationProvider provider)
   at Mono.Cecil.ImmediateModuleReader.ReadMethods(TypeDefinition type)
   at Mono.Cecil.ImmediateModuleReader.ReadType(TypeDefinition type)
   at Mono.Cecil.ImmediateModuleReader.ReadTypes(Collection`1 types)
   at Mono.Cecil.ImmediateModuleReader.ReadModule(ModuleDefinition module, Boolean resolve_attributes)
   at Mono.Cecil.ImmediateModuleReader.<ReadModule>b__2_0(ModuleDefinition module, MetadataReader reader)
   at Mono.Cecil.ModuleDefinition.Read[TItem,TRet](TItem item, Func`3 read)
   at Mono.Cecil.ImmediateModuleReader.ReadModule()
   at Mono.Cecil.ModuleReader.CreateModule(Image image, ReaderParameters parameters)
   at Mono.Cecil.ModuleDefinition.ReadModule(String fileName, ReaderParameters parameters)
   at Mono.Cecil.AssemblyDefinition.ReadAssembly(String fileName, ReaderParameters parameters)
   at Unity.Cecil.Awesome.AssemblyLoader.Load(String name) in /Users/bokken/build/output/unity/il2cpp/Unity.Cecil.Awesome/AssemblyLoader.cs:line 84
   at System.Linq.Enumerable.SelectArrayIterator`2.MoveNext()
   at System.Collections.Generic.HashSet`1.UnionWith(IEnumerable`1 other)
   at System.Collections.Generic.HashSet`1..ctor(IEnumerable`1 collection, IEqualityComparer`1 comparer)
   at Unity.IL2CPP.Common.AssemblyCollector.CollectAssembliesRecursive(IEnumerable`1 assemblies, IAssemblyLoader assemblyLoader, AssemblyDependenciesComponent assemblyDependencies) in /Users/bokken/build/output/unity/il2cpp/Unity.IL2CPP.Common/AssemblyCollector.cs:line 25
   at Unity.IL2CPP.Common.AssemblyCollector.CollectAssembliesToConvert(NPath[] assemblyPaths, IAssemblyLoader assemblyLoader, AssemblyDependenciesComponent assemblyDependencies) in /Users/bokken/build/output/unity/il2cpp/Unity.IL2CPP.Common/AssemblyCollector.cs:line 15
   at Unity.IL2CPP.AssemblyConversion.Phases.InitializePhase.Run(AssemblyConversionContext context) in /Users/bokken/build/output/unity/il2cpp/Unity.IL2CPP/AssemblyConversion/Phases/InitializePhase.cs:line 18
   at Unity.IL2CPP.AssemblyConversion.Classic.ClassicConverter.Run(AssemblyConversionContext context) in /Users/bokken/build/output/unity/il2cpp/Unity.IL2CPP/AssemblyConversion.Classic/ClassicConverter.cs:line 11
   at Unity.IL2CPP.AssemblyConversion.AssemblyConverter.ConvertAssemblies(AssemblyConversionInputData data, AssemblyConversionParameters parameters, AssemblyConversionInputDataForTopLevelAccess dataForTopLevel) in /Users/bokken/build/output/unity/il2cpp/Unity.IL2CPP/AssemblyConversion/AssemblyConverter.cs:line 21
UnityEditorInternal.Runner.RunProgram (UnityEditor.Utils.Program p, System.String exe, System.String args, System.String workingDirectory, UnityEditor.Scripting.Compilers.CompilerOutputParserBase parser) (at /Users/bokken/buildslave/unity/build/Editor/Mono/BuildPipeline/BuildUtils.cs:129)
UnityEditorInternal.Runner.RunManagedProgram (System.String exe, System.String args, System.String workingDirectory, UnityEditor.Scripting.Compilers.CompilerOutputParserBase parser, System.Action`1[T] setupStartInfo) (at /Users/bokken/buildslave/unity/build/Editor/Mono/BuildPipeline/BuildUtils.cs:65)
UnityEditorInternal.IL2CPPBuilder.RunIl2CppWithArguments (System.Collections.Generic.List`1[T] arguments, System.Action`1[T] setupStartInfo, System.String generatedCppOutputDirectory) (at /Users/bokken/buildslave/unity/build/Editor/Mono/BuildPipeline/Il2Cpp/IL2CPPUtils.cs:755)
UnityEditorInternal.IL2CPPBuilder.ConvertPlayerDlltoCpp (UnityEditor.Il2Cpp.Il2CppBuildPipelineData data, System.String outputDirectory, System.Boolean platformSupportsManagedDebugging) (at /Users/bokken/buildslave/unity/build/Editor/Mono/BuildPipeline/Il2Cpp/IL2CPPUtils.cs:727)
UnityEditorInternal.IL2CPPBuilder.Run () (at /Users/bokken/buildslave/unity/build/Editor/Mono/BuildPipeline/Il2Cpp/IL2CPPUtils.cs:560)
UnityEditorInternal.IL2CPPUtils.RunIl2Cpp (System.String tempFolder, System.String stagingAreaData, UnityEditorInternal.IIl2CppPlatformProvider platformProvider, System.Action`1[T] modifyOutputBeforeCompile, UnityEditor.RuntimeClassRegistry runtimeClassRegistry) (at /Users/bokken/buildslave/unity/build/Editor/Mono/BuildPipeline/Il2Cpp/IL2CPPUtils.cs:231)
UnityEditor.iOS.PostProcessiPhonePlayer.CrossCompileManagedDlls (UnityEditor.iOS.PostProcessiPhonePlayer+BuildSettings bs, UnityEditor.iOS.PostProcessiPhonePlayer+ProjectPaths paths, UnityEditor.AssemblyReferenceChecker checker, UnityEditor.RuntimeClassRegistry usedClassRegistry, UnityEditor.Build.Reporting.BuildReport buildReport) (at /Users/bokken/buildslave/unity/build/PlatformDependent/iPhonePlayer/Extensions/Common/BuildPostProcessor.cs:908)
UnityEditor.iOS.PostProcessiPhonePlayer.PostProcess (UnityEditor.iOS.PostProcessiPhonePlayer+BuildSettings bs, UnityEditor.iOS.PostProcessiPhonePlayer+ProjectPaths paths, UnityEditor.RuntimeClassRegistry usedClassRegistry, UnityEditor.Build.Reporting.BuildReport buildReport) (at /Users/bokken/buildslave/unity/build/PlatformDependent/iPhonePlayer/Extensions/Common/BuildPostProcessor.cs:718)
UnityEditor.iOS.PostProcessiPhonePlayer.PostProcess (UnityEditor.iOS.PostProcessorSettings postProcessorSettings, UnityEditor.Modules.BuildPostProcessArgs args) (at /Users/bokken/buildslave/unity/build/PlatformDependent/iPhonePlayer/Extensions/Common/BuildPostProcessor.cs:663)
UnityEditor.iOS.iOSBuildPostprocessor.PostProcess (UnityEditor.Modules.BuildPostProcessArgs args) (at /Users/bokken/buildslave/unity/build/PlatformDependent/iPhonePlayer/Extensions/Common/ExtensionModule.cs:45)
Rethrow as BuildFailedException: Exception of type 'UnityEditor.Build.BuildFailedException' was thrown.
UnityEditor.iOS.iOSBuildPostprocessor.PostProcess (UnityEditor.Modules.BuildPostProcessArgs args) (at /Users/bokken/buildslave/unity/build/PlatformDependent/iPhonePlayer/Extensions/Common/ExtensionModule.cs:49)
UnityEditor.Modules.DefaultBuildPostprocessor.PostProcess (UnityEditor.Modules.BuildPostProcessArgs args, UnityEditor.BuildProperties& outProperties) (at /Users/bokken/buildslave/unity/build/Editor/Mono/Modules/DefaultBuildPostprocessor.cs:29)
UnityEditor.PostprocessBuildPlayer.Postprocess (UnityEditor.BuildTargetGroup targetGroup, UnityEditor.BuildTarget target, System.String installPath, System.String companyName, System.String productName, System.Int32 width, System.Int32 height, UnityEditor.BuildOptions options, UnityEditor.RuntimeClassRegistry usedClassRegistry, UnityEditor.Build.Reporting.BuildReport report) (at /Users/bokken/buildslave/unity/build/Editor/Mono/BuildPipeline/PostprocessBuildPlayer.cs:337)
UnityEngine.GUIUtility:ProcessEvent(Int32, IntPtr, Boolean&) (at /Users/bokken/buildslave/unity/build/Modules/IMGUI/GUIUtility.cs:189)

対処方法1

Assets 直下に下記ファイル link.xml を置く。

<linker>
    <assembly fullname="netstandard" preserve="all"/>
</linker>

その他

CascadeClassifier haarCascade = new CascadeClassifier("Assets/OpenCV+Unity/Demo/Face_Detector/haarcascade_frontalface_default.xml");

で顔検出したい場合は Assets/StreamingAssets 下に haarcascade_frontalface_default.xml を置いて

CascadeClassifier haarCascade = new CascadeClassifier(Application.streamingAssetsPath + "/haarcascade_frontalface_default.xml");

で指定しれば iOS 上でも顔検出が可能。

Azure Spatial Anchor を触って ARCloud について考察する

目的

  1. Azure Spatial Anchor がどんなものなのか触ってみて理解する
  2. 今後 ARCloud のようにクラウド上で Map やそれに紐づく情報を管理するシステムを作ろうとした時 Azure Spatial Anchor がどんな感じに使えそうなのか掴む

ドキュメント

docs.microsoft.com

開発環境

事前知識

以下のリンク先の内容は開発前にざっと目を通しておくと良さそう。

1. Azure Spatial Anchors を使用する Unity HoloLens アプリを作成する

docs.microsoft.com

↑の通りに

  1. Spatial Anchors リソースを作成する
  2. Unity のサンプル プロジェクトをダウンロードして開く
  3. アカウント識別子とキーを構成する
  4. HoloLens Visual Studio プロジェクトをエクスポートする
  5. HoloLens アプリケーションをデプロイする

をやれば HoloLens にサンプルアプリがデプロイされる。

ちなみに、Visual Studio のユニバーサル Windows プラットフォーム開発のオプションの[USBデバイスの接続]にチェックを入れないと、USB 経由で HoloLens にアプリがデプロイできないので注意。 f:id:korechi:20191016183141p:plain

Android/iOS へのデプロイも同じ Scene でビルド対象プラットフォームを PlayerSettings で切り替えれば可能なのが驚き。

次は、別デバイスや別セッション間で Anchor を共有する。

2. セッションやデバイス間での Azure Spatial Anchors の共有

docs.microsoft.com

これも↑の通りやればよい。何なのかサマると

をやっている。

公式ドキュメントは何故か .NET Core 2.2 SDK 指定だが 3.0 でも問題なく動作した。

Web App(というか API) は Swagger UIで書かれている。

f:id:korechi:20191017000203p:plain:h400

試しに Anchor を登録した後にブラウザから https://<appname>.azurewebsites.net/api/anchors/last GET を叩くと

6a83ce25-2604-41bc-9199-caec66b27583

という string 値が返ってきた。これが Anchor を特定する Key である。

では次に Anchor が一定期間たっても消えないよう、メモリでなく DB に保存して例えば別の日でも Anchor が共有できるようにする。

3. Azure Cosmos DB を使って Azure Spatial Anchors を共有する

Azure Cosmos DB の概要 | Microsoft Docs

Azure Cosmos DB のキホンと使いドコロ

↑Azure Cosmos DB について参考になったリンク

docs.microsoft.com

↑の手順通り進める。

先程との違いは Azure Cosmos DB のインスタンスをたてて、Anchor をインメモリではなく DB に保存するようにしてる点。Web App も API のレイヤでは全く変わらない。

試しに Android 端末にデプロイし Anchor を追加した後 Azure ポータルから Cosmos DB の中を確認すると1行追加されている。

f:id:korechi:20191017131354p:plain

ここにある Anchor key は、

Azure Spatial Anchors についてよく寄せられる質問 | Microsoft Docs

アンカーが作成または特定されるときは、デバイス上で環境の画像が処理されて派生形式になります。 この派生形式が転送されて、サービスに格納されます。
透明性を提供するため、環境の画像と派生されたまばらな点の集まりを以下に示します。 点の集まりは、サービスに転送されて格納される環境の幾何学的表現を示します。 まばらな点の集まりの各点について、その点の視覚的特徴のハッシュが送信されて格納されます。 ハッシュは任意のピクセル データから派生されますが、そのピクセル データは含みません。

ということらしい。この key が Anchor そのものだったということか。

f:id:korechi:20191017132155p:plain

ここで一つ疑問が。

Anchor の共有はこのハッシュ値があれば可能だが、Anchor に紐付いて共有されるオブジェクト(サンプルだと黄色の Cube)は誰が持ってるんだろう?と思い調べたけどよく分からなかった。

簡単なオブジェクト(Cube)の position くらいならこの key に含まれているのかもしれない?ただそれだと共有できるオブジェクト数や種類に限界があるため、いろいろ共有したりリアルタイムに更新したいなら別 DB 等用意する必要がありそう。

MixedRealityToolkit の SharingWithUNET サンプルと比較

1年ほど前に MixedRealityToolkit のサンプルにあった(今もあるかは分からないが)UNet を使い複数 HoloLens 間で P2P 接続してクライアント・サーバに分かれてオブジェクトをリアルタイムに共有する SharingWithUNET を触ったことがある。

qiita.com

↑参考記事

それと今回の Azure App Service 経由での Anchor 共有をざっくり比較すると、

Pros Cons
SharingWithUNET 同一ネットワーク内でのP2P接続なのでリアルタイムでの同期が早い 別ネットワーク・端末間では共有できない
Azure Spatial Anchor 別端末・ネットワーク間でも共有できる
サーバ側に Anchor の情報が蓄積される
レイテンシがそれなりにありそう
オブジェクトをリアルタイムに共有するには別の仕組みを用意する必要がありそう

こんな印象。

まとめ

Azure Spatial Anchor の概要は理解できた。が、まだサンプル通りにやって動かせただけなので、いろいろなオブジェクトのリアルタイム共有やクラウド側でのデータ活用もやっていきたい。

少し勘違いしていたが HoloLens が持つような(wifi をキーとした?)Map をまるまる Azure にあげてそれを複数端末間で共有することではない。Anchor も特徴点の情報+αくらいの情報しか含まれていない。ARCore/ARKit には現時点で Map の Save/Load 機能はないので当然か。

Anchor での位置合わせは少し時間がかかるな、という印象を受けた。特徴点が多い場所を使ったり、特徴点マッチングアルゴリズムを自前で用意するのもアリかも?

ARCloud への応用例

また ARCloud のようなものを実現したいと考えた時、(何をどうクラウドで管理したいかはユースケースによりつつも)仮にいくつかの公園の Map をクラウドで管理し、特定の時間に特定の場所にアイテムを出現させたい、としたら

  • Map データは S3 or GCS に保存
  • 公園ID、中心 GPS 座標、Mapへのリンク、ゲームアイテムのプロパティ(リスト)、Anchor

あたりは最低限クラウドのデータベースに持たせる必要がありそうな気がする。

これに加えて、池や砂場がある等それぞれの公園が持つ特有のプロパティも保持れば、その公園の特徴にあったオブジェクトを表示させたり、も出来て楽しそう。(どうプロパティを集めるかはさておき)

このように Anchor では保持できない情報を別 DB 等で管理し、端末間での位置合わせは Azure Spatial Anchor を使うように分担するのが良さげかな、とざっくりと妄想してこの記事は終わり。

Nreal Developer Gathering に参加してきた

Nreal Developer Gathering@10/8渋谷に参加し、nreal 開発者のプレゼンを聞き実機デモを体験してきました。

ar-japan.connpass.com

個人的に nreal なかなか良かったので MR アプリ開発者目線で感じたことをまとめようかなと。書きなぐりですが。

(Nreal と nreal どっちが正しいか分かりませんが本記事では nreal に統一)

nreal 基本情報

https://www.nreal.ai

www.itmedia.co.jp

ここらへん読めば雰囲気掴めると思いますが、

  • 1199$の Developer Kit 版と499$の Consumer Kit 版がある
    • Dev 版は購入申請可能。Cons 版は 2020年早期より出荷予定
  • 軽い。88g
    • MagicLeap と同様にコンピューティングユニットが外出しされ、グラス部の重さを抑ている
    • Snapdragon845 以上の Android スマートフォンへUSB type-C?経由で接続することも可能
      • PC 接続も可能
  • SLAM, 6DoF, 平面検知, イメージトラッキング等が可能
  • マイクとスピーカーも搭載し将来的には音声コントロールにも対応予定
  • 対角52°の FoV
  • 日本の MR 市場を重視
    • KDDI とパートナーシップを組んでる

nreal 開発者のプレゼンで分かったこと

  • nreal の社員は現在120人
    • 45%は修士
    • 北京に HQ
  • スピーカーは DTS surround 対応
  • 用途は game/sns/education/production と特に制限なく想定
  • preorder 数は 4K で US>JP>CN の順に多い
  • 会社を立ち上げてから2年でリリース
  • 度つきレンズが装着可能(マグネット式)で、眼鏡を外して体験できた
  • 表示素子のマイクロ OLED は片目フル HD の120Hz駆動(ソニー製?
    • ほぼ半球形の曲面レンズorハーフミラーで前方に飛ばした光をレンズ部の曲面ハーフミラーで収束させて表示
  • CES2020 までは Dev 向け開発、CES2020 以降は Cons 向け開発にシフト
  • 大手町に日本支社を立ち上げ
  • 将来のロードマップにはハンドトラッキング
  • Android Native App が nreal で動作するらしい(詳細は不明
  • MESON 社、Psychic VR Lab 社は nreal のエバンジェリスト
    • MESON 社は nreal を使って街空間での AR コミュニケーションを開発、実証実験

nreal の所感

まず良かった点。

  • デザイン良し。軽いので日常使いできそう
    • デザインには相当力を入れているらしい
  • SLAM の安定感は Holo/ML1/ARKit/ARCore に比べると劣りブレが気になる
    • ただかけやすさ、つけ心地、値段等を鑑みると Holo/ML1 より全体として良い感じ
  • 視野角が Holo より広く、ML1 と同等程度の印象
    • ただし横長視野角なのでキャラクターを近くに表示させたり、とかは難しそう
  • Android 端末と接続できるので、GPS を活用したり他アプリと連携したりとコンテンツの幅を広げられそう
  • nreal でデモすれば「 AR グラスを日常的につけられる時代がきたら〜」の説得力が増しそう
  • コントローラー付きなのでインタラクティブなゲームも作れそう
    • ただしコントローラーは 3DoF なので用途は限られるかも

ここからうーんな点。

  • 現実がかなり見えづらい
    • 体感70%ほど外の情報が遮断されていた気がしたので nreal をつけて外を出歩くのは厳しそう
    • 現実がほぼほぼ見えないせいで AR オブジェクトが現実にある感(MR 感)が薄い
  • オクルージョン無いのが残念
  • Spatial Computing してる感が Holo/ML1 に比べて薄い、というか無い
    • イメージとしては SLAM つき Google Glass のような印象で、眼の前にディスプレイがあるなーという感じ(伝わるかな・・
    • 床や壁の上に何か出したり、という感じではなさそう。今まさに環境の認識まわり頑張っているところなのかも?
    • マップの保存等についても言及されてなかったけど、まだ実装されていないのかも?

という感じでした! ちょっとふわっとした感想が多いけど、少しでも nreal をかけた印象が伝われば。

Oculus Quest Mixed Reality Capture Tools セットアップ手順

(注意)現状では Oculus Quest のゲームを Mixed Realty Capture(以降 MRC と略)できないようです。自分で開発した MRC 対応アプリ、もしくは MRC に対応したアプリ(まだ0個)のみ撮影が可能です。

しかも途中で OBS Studio の使い方がわからずギブアップしてます・・すみません。何かわかれば追記していくつもりです。

ただ今後 MRC 対応 Quest アプリが増えてくるかもなので、現状できたところまでですが、公式のセットアップの日本語訳版ということでメモ。

参考リンク

developer.oculus.com

vrinside.jp

下準備

必要な機材

必要なソフトウェア

// フォルダ構成
Oculus_Quest_MRC_Tools_1.0
    |- MRCCameraCalibration_Quest
    |      |- MrcCameraCalibration.apk  // Oculus Quest にインストールする apk
    |- oculus-mrc_OBS
    |      |- data
    |      |- obs-plugin
    |- OVRCameraCalibration_PC  // PC 側で起動するカメラキャリブ用ファイルが入ってる
           |- OVRCameraCalibration.bat
           |- etc

必要な資材

  • Oculus_Quest_MRC_Tools_1.0/OVRCameraCalibration_PC/Assets/pattern.png を印刷
    • カメラのキャリブで使うためサイズや画質は変更しない方がいいとのこと(なので画像はここには貼りません)
    • ただし自分は画像をスマホ上に表示してそれを使いました。これでも問題なさそうです
  • Oculus Quest と Rift S の近接センサー(額の前あたりにあるやつ)を覆うテープ
    • キャリブ中テープを使って HMD の電源が落ちないように

カメラキャリブレーション

  1. Quest を adb コマンドが使える PC に接続
  2. adb install -r MrcCameraCalibration.apk
  3. Quest から MRC Camera Calibration Service を起動して IP アドレス(192.168.1.4)をメモ
  4. カメラを設置(今後カメラ位置は動かないように)
  5. Quest の近接センサーにテープを貼り電源がオフにならないようにする
  6. OVRCameraCalibration.bat 内の IP アドレスを VSCode 等で開き先ほど調べた Quest の IP アドレスに変更
  7. OVRCameraCalibration.bat を起動し Quest で起動しているアプリと接続が開始すると QuestMode というウインドウが開く
    • f:id:korechi:20190705012912p:plain:h200
  8. Calibrate Camera を選択
  9. 適切なカメラを選択し次へ
  10. 画質は 1920*1080 が推奨なのでそれで設定して次へ
  11. Start Intrinsic Calibration を選択
    • この時点でカメラ映像が見えてない場合はどこかで設定を間違えている可能性があります
  12. マーカーを画面上に表示されているボックスに合わせる作業を20回やりカメラの歪みを補正
    • 認識がうまくいくと枠内が虹色になります
    • 最後に出てきた値が1以下なら良いみたい
  13. static camera を選択して次へ
  14. 今度は Oculus Rift S のセンサーにシールを貼り、矩形内に入れて A ボタンを押して進める f:id:korechi:20190706124321p:plain
  15. Oculus Touch コントローラとバーチャル Touch コントローラが重なって表示されていたら成功なので mrc.xml という名前で保存

キャリブレーションファイルを Quest へ転送

Mixed Reality Capture を行いたいアプリへ先ほど作った mrc.xml をコピーします。

以下は例として Beat Saber 1. android file transfer や adb コマンドを使用して mrc.xml/Android/data/com.{yourorgname}.{yourappname}/files/ にコピー + adb なら adb push <path of the saved mrc.xml> /sdcard/Android/data/com.{yourorgname}.{yourappname}/files/ + android file transfer なら↑の場所にドラッグ&ドロップでコピー

OBS を使ってシーンを複合

  1. Oculus_Quest_MRC_Tools_1.0\oculus-mrc_OBS の下にある data, obs-plugins を obs-studio のルートにコピー
    • 例) C:\Program Files\obs-studio
  2. OBS Studio を起動
    • OBS Studio の使い方はここ

ここからは OBS Studio の使い方がよくわからなかったため、公式ドキュメントを読んで進めてください。

とりあえずここまで。中途半端ですみませんがギブアップ・・

Unity で地図が表示可能な mapbox を触ってみた(初級編)

はじめに

XR+位置情報なゲームを作りたいなーと思い Unity 向けにパッケージが公開されてる mapbox を触ってみました。

公式ドキュメントはこちら

docs.mapbox.com

今回は mapbox SDK を使って地図をUnity 上で出してみることを実現したいと思います。こんな感じです。

f:id:korechi:20190521171841p:plain

環境

Maps SDK for Unity v2.0.0

Unity 2019.1.2f1

使い方

Unity でのセットアップ方法は↑に書いてあるため割愛。

基本的には unitypackage をインポートし Mapbox->Prefabs->Map プレハブを hierarchy 上に配置後 Play すれば地図が表示されます。

f:id:korechi:20190521101650p:plain

この Map プレハブについてる Abstract Map(Script) から地図のいろんなパラメータの調整や、レイヤの追加等が可能です。

ただし↑の公式ドキュメントは SDK バージョンが1.4.0対応っぽく少し古いため、一部 API 仕様が微妙に違うので注意。(2019/5/21現在)

Scene の中心に9マスに分割された地図が表示されています。しかしこのままでは画面をドラッグしてカメラ視点を変えたり、緯度経度を自由に指定する等できません。

SDKMapbox->Examples にあるサンプルを参考にいろんな操作ができることを確かめてみてください。

では実際にいろいろ触ってみようと思います。

その前に

Unity 2019 で SDK をインポートしたら下記のエラーをはいたので、

Library/PackageCache/com.unity.xr.arfoundation@1.0.0-preview.22/Runtime/AR/ARSessionOrigin.cs(3,19): error CS0234: The type or namespace name 'SpatialTracking' does not exist in the namespace 'UnityEngine' (are you missing an assembly reference?)

https://github.com/Unity-Technologies/arfoundation-samples/issues/79#issuecomment-450263059

どうもここが参考になりそうとのことで

"com.unity.xr.legacyinputhelpers": "1.0.0"

の設定を manifest.json の一番下に追記することで暫定対応。

理由を詳しく追えていませんが、どうも AR Foundation(ARKit/ARCore のマルチプラットフォーム AR 環境)関連の問題のようなので、Unity のアップデートでいずれ対応されると思います。

AR Foundation についてはこちらが参考になりました。

tsubakit1.hateblo.jp

それでは気を取り直して

特定の緯度経度にプレハブを配置

大崎駅付近 (35.619707, 139.7283495) に適当なプレハブを表示したいと思います。

f:id:korechi:20190521170006p:plain:h400

  1. GENERAL タブ内の Location を 35.619707, 139.7283495
  2. MAP LAYERSData SourceMapbox Streets With Building Ids
  3. POINTS OF INTEREST 内の Add Layer をクリックしてレイヤーを追加
  4. 追加されたレイヤを選択して Prefab を適当なものに設定
  5. Prefab LocationsFind byAddress Or Lat Lon にして Add Location をクリック
  6. Location 035.619707, 139.7283495 を指定
  7. 実行

f:id:korechi:20190521170603p:plain

大崎駅にピンをたたせることができました!

これを応用すればポケストップを表示する、なんてこともできそうですね。

建物を立体的に

3D地図っぽい感じに建物を立体的にのばしてみたいと思います。

f:id:korechi:20190521171402p:plain:h200

  1. MAP LAYERSData SourceMapbox Streets With Building Ids に(さっきのまま)
  2. FEATURESAdd Feature をクリックして Map Features が追加
  3. Data Layerbuilding
  4. 実行

f:id:korechi:20190521171457p:plain

デフォルトの設定でここまで表示できるのは良いですね。

建物の外観は TexturingStyle Type から変更可能です。例えば Style TypeColor にすると

f:id:korechi:20190521171811p:plain:h200

f:id:korechi:20190521171841p:plain

こんな風に変えることができます。

Mapbox の簡単な使い方はおさらいできたので、今回はここまで。次はもう少し詳しくドキュメントを読んで遊んでみたいと思います。