はじめに
C++で作成したプラグインをUnityで利用する方法は調べると出てきますが、複数のプラットフォームでの対応の仕方がまとまっている記事がなかったので今回まとめたいと思い立ちました。
主要なプラットフォームのテンプレートを作成してみました。
こちらのテンプレートはC#側に計算結果や文字列を渡して表示するだけのサンプルになります。
主要なプラットフォーム
何が主要なのかと言うのは私の主観になってしまいますが、ソーシャルゲームアプリを開発する上では以下のプラットフォームが対応されれば十分だと考えています。
- Windows
- MacOS
- iOS
- Android
ビルド周りに関しては、Windows以外はMacでのやり方で書いていきます。
C++のプラグインがなぜ必要なのか?
普段の開発で自前で実装することは少ないと思いますが、必要になる理由は大きく以下の2つがあるかと思います。
1.処理速度が重要になる処理がある
2.C/C++で書かれたライブラリを利用したい
個人的には1よりも2の理由が大きいと感じています。
OpenCVやSQLiteのようにC/C++で作られたライブラリをC#で利用したいことはよくあります。
上記2つのライブラリはすでにUnityで使用できるように対応されてはいますが、自前で実装したかったり、誰も対応していないライブラリを使う必要が出てくるケースも出てくると思います。
UnityCppPluginSampleの説明
サンプルのバージョン情報
Unity 2020.3.15f2
XCode 12.4
VisualStudio Community 2019 16.11.13
サンプル内容
実用性はほとんどないですが、
- 2つの値の足し算
- 文字列を生成して返す
のサンプルになっています。
ツリー構造
├── 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プロジェクト作成時の参考
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のビルドでビルドを実行
3.UnityのPluginsに配置して設定
dllの出力先はNative\projects\windows\x64\Release\windows.dll
Assets/Plugins/x64内に格納
32bit版が必要な場合は、x86もビルドして設定
MacOS
ビルドするためにはXCodeが必要で、GUIでのビルド方法を書きます。
1.Native/projects/osx/osx.xcodeprojをXCodeで開く
2.Product/Buildでビルド実行
M1Macが手元にないので、検証できないのですが、ビルドターゲットにAppleSiliconと書いてあるのでM1もいける🤔
3.ビルドの成果物の場所を調べる
4.UnityのPluginsに配置して設定
iOS
ビルドするためにはXCodeが必要で、GUIでのビルド方法を書きます。
MacOSとほとんどやり方は同じです。
1.Native/projects/ios/ios.xcodeprojをXCodeで開く
2.Product/Buildでビルド実行
こちらからターゲットデバイスを選択できますが、サンプルに入っているプラグインはシミュレーター用になっています。
所持しているiPhoneとXCodeのバージョンが合わず、直接つないでデバイスにインストールができず・・・
検証が可能になりましたら検証します。
3.ビルドの成果物の場所を調べる
4.UnityのPluginsに配置して設定
Android
Androidのビルドに関しましては、こちらの記事を参考にさせていただきました。
一つ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
2.シェルスクリプトを用意
Native/projects/android/build.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ファイルを作成しましたが、詳しくないので必要に応じて調べる用にお願いします。
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++のソース
ソースファイル(cpp)は、文字列の渡し方以外特筆する点はないので今回は省きます。
プラットフォームが異なっても1ファイルで対応できるようにしています。
#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のみ定義しています。
参考
_WINDOWSはVisualStudioプロジェクトのDefineSymbolで定義されます。
_CRT_SECURE_NO_WARNINGSはstd::strcpyを使用するとビルドが止められるので、定義しています。
C#のソース
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++は一つのライブラリを読み込むためにも大変なので、こちらも機会があれば記事にしていきたいと思います。