これちのPost-it

技術ネタをペラペラ貼っていくぞ!

WindowsでARKitRemoteを利用する時の注意

ARKitRemoteは、iOS端末上にARKitRemoteアプリがインストールされていれば基本Windowsからでも利用できます。
ただし、iTunesWindowsマシンに入っている必要があります。

docs.unity3d.com

ここの

iOSバイス (OS XiTUnes がインストールされた Windows とUSB を通して繋がっている iPhoneiPadiPod touchApple TV)

という部分ですね。

ARKit を Unity エディタ上で動作確認出来る、ARKitRemoteについて調査した

ARKitRemoteとは

Unity エディタ上で ARKit アプリの動作確認を行うための、iOS 端末上で動作するアプリです。

背景

本来、Unityで開発しているアプリを iOS 端末上で動作確認するためには、いちいち Unity のアプリを Xcode 用にビルドし、それを Xcode 上でビルドして iOS 端末にインストールする必要があります。
それはめんどくさいです。Unity エディタ上だけで動作確認が出来れば非常に便利ですよね。
しかし、ARKit の特性上 iOS 端末からのカメラ映像やセンサーの値が必要です。
それらを iOS 端末から Unity エディタに送信して、Unity 側でよしなに受け取った値をもとに動作をエミュレートさせよう、というのが ARKitRemote シーンの役割です。

動作するアプリ

なので、まずは大きく2つのアプリが動作することになります。

  1. iOS 上で動作する、カメラ映像などを Unity エディタに送信するアプリ
    • ARKitRemoteが動作
    • UnityARKitPlugin -> ARKitRemote -> ARKitRemote.Unityのシーンそのもの
    • 以降リモートアプリと呼ぶ
  2. Unity エディタ上で動作するアプリ
    • 本来作成したいアプリ
    • 以降Myアプリと呼ぶ

動作環境

  • Unity 2017.2.0f3
  • iPhone6s plus (iOS11.2)
  • Xcode 9.2

使い方

まずUnityRemoteKitの使い方は以下のサイトが参考になるので、読んで使ってみてください。
lilea.net
本記事では、ARKitRemote(と、Myアプリに何が必要か)について調査したいと思います。

ARKitRemote シーン

まず中のシーンを開いてみますと以下のようになっており、Main CameraとDirectional LightしかHierarchyにないことが分かると思います。
f:id:korechi:20171205175415p:plain
そして、この Main Camera にアタッチされている、ConnectToEditor.csUnityRemoteVideo.csが ARKitRemote シーンのみに存在していることが他サンプルシーンと比較すると分かります。
(UnityARVideo.cs は他の ARKit サンプルシーンでも使われているスクリプトです)
そのため、上記の2スクリプトを見ていきます。

ConnectToEditor.cs

大まかに言うとこのクラスは、リモートデバイスでの変化(フレームの更新やAnchorの更新)のタイミングのたびに情報を Myアプリ(Editor上で本来動作してる)に送信するクラスです。

PlayerConnection playerConnection;

void Start()
{
        Debug.Log("STARTING ConnectToEditor");
        editorID = -1;
        playerConnection = PlayerConnection.instance;
        playerConnection.RegisterConnection(EditorConnected);
        playerConnection.RegisterDisconnection(EditorDisconnected);
        playerConnection.Register(ConnectionMessageIds.fromEditorARKitSessionMsgId, HandleEditorMessage);
        m_session = null;
}

Start() 時に PlayerConnection インスタンスを生成しています。
これはエディタ、プレイヤー間の接続を扱うクラスです。 この接続はプロファイラとプレイヤーを接続することで確立されます。
インスタンス生成後、接続開始時・終了時のコールバックを設定し、大事なのはその後の処理。
playerConnection.Register(ConnectionMessageIds.fromEditorARKitSessionMsgId, HandleEditorMessage);の第1引数にはConnectionMessageIds.fromEditorARKitSessionMsgIdというのが含まれており、これはARKitRemote.unityと同じ階層にあるConnectionMessageIds.csにIDが設定されているのが確認できます。
このIDは、当然Myアプリ側から同じ値をリモートアプリに接続要求する際に送信されます。(詳しくは、ARKitRemoteConnection.cs参照)
第2引数には、コールバック関数が指定されるため、メッセージが届くたびに HandleEditorMessage () が呼ばれます。

void HandleEditorMessage(MessageEventArgs mea)
{
        serializableFromEditorMessage sfem = mea.data.Deserialize<serializableFromEditorMessage>();
        if (sfem != null && sfem.subMessageId == SubMessageIds.editorInitARKit) {
            InitializeARKit ( sfem.arkitConfigMsg );
        }
}

HandleEditorMessage 内では、メッセージをデシリアライズし、初期化関数が呼ばれます。

UnityARSessionNativeInterface m_session;

void InitializeARKit(serializableARKitInit sai)
{
        #if !UNITY_EDITOR

        //get the config and runoption from editor and use them to initialize arkit on device
        Application.targetFrameRate = 60;
        m_session = UnityARSessionNativeInterface.GetARSessionNativeInterface();
        ARKitWorldTrackingSessionConfiguration config = sai.config;
        UnityARSessionRunOption runOptions = sai.runOption;
        m_session.RunWithConfigAndOptions(config, runOptions);

        UnityARSessionNativeInterface.ARFrameUpdatedEvent += ARFrameUpdated;
        UnityARSessionNativeInterface.ARAnchorAddedEvent += ARAnchorAdded;
        UnityARSessionNativeInterface.ARAnchorUpdatedEvent += ARAnchorUpdated;
        UnityARSessionNativeInterface.ARAnchorRemovedEvent += ARAnchorRemoved;

        #endif
}

前半ではARKitに関する初期化を行い、後半ではフレームの更新や平面の検知・更新・削除の際のイベントに関数を追加しています。UnityARSessionNativeInterfaceクラスの中身の一部は以下のようになっています。

// UnityARSessionNativeInterface.cs

public class UnityARSessionNativeInterface {
//      public delegate void ARFrameUpdate(UnityARMatrix4x4 cameraPos, UnityARMatrix4x4 projection);
//        public static event ARFrameUpdate ARFrameUpdatedEvent;
    
    // Plane Anchors
    public delegate void ARFrameUpdate(UnityARCamera camera);
    public static event ARFrameUpdate ARFrameUpdatedEvent;

ARKitでは水平面を自動で認識しそこにARAnchorを設置してくれるようになっています。
Anchors 以外のイベント(例えばARFaceAnchorAdded)もとれるみたいですが、今回は使いません。(FaceID関係かな?)
あとは、イベントが発生したタイミングで MyアプリにplayerConnection.Send()を使って送信しています。

public void ARFrameUpdated(UnityARCamera camera)
{
    serializableUnityARCamera serARCamera = camera;
    SendToEditor(ConnectionMessageIds.updateCameraFrameMsgId, serARCamera);
}

public void ARAnchorAdded(ARPlaneAnchor planeAnchor)
{
    serializableUnityARPlaneAnchor serPlaneAnchor = planeAnchor;
    SendToEditor (ConnectionMessageIds.addPlaneAnchorMsgeId, serPlaneAnchor);
}

public void SendToEditor(System.Guid msgId, object serializableObject)
{
    byte[] arrayToSend = serializableObject.SerializeToByteArray ();
    SendToEditor (msgId, arrayToSend);
}

public void SendToEditor(System.Guid msgId, byte[] data)
{
    if (playerConnection.isConnected)
    {
        playerConnection.Send(msgId, data);
    }
}

送っている情報は、ARFrameUpdated()では serializableUnityARCamera のインスタンス、ARAnchorAdded()(と書いていないがARAnchorUpdated()、ARAnchorRemoved())ではserializableUnityARPlaneAnchor のインスタンスが送られる。何が送られているのかちょっと覗いてみると

// ARKitRemote -> SerializableObjects.cs

    [Serializable]  
    public class serializableUnityARCamera
    {
        public serializableUnityARMatrix4x4 worldTransform;
        public serializableUnityARMatrix4x4 projectionMatrix;
        public ARTrackingState trackingState;
        public ARTrackingStateReason trackingReason;
        public UnityVideoParams videoParams;
        public serializableUnityARLightData lightData;
        public serializablePointCloud pointCloud;
        public serializableUnityARMatrix4x4 displayTransform;
 [Serializable]  
    public class serializableUnityARPlaneAnchor
    {
        public serializableUnityARMatrix4x4 worldTransform;
        public SerializableVector4 center;
        public SerializableVector4 extent;
        public ARPlaneAnchorAlignment planeAlignment;
        public byte[] identifierStr;

serializableUnityARMatrix4x4というのはMatrix4x4クラスのようなものです。
全部の変数の型を調べてはいませんが、名前からなんとなく送られているデータを察することはできます。
pointCloud 変数には特徴点が含まれているのでしょうね

UnityRemoteVideo.cs

    public ConnectToEditor connectToEditor;
    private UnityARSessionNativeInterface m_Session;

    public void OnPreRender()
    {
        ARTextureHandles handles = m_Session.GetARVideoTextureHandles();
        if (handles.textureY == System.IntPtr.Zero || handles.textureCbCr == System.IntPtr.Zero)
        {
            return;
        }

        if (!bTexturesInitialized)
            return;
        
        currentFrameIndex = (currentFrameIndex + 1) % 2;

        Resolution currentResolution = Screen.currentResolution;

        m_Session.SetCapturePixelData (true, PinByteArray(ref m_pinnedYArray,YByteArrayForFrame(currentFrameIndex)), PinByteArray(ref m_pinnedUVArray,UVByteArrayForFrame(currentFrameIndex)));

        connectToEditor.SendToEditor (ConnectionMessageIds.screenCaptureYMsgId, YByteArrayForFrame(1-currentFrameIndex));
        connectToEditor.SendToEditor (ConnectionMessageIds.screenCaptureUVMsgId, UVByteArrayForFrame(1-currentFrameIndex));         
    }

UnityRemoteVideo.csクラス内では、OnPreRender() が呼ばれており、これはカメラがシーンのレンダリングを開始する前に呼び出されます。関数の実行順はここを参考。
最終的に、connectToEditor.SendToEditor() でbyte[] を送信しています。
詳しくないですが、ここで送信されている Y やら UV は、色空間を表すものらしいので、おそらくこれがビデオ映像の一部ではないかと思われます。

Unity エディタ上で動作するアプリ

ふぅ長くなりましたね。次は、Myアプリ上での処理について見ていきましょう。
今回使用するのはUnityARKitPlugin -> ARKitRemote -> EditorTestScene.unityです。
まずは、このシーン内で他の ARKit サンプルシーンと何が違うか見てみましょう。
f:id:korechi:20171205195956p:plain
ARKitRemoteConnection というプレハブと、HitCubeにEditorHitTest.csがアタッチされているのが分かると思います。それぞれ見ていきましょう。

ARKitRemoteConnection.cs

ARKitRemoteConnection プレハブは、ARKitRemoteConnection.cs がアタッチされているだけのオブジェクトです。
まずは、Start() の中の一部を見てみましょう。

// Start(): 
    editorConnection = EditorConnection.instance;
    editorConnection.Initialize ();
    editorConnection.RegisterConnection (PlayerConnected);
    editorConnection.RegisterDisconnection (PlayerDisconnected);

    editorConnection.Register (ConnectionMessageIds.updateCameraFrameMsgId, UpdateCameraFrame);
    editorConnection.Register (ConnectionMessageIds.addPlaneAnchorMsgeId, AddPlaneAnchor);
    editorConnection.Register (ConnectionMessageIds.updatePlaneAnchorMsgeId, UpdatePlaneAnchor);
    editorConnection.Register (ConnectionMessageIds.removePlaneAnchorMsgeId, RemovePlaneAnchor);
    editorConnection.Register (ConnectionMessageIds.screenCaptureYMsgId, ReceiveRemoteScreenYTex);
    editorConnection.Register (ConnectionMessageIds.screenCaptureUVMsgId, ReceiveRemoteScreenUVTex);

EditorConnection のインスタンスを取得し、コールバック関数を複数設定しています。
EditorConnection はEditor側からプレイヤー側に接続要求をするクラスです。
AddPlaneAnchor() を見てみましょう。

void AddPlaneAnchor(MessageEventArgs mea)
{
    serializableUnityARPlaneAnchor serPlaneAnchor = mea.data.Deserialize<serializableUnityARPlaneAnchor> ();

    ARPlaneAnchor arPlaneAnchor = serPlaneAnchor;
    UnityARSessionNativeInterface.RunAddAnchorCallbacks (arPlaneAnchor);
}

メッセージをデシリアライズし、それをUnityARSessionNativeInterfaceのコールバック関数に登録しています。
こうすることで、カメラフレームの更新があるたびにそれがそのままUnityARSessionNativeInterfaceに送られます

void ReceiveRemoteScreenYTex(MessageEventArgs mea)
{
    if (!bTexturesInitialized)
        return;
    remoteScreenYTex.LoadRawTextureData(mea.data);
    remoteScreenYTex.Apply ();
    UnityARVideo arVideo = Camera.main.GetComponent<UnityARVideo>();
    if (arVideo) {
        arVideo.SetYTexure(remoteScreenYTex);
    }

}

こっちはテクスチャの方で、最後のarVideo.SetYTexure(remoteScreenYTex);いかにもリモートアプリから送られてきたテクスチャを Editor 上に表示させているっぽいですね。(詳しくは分かりませんがおそらくそう)
と、まぁ大まかに、
- Editor側からプレイヤー側に接続要求 - リモートアプリから受信した情報をUnityARSessionNativeInterfaceに送る

でいいのかな?(ちょっと自信ない)

EditorHitTest.cs

こっちは非常に単純です。
Editor上で疑似タップ操作を行えるように、Input.GetMouseButtonDown を Update 関数の中で呼んでいるだけです。

#if UNITY_EDITOR   //we will only use this script on the editor side, though there is nothing that would prevent it from working on device
void Update () {
    if (Input.GetMouseButtonDown (0)) {
        Ray ray = Camera.main.ScreenPointToRay (Input.mousePosition);
        RaycastHit hit;

        //we'll try to hit one of the plane collider gameobjects that were generated by the plugin
        //effectively similar to calling HitTest with ARHitTestResultType.ARHitTestResultTypeExistingPlaneUsingExtent
        if (Physics.Raycast (ray, out hit, maxRayDistance, collisionLayerMask)) {
            //we're going to get the position from the contact point
            m_HitTransform.position = hit.point;
            Debug.Log (string.Format ("x:{0:0.######} y:{1:0.######} z:{2:0.######}", m_HitTransform.position.x, m_HitTransform.position.y, m_HitTransform.position.z));

            //and the rotation from the transform of the plane collider
            m_HitTransform.rotation = hit.transform.rotation;

つまり、この関数がないと Editor 上でタップができなくなるので、ただ眺めるだけのアプリになります。笑

自分のアプリをEditor上で動作確認するためには

  1. ARKitRemoteConnection プレハブをヒエラルキーに配置
    • これだけでも見た目は確認可能
  2. EditorHitTest.cs のような Input.GetMouseButtonDown (0) が書かれたスクリプトをどこかに記述する

HoloLens の Spatial Understanding について調べてみた

参考URL

github.com

Spatial Understanding

Spatial mapping のメッシュ上にオブジェクトを置くとき、床・天井・壁の認識が必要となる。加えて、ホログラフィックオブジェクトの最も望ましい物理的な位置を決定するために、配置制約をもとに最適化することも必要となる。
- これち:この配置制約というのはおそらくSpatialMappingで取得したメッシュ内で、という制約
この課題を解決するため room solver を開発した。これらのコア技術は HoloToolkit.SpatialUnderstanding ライブラリ内に存在しこれを使うことで以下のことが可能となる。
1. 壁の空いたスペースを探す 2. 天井にオブジェクトを配置 3. キャラクターが座ることのできる場所の特定 4. その他の空間クエリ
これらのコードはソースコード内にあるためカスタマイズが可能である。また、C++コードは UWP の dll や Unity 内のプレハブにラップされている。

Ray Casting

部屋のスキャンが進むと床・天井・壁のラベルが生成される。PlayspaceRaycast関数はRayを飛ばし何かの表面にあたったら、RaycastResultクラスにそのあたった場所の情報をのせて知らせてくれる。

struct RaycastResult
{
    enum SurfaceTypes
    {
        Invalid,    // No intersection
        Other,
        Floor,
        FloorLike,  // Not part of the floor topology, 
                    //  but close to the floor and looks like the floor
        Platform,   // Horizontal platform between the ground and 
                    //  the ceiling
        Ceiling,
        WallExternal,
        WallLike,   // Not part of the external wall surface, 
                    //  but vertical surface that looks like a 
                    //  wall structure
    };https://camo.githubusercontent.com/badbca8d962fe99d35fc861aade93fbb8dcc9072/68747470733a2f2f617a3833353932372e766f2e6d7365636e642e6e65742f73697465732f6d697865642d7265616c6974792f5265736f75726365732f696d616765732f726179636173742d726573756c742e6a7067
    SurfaceTypes SurfaceType;
    float SurfaceArea;  // Zero if unknown 
                        //  (i.e. if not part of the topology analysis)
    DirectX::XMFLOAT3 IntersectPoint;
    DirectX::XMFLOAT3 IntersectNormal;
};

Unityサンプルでは、カーソルがRayを飛ばし、Unityコライダーに衝突後、understandingモジュールを介し、UIエレメントに表示される。
f:id:korechi:20171118081440j:plain
SpaceVisualizer.csに使っているクエリなどが書かれているため、参考にするとよい。

Shape Queries

dll内のShapeAnalyzer_Wにてユーザが定義したカスタムshapeとのマッチが行われる。Unityサンプルではサンプルのshapeを定義し、結果をアプリ内のクエリメニューで表示している。このカスタムshapeは水平面でしか機能しないことに注意すべき。
例えばソファーは、平らな座面と平らな頂部によって決定される。以下はUnityで使われるクエリ例である。

shapeComponents = new List<ShapeComponent>()
{
    new ShapeComponent(
        new List<ShapeComponentConstraint>()
        {
            ShapeComponentConstraint.Create_SurfaceHeight_Between(0.2f, 0.6f),
            ShapeComponentConstraint.Create_SurfaceCount_Min(1),
            ShapeComponentConstraint.Create_SurfaceArea_Min(0.035f),
        }
    ),
};
AddShape("Sittable", shapeComponents);
shapeConstraints = new List<ShapeConstraint>()
{
    ShapeConstraint.Create_RectanglesSameLength(0, 1, 0.6f),
    ShapeConstraint.Create_RectanglesParallel(0, 1),
    ShapeConstraint.Create_RectanglesAligned(0, 1, 0.3f),
    ShapeConstraint.Create_AtBackOf(1, 0),
};

f:id:korechi:20171118083035p:plain
SpatialUnderstandingDll.csを読めばより詳しいことがわかる。

Object Placement Solver

object placement solver はオブジェクトルールと制約をもとに、オブジェクトを配置する理想的な位置を探すことができる。オブジェクトのクエリはSolver_RemoveObjectで削除されるまで持続する。
オブジェクト配置クエリは3つのパートからなる。
1. placement type 2. ルールのリスト 3. 制約のリスト これらのクエリを回すためのAPIは以下のようになる。

public static int Solver_PlaceObject(
            [In] string objectName,
            [In] IntPtr placementDefinition,        // ObjectPlacementDefinition
            [In] int placementRuleCount,
            [In] IntPtr placementRules,             // ObjectPlacementRule
            [In] int constraintCount,
            [In] IntPtr placementConstraints,       // ObjectPlacementConstraint
            [Out] IntPtr placementResult)

placement typeは以下のenumで定義される。

public enum PlacementType
            {
                Place_OnFloor,
                Place_OnWall,
                Place_OnCeiling,
                Place_OnShape,
                Place_OnEdge,
                Place_OnFloorAndCeiling,
                Place_RandomInAir,
                Place_InMidAir,
                Place_UnderFurnitureEdge,
            };

ObjectPlacementDefinition構造体はこれらの定義を作成するのを手助けする関数を持っており、以下のように使用することが可能である。

 public static ObjectPlacementDefinition Create_OnFloor(Vector3 halfDims)

また、ルール、制約のリスト作成にもヘルパークラスが存在し以下のように使用することが可能である。

public static ObjectPlacementRule Create_AwayFromPosition(
    Vector3 position, float minDistance)
public static ObjectPlacementConstraint Create_NearPoint(
    Vector3 position, float minDistance = 0.0f, float maxDistance = 0.0f)

f:id:korechi:20171118103804p:plain
上画像のように床の上にオブジェクトを置きたい場合のサンプルコードは以下のように書くことができる。

List<ObjectPlacementRule> rules = 
    new List<ObjectPlacementRule>() {
        ObjectPlacementRule.Create_AwayFromOtherObjects(1.0f),
    };

List<ObjectPlacementConstraint> constraints = 
    new List<ObjectPlacementConstraint> {
        ObjectPlacementConstraint.Create_NearCenter(),
    };

Solver_PlaceObject(
    “MyCustomObject”,
    new ObjectPlacementDefinition.Create_OnEdge(
        new Vector3(0.25f, 0.25f, 0.25f), 
        new Vector3(0.25f, 0.25f, 0.25f)),
    rules.Count,
    UnderstandingDLL.PinObject(rules.ToArray()),
    constraints.Count,
    UnderstandingDLL.PinObject(constraints.ToArray()),
    UnderstandingDLL.GetStaticObjectPlacementResultPtr());

成功したら、ObjectPlacementResult構造体に、置く場所・次元・角度が返却される。
LevelSolver.csファイルを読めば様々なクエリを確認することができる。

Mac で HoloLens 用 UWP アプリをビルドする【Boot Camp】

はじめに

HoloLens アプリ開発は基本的に WindowsPC を使って行います。
しかし今回 MacBoot Camp を使って HoloLens 用アプリを開発してみました。
(探したところ Boot Camp での開発環境構築のサイトが見当たらなかったため)
結論から言えば、問題なくビルドできました!
一応手順を紹介したいと思います。自分へのメモも含めて。

動作環境

  • MacBookPro 13-inch, 2017, Four Thunderbolt 3 Ports

使用するソフトウェア

手順

BootCamp を使って Windows をインストール

  1. Windows 10インストール用の iso ファイルをダウンロード ただし iso が入った USB メディアを買ってある人はこの手順は必要ありません。
  2. Boot Camp を使って Windows10 をインストール
    ちなみに自分はWindows起動後にbootcampコントローラのインストールが失敗し wifi やら右クリックが使えなくなっていたため、以下のリンクから Windows サポートソフトウェアをインストールしなおしました。
    support.apple.com
    Windows サポートソフトウェアをインストールしたら無事に wifi が繋がりました。
    また、Windowsパーティション最低 60GB は必要です。
    ここを節約してしまうと後々困りますので注意してください。
    Windows そのものに数十GB、Visual Studio にも約 20GB 使います。
    自分は、Mac に 150GB、Windows に 100GB パーティションを区切りました。

開発者モードにしておく

Windows側、HoloLens側どちらも開発者モードにしましょう。

HoloLens のサンプルアプリをビルド

  1. HoloToolKit をダウンロードし Unity のプロジェクトに配置
    DLはこちら
  2. 適当なサンプルシーンを開き、UWP 向けにビルド Windows10 SDK がインストールされていないとビルドが通りませんので注意。
    しかし、以下のようなエラーが出ました。
Exception: Failed to locate env variables VS140COMNTOOLS or VS120COMNTOOLS.
Utility.GetVSVersion () (at C:/buildslave/unity/build/PlatformDependent/WinRT/SharedSources/CSharp/Utility.cs:623)
MetroCSharpVisualStudioSolutionCreator.WriteSolutionFile (System.String solutionFileName, UnityEditor.Scripting.ScriptCompilation.ScriptAssembly[] csharpAssemblies) (at C:/buildslave/unity/build/PlatformDependent/MetroPlayer/Extensions/Managed/MetroCSharpVisualStudioSolutionCreator.cs:196)
MetroCSharpVisualStudioSolutionCreator.CreateSolutionFileFrom () (at C:/buildslave/unity/build/PlatformDependent/MetroPlayer/Extensions/Managed/MetroCSharpVisualStudioSolutionCreator.cs:724)
PostProcessWSA.CreateVisualStudioSolution () (at C:/buildslave/unity/build/PlatformDependent/MetroPlayer/Extensions/Managed/PostProcessWSA.cs:320)
PostProcessWinRT.Process () (at C:/buildslave/unity/build/PlatformDependent/WinRT/SharedSources/CSharp/PostProcessWinRT.cs:237)
UnityEditor.WSA.BuildPostprocessor.DoPostProcess (BuildPostProcessArgs args) (at C:/buildslave/unity/build/PlatformDependent/MetroPlayer/Extensions/Managed/ExtensionModule.cs:142)
UnityEditor.WSA.BuildPostprocessor.PostProcess (BuildPostProcessArgs args) (at C:/buildslave/unity/build/PlatformDependent/MetroPlayer/Extensions/Managed/ExtensionModule.cs:149)
UnityEngine.GUIUtility:ProcessEvent(Int32, IntPtr)

これはVisual Studio 側の環境が整っていないのが問題らしいので次のステップに進みます。

Visual Studio 側の設定

  1. Visual Studio を起動
  2. ツール -> ツールと機能を取得 f:id:korechi:20171106074156p:plain
  3. ユニバーサル Windows プラットフォーム開発をインストール
    f:id:korechi:20171106074300p:plain

再度ビルド

無事 Unity でビルドが通り、Visual Studio 用ソリューションファイルが生成されました!
あとは Visual Studioコンパイルすれば HoloLens 上でアプリが動作します!

その他

リモートデスクトップ

HoloLens から リモートデスクトップアプリを使って BootCamp で起動した Windows10 に普通にリモートデスクトップ繋がりました。
f:id:korechi:20171105161143j:plain

Unity で作った ARKit 向けアプリをリリースする時に気をつけること【審査まで】

はじめに

今回 ARKit の機能を利用するアプリを Unity を使って作成し、ストアに申請しました。(まだ承認されていないのでストアには並んでいませんが)
そこで、Unity を使って ARKit の機能を利用するアプリをストアに申請する際に出たいろいろなエラーなどを紹介し、回避法やおすすめ設定をメモしておきます。
審査結果が届き次第、またフィードバックを記事にまとめたいと思います。

Unity 側

minimum iOS のバージョンを 11 に

ARKit は iOS 11.0 以降でかつ、iPhone6s 以降に発売された iPhone および iPad でしか動作しません。
そのため、File -> Build Setting -> Other Settings の Target minimum iOS Version を 11.0 にしましょう。
f:id:korechi:20171031200051p:plain:h600

必要ならターゲットデバイスiPhone のみに

もし必要ならですが、上と同じく File -> Build Setting -> Other Settings の Target Device を iPhone Only にしておきましょう。
ただ、iPad でも機種によっては ARKit が動作するため、iPad にも対応させている場合は Target Device を iPhone & iPad にしましょう。

画面回転をしない設定にしておく

これはもちろん作るアプリケーションにもよりますが、自分が今回作ったアプリでは画面回転をしない方が良かったため、このような設定にしました。
File -> Build Setting -> Other Settings の Default Orientation を Portrait にすることで、画面を縦固定にすることができます。
f:id:korechi:20171031201120p:plain:h300

XCode

こちらが意外とネック。
Unity で Xcode 用ソリューションにできたとしても安心できません。
自分は結構ビルドが通らなかったです。

Architectures を arm64 のみに(重要)

Build Setting の Architectures で対象とするデバイスアーキテクチャが選択できます。
これがデフォルトですとおそらく、arm64 armv7 が選択されているのですが、これだとビルドが通りません。以下のようなエラーが出ます。

clang: error: invalid iOS deployment version '-miphoneos-version-min=11.0', iOS 10 is the maximum deployment target for 32-bit targets [-Winvalid-ios-deployment-target]

これは、ターゲットとする iOS のバージョンを 11 以上に設定しているのに、32-bit アーキテクチャは最大で iOS10 ですよーとのエラーです。
こちらの iOSバイス一覧から armv7 は iOS11 に出来ないことがわかります。
qiita.com
そのため、arm64 armv7 の部分を選択し、Othersを選択。
f:id:korechi:20171031202120p:plain
その後、armv7 を削除しましょう。 f:id:korechi:20171031202152p:plain

1024×1024pt のアイコンを設定しておく

ARKit には関係しませんが、App Store にアプリが並ぶ時に表示される 1024*1024pt のアイコンを XCode 上で予め登録しておかないと、iTunes Connect でアプリを審査に出す時にエラーとなってしまいます。
あとでエラーになると面倒なので、予め設定しておきましょう。
1. General -> App Icons Source の右側にある矢印をクリックし、一番下にある欄にアイコンを設定しましょう。
f:id:korechi:20171031202910p:plain:w600

アイコンは非透過の png を用意

これも ARKit には関係しませんが、アイコンは非透過のものを用意しておかないと iTunes Connect でアイコンを設定することができません。
自分は、png(透過)→jpg(非透過)→png(非透過)にして非透過画像の png を用意しました。(もっとスマートなやり方は絶対ある)

とりあえずはざっとこんなところです。
アプリの申請の流れは以下のサイトが参考になりました。
i-app-tec.com

剣を自在に振ってスライムを倒すHoloQuestを作ってみた【HoloLens+ARKit】

今回作ったもの

今回HoloQuestという、HoloLensとiPhoneを使った簡単なアクションゲームを作成しました。
HoloLensを通してiPhoneの位置にARの武器が表示され、その剣を使って敵モンスターを倒すゲームです。
youtu.be

動機

  1. 現実空間でスライムと戦ってみたかった(重要)
  2. HoloLens と ARKit を組み合わせればこんなものが作れるよというのを見せたかった・試したかった

使用した機材

  • HoloLens
    • HoloQuestが動作
      • iPhoneの位置に剣を表示
      • スライムの表示なども行う
  • iPhone6s(iOS11)
    • HoloControllerアプリが動作
      • ARKitを使い自身のpositionとrotationを取得
      • 自身のpositionとrotationをHoloLensに送信
    • コントローラーとして使用

開発環境

  • Unity 2017.1.1f1
    • HoloQuestとHoloClientの実装
  • Visual Studio 2017
    • HoloQuestアプリのビルドに使用

お借りした素材

実装

剣をiPhoneの位置に表示

どうやって剣をiPhoneの位置に追従させて表示させるかのイメージ図がこちら。
f:id:korechi:20171004104458p:plain
それではiPhone側で動作するHoloClientアプリと、HoloLens側で動作するHoloQuestの実装それぞれについて説明します。

HoloClient(iPhone側)の実装

iPhone側で動作するアプリは非常に単純で、必要とした機能は以下の2つです。
1. AR Kitを動作させ常に自身のpositionとrotationを取得
2. 自身のpositionとrotationをHoloLensに毎フレーム送信
この際には凹みさんのuOSCを使用させていただきました。
github.com
3. マーカーを画面に表示
自身の位置をHoloLensに知ってもらうために表示しています。マーカーは何でも良いです。
HoloLens側ではvuforiaというマーカー認識ソフトを使用します。

HoloQuest(HoloLens側)の実装

HoloLensとiPhoneはそれぞれが別のアプリが動作しており、どちらのアプリもアプリが起動した瞬間の自分(カメラ)の位置を(0, 0, 0)としてスタートします。
そのため、HoloLensから見てiPhoneがどこに存在するかの相対位置は分かりません。
そこで2通りの位置合わせ方法を今回使用しました。

①HoloControllerアプリをHoloLensの指定された位置で起動

手順は以下の通りです。
1. HoloQuestが起動した時にHoloLensの前に剣を表示
- 今回は(2,0,2)に表示
2. iPhoneをその剣に重ねた状態でHoloClientアプリを起動
- HoloLensからした(2,0,2)の位置がiPhoneにとっては(0,0,0)だと認識される
3. iPhoneから受信したpositionである(x,y,z)に(2,0,2)を足した場所に剣を表示し続ける
f:id:korechi:20171004113533p:plain

②vuforiaを使い、iPhone上に表示されたマーカーを認識して剣の位置を再調整

しかし、iPhoneを振り続けていると剣とiPhoneの位置がだんだんとずれていってしまいます。(原因はまだわかっていません)
そこでゲームのプレイ中に位置がずれたなーと思った時にiPhoneの位置を再調整するためにvuforiaというマーカー認識ソフトを使いました。
vuforiaの参考記事はこちら
vuforiaではマーカーを認識している間、マーカーのpositionとrotationを取得することができます。
その機能を利用し、剣がずれたなーと思った時にHoloLensの前にiPhoneを持ち上げマーカーを認識させるとiPhoneの位置が再度取得できます。
f:id:korechi:20171004114542p:plain

スライムとの戦闘

これは単純にスライムオブジェクトと剣オブジェクトにcolliderを設定し、OnCollisionEnter()で衝突を検知します。
f:id:korechi:20171004114902p:plain

OnCollisionEnter (Collider collider) {
    if (collider.gameObject.tag == “Army”) {
         this.hitPoint -= 1;
    }
}

気をつけたUI

プレイヤーは敵モンスターをどこから見るか分かりません。
後ろから見た時にモンスターの後ろ姿が見えるのは問題ないのですが、HPは常にプレイヤーの方向を向くように設定しました。

今回やらなかったこ

床・壁・天井に別テクスチャを貼る

床を芝生にしたり天井を空っぽく見せることでゲーム空間っぽい演出をしようかなーと思ったのですが今回はしませんでした。
参考記事
www.naturalsoftware.jp
理由は2つあり、1つは処理が重くなる。もう1つは視野角が狭いため敵モンスターを見ていると周りの風景があまり見えないから。 ここらへんは改善の余地ありかもしれません。

最後に

HoloLens + ARKitでゲームを作ってみましたがとても相性が良かったように思えます。
両者ともセンサーを必要としないため、どんなに歩いても問題ありません。(非常にでかい) しかし、どうしても両者の相対位置はずれてきてしまいます。
実用面で考えるならば、新しいコードレスコントローラが発売されるか、vuforia以外のもっと良い位置合わせ方法が求められると思います。
HoloQuestを実際にリリースすることはありませんが、HoloLens + ARKitは面白い組み合わせですし、可能性があると思うので、みなさまも是非お試し下さい!

Hololensとスマートフォン間で通信を行う方法【uOSC】

はじめに

Hololensとスマートフォン(iPhoneAndroid)間でデータをやりとりする方法をまとめました。
具体的には、凹みさんの記事で紹介されていますUnity向けのOSC実装を使っています。
tips.hecomi.com
これがとても使いやすかったので、自分で使い方を忘れないためにも、記事に使い方をまとめておこうと思います。

前提

UnityでHololensアプリを作成し、Hololens実機でのビルドができること。

必要なもの

実装

今回、Hololensとスマートフォン間で通信を行うために、Hololens側とスマートフォン側の2つアプリを作る必要があります。

Hololens側

  1. 新規プロジェクトを作成しuOSC-v0.0.1.unitypackageHoloToolkit-Unity-v1.2017.1.0.unitypackageをインポート
    f:id:korechi:20170908164637p:plain:h200
  2. HoloToolKitタブにあるConfigureのApply*を全て行う
    f:id:korechi:20170909002517p:plain:h100
  3. uOSC Serverオブジェクトを作成し(名前は何でも良い)、uOscServer.csServerTest.csをuOSC Serverオブジェクトに追加
    場所は以下にあります。
    uOSC/Scripts/uOscServer.cs
    uOSC/Examples/Scripts/ServerTest.cs
    f:id:korechi:20170908165603p:plain
  4. File->BuildSettingでPlatformにてUniversalWindowsPlatformを選択
    今回はSDKを10.0.14393.0を選択します。
    f:id:korechi:20170908170907p:plain:h300
  5. Player Settingsから、Publishing SettingsのCapabilitiesのInternetClientとInternetClientServerにチェック
    環境認識も行いたい場合はSpatial Perceptionにチェックを入れます。忘れがちなので要注意です。
    f:id:korechi:20170908171154p:plain:h300
    また、開発マシンのセキュリティソフトやファイアーウォールを無効化しておかないと、データがHololensに届かない場合があります。
  6. Build
  7. 出力されたソリューションファイルをVisual Studio 2017で開き、Hololensを接続し以下の設定でビルド
    f:id:korechi:20170909000158p:plain
  8. おそらく、初回ビルドは失敗するので、project.lockファイルを開き、UAP,Version=v10.0.***の部分を全てUAP,Version=v10.0に書き換えます
  9. もう1度Visual Studioでビルドするとビルドが通ります
    f:id:korechi:20170909002955p:plain
    ログは右下の出力タブに出力されますので、そこを確認しましょう。

スマートフォン

こちらはずっと簡単です。
1. 新規プロジェクトを立ち上げ、uOSC-v0.0.1.unitypackageをインポート
2. uOSC Clientオブジェクトを作成し(名前は何でも良い)、uOscClient.csClientTest.csをuOSC Clientオブジェクトに追加
f:id:korechi:20170909004015p:plain:h200
場所は以下の場所にあります。
uOSC/Scripts/uOscClient.cs
uOSC/Examples/Scripts/ClientTest.cs
IPAddressや送信する内容は固定となっていますので、そこらへんを変えたい場合は上記2ファイルを修正(主にClientTest.cs)を変更しましょう。
3. AndroidもしくはiOS用にビルド

最後に

Hololensでのアプリ開発はまだ不安定な部分もあり、SDKのバージョンによっては動かなかったりします。そのため、今回ご紹介したバージョンでの開発をおすすめします。

追記

UnityのバージョンをUnity 2017.1.1f1にし、MixedRealityToolkit-Unity for Unity 2017.1.1f1を使用したところ、Hololens側の手順8,9が必要なくなりました。
1度目のビルドで無事Hololens上でアプリが起動したのを確認しました。