#!/usr/bin/env python3 """ gamepad_check.py — ゲーム用コントローラーの入力を読んで表示するだけの確認スクリプト ロボットには一切つながない。ファイルにも何も書き込まない。 使い方(Mac): python3 -m pip install pygame python3 gamepad_check.py 見ること: 1. ボタンを押す・離すと、番号と押した/離したが出るか 2. スティックを倒すと、軸の値が -1.0〜+1.0 の間で動くか 3. スティックを離したとき、値が 0 付近に戻るか(終了時の一覧で「最後の値」を見る) 終了: ウィンドウを閉じる、または ESC キー、またはターミナルで Ctrl+C """ import os import sys import time # ウィンドウが手前にないときもコントローラーの入力を受け取る os.environ.setdefault("SDL_JOYSTICK_ALLOW_BACKGROUND_EVENTS", "1") import pygame # noqa: E402 AXIS_PRINT_STEP = 0.05 # 軸の値がこれ以上変わったらターミナルに出す WINDOW_SIZE = (560, 420) def describe(js): return (f"[{js.get_instance_id()}] {js.get_name()} " f"軸={js.get_numaxes()} ボタン={js.get_numbuttons()} " f"十字キー={js.get_numhats()} GUID={js.get_guid()}") def main(): pygame.init() pygame.joystick.init() screen = pygame.display.set_mode(WINDOW_SIZE) pygame.display.set_caption("gamepad_check") # ウィンドウの文字は英数字だけにする(日本語フォントがなくても化けないように) font = pygame.font.SysFont("menlo,consolas,dejavusansmono", 16) clock = pygame.time.Clock() sticks = {} # instance_id -> Joystick last_axis = {} # (id, axis) -> 最後に表示した値 axis_stats = {} # (id, axis) -> [最小, 最大, 最後の値] t0 = time.monotonic() def ts(): return f"{time.monotonic() - t0:8.3f}s" print("コントローラーを待っています(つないでいなければ、今つないでください)") running = True try: while running: for ev in pygame.event.get(): if ev.type == pygame.QUIT: running = False elif ev.type == pygame.KEYDOWN and ev.key == pygame.K_ESCAPE: running = False elif ev.type == pygame.JOYDEVICEADDED: js = pygame.joystick.Joystick(ev.device_index) sticks[js.get_instance_id()] = js print(f"{ts()} 接続: {describe(js)}") elif ev.type == pygame.JOYDEVICEREMOVED: js = sticks.pop(ev.instance_id, None) name = js.get_name() if js else "?" print(f"{ts()} 切断: [{ev.instance_id}] {name}") elif ev.type == pygame.JOYBUTTONDOWN: print(f"{ts()} ボタン {ev.button:2d} 押した") elif ev.type == pygame.JOYBUTTONUP: print(f"{ts()} ボタン {ev.button:2d} 離した") elif ev.type == pygame.JOYHATMOTION: print(f"{ts()} 十字キー {ev.hat} {ev.value}") elif ev.type == pygame.JOYAXISMOTION: key = (ev.instance_id, ev.axis) v = ev.value st = axis_stats.setdefault(key, [v, v, v]) st[0] = min(st[0], v) st[1] = max(st[1], v) st[2] = v prev = last_axis.get(key) if prev is None or abs(v - prev) >= AXIS_PRINT_STEP: last_axis[key] = v print(f"{ts()} 軸 {ev.axis} {v:+.3f}") # ウィンドウに今の値を並べる screen.fill((20, 20, 24)) y = 10 if not sticks: screen.blit(font.render("no controller", True, (230, 180, 80)), (10, y)) for js in sticks.values(): screen.blit(font.render(js.get_name()[:50], True, (200, 220, 255)), (10, y)) y += 24 for a in range(js.get_numaxes()): v = js.get_axis(a) screen.blit(font.render(f"axis {a}: {v:+.3f}", True, (230, 230, 230)), (10, y)) # 値の棒(中央が0) cx, w = 200, 150 pygame.draw.rect(screen, (70, 70, 80), (cx - w, y + 4, 2 * w, 10), 1) pygame.draw.rect(screen, (90, 200, 120), (cx, y + 4, int(v * w), 10) if v >= 0 else (cx + int(v * w), y + 4, int(-v * w), 10)) y += 20 pressed = [str(b) for b in range(js.get_numbuttons()) if js.get_button(b)] screen.blit(font.render("buttons: " + (" ".join(pressed) or "-"), True, (230, 230, 230)), (10, y)) y += 20 for h in range(js.get_numhats()): screen.blit(font.render(f"hat {h}: {js.get_hat(h)}", True, (230, 230, 230)), (10, y)) y += 20 y += 10 pygame.display.flip() clock.tick(60) except KeyboardInterrupt: pass finally: if axis_stats: print("\n--- 軸の一覧(スティックを離した状態で終えると、最後の値=戻り位置) ---") for (iid, axis), (lo, hi, last) in sorted(axis_stats.items()): print(f"[{iid}] 軸 {axis}: 最小 {lo:+.3f} 最大 {hi:+.3f} 最後の値 {last:+.3f}") pygame.quit() if __name__ == "__main__": sys.exit(main())