前回の「UnityでAndroidの機能を拡張する2つの手法とは」では、「Unity」が提供しているネイティブコードとの連携手段についてサンプルコードを交え解説しました。今回は応用編として、Bluetoothなどデバイスとの連携や、Javaライブラリとの連携について解説します。
Androidでは、Bluetoothデバイスを搭載したものが数多く存在します。現在のところ、Unity自体はBluetoothをサポートしていませんが、例えば連載第1回の「iOSアプリのAndroid移植も簡単なUnityの基礎知識」で紹介したUnityプラグインを利用するなどで、Bluetoothデバイスと連携するコードを実装できます。
本稿では、前回説明した「JNIでUnity側からJavaクラスを呼び出す」「Unityを実行しているActivity自体をカスタマイズ」の両方を使って連携を実現します。
まず「Unityを実行しているActivity自体をカスタマイズ」の手順を用いてEclipse上でAndroidプロジェクト「BluetoothSampleActivity」を作成します。
package com.example.bluetoothsample; import android.bluetooth.BluetoothAdapter; import android.bluetooth.BluetoothDevice; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.os.Bundle; import android.os.Handler; import android.os.Message; import android.util.Log; import com.unity3d.player.UnityPlayer; import com.unity3d.player.UnityPlayerActivity; public class BluetoothSampleActivity extends UnityPlayerActivity { // [1] private static final int FLAG_REQUEST_ENABLE_BT = 1; private static final int FLAG_BTEVENT_START_SEARCH = 1; private static BluetoothSampleActivity mBluetoothSampleActivity = null; // [2] private BluetoothAdapter mBluetoothAdapter = null; private BluetoothReceiver mBluetoothReceiver = null; private Handler mCommandHandler = null; /** Called when the activity is first created. */ @Override public void onCreate(Bundle savedInstanceState) { // [3] mBluetoothSampleActivity = this; // [4] mCommandHandler = new Handler(){ public void handleMessage(Message message){ switch(message.what){ case FLAG_BTEVENT_START_SEARCH: mBluetoothSampleActivity.checkBluetoothInternal(); } } }; super.onCreate(savedInstanceState); } @Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { // [5] if (requestCode == FLAG_REQUEST_ENABLE_BT) { if (resultCode == RESULT_OK) { UnityPlayer.UnitySendMessage("CallJavaCode", "BluetoothMessage", "BT Device has been turned on."); getLocalInformation(); } else if (resultCode == RESULT_CANCELED) { UnityPlayer.UnitySendMessage("CallJavaCode", "BluetoothMessage", "BT Device is disabled."); } } } public void checkBluetooth(){ // [6] mCommandHandler.sendMessage(Message.obtain(mCommandHandler, FLAG_BTEVENT_START_SEARCH, null)); } private void checkBluetoothInternal() { mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter(); if (mBluetoothAdapter == null) { UnityPlayer.UnitySendMessage("CallJavaCode", "BluetoothMessage", "BT Device Status: Not Supported."); } else { if (mBluetoothAdapter.isEnabled()) { UnityPlayer.UnitySendMessage("CallJavaCode", "BluetoothMessage", "BT Device Status: Enabled."); getLocalInformation(); } else { UnityPlayer.UnitySendMessage("CallJavaCode", "BluetoothMessage", "BT Device Status: Disabled."); Intent enableBTIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE); startActivityForResult(enableBTIntent, FLAG_REQUEST_ENABLE_BT); // [7] } } } private void getLocalInformation() { // [8] UnityPlayer.UnitySendMessage("CallJavaCode", "BluetoothMessage", "Step 2:Checking own BT device scan mode... " + mBluetoothAdapter.getName() + ":" + mBluetoothAdapter.getAddress()); switch (mBluetoothAdapter.getScanMode()) { case BluetoothAdapter.SCAN_MODE_CONNECTABLE: Log.d("BLUETOOTH","SCAN_MODE_CONNECTABLE"); break; case BluetoothAdapter.SCAN_MODE_CONNECTABLE_DISCOVERABLE: Log.d("BLUETOOTH","SCAN_MODE_CONNECTABLE_DISCOVERABLE"); break; case BluetoothAdapter.SCAN_MODE_NONE: Log.d("BLUETOOTH","SCAN_MODE_NONE"); break; } switch (mBluetoothAdapter.getState()) { case BluetoothAdapter.STATE_OFF: Log.d("BLUETOOTH","STATE_OFF"); break; case BluetoothAdapter.STATE_ON: Log.d("BLUETOOTH","STATE_ON"); break; case BluetoothAdapter.STATE_TURNING_OFF: Log.d("BLUETOOTH","STATE_TURNING_OFF"); break; case BluetoothAdapter.STATE_TURNING_ON: Log.d("BLUETOOTH","STATE_TURNING_ON"); break; } discoverDevices(); } private void discoverDevices() { // [9] UnityPlayer.UnitySendMessage("CallJavaCode", "BluetoothMessage", "Step 4:Searching Bluetooth devices..."); mBluetoothReceiver = new BluetoothReceiver(); registerReceiver(mBluetoothReceiver, new IntentFilter(BluetoothDevice.ACTION_FOUND)); mBluetoothAdapter.startDiscovery(); } class BluetoothReceiver extends BroadcastReceiver { @Override public void onReceive(Context context, Intent intent) { // [10] UnityPlayer.UnitySendMessage("CallJavaCode", "BluetoothMessage", "Found Bluetooth devices."); String action = intent.getAction(); if (BluetoothDevice.ACTION_FOUND.equals(action)) { BluetoothDevice device = intent .getParcelableExtra(BluetoothDevice.EXTRA_DEVICE); UnityPlayer.UnitySendMessage("CallJavaCode", "addBTDevice", device.getName() + "[" + device.getAddress() + "]"); } } } }
[1]では、UnityPlayerActivityを継承します。[2]では、staticなメンバ変数として本クラス自身を参照するための変数を定義しておきます。Unityからアクセスする際に、ここから本クラスにアクセスします。[3]では、本クラス用のonCreateメソッドです。本クラスが提供する関数を呼び出すためのハンドラを登録しています。[4]で、[2]の変数に自分自身を登録しています。
[5]では、BluetoothデバイスがOFFな場合にはOn/Offを切り替えるための設定画面をIntentで呼び出しています。[6]では、[5]で起動した設定画面においてBluetoothデバイスのOn/Offを設定後、戻ってきた際の処理を記載しています。
[7]でUnityから呼び出す関数を定義します。ここでは、[3]で登録したハンドラを呼び出しています。[8]では、Bluetoothデバイスの状態を確認し、周辺のBluetoothデバイスを検出するために「discoverDevices()」関数を呼び出します。[9]で、Bluetoothデバイス検出を開始します。[10]では、Bluetoothデバイスが1つ見つかるごとにコールバックで呼ばれています。
[11]では、UnityPlayer.UnitySendMessage()関数を用いて、Unity側の関数に文字列を渡しています。第1引数に指定されたGameObjectにひも付けられているクラス名、第2引数に指定された関数名、第3引数に指定された文字列が渡されます。
UnitySendMessage("CallJavaCode", "BluetoothMessage", "Found Bluetooth devices.")"
[12]では、[11]と同じくUnityPlayer.UnitySendMessage()関数を用いて、発見されたデバイス情報をUnity側にテキストデータとして送信しています。
using UnityEngine; using System .Collections; using System .Runtime.InteropServices; using System ; public class CallJavaCode : MonoBehaviour { private IntPtr BluetoothSampleActivityClass; // [1] private int ptr_checkBluetooth; // [2] private string announceString; private ArrayList deviceArray; void Start () { // attach our thread to the java vm; obviously the main thread is already attached but this is good practice.. JavaVM.AttachCurrentThread(); //Get pointer to BluetoothSampleActivity class // [3] IntPtr cls_Activity = JNI.FindClass("com/example/bluetoothsample/BluetoothSampleActivity"); Debug.Log ("cls_Activity = " + cls_Activity); int fid_Activity = JNI.GetStaticFieldID(cls_Activity, "mBluetoothSampleActivity", "Lcom/example/bluetoothsample/BluetoothSampleActivity;"); IntPtr obj_Activity = JNI.GetStaticObjectField(cls_Activity, fid_Activity); BluetoothSampleActivityClass = JNI.NewGlobalRef(obj_Activity); ptr_checkBluetooth = JNI.GetMethodID(cls_Activity, "checkBluetooth", "()V"); // [4] announceString = ""; deviceArray = new ArrayList(); } private void checkBluetooth(){ // [5] deviceArray.Clear(); JNI.CallVoidMethod(BluetoothSampleActivityClass, ptr_checkBluetooth); } void BluetoothMessage(string message) { // [6] announceString = "Status Msg: " + message; } void OnGUI (){ resetButton(); showMessage(announceString); showDiscoveredDeviceList(); if(makeButton("Search Devices")){ checkBluetooth(); } } void addBTDevice(string deviceinfo){ // [7] deviceArray.Add(deviceinfo); } int buttonX = 0; int buttonY = 0; void resetButton(){ buttonX = 0; buttonY = 0; } void showMessage(string message){ int labelW = 400; int labelH = 50; GUI.Label(new Rect(10,80,labelW,labelH), message); } void showDiscoveredDeviceList(){ string message = "Around You:\n"; for(int i=0;i<deviceArray.Count ;i++){ message += " " + (string)deviceArray[i] + "\n"; } int labelW = 400; int labelH = 500; GUI.Label(new Rect(10,250,labelW,labelH), message); } bool makeButton(string label){ int buttonW = 150; int buttonH = 50; int buttonsInRow = 2; bool b = GUI.Button(new Rect(10+buttonX*(buttonW+5), 150+buttonYttonH+5), buttonW, buttonH), label); buttonX++; if(buttonX==buttonsInRow){ buttonX = 0; buttonY++; } return b; } void _log(string logstring){ Debug.Log (logstring); } }
[1]では、Java側で実装したメインActivityクラス「BluetoothSampleActivity」を参照するためのメンバ変数を定義します。[2]では、BluetoothSampleActivityクラス内の関数"checkBluetooth()"関数を参照するためのメンバ変数を定義します。[3]では、BluetoothSampleActivityクラスを[1]で定義したメンバ変数と結び付ける処理を記述します。
[4]では、BluetoothSampleActivityクラス内の関数「checkBluetooth()」と[2]で定義したメンバ変数を結び付ける処理を記述します。[5]では、[4]で結び付けたcheckBluetooth()関数をUnityのスクリプト内から呼び出すための関数を定義します。
[6]では、Java側からUnityPlayer.UnitySendMessage()関数を用いてUnity側に渡された文字列を取得して表示します。下記のように第2引数に関数名を指定することでUnity側で取得できます。
"UnityPlayer.UnitySendMessage("CallJavaCode", "BluetoothMessage", "Step 4:Searching Bluetooth devices...")"
[7]では、Java側で発見されUnityに対して送信されたデバイス情報を受け取り、Unity側の配列データに登録しています。
一通り実装が終わったら、Unity→Eclipseの順番でビルドします。まず、Unityのビルド機能を用いて通常のUnityアプリと同じようにビルドを行います。ビルドが完了された後、プロジェクトフォルダ内の「Temp/Staging Area/assets/bin」フォルダをEclipse側のプロジェクト内の「assets/bin」内にコピーします。
最後に、Eclipseのプロジェクトを再読み込みし、先ほどコピーした「assets/bin」フォルダ内のデータをEclipseに認識させます。その後Eclipseでビルドを行います。これでAPKファイルが生成されます。
下記は実機端末上でビルドされたAPKファイルを実行したときのスクリーンショットです。
Unityスクリプトによって生成された「checkBluetooth」ボタンをクリックすると、BluetoothデバイスがすでにOnの状態である場合は数秒後に周辺のBluetoothデバイス名が一覧表示されます。
BluetoothデバイスがOffの状態である場合は、一度BluetoothをOnにするかどうかの確認画面が表示され、その後周辺Bluetoothデバイス名が一覧表示されます。
以上で、UnityからAndroidのネイティブ機能を呼び出し、Bluetoothデバイスにアクセスできました。
今回はシンプルなネイティブ機能の呼び出しを紹介するために、画面表現自体は非常にシンプルですが、例えば、ゲーム画面で自分の周辺にいる、同じゲームを利用しているユーザーを検出する、あるいは「すれ違い通信」などに応用可能かと思います。
次ページでは、Twitter4JとUnityの連携について解説します。
Copyright © ITmedia, Inc. All Rights Reserved.