UnityとBNO055を使ったVRもどき
どうも。きさらぎです。
2023年度が終わったのでせっかくだし2023最後にネタで作ったVRもどきの作り方を説明したいと思います。
UnityでSerial通信をする方法は情報が錯綜していてわざわざ複数のスクリプト(C#)書いたり、usingつけ足したり、よくわからんAsset導入したり。 それでいて現行バージョンで動かないので半日かけました。
逆に言えば半日潰せばできるめちゃくちゃ簡単な話なのですが、備忘録もかねてやっていきます。
言い忘れてましたが今回使うマイコンはArduinoNanoEveryです。使う予定だったArduinoUnoが壊れててI2C通信ができなかったので。
まずは回路を組みます。
5Vにつなぐプルアップ抵抗は1kΩです。ArduinoNanoEveryを使うといって二秒で手のひら返しですが、画像はArduinoUnoです。

一応ピンの対応関係は、
Arduino BNO055
5v VIN
GND GND
A4 SDA
A5 SCL
となっています。
次にプログラムを書きます。マイコン側は、かなり初歩的なプログラムなのでRaspberryPiPicoだろうがArduinoUnoだろうが書き込むことができます。RaspberryPiPicoに関してはWire.setSDA()とWire.setSCL()をWire.begin()の前に実行して、I2C2を使う場合はBNO055のコンストラクタに&Wire1をいれてください。
ではまずマイコンに書き込むプログラムをば

#include <Wire.h>
#include <Adafruit_Sensor.h>
#include <Adafruit_BNO055.h>
#include <utility/imumaths.h>
Adafruit_BNO055 BNO = Adafruit_BNO055(55, 0x28, &Wire);//コンストラクタ
void setup()
{
Serial.begin(115200);//ここの数字は絶対に覚えておくこと
while (!Serial)
{
delay(10);
}
//Wire.setSDA()とかをやるならこのタイミング。
if(!BNO.begin())
{
Serial.println("error");
while(1);
}
}
void loop()
{
imu::Quaternion quat = BNO.getQuat();
Serial.print(quat.w() , 8);Serial.print(",");
Serial.print(quat.x() , 8);Serial.print(",");
Serial.print(quat.y() , 8);Serial.print(",");
Serial.println(quat.z() , 8);
delay(10);
}
これをマイコンに書き込んでください。 書き込むときに、そのマイコンの接続ポートを確認してください。Tools => Port:○○ この○○に入ってるのがポートです。COM~は絶対に覚えておいてください。こことSerial.beginの数字さえ覚えておきましょう。後で使います。
一応プログラムの解説をすると、 1~4行目は必要なライブラリの読み込み。BNO055はライブラリ3つ読み込まないといけないので自分はBNO055の公式ライブラリがあまり好きではないです。
6行目はBNO055のコンストラクタを実行しています。I2CアドレスとWire間違えてなければどうにかなります。
7~20行目がsetup関数です。
9行目にSerial通信の開始を行っています。
10~13行目はSerial通信が始まるまで待ち続けています。
15~19行目は、BNOの初期化関数を実行し、それに失敗した場合はSerialMonitorにerrorと表示するようにしています。
21~31行目はloop関数です。無限に実行されます。
23行目は変数の宣言と代入を同時に行っています。型…というかクラスは、imu内にあるQuaternionクラスです。今回はquatという変数名で、BNO.getQuat()という関数の返り値を代入しています。
25~28行目はSerial.printでquatの値を出力しています。カンマ区切りにしていますが、ここを変える場合、以下のUnity用のスクリプトも変更してください。
30行目はBNOの出力待機です。
上記を書き込んだら一度ケーブルを抜いてください。
自分はSerialReadという名前でスクリプトを作成しました。
using System.Collections;
using System.Collections.Generic;
using System;
using System.IO.Ports;
using UnityEngine;
public class SerialRead : MonoBehaviour
{
[SerializeField]private string portName;
[SerializeField]private int baurate;
private SerialPort serial;
public Quaternion Object;
void Start ()
{
this.serial = new SerialPort (portName, baurate, Parity.None, 8, StopBits.One);
try
{
this.serial.Open();
}
catch(Exception e)
{
Debug.Log ("ポートが開けませんでした。設定している値が間違っている場合があります");
}
}
void Update()
{
string message = this.serial.ReadLine();
string[] test = message.Split(",");
//Quaternion代入
Object.w = float.Parse(test[0]) * 1;
Object.x = float.Parse(test[2]) * 1;
Object.y = float.Parse(test[3]) * -1;
Object.z = float.Parse(test[1]) * -1;
this.gameObject.transform.rotation = Object;
}
}
では解説行きます。
1~5行目はライブラリの読み込みです。System.IO.Portsをusingしているのがちょっと特殊ですかね。Systemそのものもusingしています。もしかして:無駄
7行目でMonoBehaviour継承クラスの作成です。解説がだるいので詳しい説明が欲しければとりあえず他所当たってください。
10,11行目はインスペクターに表示できるよう設定されたstring型のportNameと、int型のbaurateという変数の宣言です。インスペクターにて、portNameに先述のポート名を、baurateにSerial.beginの数字を入れてください。
13行目はSerialPortクラスのserialという変数を作成します。あとでnew演算子からコンストラクタ実行します。
14行目はQuaternionクラスのObjectという変数を作成しています。名前は脳が付かれていた時に考えたので適当です。こいつには、マイコンから送られてきた値を格納してカメラのRotationに代入するまでのつなぎの役割を持ちます。
18行目です。完全に理解してないので説明が難しいですが、引数はちゃんと入れないとだめです。
22行目です。serial通信を開始して、何か問題があった場合、26行目のエラー表示が実行されます。
31行目は、serialから送られてきた1行分を読み取ってmessageという変数に格納します。変数の宣言がUpdate毎に行われているので宣言は10行目とかのところでした方がいいかも。
32行目は、Split関数で、messageをstring配列に変換して、testに格納します。変数名はちゃんと考えて決めましょう。宣言についてはmessageと同様です。
34~37行目で、Objectのw,x,y,zに各値を代入します。yとzに-1をかけてる理由は、右手系から左手系に変換するためです。詳しくはこちらをみてください(理解できるとは言っていないし自分も理論を理解したわけじゃない)
38行目は自分が付いているオブジェクトのtransformのrotationに代入しています。
これを、Main Cameraに直接Add Componentしてください。
ゲームスタートの前に頭にブレッドボードを置いてケーブルを指しましょう。
では、スタートボタンを押してください。押す前にリンクスタートというと周りの人から白い目で見られますが臨場感が得られます。
首をぐるぐる回してその通りに視点が傾いていれば成功です。だめならエラーを見て解決しましょう。質問、誤字脱字エラーなどあれば指摘お願いします。