Xamarin.FormsでKudan ARを試してみる

初投稿です。

Xamarin.FormsでKudan ARを使用したアプリを作ってみました。
iOSビルド用の開発環境がないので、当記事はAndroidのみの内容となります。
備忘録として残しておこうと思います。
**※あくまで試してみた備忘録の記事なので誤り等存在する可能性があります。ソースコードはご自身の責任でご利用ください。**

# Kudan ARとは
スマホアプリ向けにマーカーAR、およびマーカーレスARの機能を提供してくれるライブラリです。
[Kudan ホーム](https://www.xlsoft.com/jp/products/kudan/index.html)

# きっかけは？
<blockquote class="twitter-tweet"><p lang="ja" dir="ltr">【定期】iOS／Androidでマーカー、マーカーレスのARアプリを作れるKudan AR SDKの記事を書いています💪 SDKは無料、個人開発者は公開も無料なので興味ある方は是非～<a href="https://t.co/VkeqqGHUgM">https://t.co/VkeqqGHUgM</a></p>&mdash; 田淵 義人＠エクセルソフト (@ytabuchi) <a href="https://twitter.com/ytabuchi/status/1271643038897537025?ref_src=twsrc%5Etfw">June 13, 2020</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script> 

上記は、JXUG主宰の田淵さんの定期ツイートです。

>SDKは**無料**、個人開発者は公開も**無料**なので興味ある方は是非～

無料ですって。
それじゃあやってみようかな。
せっかくだからXamarinで。
というノリです。

# どういうものを作る？
Xamarin.FormsでKudan ARがまずは動くところまで確認したいと思いました。

- マーカーAR、マーカーレスARといった選択肢のボタンを表示
- ボタン押下後、選択したAR機能が使える画面に遷移

当記事では、マーカーARの機能が確認できるまでを記載します。

# 開発環境
Windows 10
Visual Studio 2019(16.6.2)
Kudan AR Android SDK 1.6.0
デバッグ環境:Android 8.0

# Kudan AR SDKのダウンロード
ダウンロードページは[こちら](https://www.xlsoft.com/jp/products/kudan/download.html)
ダウンロードには、メールアドレスの入力が必要です。
メールアドレスを送信すると、自動送信でダウンロードページのURLが送られてくるので、そちらからダウンロードに進んでください。
当記事ではAndroid SDKを選択します。

# バインドライブラリの作成
今回はネイティブの実装ではなく、Xamarinでの実装を行います。
よって、ネイティブのライブラリをXamarinで動作できるように、バインドライブラリの作成が必要になります。
ダウンロードしたKudan AR Android SDKは**aar形式**だったので、下記を参考にバインドライブラリを作成します。
[.AAR のバインド](https://docs.microsoft.com/ja-jp/xamarin/android/platform/binding-java-library/binding-an-aar)

気をつけることと言えば、`Jars`フォルダに追加したaarファイルのビルドアクションを、忘れずに`LibraryProjectZip`に設定するくらいでしょうか。
![コメント 2020-06-19 140947.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/280330/868489c2-5988-4d9e-4356-1b066e10ff41.png)
![コメント 2020-06-19 140716.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/280330/9763f575-8fbf-9643-db48-d4aaee5a856a.png)
他の設定は変更してません。
![コメント 2020-06-19 140753.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/280330/33b6c632-118b-58ea-553a-2ab99665aaea.png)


初回ビルド時、下記エラーが出ますが、再度ビルドすると消えます。
>error XACLP7024: System.IO.IOException: 別のプロセスで使用されているため、プロセスはファイル 'C:<バインドライブラリのプロジェクトパス>\obj\Debug\api.xml.class-parse' にアクセスできません。

# Xamarin.Formsアプリの作成(Androidのみ)

## 使用したNuGetライブラリ
|NuGetライブラリ名|バージョン|
|:---|:---|
|Prism.Unity.Forms|7.2.0.1422|
|ReactiveProperty|7.1.0|
|Xamarin.Essentials.Interfaces|1.5.3.2|
|Xamarin.Forms|4.6.0.847|

## バインドライブラリの参照設定
先程ビルドして生成されたバインドライブラリ(dllファイル)の参照設定をします。
Androidプロジェクトの`参照`で右クリックメニューから`参照の追加`をします。

## パッケージ名の設定
Androidマニフェストの設定画面を表示します。
無料でKudan ARを使用したい場合、Androidのパッケージ名を下記ページに記載されたバンドルIDに設定する必要があります。
[開発ライセンス キー](https://www.xlsoft.com/doc/kudan/ja/development-license-keys_jp/)

私はこの設定を忘れて進めていたため、マーカーの検出ができずに詰まっていました。
調査した結果、下記エラーが吐かれていました。

>License key not valid for Image Tracking

このときはカメラ機能までは動いたのでライセンス周りはOKだと勘違いしていました。

その他には、下記を参考に必要なパーミッション設定をします。
[Android はじめに](https://www.xlsoft.com/doc/kudan/ja/android-getting-started_jp/)

## ライセンスキーの設定
ライセンスキーはアプリ内で必ず通る箇所で設定します。
チュートリアルと同じく、`MainActivity`の`OnCreate`に実装しました。

## Kudan AR起動用Activityを実装
Kudan ARを使用するには、`ARActivity`を継承したクラスが必要となるようです。
Xamarin.Formsを作成すると自動生成される`MainActivity`は、`global::Xamarin.Forms.Platform.Android.FormsAppCompatActivity`を継承しており、同時に`ARActivity`を継承したクラスは作成できません。
ということで、`MainActivity`とは別に新しくActivityを作って進めます。

ここで1つ疑問が浮かびました。
Xamarinって複数のActivityを持つアプリを作れるのか？
検索してみたところ、下記の記事がありました。
[rksoftware - Xamarin で素の Activity と Forms とで画面遷移する](https://rksoftware.hatenablog.com/entry/2019/06/03/212022)

>こんなことをして良いものなのか、この経験が生きる日がいずれ来るのか分かりませんが、もしかしたら Xamarin に限らず何かでこの経験に救われる日が来るかもしれません。
>来ない気がしますけれど。

どうやら可能なようです。
邪道かもしれませんが。

## Assetsフォルダに画像を追加
マーカー用画像、AR表示させたい画像は、ビルドアクションが**AndroidAsset**である必要があります。
`Assets`フォルダに画像を追加すると、ビルドアクションの初期値が`AndroidAsset`となるので、このフォルダに追加しましょう。
私は最初、ビルドアクションの初期値が`AndroidResource`となる`Resources/drawable`フォルダに画像を追加していて、画像が読み込まれない状態となり、しばらく悩みました。

# 実装
以上を踏まえて実装しました。
雑に実装している箇所もあります。
ご容赦ください。

```C#:MainActivity.cs
[Activity(Label = "KudanARDemo", Icon = "@mipmap/ic_launcher", Theme ="@style/MainTheme", MainLauncher = true, ConfigurationChanges =ConfigChanges.ScreenSize | ConfigChanges.Orientation)]
public class MainActivity : global::Xamarin.Forms.Platform.AndroidFormsAppCompatActivity
{
    public static MainActivity Instance { get; private set; }
    protected override void OnCreate(Bundle savedInstanceState)
    {
        TabLayoutResource = Resource.Layout.Tabbar;
        ToolbarResource = Resource.Layout.Toolbar;

        base.OnCreate(savedInstanceState);
        
        // Kudan AR APIキー設定
        var key = ARAPIKey.Instance;
        key.SetAPIKey("ここにライセンスキーを記載してください");
        
        Xamarin.Essentials.Platform.Init(this, savedInstanceState);
        global::Xamarin.Forms.Forms.Init(this, savedInstanceState);
        
        LoadApplication(new App(new AndroidInitializer()));
        
        // MarkerARActivityをStartActivityをする際に使用
        Instance = this;
    }
    public override void OnRequestPermissionsResult(int requestCode, string[] permissions, Android.Content.PM.Permission[] grantResults)
    {
        Xamarin.Essentials.Platform.OnRequestPermissionsResult(requestCode, permissions, grantResults);
        base.OnRequestPermissionsResult(requestCode, permissions, grantResults);
    }
}
public class AndroidInitializer : IPlatformInitializer
{
    public void RegisterTypes(IContainerRegistry containerRegistry)
    {
        // Register any platform specific implementations
    }
}
```

```C#:MarkerARActivity.cs
[Activity(Label = "MarkerARActivity")]
public class MarkerARActivity : ARActivity, IARImageTrackableListener
{
    public ARImageTrackable ImageTrackable { get; set; }
    protected override void OnCreate(Bundle savedInstanceState)
    {
        base.OnCreate(savedInstanceState);
    }
    public override void Setup()
    {
        base.Setup();
        // 設定する AR コンテンツをここに記述
        
        // 画像トラッカブルを初期化して画像をロード
        // ビルドアクションがAndroidAssetのファイル名を指定
        ImageTrackable = new ARImageTrackable("Lego_Marker");
        ImageTrackable.LoadFromAsset("Kudan_Lego_Marker.jpg");
        
        // 画像トラッカーの 1 つのインスタンスを取得
        var imageTracker = ARImageTracker.Instance;
        imageTracker.Initialise();
        
        // 画像トラッカブルを画像トラッカーに追加
        imageTracker.AddTrackable(ImageTrackable);
        
        // 画像で画像ノードを初期化
        // ビルドアクションがAndroidAssetのファイル名を指定
        var imageNode = new ARImageNode("Kudan_Cow.png");
        
        // imageNode のサイズを Trackable のサイズに合わせる
        var textureMaterial = imageNode.Material as ARTextureMaterial;
        var scale = ImageTrackable.Width / textureMaterial.Texture.Width;
        imageNode.ScaleByUniform(scale);
        
        // 画像ノードをトラッカブルのワールド空間の子として追加
        ImageTrackable.World.AddChild(imageNode);
        
        // リスナー登録
        ImageTrackable.AddListener(this);
    }
    public void DidDetect(ARImageTrackable p0)
    {
        System.Diagnostics.Debug.WriteLine("Did Detect");
    }
    public void DidLose(ARImageTrackable p0)
    {
        System.Diagnostics.Debug.WriteLine("Did Lose");
    }
    public void DidTrack(ARImageTrackable p0)
    {
        System.Diagnostics.Debug.WriteLine("Did Track");
    }
}
```

以下、DependencyServiceのインタフェースと実装。

```C#:IKudanARService.cs
public interface IKudanARService
{
    Task StartMarkerARActivityAsync();
}
```

```C#:KudanARService.cs
[assembly: Dependency(typeof(KudanARService))]
namespace KudanARDemo.Droid
{
    public class KudanARService : IKudanARService
    {
        public async Task StartMarkerARActivityAsync()
        {
            var status = await Common.CheckAndRequestPermissionAsync(new Permissions.Camera());
            if (status != PermissionStatus.Granted)
            {
                // Notify user permission was denied
                return;
            }

            status = await Common.CheckAndRequestPermissionAsync(new Permissions.StorageWrite());
            if (status != PermissionStatus.Granted)
            {
                // Notify user permission was denied
                return;
            }

            status = await Common.CheckAndRequestPermissionAsync(new Permissions.StorageRead());
            if (status != PermissionStatus.Granted)
            {
                // Notify user permission was denied
                return;
            }

            // MarkerARActivityを起動
            MainActivity.Instance.StartActivity(new Android.Content.Intent(MainActivity.Instance, typeof(MarkerARActivity)));
        }
    }
}
```

# 実行結果
Xamarin.FormsでもKudan ARの実行ができました。
`MarkerARActivity`画面時に、スマホの戻るボタンを押下すると`MainPage`に戻ります。
![Screenshot_20200614-185533.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/280330/1b2d2b97-f0ed-2182-aae5-6545d59d3128.png)

# ソースコード
GitHubにてソースを公開しています。
マーカー画像、ノード画像を設定できるようにしています。
こちらのソースで動作を確認したい場合は、別途バインドライブラリが必要になりますので各自作成して、参照追加してください。
[KudanARDemo](https://github.com/takapi-Jr/KudanARDemo)

# (追記:2020/07/05)ストアアップロード
Googleさんに25ドル支払い、アップロードしてみました。
審査が通って公開できるまでに3日かかりました。
初回リリースということもあり、UIはまったく凝ってないです。
今後の課題の1つですね。
<blockquote class="twitter-tweet"><p lang="ja" dir="ltr">Kudan ARを試してみたついでにそのままアプリをストアにあげてみました<a href="https://t.co/4n31lhY0u6">https://t.co/4n31lhY0u6</a></p>&mdash; ぺた (@takapi_cs) <a href="https://twitter.com/takapi_cs/status/1279727364822364162?ref_src=twsrc%5Etfw">July 5, 2020</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script> 

# (追記:2021/02/20)UI更新
以前課題と言っていたUIを変更してみました。
Xamarin.Forms 5.0でstableになったCarouselViewや、Xamarin.Forms.PancakeViewを使用しただけですが、印象を変えるには十分かと思います。
<img width="400" alt="Screenshot_1_Home.jpg" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/280330/fa2117ab-d058-0ec8-3872-ffdab96f512c.jpeg">

# リンク
[Kudan 開発者ハブ トップページ](https://www.xlsoft.com/doc/kudan/ja/home_jp/)