unityアプリ(standalone, androidスマホ, OculusQuest2)でc++ライブラリを使う

Linux standalone、Android、Oculus Quest 2用のUnityアプリで、c++ライブラリを使う方法を説明します。
standaloneとAndroidについては、unity公式に解説がありますが、わかりづらかったので使い方をまとめました。
アプリの内容は「c++ライブラリを使って計算した結果を表示する」という簡単なものです。

# 環境
- 開発環境 + standaloneアプリ環境
    - ubuntu 16.04 
    - c++ライブラリ
        - c++ 11
        - cmake
    - unity
        - 2019.4.17f1
- Androidスマホ
    - xperia 5
- Oculus Quest 2

# C++ライブラリの開発
初めにC++ライブラリを開発します。

## ライブラリ
今回はテスト目的なので、引数で渡された２つの整数を加算する、という簡単なライブラリを開発しました。
クラス内のでのインスタンス生成と、その関数呼び出しを試したかったので、２つのクラスを用意し、１つのクラスからもう１つのクラスを使うようにしました。

```calculator.cpp
#include "calculator.h"

int Calculator::calc(const int a, const int b) {
  return calculator2_.calc2(a, b);
}
```

```calculator.h
#ifndef __CALCULATOR_H__
#define __CALCULATOR_H__

#include "calculator2.h"

class Calculator {
 public:
  Calculator() {}
  int calc(const int a, const int b);

 private:
  Calculator2 calculator2_;
};

#endif  // __CALCULATOR_H__
```

```calculator2.cpp
#include "calculator2.h"

int Calculator2::calc2(const int a, const int b) {
  return a * b;
}
```

```calculator2.h
#ifndef CALCULATOR2_H_
#define CALCULATOR2_H_

class Calculator2 {
 public:
  Calculator2() {}
  int calc2(const int a, const int b);
};

#endif  // CALCULATOR2_H_
```

## wrapperの実装
ネームマングリングの問題を回避するため、c言語でwrapperを実装します。
c#からc++の関数を直接呼び出すことができないためc言語でwrapperを用意し、このwrapperからc++の関数を呼び出すようにします。
c#からはこのwrapperを呼び出します。

ネームマングリングについては、分かりやすく解説している記事やサイトが多くあるのでここでは説明しません。

```wrapper.cpp
#include "wrapper.h"
#include "calculator.h"

extern "C" {

Calculator* pCalc;
void Test_constructor() { pCalc = new Calculator(); }
int Test_calc(int a, int b) { return pCalc->calc(a, b); }
void Test_destructor() { delete pCalc; }

}
```

```wrapper.h
#ifndef WRAPPER_H_
#define WRAPPER_H_

#ifdef __cplusplus
extern "C" {
#endif

void Test_constructor();
int Test_calc(int a, int b);
void Test_destructor();

#ifdef __cplusplus
}
#endif

#endif  // WRAPPER_H_
```

## 動作確認
standaloneアプリ、androidアプリのぞれぞれで異なる方法でビルドする必要があるため後ほど詳しくは後ほど説明しますが、
事前にコンパイルチェックと動作確認はしておいたほうが、後ほど手戻りが少なくてすみます。
動作確認は以下のファイルを用意して行いました。

```main.c
#include "wrapper.h"
#include <stdio.h>

int main(int argc, char *argv) {
    int a;
    int b;
    int c;

    a = 2;
    b = 3;

    Test_constructor();
    c = Test_calc(a, b);
    Test_destructor();

    printf("%d\n", c);

    return 0;
}
```

```CMakeLists.txt
cmake_minimum_required(VERSION 2.8)

project(calc)

set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11")

include_directories(lib)
add_library(calc SHARED wrapper.cpp lib/calculator.cpp lib/calculator2.cpp)

add_executable(a.out main.c)
target_link_libraries(a.out calc)
```

```
# Build
$ cmake .
$ make

# 実行
./a.out
6
```

# unityアプリの開発
standaloneアプリとandroidアプリでは、unityアプリの実装方法は同じです。
異なるのはアプリのビルド設定と、ライブラリのビルド方法になります。

unity(C#)側から整数をライブラリの関数に渡しその結果をテキストとして表示する、というアプリを実装します。

## standaloneアプリとandroidアプリ共通部の実装
### unityアプリの実装
unityアプリ実装では、特に特別な手順はありません。

1. アプリの種類で`3D`を選択し、新規プロジェクトを作成します。
1. Hierarchy上で右クリックし、`UI->Text`と選択し、textオブジェクトを作成します。
1. Hierarchy上で右クリックし、`Create Emply`を選択しオブジェクトを作成します。作成したオブジェクトの名前を変更します。自分は`CalcObject`としました。

### c++ライブラリ呼び出しスクリプトの実装
c++ライブラリを呼び出し、その計算結果をテキストオブジェクトに表示するC#スクリプトを実装します。
まず、Assetsフォルダ下に`Scripts`というフォルダを作成し、`Scripts`フォルダの下に`Calc.cs`というファイルを作成します。
このファイルの内容は以下の通りです。

```Calc.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
using System.Runtime.InteropServices;

public class Calc : MonoBehaviour
{

    [DllImport("libcalc.dll")] public static extern void Test_constructor();
    [DllImport("libcalc.dll")] public static extern int Test_calc(int a, int b);


    public GameObject test_obj = null;
    private int a = 0;
    private int ret;

    void Start()
    {
        Test_constructor();
    }

    void Update()
    {
        ret = Test_calc(a, 2);
        a++;

        Text text = test_obj.GetComponent<Text> ();
        text.text = ret.ToString();
    }
}
```
c++ライブラリを使うために必要な実装は以下になります。
以下を行うことで、C#スクリプト内でc++ライブラリ（厳密にはwrapperの関数）を呼び出せるようになります。

- `using System.Runtime.InteropServices;` : DllImportを使うために必要になります
- `DllImport` : c++ライブラリの関数をインポートし、使えるようにします。()内はインポートするファイル名です。これは関数ごとに必要になります。呼び出すことができる関数はwrapperで実装された関数です。

この`Calc.cs`を`CalcObject`に追加します。
すると、`text`というパラメータが表示されるので、`Canvas`下の`Text`をドラッグ&ドロップします。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/293692/9b66cfdc-e6fc-213a-b016-03c237894861.png)


## standaloneアプリの実装
### c++ライブラリのビルド
開発環境（自分の場合はubuntu 16.04）上で動作するライブラリをビルドします。開発環境上でそのままライブラリをビルドすればよいです。
自分は以下のCMakeファイルを作成し、ビルドしました。
なお、C#から使うためには動的ライブラリである必要があります（macを除く）。そのためadd_libraryのオプションで`SHARED`としています。

``` CMakeLists.txt
cmake_minimum_required(VERSION 2.8)
project(calc)

set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11")
add_library(calc SHARED wrapper.cpp calculator.cpp calculator2.cpp)
```

ビルドが成功すれば、`libcalc.so`というファイルが生成されます。
Assetsフォルダ化に`Assets`-> `Plugins` -> `x86_64` -> `ubuntu`という階層でフォルダを作成し、このフォルダに生成された`libcalc.so`を置きます。

### pluginのロード設定
この後、Android用のライブラリもビルドして`Plugins`フォルダ下に置くことになります。そうすると、同名のファイルがあるよ、というエラー  
  「unity That means one or more plugins are set to be compatible with Editor. Only one plugin at the time can be used by Editor.」  
がでます。
これを防ぐため、プラグインごとに使用する環境を設定します。

`Project`ウィンドウでプラグイン（今回はAssets/Plugins/x86_64/ubuntu/libcalc.so）を選択します。
右のインスペクタウィンドウで以下のように選択し、Applyボタンを押下します。
- Include Platforms : Editor, Standalone
- Platform Settings : Linux x86_64

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/293692/f50fefbc-de5d-c359-2f9d-8e5a07f4118e.png)

### unityアプリのビルド設定
standaloneアプリを実装する場合は、プロジェクト生成時のデフォルトの設定のままでよいです。

### ビルド&実行
通常のunityアプリの開発と同じように、`build and run`からビルドします。
アプリ実行後、画面上に数値が表示され、それが増加していけば正しく動作しています。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/293692/1c4366d0-d121-a574-e132-e0a21454848c.png)

## スマホ用Androidアプリの実装
Androidアプリ用のc++ライブラリをビルドするにはAndroidStudioを使う必要があります。
そのため最初にAndroid Studioのインストール方法と、次にAndroidプロジェクトを作成してビルドする方法を説明します。

### AndroidStudioのインストール
- [公式](https://developer.android.com/studio?hl=ja)よりAndroid Studioをダウンロードします。自分は`4.2.1 for Linux 64-bit`をダウンロードしました。
- ダウンロードしたフォルダを解凍し、Android Studioを実行します。

```
$ tar -xzvf android-studio-ide-202.7351085-linux.tar.gz
$ cd android-studio/bin/
$ ./studio.sh
```
初めてインストールする場合はセットアップウィザードが表示されるので画面に従って進めます。設定はデフォルトのままで問題ありません。
自分は、特定のバージョンのAndroid SDKをインストールしたかったので、Android 11.0とAndroid 7.1.1をインストールしました。

### プロジェクト作成
- Android Studio起動後、`Create Project`からプロジェクトを作成します。プロジェクトのテンプレートには`C++ Native`を選択します。
- プロジェクトを作成したら、c++ライブラリのビルドに必要な`Android NDK`をインストールします。
    - メニューの`Tool` -> `SDK Manager`を開く
    - `SDK Tools` タブ -> `NDK (side by side)`と`CMake`を選択し、`Apply`を押下

### c++ライブラリのビルド
- 上で作成した、c++のソースコードを、`app/src/main/cpp`フォルダ下に置きます。自分はwrapperと他のソースコードを分けたかったため、libというフォルダも作りました。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/293692/09942d27-028c-0b58-a5e2-891294891da2.png)

- `app/src/main/cpp`フォルダ化に`CMakeLists.txt`というファイルを作成し以下の内容にします。（コメントは省略）

```CMakeLists.txt
project("cpplib") # プロジェクト名は、Androidプロジェクト名となっているので、環境ごとに異なります。

set(TARGET_LIB "calc") 

include_directories(lib)

add_library(${TARGET_LIB}
            SHARED
#            native-lib.cpp
            wrapper.cpp
            lib/calculator.cpp
            lib/calculator2.cpp
)

find_library(LOG_LIB log)

target_link_libraries(${TARGET_LIB} ${LOG_LIB})
```

1つ注意点としては`native-lib.cpp`を使わないことです。これはJavaからcのライブラリを呼び出すためのコードが含まれていますが、今回はJavaを使用しないのでライブラリには含めません。

- プロジェクトのビルド設定をします。`app/src`フォルダ下の`build.gradle`を開きます。（`build.gradle`は２つファイルがあるので注意してください）
    - SDKのバージョンを確認し、異なっていたら修正します。自分は、Android 11(version 30)とAndroid 7.1.1(version 25)をインストトールしたので、以下のようにしました。

```build.gradle
android {
    compileSdkVersion 30
    buildToolsVersion "30.0.3"

    defaultConfig {
        applicationId "com.xxx.xxx" # 環境ごとに異なります
        minSdkVersion 25
        targetSdkVersion 30
```

- メニューの`Build` -> `Make Project`からビルドします。なお、デフォルトでは`debug`ビルドとなっています。`release`ビルドをしたい場合は、`Build Variants`の`Active Build Varians`で`Release`を選択します。  
  ビルドに成功したら`app/build/intermediates/cmake/release(もしくはdebug)/obj`フォルダ化にライブラリが出力されます。ライブラリはcpuアーキテクチャごとに生成されるため、`arm64-v8a, armeabi-v7a, x86, x86_64`の4つのフォルダの下に、それぞれライブラリが出力されています。

### pluginのロード設定
上記で出力されたライブラリをunityの`Assets/Plugins`フォルダ下にコピーします。そして先ほどと同様に、プラグインごとに環境を設定します。
- arm64-v8a
    - Include Platforms : Android
    - Platform Settings : CPU ARM64
- armeabi-v7a
    - Include Platforms : Android
    - Platform Settings : CPU ARMv7

### unityアプリのビルド設定
通常のAndroidアプリのビルド方法と同じです。

- メニューの`File` -> `Build Settings`を選択します
- `Platform`で`Android`を選択し`Switch Platform`を押下します
- `Player Settings`を押下します
- `Player` -> `Other Settings`を選択します
    -  `Package Name` -> Defaultとなっている箇所に、任意の名前を設定します
    -  ｀Minimum　API Level` -> Android versionを指定します。これは対象とするデバイスに合わせて設定してください

なお、自分はstandaloneアプリと別sceanとして保存したかったので、最初にメニューの`Save as`から別名のsceanとして保存しました。

### ビルド&実行
AndroidスマホをPCに接続し、`Build & Run`を押下します。
なお、事前にAndroidスマホ側でUSBデバッグを有効にしておいてください。

## Oculus Quest2用Androidアプリの実装
unityのビルド設定と、Canvasの位置調整以外は上記のスマホ用Androidアプリと同じです。

### unityアプリのビルド設定
Oculus Quest 2のビルド設定は、多くの記事で説明されているのでここでは詳しく説明しません。

- Build Settingの変更
    - File -> Build Settingを選択
    - PlatformでAndroidを選択し、Switch Platformボタンを押す
- Oculus Integrationをインポート
    - Asset StoreからOculus Integration for Unityをインポートする
- XR Plug-in Managementのインストール
    - Window -> Package Managerを選択
    - XR Plug-in Managementを選択し、インストール
- Player Settingの変更
    - Edit -> Project Settingsを選択
    - Player -> Other Settingsを選択
    - Package Nameに任意の名前を入力
    - Minimum API LevelをAndroid 7.1 'Nougat' (API level 25)にする
    - Api Compatibility Levelを.NET 4.xにする
    - XR Plug-in Managementを選択
    - Android と Standalone の “Oculus” にチェック 入れる

### unityアプリの変更
- カメラの変更
    - HierarchyからMain Cameraを削除
    - ProjectからAssets/Oculus/VR/Prefabsを選択し、OVRCameraRigをHierarchyドラッグ＆ドロップ
    - OVRCameraRigのOVR Managerスクリプトで、Target Devicesで、Quest2にチェック
- Canvasの設定変更
    - HierarchyでCanvasを選択
        - Render ModeをWorld Spaceに変更
        - Event Cameraに、CenterEyeAnchor (Camera) を設定
    - positionを(0, 0, 40)に設定
    - width, heightを(100, 50)に設定
- Textの設定変更
    - positionを(0, 0, 0)に設定
    - width, heightを(100, 50)に設定

今回はTextが画面に表示されればよかっただけなので、Canvas, Textの位置、大きさは適当に設定しました。

### ビルド&実行
Oculus Quest 2をPCに接続し、`Build & Run`を押下します。

# 参考
- [unity公式　ネイティブプラグイン](https://docs.unity3d.com/ja/2018.4/Manual/NativePlugins.html)
- [unity公式　Android の ネイティブ (C++) プラグイン](https://docs.unity3d.com/ja/2019.4/Manual/AndroidNativePlugins.html)
- [unity公式　プラグインインスペクタ](https://docs.unity3d.com/ja/2019.4/Manual/PluginInspector.html)
- [ネームマンドリング wiki](https://ja.wikipedia.org/wiki/%E5%90%8D%E5%89%8D%E4%BF%AE%E9%A3%BE#C++%E3%81%8B%E3%82%89%E3%83%AA%E3%83%B3%E3%82%AF%E3%81%99%E3%82%8B%E9%9A%9B%E3%81%AE_C_%E3%82%B7%E3%83%B3%E3%83%9C%E3%83%AB%E3%81%AE%E6%89%B1%E3%81%84)