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++ライブラリを開発します。
ライブラリ
今回はテスト目的なので、引数で渡された2つの整数を加算する、という簡単なライブラリを開発しました。
クラス内のでのインスタンス生成と、その関数呼び出しを試したかったので、2つのクラスを用意し、1つのクラスからもう1つのクラスを使うようにしました。
#include "calculator.h"
int Calculator::calc(const int a, const int b) {
return calculator2_.calc2(a, b);
}
#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__
#include "calculator2.h"
int Calculator2::calc2(const int a, const int b) {
return a * b;
}
#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を呼び出します。
ネームマングリングについては、分かりやすく解説している記事やサイトが多くあるのでここでは説明しません。
#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; }
}
#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アプリのぞれぞれで異なる方法でビルドする必要があるため後ほど詳しくは後ほど説明しますが、
事前にコンパイルチェックと動作確認はしておいたほうが、後ほど手戻りが少なくてすみます。
動作確認は以下のファイルを用意して行いました。
#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;
}
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アプリ実装では、特に特別な手順はありません。
- アプリの種類で
3D
を選択し、新規プロジェクトを作成します。 - Hierarchy上で右クリックし、
UI->Text
と選択し、textオブジェクトを作成します。 - Hierarchy上で右クリックし、
Create Emply
を選択しオブジェクトを作成します。作成したオブジェクトの名前を変更します。自分はCalcObject
としました。
c++ライブラリ呼び出しスクリプトの実装
c++ライブラリを呼び出し、その計算結果をテキストオブジェクトに表示するC#スクリプトを実装します。
まず、Assetsフォルダ下にScripts
というフォルダを作成し、Scripts
フォルダの下に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
をドラッグ&ドロップします。
standaloneアプリの実装
c++ライブラリのビルド
開発環境(自分の場合はubuntu 16.04)上で動作するライブラリをビルドします。開発環境上でそのままライブラリをビルドすればよいです。
自分は以下のCMakeファイルを作成し、ビルドしました。
なお、C#から使うためには動的ライブラリである必要があります(macを除く)。そのためadd_libraryのオプションでSHARED
としています。
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
unityアプリのビルド設定
standaloneアプリを実装する場合は、プロジェクト生成時のデフォルトの設定のままでよいです。
ビルド&実行
通常のunityアプリの開発と同じように、build and run
からビルドします。
アプリ実行後、画面上に数値が表示され、それが増加していけば正しく動作しています。
スマホ用Androidアプリの実装
Androidアプリ用のc++ライブラリをビルドするにはAndroidStudioを使う必要があります。
そのため最初にAndroid Studioのインストール方法と、次にAndroidプロジェクトを作成してビルドする方法を説明します。
AndroidStudioのインストール
-
公式より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というフォルダも作りました。
-
app/src/main/cpp
フォルダ化に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
は2つファイルがあるので注意してください)- SDKのバージョンを確認し、異なっていたら修正します。自分は、Android 11(version 30)とAndroid 7.1.1(version 25)をインストトールしたので、以下のようにしました。
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)に設定
- HierarchyでCanvasを選択
- Textの設定変更
- positionを(0, 0, 0)に設定
- width, heightを(100, 50)に設定
今回はTextが画面に表示されればよかっただけなので、Canvas, Textの位置、大きさは適当に設定しました。
ビルド&実行
Oculus Quest 2をPCに接続し、Build & Run
を押下します。