UnityC++プラグイン各種プラットフォームのテンプレート

# はじめに
C++で作成したプラグインをUnityで利用する方法は調べると出てきますが、複数のプラットフォームでの対応の仕方がまとまっている記事がなかったので今回まとめたいと思い立ちました。

主要なプラットフォームのテンプレートを作成してみました。

https://github.com/k-okawa/UnityCppPluginSample

こちらのテンプレートはC#側に計算結果や文字列を渡して表示するだけのサンプルになります。

## 主要なプラットフォーム
何が主要なのかと言うのは私の主観になってしまいますが、ソーシャルゲームアプリを開発する上では以下のプラットフォームが対応されれば十分だと考えています。

- Windows
- MacOS
- iOS
- Android

ビルド周りに関しては、Windows以外はMacでのやり方で書いていきます。

## C++のプラグインがなぜ必要なのか？
普段の開発で自前で実装することは少ないと思いますが、必要になる理由は大きく以下の２つがあるかと思います。

1.処理速度が重要になる処理がある
2.C/C++で書かれたライブラリを利用したい

個人的には1よりも2の理由が大きいと感じています。

OpenCVやSQLiteのようにC/C++で作られたライブラリをC#で利用したいことはよくあります。

上記２つのライブラリはすでにUnityで使用できるように対応されてはいますが、自前で実装したかったり、誰も対応していないライブラリを使う必要が出てくるケースも出てくると思います。

# [UnityCppPluginSample](https://github.com/k-okawa/UnityCppPluginSample)の説明

## サンプルのバージョン情報
Unity 2020.3.15f2
XCode 12.4
VisualStudio Community 2019 16.11.13

## サンプル内容

実用性はほとんどないですが、
- 2つの値の足し算
- 文字列を生成して返す

のサンプルになっています。

![Screen Shot 2022-05-06 at 15.10.59.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/188476/07237dda-4e78-f738-236a-3e4a348ebae0.png)


## ツリー構造

```
├── Native // C++のソースとビルドに必要なプロジェクト
│   ├── projects // 各プラットフォームのプロジェクト
│   │   ├── android
│   │   ├── ios
│   │   ├── osx
│   │   └── windows
│   └── src // ソースコード
├── Unity
│   ├── Assets
│   │   ├── Plugins // ビルドしたプラグインを格納する場所
```

## 各プラットフォームのプラグインタイプ

|プラットフォーム|拡張子|
|-------------|-----|
|Windows|xxx.dll|
|MacOS|xxx.bundle|
|iOS|xxx.a|
|Android|xxx.so|

## 各プラットフォームのプロジェクト作成

### Windows
VisualStudioプロジェクト作成時の参考

https://note.com/yosse95ai/n/n3e323246442e

### MacOS
XcodeでmacOS/Bundleのプロジェクトを作成

### iOS
XCodeでiOS/StaicLibrary Objective-Cのプロジェクトを作成

### Android
mkファイルを作成して、NDKを使用してコマンドビルド


## プラットフォームごとのビルド方法

ProjectSettingsのPlayer/OtherSettings/ScriptingBackendはIL2CPPで設定してください。

### Windows
#### 1.Native/projects/windows/windows.slnをVisualStudioで開く

#### 2.ビルド/windowsのビルドでビルドを実行

設定はReleaseのx64
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/188476/7eb87450-0247-0821-2877-6294ab70b831.png)

#### 3.UnityのPluginsに配置して設定

dllの出力先はNative\projects\windows\x64\Release\windows.dll

Assets/Plugins/x64内に格納

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/188476/29e7d739-e0a7-42de-574c-2dbd3519f42f.png)

32bit版が必要な場合は、x86もビルドして設定

### MacOS
ビルドするためにはXCodeが必要で、GUIでのビルド方法を書きます。

#### 1.Native/projects/osx/osx.xcodeprojをXCodeで開く

#### 2.Product/Buildでビルド実行

M1Macが手元にないので、検証できないのですが、ビルドターゲットにAppleSiliconと書いてあるのでM1もいける🤔
![Screen Shot 2022-05-06 at 16.05.30.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/188476/c2a808d9-f893-d491-51e9-70840f247b40.png)


#### 3.ビルドの成果物の場所を調べる

Products/osx.bundleを選択すると
![Screen Shot 2022-05-06 at 15.24.21.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/188476/8e4b7db2-d9e4-323f-edb1-9a406476d3ae.png)

インスペクターから場所を知ることができます。
![Screen Shot 2022-05-06 at 15.25.10.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/188476/c6b81ac7-50c3-416d-6083-e430d1978b80.png)

#### 4.UnityのPluginsに配置して設定

Assets/Plugins/OSXの中にいれる
![Screen Shot 2022-05-06 at 15.29.35.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/188476/6681af11-8e64-154b-76ef-39c499e9be78.png)


### iOS
ビルドするためにはXCodeが必要で、GUIでのビルド方法を書きます。
MacOSとほとんどやり方は同じです。

#### 1.Native/projects/ios/ios.xcodeprojをXCodeで開く

#### 2.Product/Buildでビルド実行

こちらからターゲットデバイスを選択できますが、サンプルに入っているプラグインはシミュレーター用になっています。
![Screen Shot 2022-05-06 at 15.57.22.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/188476/93a7769c-b7f3-c21a-ee21-9191d06f245b.png)

所持しているiPhoneとXCodeのバージョンが合わず、直接つないでデバイスにインストールができず・・・

検証が可能になりましたら検証します。

#### 3.ビルドの成果物の場所を調べる

Products/libios.aを選択すると
![Screen Shot 2022-05-06 at 15.33.39.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/188476/0e60f044-5f38-5088-d8d4-7f8595290699.png)

インスペクターから場所を知ることができます。
![Screen Shot 2022-05-06 at 15.47.22.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/188476/3716cd8b-aeda-2a5d-ab70-9b542b0c3d00.png)

#### 4.UnityのPluginsに配置して設定

Assets/Plugins/iOSの中に入れる
![Screen Shot 2022-05-06 at 15.49.58.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/188476/c81d8732-f332-bb2d-ef90-9c6e67a45e95.png)

### Android

Androidのビルドに関しましては、こちらの記事を参考にさせていただきました。

https://qiita.com/br_branch/items/f8d532e078ca7a2081e7

一つAndroidでハマった点がありまして、最初プラグインの名前を**SamplePlugin.so**という名前で格納して検証したところうまく読み込まれませんでした。
apkに入っていた他のプラグインの名前に合わせて**libsampleplugin.so**としたところうまく読み込まれました。
C#側で読み込むときはlibとsoの部分は除いて**sampleplugin**で読み込めます。
他に**libSamplePlugin.so**で格納してC#側で**SamplePlugin**としてもうまく読み込まれなかったので、全部小文字にしたほうが良いかもしれません。

#### 1.NDKのパスを調べる
NDKを手動でダウンロードして来ても良いのですが、UnityのPreferenceの**Android NDK installedwith Unity**からパスを知ることができます。
自分の環境の場合は以下のパスでした

```
/Applications/Unity/Hub/Editor/2020.3.15f2/PlaybackEngines/AndroidPlayer/NDK
```

![Screen Shot 2022-05-06 at 16.19.27.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/188476/b7277d62-cd6e-b5c9-8c88-585e72194e8b.png)


#### 2.シェルスクリプトを用意

Native/projects/android/build.shを作成します

```sh
ANDROID_NDK_ROOT=/Applications/Unity/Hub/Editor/2020.3.15f2/PlaybackEngines/AndroidPlayer/NDK

$ANDROID_NDK_ROOT/ndk-build NDK_PROJECT_PATH=. NDK_APPLICATION_MK=./Application.mk
```

ANDROID_NDK_ROOTは各々の環境のパスを指定してください。

Macでビルドすることを想定して、mkファイルを作成しましたが、詳しくないので必要に応じて調べる用にお願いします。

https://github.com/k-okawa/UnityCppPluginSample/tree/master/Native/projects/android

#### 3.シェルスクリプト実行

Native/projects/android/に移動して
```
./build.sh
```

権限エラーが出た場合は
```
chmod 777 ./build.sh
```

#### 4.Unityのプラグインに配置

ビルドはNative/projects/android/libs配下にそれぞれのアーキテクチャごとに出力されています。

arm64-v8a,armeabi-v7a,x86それぞれフォルダごと、UnityのAssets/Plugins/Androidに配置してください。

設定はインポート時に適切なものに設定されるはずです。

## [C++のソース](https://github.com/k-okawa/UnityCppPluginSample/tree/master/Native/src)
ソースファイル(cpp)は、文字列の渡し方以外特筆する点はないので今回は省きます。

プラットフォームが異なっても1ファイルで対応できるようにしています。

```library.hpp
#ifndef SOURCE_LIBRARY_HPP
#define SOURCE_LIBRARY_HPP

#ifdef _WINDOWS
    #define _CRT_SECURE_NO_WARNINGS
    #define DLL_EXPORT __declspec(dllexport)
#endif

#ifndef _WINDOWS
    #define DLL_EXPORT
#endif


extern "C"  {
    DLL_EXPORT int sum(int a, int b);
    DLL_EXPORT char* helloWorld();
};

#endif //SOURCE_LIBRARY_HPP
```

注目する点はDLL_EXPORTの定義で、Windowsのときだけ__declspec(dllexport)を定義しています。
dllは外に公開する関数を明示的に宣言するために必要で、dllをビルドするときに必要になるみたいです。
Windows以外のプラットフォームだとエラーになってしまうため、Windowsのみ定義しています。

**参考**

https://qiita.com/TrashBoxx/items/7cd0bf0b3f5bad60f709

_WINDOWSはVisualStudioプロジェクトのDefineSymbolで定義されます。

_CRT_SECURE_NO_WARNINGSはstd::strcpyを使用するとビルドが止められるので、定義しています。

## [C#のソース](https://github.com/k-okawa/UnityCppPluginSample/blob/master/Unity/Assets/Scripts/PluginTest.cs)

```PluginTest.cs
using UnityEngine;
using System.Runtime.InteropServices;

public class PluginTest
{
#if !UNITY_EDITOR && UNITY_IOS
    private const string PLUGIN_NAME = "__Internal";
#else
    private const string PLUGIN_NAME = "sampleplugin";
#endif
    
    [DllImport(PLUGIN_NAME)]
    public static extern int sum(int a, int b);

    [DllImport(PLUGIN_NAME)]
    public static extern string helloWorld();
}
```

iOSのみ__Internalで指定します。
他にもプラグインがあると関数名がかぶる可能性があるので、外出しの関数名は被らない名前にしたほうが良さそうです。

# 最後に
各プラットフォームごとにビルドが違えば、コンパイルエラーも違ったり骨が折れる作業ですね・・・
今回はHelloWorldプラグインサンプルを作成しましたが、実用的なプラグインを作るためには他のライブラリのインポートが必要になってきます。
C++は一つのライブラリを読み込むためにも大変なので、こちらも機会があれば記事にしていきたいと思います。