電車でGO!!専用ワンハンドルコントローラー - 瑞起 ZUIKI (以下、 ZUIKIコン) には、 型番 : ZKNS-001 の通常版 と、 型番:ZKNS-002 の EXCLUSIVE EDITION の二種類ある。
ただどうも、それ以外に製造時期による違いもあるようだ。
その違いのせいで、 JR東日本トレインシミュレータ で ZUIKIコン 使うツールがな機能しなくなって困ったので、その対処をした話。
元々、2021年の発売当初から通常版の ZUIKIコン を購入しており、公式で対応している 電車でGo!! Switch 版 や ソニックパワードの 鉄道にっぽん! シリーズ Real Pro など に加え、 Switch版電車でGOワンハンドルマスコンで操作できるようにしてみました - Steam コミュニティー で紹介されてるツールを使って JR東トレシム で使っていたりしていた。
程よい重量感や操作感で臨場感が爆上がり… というか、無いと面白みが8割減レベルに気に入っており、最近 EXCLUSIVE EDITION を買い増した。
…ところが買い増しした ZKNS-002 は、Switch 版や 鉄道にっぽん! シリーズでは問題なく動くのに、上記ツールを使って JR東トレシム しようとすると以下のようなエラーが出て動かない。
```text/plain
Traceback (most recent call last):
File "main.py", line 24, in
File "SwitchDenGo.py", line 32, in loadStatus
pygame.error: Invalid joystick button
[33064] Failed to execute script 'main' due to unhandled exception!
存在しない joystick 番号を指定されてエラーになってる。
…つまりボタン配置が違う?
しかしそもそも、この ZUIKIコン は汎用 USB コントローラ として認識されるモノだし、Windows OS 側のデバイスの設定を見ても、どちらも同じ "One Handle MasCon for Nintendo Switch" と認識され、全くボタン配置で認識されている。
なんでボタン配置がズレるのか、訳がわからないよ。
[](https://aquasoftware.net/blog/wp-content/uploads/2023/11/zuiki-dengo-controller-11-old.png)[](https://aquasoftware.net/blog/wp-content/uploads/2023/11/zuiki-dengo-controller-21-new.png)
[上記 Steam コミュニティー](https://steamcommunity.com/app/2111630/discussions/0/3360272431836180307/) でも、同じような問題に遭遇している人が何人かいる。
しかも、 通常版 ZKNS-001 か EXCLUSIVE EDITION ZKNS-002 かを問わず発生するようだ。
つまり、型番違いによる仕様変更が原因ではない…?
## 意外なる原因
せっかく、エラーが出るデバイスと出ないデバイスが揃っているので、もう少し深く掘って調べてみる。
このツール [mipsparc/JRESim_Dengo](https://github.com/mipsparc/JRESim_Dengo/blob/v1.0/SwitchDenGo.py) のソースを取って Python のデバックをしてみたところ、やはりというか [pygame](https://www.pygame.org/news) というライブラリでコントローラーを操作するあたりでエラーがでている。
```text/plain
031: # ○ボタン
032: if self.joy.get_button(15): # <- ここでエラー
033: self.buttons.append("SW_CIRCLE")
034: # HOMEボタン
035: if self.joy.get_button(5):
036: self.buttons.append("SW_HOME")
試しに、 pygame のジョイスティック実行例コード joystick.py を実行して、 pygame 側でどのように認識されているか確認してみる。
んん゙っ!?
ZKNS-002 のほうは "One Handle MasCon for Nintendo Switch Exclusive Edition" として認識されるものの、
発売日に買った方の ZKNS-001 が "Nintendo Switch Pro Controller" と認識されて、ボタンの認識軸数が大きく変わっている。
pygame は、内部的に SDL2 (libsdl-org/SDL) というライブラリでジョイスティック操作をしている。
その SDL では、既知の様々なコントローラに様々な特殊対応が為されているので、そこらへんが影響していそうだ。
こういったものは、 USB の VID (ベンダーID) と PID (プロダクトID) で場合分けしているものと相場が決まっている。
ということで、デバイスマネージャーのプロパティ(ハードウェアID)から VID と PID を確認してみる。
HID\VID_0F0D&PID_00C1&REV_0106
HID\VID_33DD&PID_0002&REV_0111
VID ちゃうやんけ。
https://www.usb.org/developers で USB ベンダーID を検索してみると…
0x0F0D (3853): HORI CO., LTD.
0x33DD (13277): ZUIKI Inc.
?? HORI ??
とりあえず、上記の VID と PID をもとに、 SDL2 (SDL 2.0.18) でどのように動作が定義されているか調べてみる。
SDL/src/joystick/controller_type.h at release-2.0.18 · libsdl-org/SDL
{ MAKE_CONTROLLER_ID( 0x0f0d, 0x00c1 ), k_eControllerType_SwitchInputOnlyController, NULL }, // HORIPAD for Nintendo Switch
ホリパッド for Nintendo Switch って書いてあるぞ。
更にここら辺 (SDL/src/joystick/SDL_joystick.c) の処理を経て、 VID: 0x0F0D & PID: 0x00C1 の組み合わせのものが "Nintendo Switch Pro Controller" と認識されているようだ。
なお、 VID: 0x33DD & PID: 0x0002 については、特に SDL 側での特殊処理は定義されていなそうだった。
このため、 USB H/W 側で定義されている "One Handle MasCon for Nintendo Switch Exclusive Edition" の名前がそのまま pygame での認識名として表示されていたのだろう。
うーん、製造当初は適当に(Switch に対応した)ホリパッドを偽装してたのを、後のロットではちゃんと VID や PID とって設定したってコトかしら…?
ライセンス品としてどうなんだソレ。
とりあえず、以下の2点が今回の問題が発生していることがわかった。
原因がわかったところで、 JRESim_Dengo の方をどちらの ZUIKIコン にも対応させたい。
とはいえ、 VID や PID で動作し分けるにしても、私が持っている2台とは別の VID や PID を持つものもありそうだ。
(製造時期の違いからか同じ不具合に遭遇していた、通常版の ZKNS-001 とか)
SDL が動作を書き換えなければ、 USB から出ている信号は同じボタン配置で出ているっぽい為、コントローラーの軸数やボタン数で SDL 側での書き換えが行われたか判別すればよさそうだ。
--- ./SwitchDenGo_old.py 2022-10-03 18:55:53 +0900
+++ ./SwitchDenGo.py 2023-10-08 05:51:32 +0900
@@ -7,36 +7,64 @@
def __init__(self):
pygame.init()
self.joy = pygame.joystick.Joystick(0)
+ self.ctrl_nums = (self.joy.get_numaxes(), self.joy.get_numballs(), self.joy.get_numbuttons(), self.joy.get_numhats())
+ if self.ctrl_nums not in [(6, 0, 16, 0), (4, 0, 14, 1)]:
+ raise Exception("サポートされていないコントローラです")
self.joy.init()
def loadStatus(self):
self.brake_knotch = 0
self.accel_knotch = 0
self.buttons = []
pygame.event.get()
- # Xボタン
- if self.joy.get_button(2):
- self.buttons.append("SW_X")
- # Yボタン
- if self.joy.get_button(3):
- self.buttons.append("SW_Y")
- # Aボタン
- if self.joy.get_button(0):
- self.buttons.append("SW_A")
- # Bボタン
- if self.joy.get_button(1):
- self.buttons.append("SW_B")
- # ○ボタン
- if self.joy.get_button(15):
- self.buttons.append("SW_CIRCLE")
- # HOMEボタン
- if self.joy.get_button(5):
- self.buttons.append("SW_HOME")
-
- knotch_level = self.joy.get_axis(1)
+ # ロンチ版
+ if self.ctrl_nums == (6, 0, 16, 0):
+ # Xボタン
+ if self.joy.get_button(2):
+ self.buttons.append("SW_X")
+ # Yボタン
+ if self.joy.get_button(3):
+ self.buttons.append("SW_Y")
+ # Aボタン
+ if self.joy.get_button(0):
+ self.buttons.append("SW_A")
+ # Bボタン
+ if self.joy.get_button(1):
+ self.buttons.append("SW_B")
+ # ○ボタン
+ if self.joy.get_button(15):
+ self.buttons.append("SW_CIRCLE")
+ # HOMEボタン
+ if self.joy.get_button(5):
+ self.buttons.append("SW_HOME")
+
+ knotch_level = self.joy.get_axis(1)
+ elif self.ctrl_nums == (4, 0, 14, 1):
+ # Xボタン
+ if self.joy.get_button(3):
+ self.buttons.append("SW_X")
+ # Yボタン
+ if self.joy.get_button(0):
+ self.buttons.append("SW_Y")
+ # Aボタン
+ if self.joy.get_button(2):
+ self.buttons.append("SW_A")
+ # Bボタン
+ if self.joy.get_button(1):
+ self.buttons.append("SW_B")
+ # ○ボタン
+ if self.joy.get_button(13):
+ self.buttons.append("SW_CIRCLE")
+ # HOMEボタン
+ if self.joy.get_button(12):
+ self.buttons.append("SW_HOME")
+
+ knotch_level = self.joy.get_axis(1)
+ else:
+ raise Exception("サポートされていないコントローラです")
if knotch_level > 0.95:
self.accel_knotch = 5
elif knotch_level > 0.75:
こんな感じの修正を入れてビルドしたものを以下のリリースで展開した。
Release JRESim_Dengo v1.0p1 · advanceboy/JRESim_Dengo
もし、最近 ZUIKIコン を JREトレシム で使おうと思って困っている人がいればどうぞ。
元の作者の方にもプルリク投げとくか。
実装のために調査した、各環境・デバイス毎の、キー割り当ては以下の通りだった。
```text/plain
index 1軸: マスコン
Button 1: Y
Button 2: B
Button 3: A
Button 4: X
Button 5: L
Button 6: R
Button 7: ZL, EB
Button 8: ZR
Button 9: -
Button 10: +
Button 11: N/A
Button 12: N/A
Button 13: HOME
Button 14: Capture
PoVハット 1: 方向キー
### pygame 経由の、ロンチ版
```text/plain
index 1軸: マスコン
index 4軸: ZL
index 5軸: ZR
Button 0: A
Button 1: B
Button 2: X
Button 3: Y
Button 4: -
Button 5: Home
Button 6: +
Button 7: N/A
Button 8: N/A
Button 9: L
Button 10: R
Button 11: Up
Button 12: Left
Button 13: Down
Button 14: Right
Button 15: Capture
# ハードウェアID: 'HID\VID_0F0D&PID_00C1&REV_0106'
joy = pygame.joystick.Joystick(0)
joy.get_guid()
# -> '030000000d0f0000c100000006016800'
joy.get_name()
# -> 'Nintendo Switch Pro Controller'
joy.get_numaxes()
# -> 6
joy.get_numballs()
# -> 0
joy.get_numbuttons()
# -> 16
joy.get_numhats()
# -> 0
```text/plain
index 1軸: マスコン
Button 0: Y
Button 1: B
Button 2: A
Button 3: X
Button 4: L
Button 5: R
Button 6: ZL
Button 7: ZR
Button 8: -
Button 9: +
Button 10: N/A
Button 11: N/A
Button 12: Home
Button 13: Capture
PoVハット 1: 方向キー
[](https://aquasoftware.net/blog/wp-content/uploads/2023/11/zuiki-dengo-controller-22-new.png)
```python
# ハードウェアID: 'HID\VID_33DD&PID_0002&REV_0111'
joy = pygame.joystick.Joystick(0)
joy.get_guid()
# -> '03000000dd3300000200000000000000'
joy.get_name()
# -> 'One Handle MasCon for Nintendo Switch Exclusive Edition'
joy.get_numaxes()
# -> 4
joy.get_numballs()
# -> 0
joy.get_numbuttons()
# -> 14
joy.get_numhats()
# -> 1
Crieitは誰でも投稿できるサービスです。 是非記事の投稿をお願いします。どんな軽い内容でも投稿できます。
また、「こんな記事が読みたいけど見つからない!」という方は是非記事投稿リクエストボードへ!
こじんまりと作業ログやメモ、進捗を書き残しておきたい方はボード機能をご利用ください。
ボードとは?
コメント