剣を振るVRゲームを作った

vrunityproductiosandroid

プレイ動画

CardboardにAndroidを入れて、

iPhoneをくくりつけた傘を動かすと、画面の剣も動くのでこれで敵を倒すゲーム。

実装

剣(iOS)

剣にくくりつけたiPhoneの傾きの値をUnity(Android)に送信している。 iOSはClassic Bluetoothを自由に使えないので、Androidと通信する場合はBLEを使う。 BLEは通常だと20byteしか一度に送れないので、これを超えないよう注意する必要がある。

BLEで通信するところは

iOS端末をBLEのPeripheralにする

で作ったので、端末の傾きを取得して送るだけ。

import UIKit
import CoreMotion

class Motion{

    let peripheral: BLEPeripheral
    let accelHandler:CMDeviceMotionHandler
    let manager = CMMotionManager()

    public init(peripheral :BLEPeripheral, label :UILabel){
        self.peripheral = peripheral

        accelHandler = {
            (data: CMDeviceMotion?, error: Error?) -> Void in
            let str = String(format: "%.1f %.1f %.1f",
                             arguments: [data!.attitude.pitch * 180 / M_PI,
                                         data!.attitude.roll * 180 / M_PI,
                                         data!.attitude.yaw * 180 / M_PI]
                )
            let res = peripheral.update(str.data(using: String.Encoding.utf8))
            label.text = str + " " + String(res)
        }
    }

    func start(){
        if manager.isDeviceMotionAvailable {
            manager.deviceMotionUpdateInterval = 1 / 12;
            manager.startDeviceMotionUpdates(to: OperationQueue.current!, withHandler: accelHandler)
        }
    }
}

ゲーム(Unity & Android)

オライリーから

UnityによるVRアプリケーション開発――作りながら学ぶバーチャルリアリティ入門

という本が出ていたので買った。Unityの初歩的なところやBlenderとかも説明があるのでおすすめ。 とりあえずGoogleのSDKをimportして、 PrefabのGvrViewerMainを置くと二眼のそれっぽい感じになる。

スコアとかゲームオーバーの状態と処理。

using UnityEngine;
using UnityEngine.UI;
using System.Collections;

public class Game : MonoBehaviour {

	public static int score = 0;
	public static bool gameOver = false;
	public static bool killed = false;

	public Text gameOverText;
	public Text scoreText;
	public GameObject enemy;

	void Start () {
		gameOverText.enabled = false;
	}

	void Update () {
		scoreText.text = "Score: " + score;
		if (gameOver) gameOverText.enabled = true;
		if (killed) {
			nextLevel ();
			killed = false;
		}
	}

	public void nextLevel(){
		var old = enemy;
		var x = Random.Range (-10, 10);
		enemy = (GameObject) Instantiate (
			enemy, new Vector3(x, 0, Mathf.Sqrt(10 * 10 - x * x)), Quaternion.Euler(0, 0, 0));
		Destroy (old);
	}
}

敵の当たり判定。剣に当たったら爆発して次のが出てくる。 体(見えないCapsule Colliderを設定している)に当たるとゲームオーバー。

using UnityEngine;
using UnityEngine.UI;
using System.Collections;

public class MonsterHit : MonoBehaviour {

	public GameObject killEffect;

	void OnCollisionEnter(Collision collision) {
		switch (collision.gameObject.name) {
		case "sword":
			if (!Game.gameOver) {
				Game.score += 1;
				Instantiate (killEffect, transform.position, transform.rotation);
				Game.killed = true;
			}
			break;

		case "Body":
			Game.gameOver = true;
			break;
		}
	}
}

敵を動かす。スコアが上がるとスピードも上がる。

using UnityEngine;
using System.Collections;

public class MonsterMove : MonoBehaviour {

	private float moveRate;

	void Start () {
		moveRate = 0.0f;
	}

	void Update () {
		transform.position = Vector3.Lerp (transform.position, new Vector3 (0, 0, -1.5f), moveRate);
		moveRate += 0.0001f * (Game.score + 1);
		transform.LookAt (new Vector3 (0, 0, -1.5f));
  }
}

センサーの値を受け取って、剣を動かす。ここで使っているネイティブライブラリは前作ったもの。

UnityでAndroidのBLEを使うネイティブプラグインを作る

using UnityEngine;
using System.Collections;

public class SwordMotion : MonoBehaviour {

	private AndroidJavaObject ble;
	private Quaternion q;

	void Start () {
		ble = new AndroidJavaObject ("net.sambaiz.unity_ble.BLE", this.gameObject.name, "received");
		q = Quaternion.Euler (0, 0, 0);
	}

	void Update () {
		transform.rotation = q;
	}

	void received(string message){
		var motionData = message.Split (' '); // pitch roll yaw
		q = Quaternion.Euler (
				90 - float.Parse (motionData [0]),
				float.Parse (motionData [1]),
				0) ;
	}

	void OnApplicationPause (bool pauseStatus)
	{
		if (ble != null) {
			if (pauseStatus) {
				ble.Call ("onPause");
			} else {
				ble.Call ("onActive");
			}
		}
	}
}