概要
Android端末の複数センサを用いて算出された回転ベクトルをUnityから取得する方法についてまとめます.
やりたいこと
Android OS は複数センサの情報を複合した回転ベクトルを算出できるので,Unityからその情報を取得したい.
解決手法
UnityからAndroidの回転ベクトルを取得するAndroid Pluginを作成.
環境
- Unity 2022.3.4f1
- Windows 11
- Google Pixel 3a
- Android バージョン12
実装
Androidプラグインの作成にはいくつかの方法がありますが,今回はJavaファイルをUnityのプロジェクト内に配置してプラグインとして読み込ませる方法をとることにしました.
プラグインの再利用や配布を考えるなら,直接Javaファイルを配置するのではなく,Android Libraries や Android Archives (AAR) を利用した方法の方が適していると思います.
Unityプロジェクトの準備
まずunityで新規プロジェクトを立ち上げます.
新規プロジェクトが立ち上がったら,File > Build Settings > Android
からswitch platform
を選択して,プラットフォームをAndroidに切り替えてください.
次に実機デバッグ用に Android Logcatという拡張機能を導入します.
Android LogcatはPCに接続されたアンドロイド端末のログをUnityの専用タブから出力してくれます.
Window > Packagemanager
を起動して,左上のPackages:○○
をPackages:Unity Registry
に切り替え,右上からAndroid Logcatと検索してインストールしてください.
Javaファイルの作成
Androidプラグインとして読み込むJavaファイルを作成します.
Unityプロジェクト内にAssets/Plugins/Android
というフォルダを作成してください(Pluginsフォルダがない場合はそれも作成).
Assets/Plugins/Android
に以下のようなRotation.java
ファイルを作成します.
package Assets.Plugins.Android;
import android.content.Context;
import android.hardware.Sensor;
import android.hardware.SensorEvent;
import android.hardware.SensorEventListener;
import android.hardware.SensorManager;
import android.os.Bundle;
import com.unity3d.player.UnityPlayerActivity;
public class rotation extends UnityPlayerActivity implements SensorEventListener {
private SensorManager sensorManager;
private Sensor rotationSensor = null;
private Float sensorX = 0f;
private Float sensorY = 0f;
private Float sensorZ = 0f;
private Float sensorW = 1f;
@Override
protected void onCreate(Bundle savedInstanceState){
super.onCreate(savedInstanceState);
sensorManager = (SensorManager)getSystemService(Context.SENSOR_SERVICE);
rotationSensor = sensorManager.getDefaultSensor(Sensor.TYPE_ROTATION_VECTOR);
sensorManager.registerListener(this,rotationSensor, SensorManager.SENSOR_DELAY_NORMAL);
}
@Override
public void onSensorChanged(SensorEvent event) {
sensorX = event.values[0];
sensorY = event.values[1];
sensorZ = event.values[2];
sensorW = event.values[3];
}
@Override
public void onAccuracyChanged(Sensor sensor, int accuracy) {
}
public float[] getRotation(){
return new float[]{sensorX, sensorY, sensorZ, sensorW};
}
}
コードの解説
public class rotation extends UnityPlayerActivity implements SensorEventListener{
//省略
}
Androidでセンサデータを見るにはActivity
クラスとSensorEventListenser
インターフェースを継承する必要があります.
今回はUnityが提供するActivity
の派生クラスUnityPlayerActivity
を継承しました.UnityPlayerActivity
に関する詳しい説明はこちらを確認してください.
@Override
protected void onCreate(Bundle savedInstanceState){
super.onCreate(savedInstanceState);
sensorManager = (SensorManager)getSystemService(Context.SENSOR_SERVICE);
rotationSensor = sensorManager.getDefaultSensor(Sensor.TYPE_ROTATION_VECTOR);
sensorManager.registerListener(this,rotationSensor, SensorManager.SENSOR_DELAY_NORMAL);
}
この部分ではセンサを取得してイベントリスナに登録しています.
取得できるセンサ情報はAndroid公式ドキュメントから確認できます.今回は回転ベクトルSensor.TYPE_ROTATION_VECTOR
を取得しました.
@Override
public void onSensorChanged(SensorEvent event) {
sensorX = event.values[0];
sensorY = event.values[1];
sensorZ = event.values[2];
sensorW = event.values[3];
}
onSensorChanged
は新しいSensorEvent
が発生するたびに呼び出されます.
eventからセンサの値を取得してセンサ情報を更新します.
ちなみに今回取得する回転ベクトルはオイラー角ではなくクォータニオン形式なので,変数は4つです.
public float[] getRotation(){
return new float[]{sensorX, sensorY, sensorZ, sensorW};
}
Unityから利用する関数です.回転ベクトルの配列を返します.
C#ファイルの作成
Androidプラグインを呼び出すC#のプログラムを書きます.
UnityのAssets
フォルダ内にScripts
フォルダを作成し,その中にRotationManager.cs
ファイルを作成します.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class RotationManager : MonoBehaviour
{
private AndroidJavaObject activity;
// Start is called before the first frame update
void Start()
{
AndroidJavaClass unityPlayer = new AndroidJavaClass("com.unity3d.player.UnityPlayer");
activity = unityPlayer.GetStatic<AndroidJavaObject>("currentActivity");
}
// Update is called once per frame
void Update()
{
var rot = activity.Call<float[]>("getRotation");
Debug.Log(new Quaternion(rot[0],rot[1],rot[2],rot[3]));
}
}
コードの解説
void Start()
{
AndroidJavaClass unityPlayer = new AndroidJavaClass("com.unity3d.player.UnityPlayer");
activity = unityPlayer.GetStatic<AndroidJavaObject>("currentActivity");
}
C#からのJavaやKotlinのプラグインの呼び出しにはAndroidJavaObject
と AndroidJavaClass
を使用します.
上記のコードではunityPlayer.GetStatic
を用いて,com.unity3d.player.UnityPlayer
クラスの静的なフィールドcurrentActivity
を取得しています.
後述するエントリポイントの設定により,currentActivity
にはRotation.java
で作成したrotation
クラスのインスタンスが登録されています.
void Update()
{
var rot = activity.Call<float[]>("getRotation");
Debug.Log(new Quaternion(rot[0],rot[1],rot[2],rot[3]));
}
毎フレーム回転ベクトルを出力するコードです.
activity
にはrotation
のオブジェクトが格納されているので,activity.Call<float[]>("getRotation")
でgetRotation
メソッドを実行し,回転ベクトルを取得します.
カスタムマニフェストの作成
カスタムマニフェストを作成し,プラグインで作成したアクティビティをアプリケーションのエントリポイントに設定します.
Edit > Project Settings
よりProject Settings
ウィンドウを起動して,Player
からAndroidのマークを選び,Publishing Settings > Build > Custom Main Manifest
にチェックを入れて有効化してください.
Assets/Plugins/Android
にAndroidManifest.xml
というファイルが生成されるため,AndroidManifest.xml
を以下のように書き換えてエントリポイントを変更します.
<?xml version="1.0" encoding="utf-8"?>
<manifest
xmlns:android="http://schemas.android.com/apk/res/android"
package="com.unity3d.player"
xmlns:tools="http://schemas.android.com/tools">
<application>
- <activity android:name="com.unity3d.player.UnityPlayerActivity"
+ <activity android:name="Assets.Plugins.Android.rotation"
android:theme="@style/UnityThemeSelector">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
<meta-data android:name="unityplayer.UnityActivity" android:value="true" />
</activity>
</application>
</manifest>
アプリケーションのビルドと動作テスト
UnityエディタのHierarchyウィンドウにEmptyオブジェクトを追加し,作成したC#スクリプトをアタッチします.
Android端末をPCに接続し,File > Build and Run
からビルドを実行します.
ビルドが成功すると,Android端末でアプリケーションが起動してUnityエディタにAndroid Logcatのウィンドウが立ち上がります.
以下の画像ようにクォータニオンが出力されたら成功です.