vr_controller
GoogleのCardboardのようなスマートフォンを利用した簡易VR用のコントローラを作りました。筐体を両手で保持しながら指でゲームパッド的にボタンを押せるものです。通信はBluetooth(BLE)を使っています。

⚫️コントローラ側
ボタン(8個)の押されている状態を8bitで表して、それをBLEでスマートフォン(iPhone)に送信します。
BLEモジュールはRN4020を、CPUはLPC1114を使用しました。下記のサンプルコードはArduino的に書いたものです。
サンプルコード:

void loop()
{
    i = 0;
    if(digitalRead(uArrow) == 0) i |= 0x01;
    if(digitalRead(dArrow) == 0) i |= 0x02;
    if(digitalRead(lArrow) == 0) i |= 0x04;
    if(digitalRead(rArrow) == 0) i |= 0x08;
    if(digitalRead(aBtn) == 0) i |= 0x10;
    if(digitalRead(bBtn) == 0) i |= 0x20;
    if(digitalRead(xBtn) == 0) i |= 0x40;
    if(digitalRead(yBtn) == 0) i |= 0x80;
	
    if(i != prevI) ble_write(i);
    prevI = i;
}

⚫️スマホ側
スマホに表示するサンプルアプリはUnityで作ります。空間にCubeがひとつだけあってコントローラのボタンで視点(カメラ位置)を前後左右に移動させることにします。

(1) まずGoogle VR SDKをインポートします。Cubeがひとつあるプロジェクトを開いた状態で、ダウンロードしたGoogleVRForUnity.unitypackageをダブルクリックしてimportします。ProjectビューにGoogleVRがでてくるのでそのなかのPrefabs>GvrViewerMainをHierarchyビューにドラッグ&ドロップします。再生ボタンで実行すると両眼用の画面がでてくると思います。

(2) コントローラからのコマンドをカメラ位置に反映させるためにMainCameraにスクリプトを追加します。コマンドをcmdとして受け取ってその値によってカメラ位置を移動します。OnUpdateBle(string command)がBLEでコマンドを受信した時にiOS側から呼ばれるコールバック関数です。

using UnityEngine;
using System;
using System.Collections;

public class CamController : MonoBehaviour {

	private int cmd;
	private float x_speed, z_speed;

	private bool IsUpdatingBle { get; set; }

	void Awake() {
		IsUpdatingBle = false;
	}

	// Use this for initialization
	void Start () {
		x_speed = 0.0f;
		z_speed = 0.0f;
		if (!IsUpdatingBle) {
			if (!BlePluginInterface.StartUpdatingBle (this.name)) {
				IsUpdatingBle = false;
				return;
			}
		} else {
			BlePluginInterface.StopUpdatingBle();
		}
	}

	// Update is called once per frame
	void Update () {

		if ((cmd & 0x01) != 0) {	//forward
			x_speed = 0.1f;
		} else if ((cmd & 0x02) != 0) {	//back
			z_speed = -0.1f;
		} else if ((cmd & 0x04) != 0) {	//left
			z_speed = 0.1f;
		} else if ((cmd & 0x08) != 0) {	//right
			x_speed = -0.1f;
		}
		transform.Translate (x_speed, 0.0f, z_speed);
	}

	public void OnUpdateBle(string command) {
		char[] c = command.ToCharArray ();

		cmd = 16 * (c [1] - '0') + (c [2] - '0');
	}
}

(3) 次にコントローラとBLE通信するiOSソフト(Native Plugin)の部分です。Objective-cで記述して.mmという拡張子にします。
UnitySendMessage()のところでUnity側にコマンドを送信しています。extern “C”の部分はNative Plugin側の呼び出しインターフェイスで、Unity側のc#インターフェイスから呼ばれます。

//
//  BlePlugin.m
//  Unity-iPhone
//
#define SERVICE_UUID  @"8d8987059e3b42a59637456f786cad8a"
#define READ_NOTIFY_CHARACTERISTIC_UUID @"4b753e33faac44ed81a6dece7bb01283"
#define WRITE_CHARACTERISTIC_UUID @"0add21c0de8b406391218f657b1c67a4"

#import <Foundation/Foundation.h>
#import <CoreBluetooth/CoreBluetooth.h>

@interface BlePlugin : NSObject <CBCentralManagerDelegate, CBPeripheralDelegate>
{
    NSString *callbackTarget;
}

+ (BlePlugin *)sharedInstance;
@property (nonatomic, strong) CBCentralManager *centralManager;
@property (nonatomic, strong) CBPeripheral *peripheral;
@property (nonatomic, strong) CBCharacteristic *writeCharacteristic;
@property (nonatomic, strong) CBCharacteristic *readNotifyCharacteristic;

@end

@implementation BlePlugin

static BlePlugin *sharedInstance = nil;
+ (id)sharedInstance
{
    @synchronized(self) {
        if (sharedInstance == nil) {
            NSLog(@"sharedInstance");
            sharedInstance = [[self alloc] init];
        }
    }
    return sharedInstance;
}

#pragma mark - CBCentralManagerDelegate

- (void)centralManagerDidUpdateState:(CBCentralManager *)central {
    
    NSLog(@"state:%ld", (long)central.state);
    [self.centralManager scanForPeripheralsWithServices:nil options:nil];
}

- (void)    centralManager:(CBCentralManager *)central
     didDiscoverPeripheral:(CBPeripheral *)peripheral
         advertisementData:(NSDictionary<NSString *,id> *)advertisementData
                      RSSI:(NSNumber *)RSSI {
    
    NSLog(@"発見したBLEデバイス : %@", peripheral);
    if ([peripheral.name hasPrefix:@"RN4020"]) {
        self.peripheral = peripheral;
        [self.centralManager connectPeripheral:peripheral
                                       options:nil];
    }
}

- (void)    centralManager:(CBCentralManager *)central
      didConnectPeripheral:(CBPeripheral *)peripheral {
    
    NSLog(@"接続成功!");
    //[_scanBtn setTitleColor:[UIColor greenColor] forState:UIControlStateNormal];
    //[self.centralManager stopScan];
    peripheral.delegate = self;
    [peripheral discoverServices:nil];
}

- (void)        centralManager:(CBCentralManager *)central
    didFailToConnectPeripheral:(CBPeripheral *)peripheral
                         error:(NSError *)error {
    
    NSLog(@"接続失敗・・・");
}

#pragma mark - CBPeripheralDelegate

- (void)    peripheral:(CBPeripheral *)peripheral
   didDiscoverServices:(NSError *)error {
    
    if (error) {
        NSLog(@"エラー:%@", error);
        return;
    }
    NSArray *services = peripheral.services;
    NSLog(@"%lu 個のサービスを発見!:%@", (unsigned long)services.count, services);
    for (CBService *service in services) {
        
        [peripheral discoverCharacteristics:nil forService:service];
    }
}

- (void)                      peripheral:(CBPeripheral *)peripheral
    didDiscoverCharacteristicsForService:(CBService *)service
                                   error:(NSError *)error
{
    if (error) {
        NSLog(@"エラー:%@", error);
        return;
    }
    NSArray *characteristics = service.characteristics;
    NSLog(@"%lu 個のキャラクタリスティックを発見!%@", (unsigned long)characteristics.count, characteristics);
    for (CBCharacteristic *characteristic in characteristics) {
        
        if ([characteristic.UUID isEqual:[CBUUID UUIDWithString:READ_NOTIFY_CHARACTERISTIC_UUID]]) {
            self.readNotifyCharacteristic = characteristic;
            NSLog(@"READ_NOTIFY_CHARACTERISTIC_UUID を発見!");
            [self.peripheral setNotifyValue:YES forCharacteristic:self.readNotifyCharacteristic];
        }
        else if ([characteristic.UUID isEqual:[CBUUID UUIDWithString:WRITE_CHARACTERISTIC_UUID]]) {
            self.writeCharacteristic = characteristic;
            NSLog(@"CHARA_WRITE_UUID を発見!");
        }
    }
}

- (void)                peripheral:(CBPeripheral *)peripheral
   didUpdateValueForCharacteristic:(CBCharacteristic *)characteristic
                             error:(NSError *)error {
    if (error) {
        NSLog(@"エラー:%@", error);
        return;
    }
    NSLog(@"読み出し成功! service uuid:%@, characteristic uuid:%@, value:%@", characteristic.service.UUID, characteristic.UUID, characteristic.value);
    if(self.readNotifyCharacteristic.isNotifying) {
        NSString *parameter = [NSString stringWithFormat:@"%@", characteristic.value];
        UnitySendMessage([callbackTarget cStringUsingEncoding:NSUTF8StringEncoding], "OnUpdateBle", [parameter cStringUsingEncoding:NSUTF8StringEncoding]);
        
    }else{
    }
}

- (void)                                peripheral:(CBPeripheral *)peripheral
       didUpdateNotificationStateForCharacteristic:(CBCharacteristic *)characteristic error:(NSError *)error {
    if (error) {
        NSLog(@"エラー:%@", error);
        return;
    }
    NSLog(@"Notify状態更新成功! is Notifying:%d", characteristic.isNotifying);
}

- (BOOL)startUpdatingBle:(NSString *)newCallbackTarget
{
    callbackTarget = newCallbackTarget;
    
    NSLog(@"startUpdatingBle");
    self.centralManager = [[CBCentralManager alloc] initWithDelegate:self queue:nil];
    
    return YES;
}

- (void)stopUpdatingBle {   
}
@end

extern "C" {
    
    BOOL _startUpdatingBle(const char *callbackTarget) {
        BlePlugin *instance = [BlePlugin sharedInstance];
        @synchronized(instance) {
            return [instance startUpdatingBle:[NSString stringWithUTF8String:callbackTarget]];
        }
    }
    
    void _stopUpdatingBle() {
        BlePlugin *instance = [BlePlugin sharedInstance];
        @synchronized(instance) {
            [instance stopUpdatingBle];
        }
    }
}

(4) 最後にUnity側のc#インターフェイスです。

using UnityEngine;
using System.Collections;
using System.Runtime.InteropServices;

public class BlePluginInterface : MonoBehaviour {

	#if UNITY_IOS
	[DllImport("__Internal")]
	private static extern bool _startUpdatingBle(string callbackTarget);

	[DllImport("__Internal")]
	private static extern bool _stopUpdatingBle();
	#endif

	public static bool StartUpdatingBle (string callbackTarget) {

		#if !UNITY_EDITOR
		return _startUpdatingBle(callbackTarget);
		#else
		return false;
		#endif
	}

	public static void StopUpdatingBle() {
		#if !UNITY_EDITOR
		_stopUpdatingBle();
		#endif
	}
}