Translate

2020年4月23日木曜日

pygameパッケージを使って画面を表示せずにジョイスティックを使おうとしたらキーボード操作を一切受け付けなくなった件

最近Donkeycar のマイナーバージョンアップ(3.1.2)があったのだけど、更新内容の一つにDonkeycar Simulatorが挙げられている。

Donkey Simulatorは、Donkeycarアプリケーションを実際の車ではなくSimulator上の仮想車体として動かすというもの。Donkeycarアプリケーションの設定を変更して起動すると、Donkey Simulator APIを呼び出しステアリング値とスロットル値を継続的に送信、その結果をDonkey Simulator側の画面上に表示するというもの。


今回のDonkey Simulator更新で機能がついたのかはわからないけど、どうもこのSimulatorは複数のDonkeycarアプリケーションからのデータを受け付けるようになっており、同じコース上で複数の仮想Donkeycarでレースができるようになっている。

なのでAWS EC2やGCPなどのIP reachableな仮想マシンでDonkey Simulatorを起動しておき、各自の家のPC上でDonkeycarアプリケーションの設定をDonkey Simulatorが起動しているマシンのIPアドレスorFQDNを設定し起動すれば複数のDonkeycarでレースができるのだ。ただ画面を共有しないといけないのでそのあたりはYoutube Liveなどで行えばいい(UltraVNCなどのリモート接続系ソフトだとキーボードやマウスなどの操作も渡してしまうので)。

レースを行う場合、手動運転による学習データ収集を行わないといけないのだけど、WindowsPCの場合現時点ではWebアプリ経由の操作しかない。なぜならdonkeycarパッケージ上のジョイスティックパーツは/dev/input/js0fctlパッケージで排他管理しつつ読むことで実装しているので、そもそも/devのないWindows PCでは動作しない。ただコードをよく読むとWindows PCでも動作するpygameパッケージベースのジョイスティックパーツクラスもいくつか存在する。しかしジョイスティックオブジェクトを取得するget_js_controllerというファクトリ関数はこのパーツクラスに対応していない。

そこでmanage.pyのファクトリ関数部分を直接パーツクラスを生成して動かそうとしたけど..動かない。

しょうがないので、調べてみた。


pygameパッケージはもともと画面のあるゲームをPythonで実装するために便利な機能を提供する統合ゲームアプリフレームワークだ。Window画面を表示したり描画する機能やマウス操作やキーボード入力を管理する機能などを提供してくれる。その中のひとつにジョイスティック操作をPythonプログラム側で取得できるようにするサブモジュールpygame.joystickが存在する。

donkeycarパッケージのdonkeycar.parts.controllerモジュールにあるpygame用ジョイスティックパーツはこのサブモジュールを使用している。..が、pygame自体の初期化処理が実装されていない..

pygameパッケージを使用するにはまず
インポート

import pygame

pyagmeモジュールの初期化

pygame.init()

pygame.joystickサブモジュールの初期化

pygame.joystick.init()

そしてWindows PCに接続されているジョイスティックをあらわすオブジェクトの生成

myjoystick = pygame.joystick.Joystick(0)

を行わなければならないのだけど、ソースコードに pygame.init() 処理が書かれていなかった。


で、pygame.init() を追加したのだけど..今度はなぜかジョイスティックオブジェクトから操作情報を取り出そうとしても全部ゼロ..

これも調べてみると、pygame.eventサブモジュールを使って直近に発生するイベント群をとりださないと操作情報を取得できない仕組みになっていた。

events = pygame.event.get()

を加えてみると、たしかにジョイスティック操作したデータを取得できるようになった。
しかし今度はDonkeycarアプリケーションが終了できなくなった。Donkeycarアプリケーションはコマンドライン実行内で無限ループ処理しているので、Ctrl+Cを押して停止させなくてはならない。でもこのCtrl+Cを何度入力しても...全く止まる気配がない...

ということでまたもや調べてみた。

どうも pygame.init() を実行するとpygameサブモジュールのうち使用可能なすべてが初期化される。初期化対象には画面系のpygame.screenサブモジュールも含まれている。Window画面を使ったゲームの操作は、キーボードやジョイスティック操作をベースにすることが多い。このため pygame.init() を実行した以降は、すべてのキーボード入力がフックされ pygame.event管理下のイベント化されてしまうのだ。いくらKeyboardInterrupt 例外を出しても pygame.event が奪い取ってしまうように設計されているのだ。

つまり Ctrl+C 押下した場合は pygame を終了( pygame.quit() )させてからKeyboardInterrupt例外を自分でraiseするように実装してやらなくてはならない。

for events in pygame.event.get():
    if event.type == pygame.QUIT:
        pygame.quit()
        import time
        time.sleep(2)
        raise('catch pygame.QUIT event')

これでようやくCtrl+Cで脱出できるジョイスティックパーツを作成することができた。

以下作成した pygame ベースのLogicool Wireless Gamepad F710 donkeycarパーツクラスのリポジトリリンクである。

GitHubリポジトリ
https://github.com/coolerking/mysim_joystick

動作サンプル動画


pygame パッケージがゲームアプリケーション用統合フレームワークであることを知らないと、おそらくリファレンスドキュメントだけでは見抜けない人、少なくないのではなかろうか..

製作者・設計者の想定した用途に合致しない使い方、たとえば

  • ドキュメント共有のために開発されたWebシステムでアプリを組む
  • Webアプリで帳票を出す


などのようなことをすると、
痛いしっぺ返しが来ることは身にしみてわかっていたことなのに、
今回は気づくのに時間がかかってしまった...

ご参考まで。

既存アプリケーションをK8s上でコンテナ化して動かす場合の設計注意事項メモ

既存アプリをK8sなどのコンテナにして動かすには、どこを注意すればいいか..ちょっと調べたときの注意事項をメモにした。   1. The Twelve Factors (日本語訳からの転記) コードベース   バージョン管理されている1つのコードベースと複数のデプロイ 依存関係 ...