B737-800シミュレータの起動・停止方法
この記事は CBcloud Advent Calendar 2020 - Qiita の23日目です。
航空ファンのみなさん(?)、こんばんは!
業務都合と自分の興味が噛み合う時にしかブログを書かなかった結果、2年の時が流れました。 その間に会社を移り、興味の対象もF1 2018からMSFS2020へ広がりを見せています。
まぁそれは置いといて早速やっていきましょう
はじめに
CBcloud社内にはコクピットエリアと呼ばれるオープンスペースがあり、その前方には文字通りコクピットがあります。 www.wantedly.com
こちら実機のパイロットが訓練に使う機材そのままのいわゆるガチなシミュレータとなっています。 代表の松本は元航空管制官であり、実機パイロットの友達がオフィスでガチ練習をしていることもあります。 ※事情を知らずに近くで作業していたらチェックリスト読み上げてる風のガチっぽい人が飛ばしていてすごいなーと思ったら本物だったという
ここから本題なのですが、現状これを扱えるのが代表の松本だけであり、こんなすごい機材があるのに他のメンバーは触り方がわからなくてほぼ眠っています。 それはもったいないし、僕が好きに飛ばしたい(重要)のでここに起動・停止の手順と注意点などをまとめたいと思います。
シミュレータについて
松本によると ということです。このあたりは別途掘り下げる機会があればまとめたいと思います。
CHECKLIST(起動)
1. シミュレータPC
1. 電源タップ&黒いスイッチON
2. 中央のディスプレー点灯を確認
2. プロジェクター
コクピットを囲むようにスクリーンがあり、3台のプロジェクターで投影しています
1. プロジェクターON
2. ランプ交換のメッセージがでるのでOKを2回
基本は2回押せばOKですが、3台のプロジェクターを同時に操作するので、どれか反応していない場合もあります。 その際はメッセージが消えるまでOK押下を繰り返しです。
3. 副操縦士側 PFD、ND
コクピットの向かって右、副操縦士が使用するPFD、NDのディスプレーは別途スイッチを入れる必要があります ※PFD(プライマリー・フライト・ディスプレイ) ※ND(ナビゲーション・ディスプレイ)
1. トグルスイッチ2つをON、両ディスプレイが映っていることを確認
4. シミュレータ設定
1. Edgeを起動
2. ローディング画面がしばし続くが、いったん画面更新すると消える
3. 訓練用の障害をOFFにする
デフォルトでは訓練用なのかトラブルが起きる設定が入っているのでOFFにしておきましょう。 左メニューのFailuresです
起動完了
以上でシミュレータの起動は完了です! この状態は、いわゆる「Cleared for Takeoff」をもらった状態(だと思われます)なので、パーキングブレーキを解除し、スロットルを入れれば離陸滑走を始めることができます。 シム側の設定次第で実機同様エンジン始動から始めることもできますが、デフォでは滑走路上で離陸準備完了という感じになっていそうです。
出発地の変更
以下の手順で現在地を変更できるので、好きな空港から出発することができます
シミュレータのポーズ・解除
操縦席・副操縦席共にシムをポーズする為のボタンがあります。設定変更やシミュレーター終了時に必要になります。
CHECKLIST(停止)
1. シミュレータ終了
2. プロジェクター停止
3. 電源タップOFF
4. 終了
F1 2018のUDPテレメトリーでオリジナルダッシュボードを作る
この記事は SmartDrive Advent Calendar 2018 - Qiita の15日目です。
F1ファンのみなさん(?)、こんばんは!
このブログの表題を実現すべく日夜タイムアタックで世界1位を目指し(いまのところ4900位あたりを彷徨って)いる id:jkym99 です。 その傍らでSmartDriveのバックエンドエンジニアとしてRubyやGoで色々していたりもします。
アドベントカレンダーをきっかけに作ったブログですが、当面はF1 2019が発売されるまでに公約を実現できるよう頑張っていきます。
さて、初回はPS4版 F1 2018に用意されているUDPテレメトリー機能で色々やるための準備編といった感じでお送りします。
F1 2018とUDPテレメトリー
F1 2018はコードマスターズが開発しているF1公式レースゲームでPC/Xbox One/PS4向けに展開されています。 毎年夏頃にその年のデータを反映した新作が発売されていて、2018年も9/20に発売されました。
最近のレースゲーム、中でもレースシムと言われるようなカテゴリのタイトルには実車F1さながらに走行中の速度やタイム、ハンドル・アクセル・ブレーキ操作をはじめとした様々なデータをUDPプロトコルで送信する、いわゆるテレメトリー機能が搭載されています。
ユーザーはこれに対応したアプリケーションを利用する事でゲーム画面には表示されない多くの情報を得ることができます。
例えばこのRS Dashというアプリを使うと、 こんな感じのデータを簡単に確認することができます。
これだけでもまぁ便利ではあるんですが、以下のような不満がでてきます
- 走行中に見たいデータとあとで振り返る際に見たいデータの内容やそれに適した表現は異なる
- そもそもあとからデータを振り返るような機能がない
- レイアウトやフォントサイズの問題で視認性が悪い
- もっとカッコいいやつがいい
現代F1はデータドリブンですから、闇雲に走り回るのはいったんやめにして、このあたりを解消しつつカッコいい感じのやつを作っていきましょう
UDPテレメトリーを受信する
まずはゲームからUDPで送信されてくるデータを受信してプログラムから扱えるようにしなくてはいけません。
パケットのデータ構造
UDPパケットをデコードするためには構造を知らなければいけませんが、そもそも公開されているのか・・・
前述のRS Dashなど3rd partyのアプリが複数存在しているので、どこかにあるはずだと思ったらCodemastersのフォーラムにありました! (下記のスレッドを読み進めるとわかりますが、内容が所々間違っていてツッコミが入ってるので注意が必要です...)
F1 2017 D-Box and UDP Output Specification — Codemasters Forums
データをJSONで出力する
まず、ゲーム側のテレメトリー設定をこのようにします (ゲームのポーズメニュー > お好みの設定 > テレメトリー設定)
テレメトリーデータをJSON文字列で表示するプログラムをGoで実装するとこんな感じです。
https://gist.github.com/jkym/cac411448ca5f5ad8e9098d2cecacc0b
これを go run main.go
などとすれば60Hzで巨大なJSONがガガーっと流れてきます。
ちょっと長いですが、こんな感じ
{ "Time": 484.2308, "LapTime": 10.225553, "LapDistance": 4689.0405, "TotalDistance": 9990.318, "X": 342.84232, "Y": 2.9368935, "Z": 679.2339, "Speed": 24.266415, "Xv": -24.263298, "Xy": -0.1229023, "Xz": -0.36903763, "Xr": 0.04496999, "Yr": -0.008158523, "Zr": -0.9989565, "Xd": -0.9989795, "Yd": -0.004903322, "Zd": -0.044930995, "SuspPos": [ -3.1337132, 6.698986, -1.6587093, 5.8877153 ], "SuspVel": [ 67.383484, 138.62654, -5.815205, 10.607468 ], "WheelSpeed": [ 23.361992, 24.848207, 23.61155, 25.07684 ], "Throttle": 0.028936876, "Steer": 0.60996044, "Brake": 0, "Clutch": 0, "Gear": 3, "GforceIat": 2.3192554, "GforceIon": -0.56821126, "Lap": 2, "EngineRate": 8281.013, "SliProNativeSuport": 0, "CarPosition": 1, "KersLevel": 400000, "KersMaxLevel": 400000, "Drs": 0, "TractionControl": 0.008800052, "AntiLockBrakes": 1, "FuelInTank": 10, "FuelCapacity": 105, "InPits": 0, "Sector": 2, "Sector1Time": 0, "Sector2Time": 0, "BrakesTemp": [ 32.077118, 32.077118, 32.077118, 32.077118 ], "TyresPressure": [ 21.5, 21.5, 23, 23 ], "TeamInfo": 1, "TotalLaps": 1, "TrackSize": 5301.278, "LastLapTime": 91.03247, "MaxRPM": 13500, "IdleRPM": 4300, "MaxGears": 9, "SessionType": 0, "DrsAllowed": 0, "TrackNumber": 0, "VehicleFIAFlags": 0, "Era": 0, "EngineTemperature": 90, "GforceVert": 0.012075849, "AngVelX": -0.039477743, "AngVelY": 0.90246755, "AngVelZ": -0.025669953, "TyresTemperature": [ 100, 100, 100, 100 ], "TyresWear": [ 0, 0, 0, 0 ], "TyreCompound": 1, "FrontBrakesBias": 60, "FuelMix": 3, "CurrentLapInvalid": 0, "TyresDamage": [ 0, 0, 0, 0 ], "FrontLeftWingDamage": 0, "FrontRightWingDamage": 0, "RearWingDamage": 0, "EngineDamage": 0, "GearBoxDamage": 0, "ExhaustDamage": 0, "PitLimiterStatus": 0, "PitSpeedLimit": 37, "SessionTimeLeft": 3.4028235e+38, "RevLightsPercent": 0, "IsSpectating": 0, "SpectatorCarIndex": -1, "NumCars": 1, "PlayerCarIndex": 0, "CarData": [ { "WordPosition": [ 342.84232, 2.9368935, 679.2339 ], "LastLapTime": 91.03247, "CurrentLapTime": 10.225553, "BestLapTime": 91.03247, "Sector1LapTime": 0, "Sector2LapTime": 0, "LapDistance": 4689.0405, "DriverID": 13, "TeamID": 1, "CarPosition": 1, "CurrentLapNum": 3, "TyreCompound": 1, "InPits": 0, "Sector": 0, "CurrentLapInvalid": 0, "Penalties": 0 } ], "Yam": -1.6157819, "Pitch": -0.0045316555, "Roll": 0.008158601, "XLocalVelocity": 0.72146493, "YLocalVelocity": -0.0098551065, "ZLocalVelocity": 24.25572, "SuspAcceleration": [ 4349.485, 3506.0671, 1856.6912, 4040.6658 ], "AngAccX": -2.3219652, "AngAccY": -0.38878247, "AngAccZ": 2.6509306 }
さて、ようやくテレメトリーデータを自由に加工する準備が整いました。
カッコいいGUIを作る
ちなみに走行中にドライバーが見る為のダッシュボード(スピードメーターとかですね)ならSimHubというのがあります。 ハンドルコントローラーにLEDのシフトインジケーターをつけてピカピカさせたい場合に必要な制御などをいい感じにやってくれるようです。
SimHub, DIY Sim racing Dash - Going mobile | RaceDepartment
こんなやつです
カッコいい。
もうこれでいいじゃんと思ってしまいそうですが、SimHubは走行時のメーターなのでちょっと用途が違うのと
- 後から任意のデータをグラフ表示したい
- 車両やコース、天候などで検索したい
などなど色々やりたい、というか人と同じことをしていては1位はとれません的に考えるとDIYしないといけません。 この辺も実車F1同様に競争の一部ということですね。
今回は同僚におすすめされたUnityで作ってみることにします。 Unity未経験ですが、とりあえずやってきましょう。
GoからUnityにテレメトリーデータを送信する
先程のGoのプログラムを少し修正して流用します。 とりあえずUnityで速度を表示してみたいので、Goのサーバーでは
- クライアントからwebsocket接続を受け付け
- UDPパケットを受信してwebsocketで送信
ということをやります。
websocketサーバの実装にはこちらを利用しました
GitHub - gorilla/websocket: A WebSocket implementation for Go.
go get github.com/gorilla/websocket
してから以下のコードを main.go
として保存し、go run main.go
でwebsocketサーバが起動します。
Decode UDP Telemetry & Websocket Server · GitHub
Unityのセットアップ
Unity初心者なので公式ドキュメントをいったん読み込みつつ進めます。
Unity マニュアル (2018.2) - Unity マニュアル
詳しい説明は公式や他のサイトに譲ってここではポイントだけ
- Unity HubとUnityをインストール
- 新規プロジェクトを作成する際のTemplateを2D
- エディタが開いたら Hierarchy > CreateのドロップダウンからUI > Textをクリック
- Hierarchy > MainScene > Canvas の下にTextが作成されるのでこれを
Speed
にリネーム
このSpeedという名前のTextに次で作成するC# Scriptを割り当てていきます。
Websocketライブラリ
Websocket Clientの実装にはこのライブラリを使うことにしました。
https://github.com/sta/websocket-sharp
READMEを参考にdllを作成し、プロジェクトのAssetsに配置します。
C# Scriptの作成
まずはWebsocketから受信したJSONを扱うためにGoと同じ要領でClassを定義します。
Project > Assets > Scripts > TelemetoryModel
using System; [Serializable] public class TelemetryModel { public double AngAccX; public double AngAccY; public double AngAccZ; public double AngVelX; public double AngVelY; public double AngVelZ; . . . (省略)
Websocket Clientはサンプルを参考にしてこんな感じに
https://github.com/sta/websocket-sharp
Project > Assets > Scripts > WebsocketClient
using System.Collections.Generic; using UnityEngine; using WebSocketSharp; public class WebsocketClient : MonoBehaviour { public WebSocket ws; private TelemetryModel telemetry; // Websocketから受信したデータを保持 protected List<TelemetryModel> telemetryList = new List<TelemetryModel>(); void Start() { ws = new WebSocket("ws://localhost:9999/ws"); ws.OnOpen += (sender, e) => {}; // このOnMessage内の処理はバックグラウンドスレッドから実行される // データを受信したらリストに追加 ws.OnMessage += (sender, e) => { lock (telemetryList) { telemetryList.Add(JsonUtility.FromJson<TelemetryModel>(e.Data)); } }; ws.Connect(); } // こちらはメインスレッドからフレーム毎に呼び出される void Update () { lock (telemetryList) { if (telemetryList.Count > 0) { telemetry = telemetryList[0]; telemetryList.RemoveAt(0); } } } // メートル/秒から時速に変換 public string Speed() { if (telemetry != null) { return Mathf.Round((float)(this.telemetry.Speed * 60 * 60 / 1000)).ToString(); } return ""; } }
UIにアタッチするコードはフレーム毎に↑に実装した Speed()
メソッドから速度の値を取得しUIに反映します。
Project > Assets > Scripts > Speed
using System.Collections; using System.Collections.Generic; using UnityEngine; using UnityEngine.UI; public class Speed : MonoBehaviour { GameObject refObj; public Text uiText; void Start () { refObj = GameObject.Find("TelemetryModel"); uiText = this.GetComponent<Text>(); uiText.text = "0"; } void Update () { // Websocket Clientからスピードを取得してUIのテキストにセット uiText.text = refObj.GetComponent<WebsocketClient>().Speed(); } }
このSpeedというスクリプトを同名のSpeedというTextにドラッグするとスクリプトから制御できるようになります。
これらのC#コードとUnity上に配置した2D Textの関連はこんな風になっています
ちょっと雑な説明になりましたが、これでUnity側も完成です!
動かしてみる
さっそく動かしてみましょう
- PS4側でタイムトライアルなど走行中の状態にしてポーズ
- Goのサーバを起動
- Unityエディタ上でPlay
この状態でゲームのポーズを解除すると・・・
カッコいいには程遠いですが、ゲーム内のUIから数フレームくらいの遅延で表示できてそうです!
まとめ
ここまででゲームのテレメトリーデータをUnityアプリで表示する基本部分が完成しました。
いったん動くようになったのはいいんですが、今回の検証ではGoのサーバを介してJSONでやり取りしているせいもあって、ゲーム画面とUnityで1〜最大10フレーム程度遅延しています。
今後はそのあたりのアーキテクチャ変更や以下のあたりをやりたいと思います
- Simhubのようにエンジン回転数やラップタイムなどを追加してカッコいいダッシュボードにする
- Unityアプリで直接テレメトリーデータを受信して遅延低減
- 時系列データベースにデータを投げ込んで検索できるようにする
- 走行中のデータと過去のデータをリアルタイムに比較してレース戦略に活かせるような何か
す、すごく色々あって走り込む時間とか仕事する時間が無くなりそうです。
おわりに
弊社では 僕と一緒にF1 2018で走ったり作ったりして遊びたい エンジニアを募集してます!
※ちなみにこういう人種は社内で少数派なので、車自体には興味がない方もぜひ!
※免許持ってないデータサイエンティストやディレクター、エンジニアも活躍してます