こちらはクラスター Advent Calendar 2022(1ページ目)の17日目の記事です。
昨日は @Hashimoto_Mami さんの「ボクセル作成ツール「MagicaVoxel」を活用して3Dモデリングをしてみよう!」でした。ボクセルは手軽に作れる上に味があって良いですよね。
はじめに
こんにちは、スワンマンです。クラスター株式会社でディレクターとかカスタマーサポートとかスワンマンをしています。
今回のAdvent Calendarではすでに「Slackで同じワークフローのショートカットを複数チャンネルに設置する」と「Unityのエディタ拡張で動的にメニューを追加・削除する」という2つの記事を書いてるんですが、ちょっと中身が堅すぎたので今回は肩の力を抜いてSwitchで遊んでいきたいと思います!
ところが両の手にはVRのコントローラが!
なんてことだ…
人間の手は2つ…
別のコントローラをさらに持つことはできない…
構造的欠陥…
ということで今回は神による設計ミスのワークアラウンドとして、このコントローラを使ってSwitchで遊ぶ方法を考えたいと思います。
VRデバイスの入力を取得する
OpenVRを使うとVRデバイスの入力を簡単に取れると聞いたので、今回はこれを使ってみます。
headersの中に「openvr_api.cs」があるので、こいつをC#のプロジェクトに取り込んで、bin/win64の「openvr_api.dll」をフォルダに配置したら以下のようなコードでざっくり入力が取れます。
(これはLegacy APIらしいけど、今回は遊べればいいのでよしとする)
EVRInitError error = EVRInitError.None;
OpenVR.Init(ref error, EVRApplicationType.VRApplication_Overlay);
// 左手コントローラの状態を取る
var leftHandIndex = OpenVR.System.GetTrackedDeviceIndexForControllerRole(ETrackedControllerRole.LeftHand);
VRControllerState_t leftControllerState = default;
TrackedDevicePose_t leftTrackedDevicePose = default;
OpenVR.System.GetControllerStateWithPose(ETrackingUniverseOrigin.TrackingUniverseSeated, leftHandIndex, ref leftControllerState, (uint)Marshal.SizeOf(typeof(VRControllerState_t)), ref leftTrackedDevicePose);
// ulButtonPressedに各ボタンの押下状態がビットフラグで入るのでいい感じに取る
if ((leftControllerState.ulButtonPressed & 1ul << (int)EVRButtonId.k_EButton_A) != 0)
{
...
}
// スティックのアナログ入力はleftControllerState.rAxis0に-1.0~1.0で入ってる
// xは左が-1.0で右が1.0
// yは下が-1.0で上が1.0
...
// 右手も同様に
...
入力をSwitchに送る
世の中にはかしこい人がいるので、NXBTというライブラリを使うとPCに生やしたBluetoothアダプタをコントローラとしてSwitchに認識させることができます。すごい。
Windows上で使うには仮想環境にUbuntuを入れてその中で立ち上げる必要があるんですが、vagrantを使って簡単にセットアップする仕組みが同梱されており、順調に進めば恐らく30分もかからずに接続までいけます。
(僕は運悪く非対応Bluetoothアダプタを使っていたせいで3時間くらい無駄に格闘した果てにAmazonで別のBluetoothアダプタを買いました…)
普通のゲームコントローラを使うだけであれば自身でコードを書くまでもなく使えてしまうんですが、今回は自前で取得した入力データを送りたいので以下のような感じのサーバーを書きます。
(送り手はこのDIRECT_INPUT_PACKETに入力値を詰めてjsonとして送ればOK)
from http.server import HTTPServer
from http.server import BaseHTTPRequestHandler
import json
import nxbt
def run():
nx = nxbt.Nxbt()
controller_idx = nx.create_controller(nxbt.PRO_CONTROLLER)
nx.wait_for_connection(controller_idx)
class Handler(BaseHTTPRequestHandler):
def log_message(self, format, *args):
pass
def do_PUT(self):
content_len = int(self.headers.get('content-length'))
recv_data = json.loads(self.rfile.read(content_len).decode('utf-8'))
nx.set_controller_input(controller_idx, recv_data)
self.send_response(204)
server = HTTPServer(('0.0.0.0', 8000), Handler)
server.serve_forever()
if __name__ == '__main__':
run()
試してみる
やったぜ。
今回わかったこと
こいつが一番遊びやすいや。
おわり
明日はクラスター広報 @MIRINPR さんの「なんか」です!何が書かれるか楽しみ!