Raspberry Pi 3でアルプスIoT Smart Moduleのモーションデータを受け取り、シリアル通信でパソコンにこのモーションデータを送ります。送られたモーションデータをパソコン上で動作しているUnity5で受け取り、モーションデータのロール、ピッチ、ヘディングをAsset Storeから取得したaircraft「Stealth Bomber」に反映させ、アルプスIoT Smart Moduleの動きに応じてaircraft「Stealth Bomber」を動かします。使用している各アプリ/ドライバのバージョンを次に示します。
- Unity 5.6.3f1
- AQUOS PAD SH-08E android 4.2
- Android SDK(ADT 25) tools_r25.2.5-windows
- Windows 7 Professional 32ビット版
Raspberry Pi 3でアルプスIoT Smart Moduleのモーションデータを受け取り
Raspberry Pi 3でアルプスIoT Smart Moduleのモーションデータを受け取るために、「アルプスIoT Smart Moduleの加速度センサと地磁気センサの値をanyPiで取得/表示」に使用したプログラムをベースにします。今回は必要ないので、全体の制御モジュール「actionmain.py」のanyPiのPiConsole I/Fのテキスト表示ディスプレイに表示するdisplayAngle関数と範囲チェックとLED(赤/黄)の点灯/消灯を行うbuzzer関数を削除します。
Raspberry Pi 3とパソコン間のシリアル通信
シリアル通信を行うためのRaspberry Pi3とパソコンの設定は、「Raspberry Pi3とパソコンとをシリアル接続」に従います。アルプスIoT Smart Moduleから受け取ったモーションデータをRaspberry Pi 3からパソコンにシリアル通信するために、Raspberry Pi 3のプログラムの全体の制御モジュール「actionmain.py」に、次のシリアル送信関数「sendAngle」を追加します。
def sendAngle(Roll,Pitch,Heading): global counter counter += 1 if(counter % 1) == 0: print('sendAngle') con.write('{0:.2f},{1:.2f},{2:.2f}\t'.format(Roll,Pitch,Heading))
なお変数「con」は次のように定義されます。また変数「counter」により、パソコンへの送信間隔を調整します(現状のコードでは、アルプスIoT Smart Moduleのデータ取得間隔「100ms」となります)。
con=serial.Serial(‘/dev/ttyS0’, 115200)
Unity5でAsset Storeから取得したaircraft「 Stealth Bomber」の表示
今回のゲームソフトは、「unity5でAndroidのセンサーを用いたゲーム作成」で作成したプロジェクトを使用します。ただし、オブジェクト「Camera Set」に設定したC#スクリプトは、今回作成するシリアル通信用のC#スクリプトに置き換えます。なおオブジェクト「Camera Set」は、Main Cameraの親となるオブジェクトで、オブジェクト「Camera Set」を回転させると、同じ位置に設定されたaircraft「Stealth Bomber」を基点にMain Cameraが回転する状態を作れます。
Unity5でシリアルデータの受信
Unity4.5ではすでに「UnityでC#によりCOMポートからのデータ入力」でシリアル通信を行いましたが、Unity5では、同じブログラムを使ってシリアル通信ができませんでした。次のC#スクリプト「SerialHandler」のように、ReadメソッドのserialPort_.ReadLineメソッドを serialPort_.ReadByteメソッドに変更し、1バイトづつシリアルデータを入力するように変更しました。これに伴い、文字「’\t’」を1行の最後の文字とし、前回のシリアル通信と同様に1行づつ処理します。次に、作成したC#スクリプト「SerialHandler」を、オブジェクト「Camera Set」にドラッグ&ドロップします。
SerialHandler.cs
using UnityEngine; using System.Collections.Generic; using System.Collections; using System.IO.Ports; using System.Threading; using System; using System.Runtime.InteropServices; using System.Text; public class SerialHandler : MonoBehaviour { public string portName = "COM3"; public int baudRate = 115200; private SerialPort serialPort_; private Thread thread_; private bool isRunning_ = false; private string lastrcvd = ""; private string message_; private bool isNewMessageReceived_ = false; private List<Vector3> angleCache = new List<Vector3>(); public int angleCacheNum = 10; public Vector3 angle { private set { angleCache.Add(value); if (angleCache.Count > angleCacheNum) { angleCache.RemoveAt(0); } } get { if (angleCache.Count > 0) { var sum = Vector3.zero; angleCache.ForEach(angle => { sum += angle; }); return sum / angleCache.Count; } else { return Vector3.zero; } } } void Start() { lastrcvd = ""; Open(); } void Update() { if (isNewMessageReceived_) { OnDataReceived(message_); } } void OnDestroy() { Close(); } private void Open() { serialPort_ = new SerialPort(portName, baudRate, Parity.None, 8, StopBits.One); serialPort_.ReadTimeout = 5000; serialPort_.Open(); isRunning_ = true; thread_ = new Thread(Read); thread_.Start(); } private void Read() { byte rcv; char tmp; while (isRunning_ && serialPort_ != null && serialPort_.IsOpen) { try { rcv = (byte)serialPort_.ReadByte(); if (rcv == '\t') { message_ = lastrcvd; Debug.LogFormat("textLine:{0}", message_); lastrcvd = ""; isNewMessageReceived_ = true; } else { tmp = (char)rcv; // Debug.LogFormat("rcv:{0}", tmp.ToString()); lastrcvd = lastrcvd + tmp.ToString(); } } catch (System.Exception e) { Debug.LogWarning(e.Message); } } } private void Close() { isRunning_ = false; if (thread_ != null && thread_.IsAlive) { thread_.Join(); } if (serialPort_ != null && serialPort_.IsOpen) { serialPort_.Close(); serialPort_.Dispose(); } } void OnDataReceived(string message_) { float roll; float pitch; float Heading; var data = message_.Split( new string[] { "," }, System.StringSplitOptions.None); if (data.Length < 3) return; try { roll = float.Parse(data[0]); pitch = float.Parse(data[1]); Heading = float.Parse(data[2]); angle = new Vector3(-roll, Heading, pitch); transform.rotation = Quaternion.Euler(angle); Debug.LogFormat("roll:{0} pitch:{1} Heading:{2}", roll, pitch, Heading); } catch (System.Exception e) { Debug.LogWarning(e.Message); } } }
再生ボタンを押してゲームを実行することにより、Console領域に表示されたデバック情報を次に示します。Raspberry Piから受け取ったモーションデータを、それぞれroll、pitch、Headingの各データごとに分割されたことが確認できます。
・・・ textLine:-0.84,-1.93,315.66 UnityEngine.Debug:LogFormat(String, Object[]) SerialHandler:Read() (at Assets/SerialHandler.cs:96) roll:-0.84 pitch:-1.93 Heading:315.66 UnityEngine.Debug:LogFormat(String, Object[]) SerialHandler:OnDataReceived(String) (at Assets/SerialHandler.cs:147) SerialHandler:Update() (at Assets/SerialHandler.cs:61) ・・・
アルプスIoT Smart Moduleのモーションデータをaircraft「 Stealth Bomber」に反映
C#スクリプト「SerialHandler」のOnDataReceivedメソッドで、CSV形式で受け取ったロール、ピッチ、ヘディングを、3D での位置や方向のために使用されるVector3形式の変数「angle」に保存します。 ロール、ピッチ、ヘディングによる回転をオブジェクト「Camera Set」に設定するために、変数「angle」をパラメータにしたQuaternion.Euler関数で計算し、その結果をオブジェクト「Camera Set」の回転変数「transform.rotation」に設定します。実際のロジックは上記のC#スクリプトを参照してください。
パソコン上で実行するために、Unity5のメインメニューから「File」→「Build Settings」を選択し、表示された次の画面で、「PC,Mac・・・」を選択してBuild and Runボタンを押すと、パソコンで動作する実行ファイルが作成されゲームが開始されます。
ゲームソフトが実行されると、次のように、アルプスIoT Smart Moduleを回転するとStealth Bomberも同様に回転し、アルプスIoT Smart Moduleを傾けるとStealth Bomberも同様に傾きます。
Windowsへのbuild時にオブジェクトのテキスチャがピンクぽくなる件
Unity5のメインメニューから「File」→「Build Settings」を選択し、表示された画面で、「PC,Mac・・・」を選択してBuild and Runボタンを押すと、パソコンで動作する実行ファイルが作成されますが、この時に次のようにaircraft「Stealth Bomber」のテキスチャがピンクぽくなります。
これは、Material「Stealth_Bomber」が使用するShaderの参照が見つからない状態になっており、「Standard」ではShaderが適切でないことになります。次のように、Inspector領域のShaderを「Mobile/Bumped Specular」にしてみます。
次のようにビンクぽかったテキスチャがきれいに表示されるようになりました。