yotiky Tech Blog

とあるエンジニアの備忘録

de:code 2018 で個別に公開されたセッション資料等

参加者向けに de:code 2018 の資料が公開され始めたようですが、私はブースに少し顔を出しただけなのでまだ見れないようです。。
de:code 2017 で検索するとおよそ3週間後とかに公開されたようで、ちょっと熱冷めそうなので、現在(5/30時点で)拾えたものをメモっておきます。
今回は参加レポ等は省略、登壇者が公開されたサイトをベースに集めています。登壇者がご自身で補足されているものもあるので気になる内容がある方は必読。

※動画と資料が公開されたようですので、以降補足としてご利用ください。(2018/6/3更新)
※リンクが切れていたので差し替えました。動画は見当たりませんが。。(2020/6/2更新)

www.microsoft.com

目次

AI:AI

AI61 C#ニューラルネットワークをスクラッチで書いて機械学習の原理を理解しよう

AC:App Client

AC02 最新 Windows10 に最適なデスクトップアプリ開発手法ご紹介

AC16 HoloLens×AI×IoTアプリが開発できるまでの挫折と成功

AC62 簡単!!HoloLensで始めるCognitive Services~de:code 2018 特別バージョン~

AD:App Development

AD05 ハードコア デバッギング :サポート直伝! Azure Webアプリケーション バグバスター!!

AD08 Visual Studio App Center でモバイルアプリ開発/運用サイクルを高速化させよう!

AD09 進化する Web ~ Progressive Web Apps の実装と応用 ~

AD13 DevOps エンジニアに送るガチ Kubernetes 講座with Azure Kubernetes Service (AKS)

AD15 NoOps へ舵を切れ ~ Azure で実現するサーバレス自律運用システム

AD16 3大フレームワーク(Angular, React, Vue.js)比較によるエンタープライズ Web アプリケーション開発の最適化

AD19 .NET Core 2.x 時代の C

AD21 開発者におくる Power BI を使う時に考えるべきアーキテクチャ ~ データを溜めるのは誰だ? ~

AD25 Angular でもっと API ファースト・もっとモダンデザインなWebアプリケーションを作ろう!

AD28 ワタシハ Azure Functions チョットデキル

AD35 オープンソースのマイクロサービス/コンテナー プラットフォーム「Azure Service Fabric」の使いどころ

AD45 今からはじめる Xamarin.Forms 開発~Build 2018 で発表された最新情報も添えて~

AD62 App Center Analytics を使い倒そう~静的コード生成を活用した Xamarinにおける AOP 活用~

CI:Cloud Infrastructure

CI01 帰ってきた インフラ野郎 Azureチーム ~Azure データセンターテクノロジー解体新書2018春~

CI14 こわくない! Azure 運用管理 DA21 今年もまかせて!SQL Database〜その進化はまだまだ止まらない!編〜

DA:Data

DA06 SQL Server 管理者におくる SQL Server on Linux Tips

DA19 次世代データベース サービス「Azure Cosmos DB」を使いこなそう ~ Azure Cosmos DB Deep Dive ~

DA20 Azure データ サービスを使ったアーキテクチャ設計 ~ 「Azure データ アーキテクチャ ガイド」を中心に ~

etc

Microsoft MVP パーソナルスポンサー

HoloLens Emulator build 10.0.17134.80 のインストール

May 21, 2018 に最新の HoloLens Emulator がリリースされたようです。
HoloLens Emulator build 10.0.17134.80 で、Windows 10 April 2018 Update 対応です。
一つ前のバージョンが 10.0.14393.1358 で、Windows 10 のアップデート2つぶんくらいは更新されずにいたのでそこをターゲットにしてしまうとエミュレータは使えない状態でした。 半ば諦めてましたが漸くアップデートされました。

HoloLens Emulator のインストール

私の環境はすでに 10.0.14393.1358 がインストール済みのため、新規インストールの方で何か躓くようでしたらGoogleさんに前バージョンのインストール方法を聞くことをおすすめします。

インストーラーはこちらにありました。

docs.microsoft.com

ダウンロードしたインストーラーを起動したらぽちぽちするだけです。

インストール先を指定して、
f:id:yotiky:20180524042520p:plain
Next & Accept、
f:id:yotiky:20180524042144p:plain
f:id:yotiky:20180524042139p:plain

現時点ではオプションの詳細が皆無なので、フルインストールして情報が出てくるの暫く待ちましょう。(ポチッ f:id:yotiky:20180524042113p:plain

f:id:yotiky:20180524042118p:plain
想像以上に時間がかかるます...
f:id:yotiky:20180524042124p:plain
f:id:yotiky:20180524042128p:plain

完了です。

Windows SDK のインストール

Windows 10 バージョン 1803 用 Windows 10 SDKも入れときます。
またはVisual Studioの最新バージョンには含まれるとあるのでアップデートでもいけるかも?
インストーラーはこちらから。

Windows 10 SDK – Windows アプリ開発

Emulator とほぼ同じ。
f:id:yotiky:20180524043552p:plain
f:id:yotiky:20180524043558p:plain
f:id:yotiky:20180524043604p:plain
C++ 入れちゃったけど使わないなら入れなくてもよかったかもね。
f:id:yotiky:20180524043609p:plain
こっちも負けじと結構時間かかる。。 f:id:yotiky:20180524043614p:plain
f:id:yotiky:20180524043619p:plain

まとめ

これで使えるようになりました。
以前はビルド14393までしかEmulatorが表示されなかったんですが、しっかりビルド15063以降でもEmulatorが選べるように。
しかし以前にもまして起動時間おそっ。。
f:id:yotiky:20180524043920p:plainf:id:yotiky:20180524043923p:plain

疲れている人のためのコードスニペット in HoloLens

10年前の @zecl さんより(勝手に)バトンを受け取ってコードスニペットを作りました(これはひどい)。 元ネタはこちら

さて、HoloLensの実装してると #if UNITY_UWP が頻出するケースが稀によくあります。
手打ちもいいですがめんどくさいのでスニペット作って追加しちゃいましょう。
作業は簡単で、xmlファイルを「.snippet」の拡張子で保存し、Visual Studio からインポートしするだけです。
こんな感じで使えるようになります。 f:id:yotiky:20180523035639g:plain

目次

コードスニペットを作成する

下記のようなXMLを記述し、適当な名前つけて保存します。サンプルは「ifuwpsymbol.snippet」。

<?xml version="1.0" encoding="utf-8"?>
<CodeSnippets xmlns="http://schemas.microsoft.com/VisualStudio/2005/CodeSnippet">
    <CodeSnippet Format="1.0.0">
        <Header>
            <Title>IfUnityUwpSymbole</Title>
            <Shortcut>ifuwp</Shortcut>
            <Description>UNITY_UWP シンボルで #if ステートメントを作成する</Description>
            <Author>yotiky</Author>
            <SnippetTypes>
                <SnippetType>Expansion</SnippetType>
                <SnippetType>SurroundsWith</SnippetType>
            </SnippetTypes>
        </Header>
        <Snippet>
            <Code Language="csharp"><![CDATA[#if UNITY_UWP
$selected$
#endif$end$]]>
            </Code>
        </Snippet>
    </CodeSnippet>
</CodeSnippets>

簡単にポイントだけ紹介します。
Header 要素の下にある Title 要素はインテリセンスで表示される名前です。
Shortcut 要素に指定した文字は、入力し始めると即座にインテリセンスで候補として表示されます。
SnippetTypes 要素は、Visual Studioスニペットをどのように挿入するかを指定します。
SurroundsWith は選択しているコードを囲むように配置します。これを指定しなかった場合、コードを選択して実行するとスニペットで上書きされます。
Expansion はスニペット挿入後のカーソル位置を指定できるようになります。

続いて Snippet 要素の中ですが、CDATA セクションで実際に挿入するコードを定義します。
ここに$selected$$end$で、それぞれ「選択したコードの配置」と「挿入後のカーソル位置」を指定します。
ざっとリファレンス見た限り、このような$で囲まれた予約語はこの2つしかないようです。
挿入するコード自体は説明の必要がないほど簡単なものですね。

より詳しく知りたい方は以下のリンクを参照すると良いでしょう。

コードスニペットを追加する

Visual Studioで、[ツール]->[コードスニペットマネージャー]を開くか、[Ctrl+K, Ctrl+B]でも開きます。
f:id:yotiky:20180523032447p:plain

言語は C# を選択し、[インポート]をクリック、
f:id:yotiky:20180523032450p:plain

先程作成したsnippetファイルを選択し、コードスニペットを追加する場所を選んで完了。
f:id:yotiky:20180523032454p:plain

一度追加したスニペットは再度同じファイル名でインポートすれば上書きされます。
f:id:yotiky:20180523032458p:plain

まとめ

Let's Enjoy Programming! f:id:yotiky:20180523035555g:plain

[iOS]Deeplink(Custom URL Scheme)を使ってアプリからアプリを起動する

アプリから別アプリを起動するケースがあまり多くないのかまとまった情報はそれほど多くありません。
作業の覚書としてここに書き記しておきます。

目次

アプリからアプリを起動する

Deeplink(Custom URL Scheme)を使ってアプリAからアプリBを起動するには、

  1. アプリB(起動される側)にカスタムURLスキームを定義する
  2. アプリA(呼び出し側)に呼び出し処理を実装する
  3. アプリA(呼び出し側)で、定義したカスタムURLスキームを呼び出す宣言をする (iOS9以降)

が必要となります。

起動される側(アプリB)

まずURLスキームを定義します。
Xcodeで開いている場合はプロジェクト直下にある info.plist を開いて、Information Property List に以下の項目を定義します。

URL types
└Item 0
 ├URL Identifier : myHost
 └URL Schemes
  └Item 0 : myScheme

f:id:yotiky:20180521012513p:plain

これで、myScheme://myHostで起動する準備ができました。1 2

また、Unity上では URL Scheme のみ設定できるようになっています。
File > Build Settings > Player Settings > Inspector を開いて、iOSのタブに「Supported URL schemes」の設定があるのでURLスキームを追加します。

f:id:yotiky:20180521012516p:plain

呼び出し側(アプリA)

iOSで実装するには、ネイティブでプラグインを実装してUnity側からプラグインの関数を呼ぶのが簡単です。

プラグインの実装

iOSプラグインは、 Plugins\iOS に h ファイルと mm ファイルを追加するだけ。あとはビルド時に良しなにやってくれます。 実装ファイルだけで完結する場合は ヘッダーファイルは不要です。

f:id:yotiky:20180521012518p:plain

mm は、Objective-CC++のコードが混在したXcodeで使われるファイルです。Objective-C++なんて呼ばれたりもしますが、言語としては存在しないらしい。
extern "C" { } に記述した関数がUnity側から呼び出すインタフェースになります。
プレース内はCのコードで、名前マングリングをしてくれるらしい。(なんのこっちゃ)
おまじないとして覚えておきましょう。詳しく知りたい人は「名前マングリング」や「リンケージ指定」とかで検索すると良いかも。

サンプルは、URLスキームとiTunesAppIdを引数に渡すと、端末にURLスキームで起動できるアプリがある場合はアプリを、なければApp Storeを指定したアプリIDで開きます。
iOS 10 以降はcanOpenURL と openURL の仕様が変わっており引数が増えています。バージョン判定を行った上で処理を別けています。

@interface Launcher : NSObject
+(void)launch:(NSString*) urlStr itunesAppId : (NSString*) itunesAppId;
@end

@implementation Launcher
+(void)launch:(NSString*) urlStr itunesAppId : (NSString*) itunesAppId{
    NSURL *url = [NSURL URLWithString:urlStr];
    NSURL *storeurl = [NSURL URLWithString:[NSString stringWithFormat:@"itms-apps://itunes.apple.com/app/%@", itunesAppId]];
    
    if(floor(NSFoundationVersionNumber) > NSFoundationVersionNumber_iOS_9_x_Max){
        // iOS10 以降
        if([[UIApplication sharedApplication] canOpenURL:url]){
            [[UIApplication sharedApplication] openURL:url options:@{} completionHandler:nil];
        }else{
            [[UIApplication sharedApplication] openURL:storeurl options:@{} completionHandler:nil];
        }
    }else{
        // iOS9 以前
        if([[UIApplication sharedApplication] canOpenURL:url]){
            [[UIApplication sharedApplication] openURL:url];
        }else{
            [[UIApplication sharedApplication] openURL:storeurl];
        }
    }
}
@end

extern "C"{
    void launch(const char *url, const char *itunesAppId){
        NSString *urlString = [NSString stringWithCString:url encoding:NSUTF8StringEncoding];
        NSString *itunesAppIdString = [NSString stringWithCString:itunesAppId encoding:NSUTF8StringEncoding];
        
        [Launcher launch:urlString itunesAppId:itunesAppIdString];
    }
}

Unityからプラグインを呼び出す

続いてUnityから呼び出す処理を実装します。
配置したプラグインの extern で定義した関数をDLL関数として呼び出してあげましょう。

public class NewBehaviourScript : MonoBehaviour 
{
    public void OnClick1()
    {
        new MyPlugin().Launch("myScheme://myHost", "id0123456789");
    }
}

public class MyPlugin
{
    [DllImport("__Internal")]
    private static extern void launch (string url, string itunesAppId);

    public void Launch(string url, string itunesAppId)
        {
        launch (url, itunesAppId);
    }
}

URLスキームを呼び出す宣言 (iOS9以降)

iOS9以降では、info.plist にそのアプリが起動できるURLスキームを予め宣言しておく必要があります。
この宣言がないと、URLスキームが一致していてもアプリを起動することができません。
info.plist を開いて、Information Property List に LSApplicationQueriesSchemes の項目を定義します。

LSApplicationQueriesSchemes
├Item 0 : myScheme
└Item 1 : 必要な分だけ

f:id:yotiky:20180521012521p:plain

パラメータを渡す場合

Deeplinkで起動する際にパラメータを渡したい場合は、更に対応が必要になります。
今回はURLにパラメータを付けて渡す方法なので、呼び出し側(アプリA)はURLにパラメータを付けて呼び出すだけです。特に実装の必要はありません。起動される側(アプリB)に以下の実装を加えます。

  1. 起動時のUnityAppControllerをフックして値をUnity側へ橋渡しするプラグインを作成する
  2. Unity側からプラグインを呼び出して値を受け取る

プラグインの実装と呼び出し

プラグイン作成のポイントは、

  • UnityAppController を継承したControllerを定義し、openURLを実装する
  • staticな領域に値を退避する
  • IMPL_APP_CONTROLLER_SUBCLASS を使って定義したControllerをサブクラスとして登録
  • Unityから値を取り出せる関数を用意する

です。

前項の通り、作成したヘッダーファイルと実装ファイルは、Plugins/iOSの配下に置きます。

#import <Foundation/Foundation.h>
#import "UnityAppController.h"


// static な変数保持用
@interface IntentParameter : NSObject
+ (NSString*)getUrlString;
+ (void)setUrlString:(NSString*)str;
@end

@implementation IntentParameter
static id urlString = @"";
+(NSString*)getUrlString{
    return urlString;
}
+(void)setUrlString:(NSString*)str{
    urlString = [str copy];
}
@end


// openURLをフックする処理
@interface UrlHandlerAppController : UnityAppController
@end

@implementation UrlHandlerAppController
- (BOOL)application:(UIApplication *)application openURL:(NSURL *)url options:(NSDictionary<UIApplicationOpenURLOptionsKey, id> *)options {
    
    [IntentParameter setUrlString:[url absoluteString]];
    
    return YES;
}
@end

IMPL_APP_CONTROLLER_SUBCLASS(UrlHandlerAppController);


// APIの定義
extern "C"{
    const char* getUrlString();
}

const char* getUrlString(){
    NSString *string = [IntentParameter getUrlString];
    const char *str = [string UTF8String];
    
    char* retStr = (char *)malloc(strlen(str) + 1);
    strcpy(retStr, str);
    return retStr;
}

Unity側から、extern で定義した関数をDLL関数として呼び出せば値が取り出せます。

public class NativeIntentParameterApi
{
    [DllImport("__Internal")]
    private static extern string getUrlString();

    public static string GetUrlString()
    {
    return getUrlString();
    }
}

プラグインでフックしたopenURLは、起動される側が起動していない場合はもちろん、既に起動していても、カスタムURLスキームを使ってアプリに遷移する時に呼び出されます。アプリ単体で起動した場合は値が取れません。


  1. Identifierは一意性を確保する識別子として定義されているが、カスタムURLスキームを使って起動する時はURLスキームのみで判定されておりあまり意味はないようです。(ここが不一致でもURLスキームが一致していれば起動する)

  2. Identifierについては、iOSは一意性を確保する識別子として逆DNS形式が慣習化されているのに対し、Androidは host という名前でDNS形式となるため注意が必要です。

[Android]Deeplink(Custom URL Scheme)を使ってアプリからアプリを起動する

アプリから別アプリを起動するケースがあまり多くないのかまとまった情報はそれほど多くありません。
作業の覚書としてここに書き記しておきます。

目次

アプリからアプリを起動する

Deeplink(Custom URL Scheme)を使ってアプリAからアプリBを起動するには、

  1. アプリB(起動される側)にカスタムURLスキームを定義する
  2. アプリA(呼び出し側)に呼び出し処理を実装する

が必要となります。

起動される側(アプリB)

まずURLスキームを定義します。 Plugins\Android\AndroidManifest.xml を開いて、<action android:name="android.intent.action.MAIN" />が定義されている <activity>の直下に<intent-filter>を追記します。

    <activity android:name="com.unity3d.player.UnityPlayerActivity" android:label="@string/app_name" android:screenOrientation="fullSensor" android:launchMode="singleTask" android:configChanges="mcc|mnc|locale|touchscreen|keyboard|keyboardHidden|navigation|orientation|screenLayout|uiMode|screenSize|smallestScreenSize|fontScale|layoutDirection">
      <intent-filter>
        <action android:name="android.intent.action.MAIN" />
        <category android:name="android.intent.category.LAUNCHER" />
        <category android:name="android.intent.category.LEANBACK_LAUNCHER" />
      </intent-filter>
      <intent-filter>
        <action android:name="android.intent.action.VIEW" />
        <category android:name="android.intent.category.DEFAULT" />
        <category android:name="android.intent.category.BROWSABLE" />
        <data android:scheme="myScheme" android:host="myHost" />
      </intent-filter>
      <meta-data android:name="unityplayer.UnityActivity" android:value="true" />
    </activity>

これで、myScheme://myHostで起動する準備ができました。 1

呼び出し側(アプリA)

ネイティブ実装の場合

まず、ネイティブでの呼び出し方法は次の通りです。

Uri uri = Uri.parse("myScheme://myHost");
Intent i = new Intent(Intent.ACTION_VIEW, uri);
// アプリが見つからなければ、ActivityNotFoundException
startActivity(i);

以上。

Unityで実装の場合

Unityで実装するには、AndroidJavaClassとAndroidJavaObjectを使ってネイティブと同じ処理を呼び出します。
AndroidJavaClassなどはマーシャリングとかリフレクションみたいな感じのやつです。

        var url = "myScheme://myHost";
        var packageName = "myApplication";

        using (AndroidJavaClass unityPlayer = new AndroidJavaClass("com.unity3d.player.UnityPlayer"))
        using (AndroidJavaObject currentActivity = unityPlayer.GetStatic<AndroidJavaObject>("currentActivity"))
        using (AndroidJavaClass intentStaticClass = new AndroidJavaClass("android.content.Intent"))
        using (AndroidJavaClass uriClass = new AndroidJavaClass("android.net.Uri"))
        using (AndroidJavaObject uriObject = uriClass.CallStatic<AndroidJavaObject>("parse", url))
        {
            var actionView = intentStaticClass.GetStatic<string>("ACTION_VIEW");

            using (AndroidJavaObject intent = new AndroidJavaObject("android.content.Intent", actionView, uriObject))
            {
                try
                {
                    currentActivity.Call("startActivity", intent);
                }
                catch (Exception e)
                {
                    // 未インストールならストアを開くなど
                    Application.OpenURL("https://play.google.com/store/apps/details?id=" + packageName);
                    
                    // ストアアプなら
                    var urlStore = "market://details?id=" + packageName;
                    using (AndroidJavaClass uriClassStore = new AndroidJavaClass("android.net.Uri"))
                    using (AndroidJavaObject uriObjectStore = uriClassStore.CallStatic<AndroidJavaObject>("parse", urlStore))
                    using (AndroidJavaObject intentStore = new AndroidJavaObject("android.content.Intent", actionView, uriObjectStore))
                    {
                        currentActivity.Call("startActivity", intentStore);
                    }
                }
            }
        }

アプリがインストール済みかどうかは、startActivityやPackageManagerを使ってエラーハンドリングするのが一般的?なようです。

パラメータを渡す場合

Deeplinkで起動する際にパラメータを渡したい場合は、上記に更に対応が必要になります。

  1. 起動時のActivityをフックして値をUnity側へ橋渡しするプラグインを作成する
  2. 起動されるアプリケーションにカスタムURLスキームを定義し、プラグインを呼び出す実装をする

プラグイン

【Unity-Android 】 (1) Android StudioでUnity向けにmoduleを作る方法 - Cross Roadを参考にさせて頂きました。これで目的のプラグインのモジュール(jar)が取り出せます。
具体的にパラメータを橋渡しするコードは、[Unity][Android] カスタムURLスキームを使ってアプリを起動する(Unity Android編) : うえすと開発メモUnityでカスタムURLスキームを使用してアプリを起動する【iOS】【Android】 - LotosLaboなどが参考になります。 ポイントは、

  • Activityを継承した独自Activityを実装する
  • オーバーライドしたonCreateメソッド内で、Intentからパラメータを取得
  • 取り出したパラメータをUnityから呼べるようにする

となります。

呼び出す時は

Intent i = new Intent(Intent.ACTION_VIEW, uri);
i.putExtra("KEY", "VALUE");

で設定し、取り出す時は

Intent intent = getIntent();
String key = intent.getStringExtra("KEY");

// Unityからパラメータ取得するために取っておく
IntentParameter.Key = key;

で取得します。
Unityから呼び出せるように口を用意しておきましょう。

public class IntentParameter {
    public static String Key;

    public static String getKey(){
        return Key;
    }
}

Intent の putExtra は Bundleに値を保存しActivityに直接紐付けているようなので、パラメータ付きのURLを直にやり取りしたい場合は、呼び出し側のURLスキームに直接書き足すのが楽そうです。

その場合呼び出す時は

Uri uri = Uri.parse("myScheme://myHost?param=mogmog");
Intent i = new Intent(Intent.ACTION_VIEW, uri);

で、取り出す時は

Intent i = getIntent();
String urlString = i.getDataString();

myScheme://myHost?param=mogmogが出力されます。

起動される側

AndroidManifest.xmlの修正

次にURLスキームを定義します。パラメータがない場合とは定義の仕方が変わるので注意が必要です。
プラグインで実装したActivityを利用するので、 <action android:name="android.intent.action.MAIN" />が定義されている<activity>と並列に別の<activity>を定義します。

    <activity android:name="com.unity3d.player.UnityPlayerActivity" android:label="@string/app_name" android:screenOrientation="fullSensor" android:launchMode="singleTask" android:configChanges="mcc|mnc|locale|touchscreen|keyboard|keyboardHidden|navigation|orientation|screenLayout|uiMode|screenSize|smallestScreenSize|fontScale|layoutDirection">
      <intent-filter>
        <action android:name="android.intent.action.MAIN" />
        <category android:name="android.intent.category.LAUNCHER" />
        <category android:name="android.intent.category.LEANBACK_LAUNCHER" />
      </intent-filter>
      <meta-data android:name="unityplayer.UnityActivity" android:value="true" />
    </activity>
    <activity android:name="com.example.myPlugin.MyActivity">
      <intent-filter>
        <action android:name="android.intent.action.VIEW" />
        <category android:name="android.intent.category.DEFAULT" />
        <category android:name="android.intent.category.BROWSABLE" />
        <data android:scheme="myScheme" android:host="myHost" />
      </intent-filter>
    </activity>
  </application>

これで、myScheme://myHostで起動した場合に、プラグインで実装したActivityを使用するようになります。

プラグインの取り込みとパラメータの取得

UnityでカスタムURLスキームを使用してアプリを起動する【iOS】【Android】 - LotosLaboを見ながら、Unityにプラグインを取り込みます。
Plugins\Android に追加したclasses.jar は適当な名前にリネームするとわかりやすくなります。 f:id:yotiky:20180521015221p:plain

あとはプラグインに用意しておいたUnity用の口を使って値を取り出します。

        using (AndroidJavaClass plugin = new AndroidJavaClass("com.example.myPlugin.IntentParameter"))
        {
            var key = plugin.CallStatic<string>("getKey");
        }

パラメータを付けてカスタムURLスキームで起動した場合、プラグインの独自Activityにより値が取得できます。既に起動していてる場合でも、独自Activityのコードを通るようです。
パラメータが設定されてない場合や直接アプリを起動した場合は値が取れません。


  1. Unityでプロジェクト新規作成時などデフォルトでは、AndroidManifest.xml は含まれていません。追加する場合は、PlatformをAndroidにしてビルドすると、Xxx.Unity\Temp\StagingArea にUnityが現在の構成で良しなに作成したAndroidManifest.xmlが生成されているのでこれを雛形にすると良いかもです。

HoloLens で NuGet の UWP 向けの Library を利用したい

記事にしたいテーマはいくつかあったので、題材を探していたのですがまたしても目に止まったのはかずきさんのブログ。しかも割と最近の記事。
いつも大変お世話になっております。

blog.okazuki.jp

今回のネタは、やりたいことは記事と同じ「 HoloLens で NuGet のライブラリ使いたい」なんですが、 ちょっと前から手元で導入している方法があったので別の方法として紹介しようと思います。
こんな手もあるよ、くらいの感じで参考にしていただけると。

この記事で紹介する方法

インストールコマンドをまとめた ps1 ファイルを Unity プロジェクトフォルダの直下などに用意しておき、Unity のビルド時にソリューションが生成されるフォルダにコピー、初めて生成した時は手動で ps1 をパッケージマネージャコンソールで実行するという方法になります。
* 2回目以降上書きでビルドする場合は不要

目次

NuGet で追加するライブラリ

参照記事と同じ Json.NET を利用しようと思ったのですが、Unity が出力するプロジェクトの参照をよく見るとすでにライブラリが...。

f:id:yotiky:20180426023930p:plain

同じライブラリを追加しても仕方がないので、筆者に敬意を表して ReactiveProperty を追加しましょう。
ただこちらも Json.NET 同様かなり古いバージョンじゃないとインストールに失敗しました。
成功したのは「2.9.0」。一方、最新バージョンは「4.2.2」...。
バージョンの依存関係がかなりシビアになってる様子。

f:id:yotiky:20180426024040p:plain

ReactiveProperty を使ったプログラム

以下のコードを Unity 上で追加します。
Editor 上では利用できない ReactiveProperty を使ったコードが書かれています。
#if ディレクティブによる回避などは記事同様ですね。

#if UNITY_UWP
using Reactive.Bindings;
#endif
using UnityEngine;

public class SampleBehaviourScript : MonoBehaviour
{
#if UNITY_UWP
    public ReactiveProperty<string> Name { get; } = new ReactiveProperty<string>();
#endif
    
    void Start() { }
    
    void Update() { }
}

ps1 ファイルにコマンドをしたためる

予め追加するライブラリのインストールコマンドをしたためた ps1 ファイルを用意します。

Install-Package ReactiveProperty -Version 2.9.0 -Project Assembly-CSharp
Install-Package ReactiveProperty -Version 2.9.0 -Project UsingUwpLibrary

今回は Unity プロジェクト直下に、"install-package.ps1" という名前で作成しました。

f:id:yotiky:20180426024339p:plain

Unity のビルドで下準備

PostProcessBuildAttribute を使って、ソリューションの出力先に先程の ps1 ファイルをコピーするようにします。
このようなコードを、 Editor フォルダの下に入れることで Unity がよしなに実行してくれます。

using System.IO;
using UnityEditor;
using UnityEditor.Callbacks;
using UnityEngine;

public class AddPackageBuildPostprocessor
{
    [PostProcessBuild]
    public static void OnPostProcessBuild(BuildTarget target, string pathToBuildProject)
    {
        if (File.Exists("./install-package.ps1"))
        {
            File.Copy("./install-package.ps1", "./App/install-package.ps1", true);
        }
    }
}

f:id:yotiky:20180426024446p:plain

生成されたソリューション

生まれたばかりはこんな感じ。このままビルドするとエラーになります。

f:id:yotiky:20180426024751p:plain

パッケージマネージャコンソールが開いてない場合は開きます。

f:id:yotiky:20180426024602p:plain

コピーされている install-package.ps1 を実行します。
別にあれこれやってコピーしなくても「..\」一つ書き足すだけでも動くんですががが。

PM> .\install-package.ps1

実行結果はこちらになります。
参照に必要なライブラリが追加されていますのでビルドが通るようになりました。

f:id:yotiky:20180426024821p:plain

Unity から初めてソリューションを生成した場合に1回実行すれば、次回以降上書きでビルドしている分にはライブラリを追加する作業は不要になります。
とは言え、たまにソリューションの調子が悪くなってフォルダ毎ゴミ箱に突っ込む事もあるので、その場合は再度 ps1 を実行してあげれば復元できます。

まとめ

今回は、HoloLens で NuGet の UWP 向けのライブラリを利用する方法を紹介しました。
簡単におさらいすると、 Install-Package のコマンドを用意しておき、Unity ビルド後に手動で実行するという方法でした。

PostProcessBuild と install-package.ps1 は、どのプロジェクトでも共通で使い回せるので、github などに最初から突っ込んで置くと便利です。
必要になったタイミングで install-package.ps1 に想いをしたためてあげると良いでしょう。

HoloLens で Managed Plugin を実装する際の落とし所

HoloLens でプラグインを作成する理由はいくつかありますが、ひとつは「共通機能をモジュール化してプロジェクト間で共有する」、もうひとつは「UWPで使えるFramworkのAPIを頻繁に使う」あたりが大きいかなと思います。
プラグインの作り方はググれば出てきますが、楽をするためにプラグインを選んだもののその縛りのキツさに大体皆さん悶絶しながらコードを書いているのが垣間見えます。。
そこで個人的な「めんどくさい」をできるだけ排除して、現時点でベターな実装の落とし所をまとめてみようかと思います。

目次

先人の知恵

HoloLens でプラグインを作るのに、以下のサイトを参考にさせて頂きました。

blog.okazuki.jp

更にその中で紹介されているこちらのサイトもとても参考になります。

satoshi-maemoto.hatenablog.com

blog.d-yama7.com

何をやってるか簡単に説明すると、
HoloLens は UWP のプロジェクトなので UWP 用のライブラリ(.NET Core Framework)を使う必要があり、 一方 Unity Editor 上では .NET Framework 3.5 互換のライブラリを使う必要があります。
インターフェイスが全く同じ2つのライブラリをそれぞれの環境でスイッチさせて読み込むことで辻褄を合わせプラグインを利用できるようになっています。

先に挙げたサイトの手順を参考に作って頂ければ間違いないです。 (執筆時点) 間違いないです、、、ないのですが、サイトの中でも苦悩されているように、
「同じインターフェイスの UWP 用 / Editor 用の dll を出力する必要がある」という特殊な事情のため、その設計をどうするかはとても悩ましい問題です。

問題点

個人的に悩ましい問題は以下の点です。

  • Editor 用のコードがお荷物になる
    • 仕様に対して実装が2重管理となり、手の混んだことをすればするほど Editor 用のコードのメンテがめんどくさい
  • 共有プロジェクトや partial クラスを使うと見るべき箇所が増える
    • 特にコード書き始めの初期の段階では実装、リファクタリングの対象が複数箇所に散らばっていてファイルを行き来したりめんどくさい
  • 条件付きコンパイル地獄

結論、めんどくさいことが多い!

どうしたか

そんな問題を踏まえ、現時点での実装の落とし所はこうなりました。
もちろん正攻法な設計が必要な場面もあるので、この手抜き設計が無条件に適用できるわけではありません。

こちらが簡単なサンプルコード。

namespace HelloWorldPlugin
{
    public abstract class MessageBuilderBase
    {
        public virtual string Create(string name) => name;
    }

#if !NETFX_CORE
    // for Editor
    public class MessageBuilder : MessageBuilderBase { }
#else
    // for UWP
    public class MessageBuilder : MessageBuilderBase
    {
        public override string Create(string name)
            => $"Hello {name}. Enjoy your HoloLens!";
    }
#endif
}

コード量が少なければ一つのファイルにまとめてしまいます。
コード量が多くなってくれば、ベースクラスと Editor 用のクラスを一つのファイルに、 UWP 用のクラスを一つのファイルにし、計2つのファイルで管理することが多いです。

f:id:yotiky:20180425034348p:plain

ポイント1 抽象クラス

まず大前提として Editor 用のコードは捨てます。これは問題に挙げた通りメンテしたくし実機で確認しないとわからないこと多いから。
ベースクラスは abstract で定義し、インターフェイスとなるメソッドを virtual で定義することで、共通のインターフェイスとします。
また virtual で定義するので、Editor 用クラスの中身は必要がない限りは override 不要で、最小限のコードでダミークラスを作れます。

ポイント2 UWP 用のクラス

#if をクラス毎囲ってしまうことで、メソッド内に条件付きコンパイルが混在することがなくなりコードが読みやすくなります。 インテリセンスも効くので心置きなくコーディングに専念できます。

ポイント3 csファイル(物理)を分けすぎない

コードの書き始めなどインターフェイスや実装自体が猛スピードで変化します。
ファイルを分けると、2つや3つ(またはより多く)のタブを行ったり来たりしながら修正しなければなりません。
コード量が多くなってくれば個別に分けることもありますが、少ないうちは1つのファイルにすべて書いてしまいます。

補足

この方法に限ったことではありませんが、csファイルはリンクとして追加されているため、どちらのプロジェクトとして開いているかによってビルド条件が変わってきます。 他方のコードはグレーアウトするため、適宜開き直して修正することでインテリセンスの恩恵を受けることもできます。

f:id:yotiky:20180425034352p:plain

なお、プロジェクトの設定やビルド、Unity での設定などは参考にさせて頂いたサイトと同じ内容ですので是非そちらを参考にしていただければと思います。

まとめ

個人的に今ベターだと思う Managed Plugin の実装の落とし所をまとめてみました。