「M5Stack Core2内蔵のジャイロ加速度計「MPU6886」の入力」で出力する加速度・ジャイロデータを使って、Unityで作成したジェット機を飛行させてみます。M5Stack Core2とUnity間はシリアルインタフェースで接続し、加速度データと姿勢角を使用します。
・Unityバージョン:2022.3.11f
・プロジェクト名:GyroTest
シリアルインタフェイスの設定
Unityでシリアル通信を行うために、次のように設定します。
Editメニューから「ProjyectSettings」を選択します。
表示されたダイアログで「Player」→「Configuration」→「Api Compatibility Level」で次のように設定します。
背景のアセットの組み込み
「Polylised – Medieval Desert City」をアッセットストアからダウンロードしてプロジェクトにインポートします。
「Assets/Polylised – Medieval Desert City/Demo」の「Demo_01.unity」をダブルクリックして、シーンを設定します。
ジェット機のアセットの組み込み
「Fighter Jet Low Poly」をアッセットストアからダウンロードしてプロジェクトにインポートします。
「Assets/Raptor3D/FA_N26/0_Prefabs」の「FA_N26_Color_2_Prefab.prefab」をシーン「Demo_01」にドラッグ&ドロップします。
Main Cameraの設置
ジェット機を後ろから追従するようにMain Cameraを設置するため、Main Cameraをジェット機の子オブジェクトとします。具体的には、FA_N26_Color_2_Prefab」にMain Cameraをドラッグ&ドロップします。
「FA_N26_Color_2_Prefab」の位置を次に示します。
「FA_N26_Color_2_Prefab」と「Main Camera」の位置合わせ
- 「FA_N26_Color_2_Prefab」と「Main Camera」との親子関係を解除します。
- 「Move Tool」ツールを選択し「FA_N26_Color_2_Prefab」をクリックして軸を表示し、右上の3Dアイコン(シーンギズモ)を用いて調整しやすい位置に設定します。X軸は左右、Z軸は進行方向、Y軸は上下方向を示します。シーンギズモは、「Sift+シーンギズモの□」をクリックすると初期状態に戻ります。
- 希望する位置に設定に設定したら、「Main Camera」の位置も同じにして、「FA_N26_Color_2_Prefab」の子として「Main Camera」を設定します。
- 「Main Camera」の位置を次のように変更します。
BGMの組み込み
BGMの音源をゲームに組み込みます。
- BGMの音源「effect.mp3」をAssets画面にドラッグ&ドロップします。
- 「Demo_01」を右クリックし、「GameObject」→「Create Empty」を選択します。
- 「Inspector」タブに表示された「Add Component」をクリックし、次のように「Audio Source」選択します。
- 表示されたAudio Sourceの「AudioClip」にAssets画面のBGMの音源「effect.mp3」をドラッグ&ドロップし、タイトルを「BGM」、「Loop」をチェックします。
スクリプトの作成
Unityの作成画面を次に示します。
作成したスクリプトを次に示します。姿勢角を使用する場合は、最初は机にM5Stack Core2を置いて、補正値を求めます。
- 39-55行目で、’a’(加速度)の場合はOnAccelReceived()関数、 ‘p’(姿勢角)の場合はOnGyroReceived()関数をそれぞれ呼び出します。
- 85行目で、’a’(加速度) ‘p’(姿勢角)のみデータを使用します。
- 162行目で、姿勢角の平均値を求めます。
- 200行目で、求めた姿勢角の平均値を入力した姿勢角に反映させます。
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 = "COM9"; public int baudRate = 9600; private SerialPort serialPort_; private Thread thread_; private bool isRunning_ = false; private string lastrcvd = ""; private byte dataid = 0x0; private string message_; private bool isNewMessageReceived_ = false; private int gyrocnt = 0; private float offsetpitch; private float offsetroll; private float offsetyaw; private float[] setpitch = new float[10]; private float[] seteroll = new float[10]; private float[] setseyaw = new float[10]; void Start() { lastrcvd = ""; Open(); } void Update() { if (isNewMessageReceived_) { if (dataid == 'a') { gyrocnt = 0; OnAccelReceived(message_); } if (dataid == 'p') { OnGyroReceived(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 == 'a') || (rcv == 'p')) { dataid = rcv; 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.LogError("Read:" + e.Message); } } } private void Close() { isRunning_ = false; if (thread_ != null && thread_.IsAlive) { thread_.Join(); } if (serialPort_ != null && serialPort_.IsOpen) { serialPort_.Close(); serialPort_.Dispose(); } } void OnAccelReceived(string message_) { float x; float y; float z; float speed = 10.0f; var data = message_.Split( new string[] { "," }, System.StringSplitOptions.None); if (data.Length != 3) return; try { var data1 = data[0].Split(new string[] { ":" }, System.StringSplitOptions.None); x = float.Parse(data1[1]); data1 = data[1].Split(new string[] { ":" }, System.StringSplitOptions.None); y = float.Parse(data1[1]); data1 = data[2].Split(new string[] { ":" }, System.StringSplitOptions.None); z = float.Parse(data1[1]); Debug.LogFormat("Accel x:{0} y:{1} z:{2}", x, y, z); var dire = Vector3.zero; dire.z = x * -1; dire.x = y * -1; dire *= Time.deltaTime; transform.Rotate(dire * speed); this.transform.Translate(Vector3.forward * Time.deltaTime * speed); } catch (System.Exception e) { Debug.LogError("OnAccel:" + e.Message); } } float Average(float[] data) { float sum = 0f; //合計値計算 for (int i = 0; i < data.Length; i++) { Console.WriteLine("Data[{0}] = {1}", i + 1, data[i]); sum += data[i]; } int count = data.Length; //平均値計算 return sum / count; } void OnGyroReceived(string message_) { float pitch; float roll; float yaw; float speed = 10.0f; var data = message_.Split( new string[] { "," }, System.StringSplitOptions.None); if (data.Length != 3) return; try { var data1 = data[0].Split(new string[] { ":" }, System.StringSplitOptions.None); pitch = float.Parse(data1[1]); data1 = data[1].Split(new string[] { ":" }, System.StringSplitOptions.None); roll = float.Parse(data1[1]); data1 = data[2].Split(new string[] { ":" }, System.StringSplitOptions.None); yaw = float.Parse(data1[1]); Debug.LogFormat("pitch:{0} roll:{1} yaw:{2}", pitch, roll, yaw); if (gyrocnt >= 10) { this.transform.rotation = Quaternion.Euler(roll - offsetroll, yaw - offsetyaw, pitch - offsetpitch); //this.transform.rotation = Quaternion.Euler(pitch, yaw, roll); this.transform.Translate(Vector3.forward * Time.deltaTime * speed); } else { setpitch[gyrocnt] = pitch; seteroll[gyrocnt] = roll; setseyaw[gyrocnt] = yaw; if (gyrocnt == 9) { offsetpitch = Average(setpitch); offsetroll = Average(seteroll); offsetyaw = Average(setseyaw); Debug.LogFormat("offsetpitch:{0} offsetroll:{1} offsetyaw:{2}", offsetpitch, offsetroll, offsetyaw); } gyrocnt++; } } catch (System.Exception e) { Debug.LogError("OnGyro:" + e.Message); } } }
ゲームプレイ
M5Stack Core2を傾け、入力した加速度データを使って、Unityでジェット機を操作しました。