昔、私が高校生くらいの時にもVRブームがあり、バーチャルリアリティエキスポなるイベントで、 筋斗雲的な何かに乗ったり、高速に上下するLEDアレイディスプレイで三次元を表示するゲームなどに、 ワクワクしたことを思い出します。
そして、ここ数年、再びバーチャルリアリティー(VR)が盛り上がっています。 Oculus Riftや SCEのProject Morpheusといった ヘッドマウントディスプレイも格段に進歩し、 コンピュータグラフィックスやGPUなどの技術の進歩のおかげで、 非常にリアルな仮想現実没入体験が、簡単に手の届く領域にきています。
そんななか、私がもっともワクワクしているのが、 Googleの「Cardboard」です。 なぜなら、非常に安く手軽に手に入れ使うことができ、特にユーザーに身近なものになりうるからです。
本稿では、Cardboardを使ってVRでロボットに搭乗して操縦するデモを作成したことを紹介します。
CardboardはGoogleが公開した段ボールでできたスマフォ用VRアタッチメントです。 設計図も公開されているので、100円ショップで買ったルーペのレンズと、 段ボールと、テープと工具があれば自分で作ることができます。
実際にGoogleが公開しているデータで、 Cardboardを二つほど作ってみました。 レンズは、100円ショップで手に入るルーペを分解すると手に入ります。 私は、ダイソーで写真のルーペを買いました。なんとレンズが二つ入っていて、 一つ買うだけで両目分のレンズが手に入ります。
レーザーカッターがあれば簡単なのでしょうが、残念ながらないので、 プリントアウトした型紙を段ボールに貼り、 地道に普通のカッターで切り抜いて組み立てます。
純正のCardboardには、頭に固定するバンドがないので、 手芸用のゴム紐とマジックテープで簡単な固定用バンドを追加しました。
以下が実際に自作してみたものです。
最初に試したのが、このDurovis Diveです。 リンク先の開発者ページにあるSDKをダウンロードし、 unitypackageをインポートして使います。
インポートしたDive/Prefabs/にあるDive_CameraというPrefabを、 シーンに追加すると、ヘッドトラッキング付両眼カメラになります。 このカメラをメインカメラにすることで、非常に簡単にCardboardなどの スマフォ用VRアタッチメントに対応させることができます。
次に、Google Cardboard公式のUnitySDKを試してみましょう。 Cardboard Developper Page(Unity)を見ると、Download and Samplesからunitypackageをダウンロードできます。 プロジェクトにインポートすると、Cardboardフォルダができます。
Durovis Diveと同様に、 Cardboard/Prefabs/にある、CardboardMainというPrefabをシーンに追加することで、カメラになります。 これだけで、Cardboardに対応させることができます。
今回は、両方とも実装してみての確認は行いましたが、 詳細な評価と比較はできていません。 実際のアプリではDurovis Diveを使いましたが、 これは作り始めた初期の時点でCardboardのUnity対応SDKがリリースされてなかったという理由によります。
スマフォVRアタッチメントを使用する場合、大きな問題となるのがユーザーインターフェースです。 Cardboardでは、これを磁石とスマフォの磁気センサーでスイッチを作って解決しています。 磁石二つの位置関係による磁力の変化を、スマフォの磁気センサーで検出することで、一ボタンに対応する入力を実現しています。 非常に面白い仕組みです。 Ver.2のCardboardでは、導電性の布とレバー機構を使うことで、ボタンを押すと画面にタッチされる機構が作られています。これも面白い仕組みですね。 一方で、どちらの入力方式でもアクション性の高いゲームに利用するには入力の少なさと、反応速度と操作しやすさから難しいです。
ここで、私が考えたのが、スマフォの加速度センサーやタッチパネルをコントローラーとして使えないかということでした。 スマフォの普及率が爆発的に増加した昨今、二つくらい前に使っていたスマフォが机の引き出しの中に眠っていたりしないでしょうか。 そうです、それらをコントローラとして使えばいいのです!!
そうなると、表示用と左手用右手用の都合三台のスマフォが必要です。 我が家には、幸い私と妻が以前使っていたiPhone4とiPhone4sが余っていました。 これをコントローラーとして使えるようにしてみましょう。
では、どのように表示+ゲーム実行用の本体スマフォと、コントローラー用スマフォをつなげばいいのでしょうか。 ここで今回は、WebSocketを使ってみました。 サーバーを介して、本体とコントローラをWebSocketでつないでみました。
クライアント側のUnityでは、WebSocketSharpのUnity対応のためのKLab改変版を使って、以下のような受信用プログラムが動いています。
using UnityEngine;
using System.Collections;
using WebSocketSharp;
public class WebSocketClient : MonoBehaviour {
public const int RIGHT = 0;
public const int LEFT = 1;
public const int B1 = 0;
public const int B2 = 1;
public Vector3[] accel = new Vector3[2];
public bool[,] button = new bool[2,2]{{false,false},{false,false}};
private WebSocket ws;
// Use this for initialization
void Start () {
ws = new WebSocket ("ws://URL/chat/test");
ws.OnMessage += (object sender, MessageEventArgs e) => {
string [] message = e.Data.Split(new char[]{':'});
int controllerNo=RIGHT;// Default Right it is not good
switch(message[1]){
case "R":
controllerNo = RIGHT;
break;
case "L":
controllerNo = LEFT;
break;
default:
break;
}
switch(message[2]){
case "AC":
accel[controllerNo] = new Vector3(float.Parse(message[3]),
float.Parse(message[4]),
float.Parse(message[5]));
break;
case "B":
button[controllerNo,int.Parse(message[3])-1] =
(message[4].Equals("DOWN")) ? true : false;
break;
default:
break;
}
};
ws.Connect ();
}
// Update is called once per frame
void Update () {
}
}
Playerのコントロールスクリプトなどで、このクラスから情報を取得して操作します。
一方で、コントローラーは、HTMLファイルで作りました。ブラウザで表示するだけでコントローラーになります。
<!DOCTYPE html>
<html><head>
<meta name="viewport" content="width=device-width; initial-scale=1.0; maximum-scale=1.0; user-scalable=no;">
<meta name="apple-mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-status-bar-style" content="black">
</head>
<body style='user-select: none; -webkit-user-select: none;'>
<script type="text/javascript">
document.addEventListener('touchmove', function(e) {
e.preventDefault();
},false);
var ws;
ws = new WebSocket("ws://URL/chat/test");
function send(message){
ws.send(message);
}
window.addEventListener("devicemotion",function(evt){
var x = evt.accelerationIncludingGravity.x; //横方向の傾斜
var y = evt.accelerationIncludingGravity.y; //縦方法の傾斜
var z = evt.accelerationIncludingGravity.z; //上下方向の傾斜
send("1:L:AC:"+x+":"+y+":"+z);
},false);
</script>
<div style="width: 100px; height: 100px; background-color: red; margin: 20px; float: right;" ontouchstart="send('1:L:B:1:DOWN')" ontouchend="send('1:L:B:1:UP')" ></div>
<div style="clear: right;"></div>
<br>
<br>
<div style="width: 100px; height: 100px; background-color: blue; margin: 20px; float: right;" ontouchstart="send('1:L:B:2:DOWN')" ontouchend="send('1:L:B:2:UP')" ></div>
</body></html>
このHTMLをコントローラーの識別番号を変えて左手用と右手用の二つ作ります。
中継用サーバーは、単純なWebSocketで届いたメッセージを接続しているクライアントにブロードキャストするだけのサーバーです。 clojure-aleph-chatを参考にしました。
二本のスティックでロボットを操作すると言えばあれですね。 加速度センサにより、端末の傾きをとることにより、 左手右手の両方のスティックを前に傾けると前進、 後ろに傾けると後退、前後に互い違いに倒すと回転、 横方向に同じ方向に倒すと横移動、 横方向に開くと上昇、閉じると下降という操作体系です。 皆さん、よくご存じですよね。
コントローラー画面には、攻撃ボタンとブーストボタンを表示してみました。 タップするとそれぞれの動作をします。
写真にあるように、ブラウザでコントローラー画面を表示させます。 赤が攻撃ボタン、青がブーストボタンです。 AndroidとiOSのどちらでも動きますが、AndroidとiOSで加速度センサーの軸が違っているので気をつけましょう。
遊んでいるところ(筆者近影)
実際の画面のスクリーンショット
今回の実験は、三台のスマフォが必要というネックがありますが、 最近のスマフォの普及率を考えると意外と何とかなるのではないでしょうか。 実際我が家には、iOSだけで、3GS,4,4s,5,5s,6,初代iPadがありますし、 AndroidはHTC AriaとKindle Fire HDとKobo Arcがあります。 3台くらいきっとなんとかなりますよね。
また、WebSocketで一回サーバーを介するレイテンシも、プレイしてる分にはあまり気にならず、 作り方次第で十分隠蔽可能なのではないかという感想です。
そして最高ですね。この没入感、飛翔感。楽しいです。文章ではこの楽しさが伝わらないのが残念です。 今回は、搭乗して操縦する体験を目標にしたので、ゲーム性は皆無に等しいのですが、 今後ゲームとして成り立たせる方向も模索したいと思います。
oho-s
KLabのゲーム開発・運用で培われた技術や挑戦とそのノウハウを発信します。
合わせて読みたい
KLabのゲーム開発・運用で培われた技術や挑戦とそのノウハウを発信します。