某所に書いたメモ書きをちょっとだけ書き換えてコピペ。
とにかく早く遊びたい人向け
https://github.com/sonarAIT/ZuikiController2PS1DenshaDeGo/tree/main
これをクローンしてmake run
すれば動きます。エミュレーターのキーマップだけ以下の通りにしてください。あと、Windowsにおけるキーボードの設定で、Microsoft IME(日本語+英語)ではなくUSキーボード(英語のみ)を使用してください。
あと、エミュレータとかじゃなくて普通にWindows版の電車でGO!で遊びたい人は、以下のサイトを見ると幸せになれると思います。
https://autotraintas.hariko.com/
基本方針
- Zuiki製のワンハンドルコントローラ(新しい)で電車でGO!(PS1版)を遊ぶ。
- 電車でGO!(PS1版)にも専用コントローラが存在する。その専用コントローラを、Zuiki製のワンハンドルコントローラで代替する形で実装を行う。
- 電車でGO!(PS1版)の専用コントローラは、あくまでコントローラに過ぎない。専用コントローラは、複雑なコマンドをPS1に送り続けるための装置であると言える。
- ゲーム側で専用コントローラの接続を認識するためのコマンドが存在し、それが入力されているか否かで、通常のコントローラか専用のコントローラかを識別しているらしい。
- 物凄く極端なことを言えば、実機の通常のコントローラで同じ操作をすれば、専用コントローラと誤認識させた状態でプレイすることができる。
- この仕組みを利用して、専用コントローラが接続されているかのように誤認識させる形で実装を行う。
- Zuiki製のコントローラから入力を受け取り、それを電車でGO!が認識できる形に変換し、エミュレータに入力すれば、Zuiki製のコントローラでエミュレータ上の電車でGO!(PS1版)を遊ぶことができる。
- エミュレータへの入力は、単にマクロによるキーボード入力で行う。
モジュール設計
- 入力モジュール
- Zuiki製ワンハンドルコントローラからの入力を受け取る。
- 変換モジュール
- 変換を実行する。変換に必要な状態があれば、保持する。
- 出力モジュール
- キーボード入力を実際に実行する。
コントローラを変えるときは、入力モジュールと変換モジュールを書き換えれば良い。
出力先のエミュレータ・ゲームを変える時は、変換モジュールと出力モジュールを書き換えれば良い。
Zuiki製のコントローラーから入力を受け取る
https://qiita.com/10_tenk/items/973d78eff7bb3d9de3a2
この記事にまんま書いてある。
エミュレータにマクロによるキーボード入力を行う
「複雑なコマンド」を入力して、実際に電車でGO!をPythonから操作できるかどうかを確かめる。
「複雑なコマンド」は以下のリポジトリから確認できる。
https://github.com/KeKe115/Densya_de_go
/ | left | down | right | triangle | r1 | l1 | r2 | l2 |
---|---|---|---|---|---|---|---|---|
P5 | 0 | 1 | 0 | 1 | 1 | 0 | 1 | 1 |
P4 | 1 | 1 | 0 | 0 | 1 | 0 | 1 | 1 |
P3 | 1 | 1 | 0 | 1 | 1 | 0 | 1 | 1 |
P2 | 0 | 1 | 1 | 0 | 1 | 0 | 1 | 1 |
P1 | 0 | 1 | 1 | 1 | 1 | 0 | 1 | 1 |
N | 1 | 1 | 1 | 0 | 1 | 0 | 1 | 1 |
B1 | d | d | d | d | 1 | 1 | 1 | 0 |
B2 | d | d | d | d | 1 | 0 | 1 | 0 |
B3 | d | d | d | d | 0 | 1 | 1 | 1 |
B4 | d | d | d | d | 0 | 0 | 1 | 1 |
B5 | d | d | d | d | 0 | 1 | 1 | 0 |
B6 | d | d | d | d | 0 | 0 | 1 | 0 |
B7 | d | d | d | d | 1 | 1 | 0 | 1 |
B8 | d | d | d | d | 1 | 0 | 0 | 1 |
EB | d | d | d | d | 0 | 0 | 0 | 0 |
1は押下した状態。0は押下していない状態。dはどうでもいい(ドント・ケアの意味)。
なお、以下のツイートから上キーを押しっぱなしにすることが明らかになった。
https://twitter.com/wakamesoba98/status/1208039666076680193
つまり、マスコンを5に入れたいなら、上ボタン, 下ボタン, △ボタン, R1ボタン, R2ボタン, L2ボタンを同時押しすれば良い。
エミュレータのキーマッピングが以下の通りになっていることを前提として、コードを書いてみる。
class PS1DengoKeyPresser:
def __init__(self):
self.NOTCH2BITS = {
"P5": 0b01011011,
"P4": 0b11001011,
"P3": 0b11011011,
"P2": 0b01101011,
"P1": 0b01111011,
"N": 0b11101011,
"B1": 0b11101110,
"B2": 0b11101010,
"B3": 0b11100111,
"B4": 0b11100011,
"B5": 0b11100110,
"B6": 0b11100010,
"B7": 0b11101101,
"B8": 0b11101001,
"EB": 0b11100000,
}
self.BIT2KEY = {
0: "2",
1: "0",
2: "1",
3: "9",
4: "i",
5: "d",
6: "s",
7: "a",
}
def press(self, notch):
bits = self.NOTCH2BITS[notch]
pyautogui.keyDown("w")
print(notch)
for i in range(8):
if bits & (1 << i):
pyautogui.keyDown(self.BIT2KEY[i])
else:
pyautogui.keyUp(self.BIT2KEY[i])
入力モジュールからnotchの情報をpress関数に渡すと、対応した「複雑なコマンド」を入力するキー入力が実行される。
ボタン動作を追加
https://qiita.com/10_tenk/items/f21203d7c55fd3b19d62
これを参考にする。
マッピングは以下の通り。
button | idx | key |
---|---|---|
a | 0 | l |
b | 1 | k |
y | 3 | j |
- | 4 | b |
+ | 6 | n |
Zuikiコントローラーによるボタン入力がidx(数字)で得られるので、それをキーボード入力に変換する。
実装
ぶっちゃけ、実装した結果は以下のコードを見てもらえればよい。
https://github.com/sonarAIT/ZuikiController2PS1DenshaDeGo/blob/main/main.py
これだけだとあんまりなので、各モジュールについて必要最低限の解説を書く。
-
ZuikiControllerInputGetter
- Zuikiのコントローラーから入力を受け取り、現在のノッチ、ボタン入力を取得する。
-
getNotch
- 現在のノッチを取得し、P5, EBのような形式でreturnする。
-
getButtons
- 現在のボタン入力を取得し、ボタンのidxでreturnする。
-
Zuiki2PS1Dengo
- Zuikiのノッチ・ボタン入力を、PS1の電車でGO専用の「複雑なコマンド」に変換する。
-
getKeys
- P5, EBなどのZuikiコントローラーによるノッチ入力とボタン入力から、対応するキーボード入力をreturnする。押すキーと、離すキーをそれぞれ求める。
-
PS1DengoKeyPresser
-
Zuiki2PS1Dengo
で得たキーボード入力を、実際に実行する。 -
input
- この関数から、押すキーと離すキーを求める。あんまりキー入力を連打するとエミュレーターが壊れる(気がする)ので、「コントローラー入力の直後に0.025秒待って、新しい入力が来なかったら、実際にエミュレータに対してキーボード入力を流す。」ということをしている。
-
press
- 押す。
-
テスト
上がプログラム実行中の様子で、下がプログラムを実行していない状態。大体狙い通りに動いた。
また、Windowsにおけるキーボードの設定において、Microsoft IME(日本語+英語)ではなくUSキーボード(英語のみ)を使用することで、レスポンスが格段に向上した。ぶっちゃけ、理由は不明だが、少なくとも間違いなさそうだ。