UnityとArduinoを連携させたゲームの作り方(Unity4.3.1版)

ゲームの概要
ARは現実世界の中にバーチャルな表現を持ち込み、現実空間を拡張するものですが、逆にバーチャルな世界の中に現実の情報を持ち込んだら、仮想空間を拡張することになるんだろうか。そんなことを考えて、Arduinoを用いた「現実世界とゲームの世界を連動させたゲーム」を作ってみました。
ゲーム自体を一から作ると大変なので、今回は以下の本で紹介されているゲームを改造して制作しました。
改造元にしたゲームは、第3章の「Dangeon Eater」です。
このゲームは「パックマン」に代表されるドットイート系のゲームですが、このゲームの光情報を現実世界と連動させ、明るい(=昼間)だと騎士に、暗い(=夜)だと幽霊に追いかけられる、現実世界の光情報でゲーム内の昼夜が切り替わるようなゲームにしてみました。
環境
MacOS10.11.4, Unity4.3.1f1, Arduino UnoArduino側の設定
さて、それでは作り方を説明していきます。
まずArduino側の設定ですが、こちらはとても簡単です。 単純に光センサの値を取得し、シリアル通信で送信するだけのスケッチになっています。

1 2 3 4 5 6 7 8 |
void setup() { Serial.begin(9600); } void loop() { int v = analogRead(0); Serial.println(v); } |
UnityでArduinoの情報を取得する
“Uniduino”というアセットを使えば簡単にできるようなのですが、少々お高いので今回は自作することにしました。とはいっても調べてみると、すでにやっている方がそのまま使える素晴らしいコードを公開してくれていましたので、これをそのまま使用しています。
ありがとうございます。
UnityとArduinoをシリアル通信基本的にはこちらの記事のまま設定すればいいのですが、2箇所ほどそのままでは動かなかったので、そこだけ紹介しておきます。
なお、記事中の”SerialHandler.cs”は空のゲームオブジェクト”SerialHandler”を作成してアタッチ、”DoSomething.cs”はゲーム内のライトと連動させたいので”Spot Light”にアタッチしています。
1 2 3 4 5 6 |
//46行目あたり //変数ではなぜか通信がつながらなかったため、直接指定 //serialPort_ = new SerialPort(portName, baudRate, Parity.None, 8, StopBits.One); //または //serialPort_ = new SerialPort(portName, baudRate); serialPort_ = new SerialPort("/dev/tty.usbmodem1421", 9600); |
1 2 3 4 |
//今回はデータを1つしか送っておらず、区切り文字も入れていないためdata.Lengthが1になる。 //以下の記述があると、すべてreturnされてしまうのでコメントアウト //if (data.Length < 2) return; |
受け取った光情報を元に、ゲーム内の明るさを切り替える
取得した光情報をもとに、ゲーム内の明るさを切り替える処理を”DoSomething.cs”に加えていきます。
なお、処理の流れに重点を置いた説明になっていますので、変数の宣言が記載されていない箇所があります。エラーになった場合は適宜、宣言しておいてください。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
//DoSomething.cs //void OnDataReceived(string message)のtry{}内に追加 //最初に取得した光の強さを初期値として保存 //初期値とそれ以後の光の強さを比較して明るくなっていれば明るく、暗くなっていれば暗くなるようにする if(!firstLightFlag){ firstLightIntensity = int.Parse(data[0]);// 1 firstLightFlag = true; } //changeLightIntensity → 初期値以後の光の強さ float changeLightIntensity = float.Parse(data[0]);// 2 //現実の光の変化をゲーム内の光の変化に変換する処理 changeLightIntensity = Mathf.Clamp(firstLightIntensity - changeLightIntensity,50,-180) / 100;// 3 //「現在のゲーム内の光の強さ」から、「現実の光の強さを考慮した変更後の光の強さ」へとMathf.Lerpで滑らかに変化させる float nowLightIntensity = this.GetComponent<Light>().intensity; this.GetComponent<Light>().intensity = Mathf.Lerp(nowLightIntensity, 2f+changeLightIntensity, 8f*Time.deltaTime);// 4 |
処理の流れは以下のとおりです。
- 最初の光の強さを保存
- 以後の光の強さを保存
- 最初の光の強さとそれ以後の光の強さを比較し、その差を一定の範囲(+50〜-180)に制限。ゲーム内の光の強さは”spot light”の”Intensity”で設定されるが、これが取れる値の範囲は0〜8の間なので、+50〜-180に制限された値を100で割り、+0.5〜-1.8までの値に変換
- 2f(ゲーム内の光の強さの初期値)に③で求めた数字を足し、「現実の光の強さを考慮した変更後の光の強さ」とする。Mathf.Lerpで「現在のゲーム内の光の強さ」から、「現実の光の強さを考慮した変更後の光の強さ」へと滑らかに変化させる
昼用の敵を追加する
敵が幽霊しかいないので、昼用の敵を追加します。
ここではプレイヤー用に用意されているキャラを使いまわして、昼用の敵にしていきます。

まず、ヒエラルキービューのPlayerを複製し、名前とタグを「Enemy_Knight」に変更します。プレイヤーではなく敵になるので、アタッチされているPlayerControllerを外して、MonsterCtrlをアタッチします。
また、Capssule Colliderのis Triggerにチェックをつけておきます。
敵の色を変える
このままだと見た目がプレイヤーと全く同じになってしまうので、敵の色を変えます。

「3DAssets」-「Material」内の「knight」マテリアルを複製し、「enemy_knight」マテリアルを作ります。複製はメニューバーの「edit」-「duplicate」から可能です。「enemy_knight」マテリアルの「Main Color」を真っ黒に変更します。

ヒエラルキービューから「Enemy_Knight」-「knight」以下の要素をすべて選択します。「Materials」の「Element 0」が「knight」になっていますので、先ほど作成した「enemy_knight」に変更します。

ヒエラルキービューの「Enemy_Knight」を「Assets」-「Object」内にドラッグアンドドロップし、プレファブ化します。設定がうまくいっていれば、プレビューに影のようになった真っ黒なナイトが表示されます。
マップに敵を追加する
設定した「Enemy_Knight」がゲーム内に表示されるように、マップの設定をしていきます。

「MapData」から「Map_Stage1」を開きます。マップの作り方については本に記載されてますので説明を省きますが、pがプレイヤーを、数字の1〜4が敵を表しています。今回は4隅に敵を配置しました。
番号によって敵を振り分ける
初期設定では1〜4まですべて幽霊型のenemy(=夜用の敵)に割り振られていますので、昼用の敵が現れるようにGameCtrl.csを変更していきます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
//GameCtrl.cs //public void OnStageStart()内抜粋 // 敵スポーン. for (int i = 1; i < 5; i++) { Vector3 pos = m_map.GetSpawnPoint (i); if (pos == Vector3.zero) continue; GameObject e; //変更箇所 if(i < 3){ e = (GameObject)Instantiate(m_enemyPrefab,pos,Quaternion.identity); }else{ e = (GameObject)Instantiate(m_enemyKnightPrefab,pos,Quaternion.identity); } //変更箇所終わり MonsterCtrl mc = e.GetComponent<MonsterCtrl>(); mc.m_aiType = (MonsterCtrl.AI_TYPE)((int)MonsterCtrl.AI_TYPE.TRACER + i - 1); mc.SetSpawnPosition(m_map.GetSpawnPoint(Map.SPAWN_POINT_TYPE.BLOCK_SPAWN_POINT_PLAYER + i)); mc.SetDifficulty(m_stageNo); mc.SendMessage("OnStageStart"); } |
「敵スポーン」とコメントされている箇所が敵をマップに配置する処理です。数字が1,2の場合は初期設定の幽霊型の敵を、3,4の場合は昼用のナイト型の敵が現れるように設定しています。
GameCtrl.cs内にはリスタートの処理など多数、enemyの処理が記載されていますので、それらの処理をすべて昼用の敵にも追加する必要があります。スクリプト内を「enemy」で検索するなどし、enemyに対して行われている処理をenemy_knightに対しても行われるように追加しておいてください。
ゲーム内の明るさによって表示される敵を切り替える
最後にゲーム内が明るいときはナイト型の敵が、暗いときは幽霊型の敵が表示されるように設定していきます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
//DoSomething.cs //void OnDataReceived(string message) try{ }内に追記 //ライトの明るさを取得 nowLightIntensity = this.GetComponent<Light>().intensity; //enemy,enemy_knightのインスタンスを取得しておく if(!getEnemys){ enemys = GameObject.FindGameObjectsWithTag ("Enemy"); enemy_knights = GameObject.FindGameObjectsWithTag ("Enemy_Knight"); getEnemys = true; } //SetActiveで明るさによって敵の表示を切り替える if(nowLightIntensity > 1){ for (int i = 0; i < enemys.Length; i++){ enemys[i].SetActive (false); } for (int i = 0; i < enemy_knights.Length; i++){ enemy_knights[i].SetActive (true); } } if(nowLightIntensity <= 1){ for (int i = 0; i < enemy_knights.Length; i++){ enemy_knights[i].SetActive (false); } for (int i = 0; i < enemys.Length; i++){ enemys[i].SetActive (true); } } |
これでひととおり完成です。あとは敵の表示位置やスピードを変えたりして、ゲームバランスを調整してみてください。