yotiky Tech Blog

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

[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 の実装の落とし所をまとめてみました。

Unityプロジェクトをエクスプローラーから直接起動する

Unityのバージョンをいくつもインストールして動作検証などを行っていると、一々Unityからプロジェクトを開くのがとても面倒だったのでエクスプローラーから直接Unityのバージョンを指定して起動するようにしたお話。

もうずいぶん前からエクスプローラーはQTTabBarを愛用しているので、QTTabBarのアプリケーションランチャーを使います。
(汎用的じゃなくてすいません)

まぁ特に解説することはないのだけど、オプションウィンドウ開いて、アプリケーションランチャに以下のように設定していくだけ。
引数に「-projectPath '現在のフォルダパス'」を指定して、各バージョンの Unity.exe を起動する。

f:id:yotiky:20180425000100p:plain

これで現在のフォルダからUnityを起動できる。 f:id:yotiky:20180425000107p:plain

なんか応用すればコンテキストメニューとか他の手段できるかな?

以上。

「みちびき」受信対応 GPS受信機キット(AE-GYSFDMAXB)が3機受信可能になったので試してみた

2016年発売のGPS受信機キット(AE-GYSFDMAXB)は、「みちびき」1号機の信号しか受信できませんでした。
2018年3月30日最新のファームウェアがリリースされ、ファームウェアをアップデートすることでこのモジュールでも3機の「みちびき」の信号を受信することができるようになりました。
気になる精度がどれほど変わったのか検証してみました。
(あぁ、ちなみにキット手に入れて検証始めるまで「みちびき」の「み」の字も知りませんでした、はい。)

目次

準天頂衛星システム「みちびき」について

ざっと「みちびき」について分かったことを。

GPS

  • 位置情報といえばまずはGPS
  • GPSはアメリカの衛星測位システムで、「全地球測位システム」とも言われるように地球上をカバーしている
  • あくまでアメリカの衛星であり、都市部や山間部など電波が遮られることも含め精度が安定しない

「みちびき」とは

  • 準天頂衛星システム(QZSS)
  • GPSの代わりではなく、GPSと併用してGPSを補完する日本の衛星測位システム
  • 2018年4月現在4機体制
    • 3機の準天頂軌道衛星(1,2,4号機)と1機の静止軌道衛星(3号機)
      • 静止軌道衛星は常に日本上空の一定の位置に滞在
      • 準天頂軌道衛星は24時間を3交代するように日本上空をカバーする
      • 衛星の軌道については、[映像] 準天頂軌道 衛星模型の動画が面白かった
    • 3号機の信号受信は限定されており現状利用できない
    • PNR番号
      • 1号機:193
      • 2号機:194
      • 3号機:199
      • 4号機:195
  • 2018年3月、運用時期を11月1日に延期すると発表された

精度について

「みちびき」は精度の異なる3つのサービスを提供しています。
それぞれ信号が違うため利用したいサービスに対応した製品(モジュール)が必要になってきます。

  • サービス毎の精度

    • 衛星測位サービス:誤差10m程度(GPSと同程度)
    • サブメータ級測位補強サービス:誤差1~2m程度
    • センチメータ級測位補強サービス:誤差6~12cm程度
  • 信号の種類

    • 衛星測位サービス:信号 L1C/A, L1C, L2C, L5
    • サブメータ級測位補強サービス:信号 L1S
    • センチメータ級測位補強サービス:信号 L6

対象モジュール

検証に用いた受信機は、秋月電子通商のGPS受信機キット 「みちびき」対応になります。 受信周波数は「L1,C/Aコード」となっており、衛星測位サービスつまりGPSと同程度の精度になると思われます。
2018年3月29日以前のファームウェアでは、1号機の信号しか受信できませんでした。 3月の1号機の動きを見ると、AM2時~AM8時くらいに日本上空を飛んでおり、とても検証できる状況ではありませんでした。。
アップデートされたファームウェアでは、3号機を除く3機の衛星の信号を受信できます。
これで日中でもGPSと同程度(漸く)のサービスを受けられる!

ファームウェアのアップデート

製品のネット通販のページよりパッケージが無償で配布されています。 ダウンロード後解凍して手順書を確認してください。

モジュールにPCを直接接続し、MiniGPSを使って設定を確認しつつ、パッケージに含まれているbatを実行すればOKです。 f:id:yotiky:20180420035057j:plain

検証

というわけで長い前振りが終わりやっと検証です。
まず一緒に散歩してもらう相棒のご紹介。これもってひたすら練り歩きました... f:id:yotiky:20180420035307j:plain

次にこの時間帯「みちびき」がどのあたりにいたのかを見てみましょう。 これを見ると2号機と4号機が近くを飛んでいたようです。
f:id:yotiky:20180420034841p:plain

検証1

都庁をぐるっと一周してみました。
割と車道ギリギリに線が描かれ、GPSと同程度と言いつつ状況次第ではサブメータ級くらいの精度が出てそうです!すごい!

f:id:yotiky:20180420034103p:plain
都庁一周。かなりいい感じ

検証2

新宿駅西口方面の動く歩道のあるU字地下道に都庁側からぐるっと潜入してみました。
京王プラザホテル脇から天井のある地下道に入るのですが、数m歩くと信号が遮断されて受信できていない状態になりました。 その後駅に近づくと、中央に巨大な穴が開いており空が見えるあたりで信号を受信するも、かなりご乱心の様子。 ブックファースト新宿店(コクーンタワー)のあたりで地上に出ました。
屋根があると全く使い物にならず。

f:id:yotiky:20180420034100p:plain
屋根のあるところ歩いてみたが...

検証3

コクーンタワーの麓に壁に囲まれて頭上がぽっかり開けたスペースがあったので信号受信できるか試してみました。 信号自体は途切れることなく受信できてますが、精度はGPS程度といったところでしょうか。

f:id:yotiky:20180420034114j:plainf:id:yotiky:20180420034106j:plain
コクーンタワーの麓でこんな感じのところ。空がちっちゃく見える
f:id:yotiky:20180420034056p:plain
受信した緯度経度。道路の真ん中行ったり来たりしていないので多少誤差が大きく出てる感じはする

まとめ

アップデート前は1号機が日本上空にいる時間に検証ができなかったため、km単位でズレが生じていました。(これはマジ使えねぇ...って思った)
3機の衛星から受信可能になったことで、日本上空にいる衛星で衛星測位サービスが利用できたことになります。
状況によってはサブメータ級くらいの精度が出ており、ビル群はもとより受信機真上に数m程度上空とつながるスペースがあれば信号自体も受信できていることが確認できました。 今後サブメータ級に対応したモジュールが出てきたらどこまで精度が上がるのか楽しみです。

というかまだGPSと同程度!
みちびきのポテンシャル全然出てないじゃん!

(素人の感想です)

参考にしたサイト