「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でジェット機を操作しました。











