いきなりOculus RiftとLEAP Motionでゲームを作ってしまった軌跡

# 注意
**この記事はパブリックな場に出す初めての自作ゲームを「なぜか」Oculus RiftとLEAP Motionを使って作ることにした無謀な男の開発記録です。そのため記述やソースコードの中で技術的に至らぬ点がありましたら生温かい目で編集リクエストをお送りください。**

# 経緯（飛ばすのが推奨です）

## **2010年8月**
[Unityを知る](https://twitter.com/yasei_no_otoko/status/21980858327)

##**2012年前半**
当時2012年6月発売予定だったXbox360用ソフトのクリムゾンドラゴンを大画面で楽しむため当時唯一の民生向けヘッドマウントディスプレイだったHMZ-T1が欲しくなる→銀座ソニービルで試着→「えっショボ…」

＿人人人人人人人＿
＞　えっショボ　＜
￣Y^Y^Y^Y^Y^Y￣

＿人人人人人人人人人人人人人人人人人人人人人＿
＞　[そもそもクリムゾンドラゴンが亡くなった](http://www.xbox.com/ja-JP/support/info_120608)　＜
￣Y^Y^Y^Y^Y^Y^Y^Y^Y^Y^Y^Y^Y^Y^Y^Y^Y^Y￣

##**2012年5月**
[LEAP Motion](http://www.youtube.com/watch?v=_d6KuiuteIA)を知り、ジェスチャー認識デバイスはクッソ高い業務用デバイスを仕事で使ったこともあったので製品版1つと開発者キットを予約

* 31日：最終出社日

##**2012年6月**
* 1日：入社日
* 9〜10日：[同人ゲーム開発者合同温泉合宿 2012](http://heppoko-net.jp/onsen/)
* 10日：横浜市から渋谷区へ引っ越す
* 11日：Macbook Pro with Retinaディスプレイを購入
* WiMAX民から有線1Gbps回線になりAdobe Creative Cloudが30分でインストール出来るように

## **2012年8月**
* Engadgetで[Oculus Riftの記事](http://japanese.engadget.com/2012/08/02/vr-oculous-rift-70-cloud/)を見る
→えっHMZ-T1の2倍の視野角でヘッドトラッキング？凄いけどHMZ-T1の解像度の半分で12月出荷か…どうしようかな…（6月に引っ越したばかりでお金が無かった）
→とりあえずKickstarterギリギリの8月31日深夜にDevelopment Kit $330をBack

* 早くて8月全世界発送開始予定だったLEAP Motionの開発者キットが来ない

##2012年9月
LEAP Motionの開発者キットが来ない

##2012年10月
LEAP Motionの開発者キットが来ない

##2012年11月
* LEAP Motionの開発者キットが来ない

* この頃Oculus Rift Development Kitのデザイン変更が発表される

* http://www.youtube.com/watch?v=n6X2yzwF9Zo
ファッ！？
なんだこの神デバイス！？（驚愕）

##2012年12月
* LEAP Motionの開発者キットが来ない

* 初頭にOculus Riftの延期が発表される

* コミックマーケット83でStudio Rice Cakeの萃香と百鬼兵体験版頒布
Unityの起動時キーコンフィグのテキストをバイナリ編集で日本語化する作業『のみ』を担当

##2013年1月
萃香と百鬼兵の完成版が2013年コミックマーケット85(2013年12月30日)予定となり2013年前半にStudio Rice Cakeでやる作業が無くなったため、まだどちらの開発キットも届いていないにも関わらず初の個人サークルでコミックマーケット84に申し込む

* LEAP Motionの開発者キットが来ない

##2013年2月
http://www.youtube.com/watch?v=k3N5nGit7ZI

のように国内でもLEAP Motionの事例が出始め、流石におかしいだろうと公式に問い合わせるも返事無し。ダメ元で開発者登録ページを見るといつの間にか改装されていた

→別のメールアドレスで登録してみた

→半年以上シカトされていた開発者登録が即承認された

→タダで開発者キットを送りつけてきた

あのさぁ…

##2013年3月
* LEAP Motion開発者キット到着  
*んんwwwwwwこれは手と指は取れてもコントローラの完全代替にはできないデバイスですぞwwwwwww*
→Perilous Dimensionで照準にしかLEAP Motionを使わなくした理由

* GDC2013でOculus Riftの展示ブースが[話題になる](http://www.4gamer.net/games/195/G019528/20130330009/)

* Oculus Rift Development Kitのアメリカでの発送が始まる

##2013年5月
* GW前後から日本でも届く人が出始める

* E3でOculus RiftがPlaystation 4とXbox Oneを抑えてベストハードウェアAWARDを獲得

* 何かKickstarter組じゃないのに俺より早くOculus Riftを触ってる人が居るぞ…

* 飲み会で偶然出会った[needle](https://twitter.com/needle)さんにOculus Riftを初体験させてもらう  
*んんwwwwww座敷居酒屋でRiftコースターは犯罪級ですぞwwwwwww*

* 最終出社日29日にOculus Riftが届く

##2013年6月
* 松江へ帰るための引っ越し作業でOculus Rift触れない状態

* 8〜9日:[洗いっ子合宿](http://twipla.jp/events/49603)で30人以上のナウでヤングな同人ゲーム製作者にOculus Riftを体験してもらったところ大好評
合宿がきっかけでOculus Riftを買ってゲームをOculus Riftに移植する作者も出たので大成功だった＞https://share.oculusvr.com/app/suwako-chan-cubic-for-oculus-rift

* 10日：サンライズ出雲で島根に向かう
* 11日：渋谷区から松江市に引っ越し
* 12日：自動車学校に入る
* 松江にも1Gbps回線を引く

* Unity Proを購入してPerilous Dimensionの開発を本格的にスタートさせる

＿人人人人人人人人人人人人＿
＞　↓からやっとUnityの話　＜
￣Y^Y^Y^Y^Y^Y^Y^Y^Y^Y￣

# Unityを使って完全新規デバイス×2向けのゲームを開発する

## 開発開始
###ゲームデザイン
* **大前提**
Oculus RiftとLEAP Motionを使ってゲームを作る

* **差別化要素**
 * **Oculus RiftとLEAP Motionの時点で従来のゲームとの違いは誰でも分かる**
 * Oculus Riftのソフトは一人称視点の体感型で溢れていた  
→三人称でゲームゲームした視点のシューティングゲームを作る
 * LEAP Motionのソフトは何にでもマウスやコントローラの代わりに手を使わせていた→ **操作ミスの頻発でストレスが溜まる**  
→ **操作ミスを起こしてもペナルティが無いシステム＝それぞれの指によるマルチロックオンシステム**

* **マイルストーンはコミックマーケット84完成版厳守**  
 * 夏コミを体験版で済ませたとして、次に完成版を発表する場が決まらない  
      * 冬コミ、来年の夏コミに受かるとは限らない(実際C85落ちた)  
   * 同人活動では締め切りと同じ人日だけ開発が伸びるわけじゃない(そもそも夏→冬は4ヶ月しか無い)  

* **顧客（自分）の要望とPM（自分）の対応**
 * 激しい敵の弾幕！←どうやって今年初めてOculusとLEAPを触ったプレイヤーに弾を避けさせるのか言ってみろ
 * カッコいいカットシーン！←時間考えろや
 * リアルなグラフィック！←[HAWKEN](http://www.youtube.com/watch?v=EhkiTXVX8Jk)やれ  
  
人が居ない・時間が無い・金が無いの三重苦だったので、  

* ゲームプレイに直接寄与しないフレーバー要素とメタゲーム要素

* 新しいデバイスの操作に慣れないプレイヤーにストレスを与えてしまう敵弾ギミック  

をゲームから完全に排除した結果、ゲームを起動した瞬間に宇宙に放り出され **飛ぶ→狙う→撃つ→爆発！**という至極脳筋なゲームメカニクスの3Dシューティングゲームが誕生。
具体的には以下の通り

* **画面真ん中の照準をオブジェクトに合わせるだけでロックオンレーザーがオートショットされるシンプルなゲームメカニクス**  
パンツァードラグーンをショットのみでプレイする動画を見たトラウマから極限まで操作を簡略化
* **Oculus Riftで頭を動かすと真横や後ろにあるターゲットも見えるように**  
意外と頭を動かさない人も居たので現在はカメラではなく機体を動かすように
* **マウス、キーボード、コントローラを使って機体自体の方向転換、加速**  
Oculus Riftを着けながらだと普通の人は操作しにくいらしいことが東京ロケテゲームショウで分かったので現在は自動加速と↑の仕様に
* **LEAP Motionを使って全ての指を照準カーソルに**  
	* LEAP Motionを使っての移動やその他コマンドは検出ノイズで普通のコントローラよりストレスが溜まるため
	* **フィンガートラッキングでマルチ照準カーソルを出すのは検出ミスがあってもプレイヤーにペナルティが無く、しかもLEAP Motionにしか出来ない操作**
	* Oculus Riftを着けて立ちながらだとかなりキツい（コミケ/デジゲー博時）が、座りながらやる（自宅/東京ロケテゲームショウ時）と快適に遊べる仕様

### 開発体制
* 企画：野生の男
* プログラム：野生の男
* グラフィック：野生の男
* サウンド：野生の男
* ミュージック：野生の男

**伝達ロスの一切発生しない完璧な布陣（棒読み）**

### 開発環境
* ゲームエンジン：[Unity Pro 4]()
* バージョン管理：[Git](http://progit-ja.github.io)([Source Tree](https://www.atlassian.com/ja/software/sourcetree/overview))
* リモートリポジトリ：無料無制限の[Bitbucket](https://bitbucket.org)でバックアップ
* コードエディタ：日本語を使わないので安心してUnity付属のMonoDevelopと[SublimeText2/3](http://www.sublimetext.com)
* BGM作成：[Studio One 2 Professional](http://www.mi7.co.jp/products/presonus/studioone/what-is-studio-one/) ＋ [KOMPLETE 8 Ultimate](http://www.native-instruments.com/jp/products/komplete/bundles/komplete-9-ultimate/) ＋ [Z3TA+2](http://www.cakewalk.com/products/z3ta/) + [Ozone 5](http://www.izotope.com/products/audio/ozone/)
* SE作成：[Adobe Audition CC](http://www.adobe.com/jp/products/creativecloud.html) ＋ MacBook Pro内蔵マイク
* 自機・敵モデル作成ソフト：[MXD GameDraw](http://mixeddimensions.net)
* 宇宙背景：[Space for Unity](http://www.spaceunity.com)
* 見た目通りのコリジョン設定：[Concave Collider](https://www.assetstore.unity3d.com/#/content/4596)
* オーディオ管理：[Audio Toolkit](https://www.assetstore.unity3d.com/#/content/2647)
* 照準GUI：Unity標準GUI→（V1.10以降）→[NGUI3](https://www.assetstore.unity3d.com/#/content/2413)
* 2Dアセット・CDジャケット作成：[Photoshop/Illustrator CC](http://www.adobe.com/jp/products/creativecloud.html)

# 開発でのハイライト
## MXD GameDrawでモデルを作成
自機のオロチとターゲットのゼルガニウムのモデリングはGameDrawで行いました。
これまで全くモデリングしたことの無い素人が時間をかけてもクオリティはそこまで上げられないのでAsset Storeでモデルを購入することも検討していましたが、
GameDrawは完全にUnityシーンビューの操作方法で各頂点を操作してモデリングを行えたので、合わせて数時間で求めていたモデルを作成することが出来ました。
Free版があってPro版も非常に安いので、モデリング未経験の方も必ず触ってみるべきプラグインだと思います。

## Adobe AuditionでレーザーのSEを作成しAudio Toolkitで再生
爆発のSEには既存の素材を使用していますが、レーザーSEの作成はCEDEC2011現地で聞いた[プロの講演](http://gigazine.net/news/20110911_anime_soundeffect_cedec2011/)を参考に、録音した波形を加工することで行いました。

1. 口で破裂音を出して内蔵マイクで録音（この時点からAdobe Auditionを使用）
2. Adobe Auditionでピッチ変更やタイムストレッチをかけて加工
3. 上手に出来ました

これをゲーム中で各レーザーが発射される毎に再生するわけですが、Unity標準のAudio機能では連続再生でノイズが走ったため、サウンドファイルのプール機能があるAudio Toolkitを使って音声を管理しました。
また、Audio Toolkitには同じ関数呼び出しで再生するファイルをUnityのインスペクタで設定した確率に応じて振り分けてくれる機能も存在したので、異なるレーザーのSEを複数作成し、発射時にランダムで切り替わるようになっています。

## Oculus Rift有り・無しを自動検出
Oculus Riftが無いと遊べないのはちょっと辛すぎるので無くても遊べるようにしました。

[UnityでOculus Riftの接続状態によって処理を切り替える](http://qiita.com/yasei_no_otoko/items/5104e882d651c70d7563)

ゲーム中で動作している実際のコードはこんな感じです。
コメント部分は今回の記事のために付け加えています。

```csharp:GameController.cs
void Start() {
	// シーンに配置された全てのGameObjectの初期化を待つためAwakeではなくStartに書く
	if ( OVRDevice.IsHMDPresent() && OVRDevice.SensorCount > 0) {
		// HMD表示かつセンサーが存在したらOculus Riftモードにする
		this.enabledOculus = true;

		// Oculus用のメインカメラと3D Skyboxカメラを有効化
		ovrMainCamera.SetActive(true);
		ovrSpaceCamera.SetActive(true);

		// 通常のメインカメラと3D Skyboxカメラを無効化
		baseMainCamera.SetActive(false);	
		baseSpaceCamera.SetActive(false);

		// GUIを表示するカメラにRenderTargetを設定する
		baseGUICamera.camera.targetTexture = Resources.Load("Texture/NGUITarget") as RenderTexture;
		baseGUICamera.camera.clearFlags = CameraClearFlags.Skybox;

		// Oculus用メインカメラからポストエフェクト用コンポーネントを取得
		pixels = ovrMainCamera.GetComponentsInChildren<CC_Pixelate>();

		// Oculus Riftのセンサーを用いた操作のためにOVRCameraControllerのインスタンスを取得
		cacheOVRCamera = ovrMainCamera.GetComponent<OVRCameraController>();
	} else {
		// 通常カメラの場合の処理
		pixel = Camera.main.GetComponent<CC_Pixelate>();
	}
	// 以下共通の初期化処理
}
```

## LEAP Motionで指を照準にする
LEAP Motionで検出した全てのフィンガートラッキング情報を取得し、マルチカーソル用に用意したHUDの数だけループを回すようにしています。


```csharp:LeapFinger.cs
// LEAP Motionのフレーム情報を取得
Frame frame = m_leapController.Frame();

// LEAP Motionの検出領域情報
InteractionBox interactionBox = frame.InteractionBox;

for(int i = 0; i < crossHairs.Length; ++i) {
	if (i >= frame.Pointables.Count) {
		crossHairs[i].SetActive( false );
		continue;
	} else {
		crossHairs[i].SetActive( true );
	}

	// LEAP Motionの検出空間座標をUnityのスクリーン座標に変換
	Vector3 screenPos = interactionBox.NormalizePoint(frame.Pointables[i].StabilizedTipPosition).ToUnity();

	// 検出した座標をGUIカメラ用の座標に変換
	if (OVRController.enabledOculus){
		// Oculus Riftの場合は水平解像度が半分で歪みもあるので補正
		x[i] = (screenPos.x - positionCorrection) * Camera.main.pixelWidth - Camera.main.pixelWidth / 2f;
		y[i] = screenPos.y * UnityEngine.Screen.height - UnityEngine.Screen.height / 2;
	} else {
		x[i] = screenPos.x * UnityEngine.Screen.width  - UnityEngine.Screen.width / 2;
		y[i] = screenPos.y * UnityEngine.Screen.height - UnityEngine.Screen.height / 2;
	}

	// NGUIのTween関数を使って照準カーソルを移動
	TweenPosition comp = UITweener.Begin<TweenPosition>(crossHairs[i], 0f);
	comp.from = crossHairs[i].transform.position;
	comp.to.Set(x[i], y[i], 0);
	comp.Sample(1f, true);
	comp.enabled = false;

	// LEAP Motionで取得した座標をビューポート座標に変換してRaycastを取得
	Vector3 ViewPoint = new Vector3( screenPos.x, screenPos.y, 0.0f);
	var ray = Camera.main.ViewportPointToRay(ViewPoint);
	RaycastHit[] hits;
	if ((hits = Physics.RaycastAll(ray)) != null) {
		foreach( var hit in hits ) {
			if (hit.transform.tag == "Zelganium") {
				// 予め作成したレーザーGameObjectのListから未使用のものを利用
				if (OVRController.lazers.Exists(g => g.activeSelf == false)) {
					var lazer = OVRController.lazers.Find(g => g.activeSelf == false);
					// 発射位置、目標などを設定してレーザーを発射
				}
			}
		}
	}
}
```

## Oculus Riftを使って自機を移動する
OVRCamera.csを参考に現在HMDが向いている方向と起動時の基準方向との差分を取り、自機を回転させています。

```csharp:GameController.cs
if (this.enabledOculus)	{
	float yRotation = 0.0f;
	Vector3 orientation;
	Vector3 dir = Vector3.forward;
	Quaternion q = Quaternion.identity;
	Quaternion deviceOrientation = Quaternion.identity;
	Quaternion orientationOffset = Quaternion.identity;

	// 基準方向を取得
	cacheOVRCamera.GetOrientationOffset(ref orientationOffset);
	q = orientationOffset * q;

	// デバイスの方向を取得
	OVRDevice.GetPredictedOrientation(0,ref deviceOrientation);
	q = q * deviceOrientation;

	// クォータニオンをオイラー角に変換
	orientation = q.eulerAngles;

	// 他にもっとクォータニオンを使った上手いやり方があるはずだけどこっちでドリフト防止
	if (orientation.x > 180) orientation.x -= 360;
	if (orientation.y > 180) orientation.y -= 360;
	if (orientation.z > 180) orientation.z -= 360;

	orientation.x *= Time.fixedDeltaTime;
	orientation.y *= Time.fixedDeltaTime;
	orientation.z *= Time.fixedDeltaTime;

	orochi.transform.Rotate(orientation);
}
```

## [SerializeField]/[System.Serializable]を使ってパラメータをGUIで編集
初歩的なプラクティスですが、[SerializeField]/[System.Serializable]を用いることでprivateメンバもインスペクタで編集することが出来ます。

参考：[[1]](http://www.wisdomsoft.jp/260.html) [[2]](http://terasur.blog.fc2.com/blog-entry-334.html) [[3]](http://d.hatena.ne.jp/nakamura001/20110805/1312563959)

今回は各カメラの参照やステージ毎に変えているBGMファイルなどをSerializeFieldで編集できるようにしたおかげで、GameObjectやファイルの参照についてプログラム側で考慮する手間が大きく省けました。
もしまだSerializeFieldを活用していない方が居たら必ず活用しましょう。

# 現実での開発進行
コミットログ調べなので多少誤差がありますがこんな感じの進行でした。
自動車学校では最短取得のために月1回の休校日を除いて平日も土日も朝8時から6時までフルタイムで入れていたので入社までの開発時間は夜だけでした。

6月22日：Oculus Riftで宇宙を飛ぶプロトタイプが出来たのでGitでファーストコミット
7月5日：普通自動車免許取得。Oculus有り無しの処理を実装
7月6日：照準クロスヘア表示とレーザーを実装
7月7日：LEAP Motion対応
7月8日：今の会社に入社
7月14日：オロチのモデルとOculusCameraControllerの3人称視点を実装
7月15日：レーザーSE作成
7月27日：Audio Toolkit導入
7月29日：ゼルガニウムのモデルを実装
8月1日：ステージ作成
〜8月4日：BGM再生を含む十数件のコミットの後マスターアップ

# ゲーム完成後
そんなこんなで実作業期間2ヶ月弱という超短期で何とか形になった[Perilous Dimension](http://www.circle-hydrangea.net/perilous-dimension/)ですが、
コミックマーケット84で[へっぽこさん](https://twitter.com/heppoko)のHyperJuiceをお借りしてプレイ可能な状態で展示したところ、
[電撃オンラインに載ったり](http://dengekionline.com/elem/000/000/694/694813/)、
[目の前でZUNさんに遊んでもらったり](https://twitter.com/okonyogi/status/366849350510460928)、
また、Oculus Rift無しでも動くようにしたおかげで想定よりも数が出るという、
開発者冥利に尽きる大金星を残すことができました。

もしOculus RiftとLEAP Motionが無かったら、
両者のSDKがUnityに対応していなかったら、
そもそもUnityが無かったら、Perilous Dimensionを作ることは出来ませんでした。
この三社には感謝してもしきれません。

来年2014年はPS4とXbox Oneの日本発売と、それに続くOculus Rift完成版の発売が待っているゲームにとって激動の年です。
PS4とXbox Oneはどちらも個人の開発者がゲームを出せる道が開かれていて、この3つ全てが開発環境としてUnityに対応していることを考えるとワクワクしてきませんか？（自分には数年かかっても無理でしょうが）

フリーゲームでも同人ゲームでもインディーゲームでも、ゲームを作ることは楽しいことです。
まだゲームを作って公開したことが無い方にとって、Unityはトップクラスに入る良い選択肢だと思います。