pygameでXBOXコントローラの入力を取得してPyQt6に表示する
2022/05/28 categories:Python| tags:Python|pygame|PyQt6|XBOX|
pygameでXBOXコントローラの入力を取得して、PyQt6に表示するプログラムを作成してみました。
処理内容
QtにはQGamepadなどのゲームパッド用のクラスが用意されているようですが、PyQt6でそれらのクラスを見つけられませんでした。そこで、QThread内の無限ループでpygameによるXBOXコントローラの入力状態を監視して、その入力に応じてシグナルを発行するような処理にしました。これにより、メインのスレッドとは別でコントローラの状態を監視できるので、メインのUIの処理なども問題なく行うことが出来ました。
動作の様子
録画に使用したソフトや機器の関係でコントローラのボタンを押すタイミングとソフト上の表示にラグがありますが、実際にはラグは体感できないレベルです。
ソースコード
# -*- coding: utf-8 -*-
import sys
from PyQt6 import QtWidgets, QtCore, QtGui, QtSerialPort
import pygame
import pygame.locals
class Mainwindow(QtWidgets.QMainWindow):
def __init__(self) -> None:
super().__init__()
self.setWindowTitle('PyQt6 and pygame XBOX controller test')
self.setStyleSheet('''
QPushButton {
min-height: 32px;
}
QDoubleSpinBox {
min-height: 32px;
}
QPushButton:checked {
background-color: red;
}
''')
self.joystick = Joystick(self)
self.A_button = QtWidgets.QPushButton('A')
self.B_button = QtWidgets.QPushButton('B')
self.X_button = QtWidgets.QPushButton('X')
self.Y_button = QtWidgets.QPushButton('Y')
self.R_button = QtWidgets.QPushButton('R')
self.L_button = QtWidgets.QPushButton('L')
self.UP_button = QtWidgets.QPushButton('UP')
self.DOWN_button = QtWidgets.QPushButton('DOWN')
self.LEFT_button = QtWidgets.QPushButton('LEFT')
self.RIGHT_button = QtWidgets.QPushButton('RIGHT')
self.VIEW_button = QtWidgets.QPushButton('VIEW')
self.MENU_button = QtWidgets.QPushButton('MENU')
self.left_horizontal = QtWidgets.QDoubleSpinBox()
self.left_horizontal.setMinimum(-1)
self.left_horizontal.setMaximum(1)
self.left_vertical = QtWidgets.QDoubleSpinBox()
self.left_vertical.setMinimum(-1)
self.left_vertical.setMaximum(1)
self.right_horizontal = QtWidgets.QDoubleSpinBox()
self.right_horizontal.setMinimum(-1)
self.right_horizontal.setMaximum(1)
self.right_vertical = QtWidgets.QDoubleSpinBox()
self.right_vertical.setMaximum(1)
self.right_vertical.setMinimum(-1)
self.left_trigger = QtWidgets.QDoubleSpinBox()
self.left_trigger.setMinimum(-1)
self.left_trigger.setMaximum(1)
self.right_triggr = QtWidgets.QDoubleSpinBox()
self.right_triggr.setMinimum(-1)
self.right_triggr.setMaximum(1)
self.A_button.setCheckable(True)
self.B_button.setCheckable(True)
self.X_button.setCheckable(True)
self.Y_button.setCheckable(True)
self.R_button.setCheckable(True)
self.L_button.setCheckable(True)
self.UP_button.setCheckable(True)
self.DOWN_button.setCheckable(True)
self.LEFT_button.setCheckable(True)
self.RIGHT_button.setCheckable(True)
self.VIEW_button.setCheckable(True)
self.MENU_button.setCheckable(True)
self.setCentralWidget(QtWidgets.QWidget())
self.centralWidget().setLayout(QtWidgets.QGridLayout())
self.centralWidget().layout().addWidget(self.UP_button, 0, 0, 1, 1)
self.centralWidget().layout().addWidget(self.DOWN_button, 1, 0, 1, 1)
self.centralWidget().layout().addWidget(self.LEFT_button, 2, 0, 1, 1)
self.centralWidget().layout().addWidget(self.RIGHT_button, 3, 0, 1, 1)
self.centralWidget().layout().addWidget(self.R_button, 4, 0, 1, 1)
self.centralWidget().layout().addWidget(self.L_button, 5, 0, 1, 1)
self.centralWidget().layout().addWidget(self.left_horizontal, 6, 0, 1, 1)
self.centralWidget().layout().addWidget(self.left_vertical, 7, 0, 1, 1)
self.centralWidget().layout().addWidget(self.right_horizontal,8, 0, 1, 1)
self.centralWidget().layout().addWidget(self.A_button, 0, 1, 1, 1)
self.centralWidget().layout().addWidget(self.B_button, 1, 1, 1, 1)
self.centralWidget().layout().addWidget(self.X_button, 2, 1, 1, 1)
self.centralWidget().layout().addWidget(self.Y_button, 3, 1, 1, 1)
self.centralWidget().layout().addWidget(self.VIEW_button, 4, 1, 1, 1)
self.centralWidget().layout().addWidget(self.MENU_button, 5, 1, 1, 1)
self.centralWidget().layout().addWidget(self.right_vertical, 6, 1, 1, 1)
self.centralWidget().layout().addWidget(self.left_trigger, 7, 1, 1, 1)
self.centralWidget().layout().addWidget(self.right_triggr, 8, 1, 1, 1)
self.joystick.A_PRESSED.connect(self.A_button.toggle)
self.joystick.B_PRESSED.connect(self.B_button.toggle)
self.joystick.X_PRESSED.connect(self.X_button.toggle)
self.joystick.Y_PRESSED.connect(self.Y_button.toggle)
self.joystick.R_PRESSED.connect(self.R_button.toggle)
self.joystick.L_PRESSED.connect(self.L_button.toggle)
self.joystick.UP_PRESSED.connect(self.UP_button.toggle)
self.joystick.DOWN_PRESSED.connect(self.DOWN_button.toggle)
self.joystick.LEFT_PRESSED.connect(self.LEFT_button.toggle)
self.joystick.RIGHT_PRESSED.connect(self.RIGHT_button.toggle)
self.joystick.VIEW_PRESSED.connect(self.VIEW_button.toggle)
self.joystick.MENU_PRESSED.connect(self.MENU_button.toggle)
self.joystick.A_RELEASED.connect(self.A_button.toggle)
self.joystick.B_RELEASED.connect(self.B_button.toggle)
self.joystick.X_RELEASED.connect(self.X_button.toggle)
self.joystick.Y_RELEASED.connect(self.Y_button.toggle)
self.joystick.R_RELEASED.connect(self.R_button.toggle)
self.joystick.L_RELEASED.connect(self.L_button.toggle)
self.joystick.UP_RELEASED.connect(self.UP_button.toggle)
self.joystick.DOWN_RELEASED.connect(self.DOWN_button.toggle)
self.joystick.LEFT_RELEASED.connect(self.LEFT_button.toggle)
self.joystick.RIGHT_RELEASED.connect(self.RIGHT_button.toggle)
self.joystick.VIEW_RELEASED.connect(self.VIEW_button.toggle)
self.joystick.MENU_RELEASED.connect(self.MENU_button.toggle)
self.joystick.AXIS_MOVED.connect(self.joystick_axis_moved)
self.joystick.start()
def joystick_axis_moved(self, left_horizontal, left_vertical, right_horizontal, right_vertical, left_trigger, right_triggr):
self.left_horizontal.setValue(left_horizontal)
self.left_vertical.setValue(left_vertical)
self.right_horizontal.setValue(right_horizontal)
self.right_vertical.setValue(right_vertical)
self.left_trigger.setValue(left_trigger)
self.right_triggr.setValue(right_triggr)
def closeEvent(self, a0: QtGui.QCloseEvent) -> None:
self.joystick.terminate()
return super().closeEvent(a0)
class Joystick(QtCore.QThread):
A_PRESSED = QtCore.pyqtSignal()
B_PRESSED = QtCore.pyqtSignal()
X_PRESSED = QtCore.pyqtSignal()
Y_PRESSED = QtCore.pyqtSignal()
R_PRESSED = QtCore.pyqtSignal()
L_PRESSED = QtCore.pyqtSignal()
UP_PRESSED = QtCore.pyqtSignal()
DOWN_PRESSED = QtCore.pyqtSignal()
LEFT_PRESSED = QtCore.pyqtSignal()
RIGHT_PRESSED = QtCore.pyqtSignal()
VIEW_PRESSED = QtCore.pyqtSignal()
MENU_PRESSED = QtCore.pyqtSignal()
A_RELEASED = QtCore.pyqtSignal()
B_RELEASED = QtCore.pyqtSignal()
X_RELEASED = QtCore.pyqtSignal()
Y_RELEASED = QtCore.pyqtSignal()
R_RELEASED = QtCore.pyqtSignal()
L_RELEASED = QtCore.pyqtSignal()
UP_RELEASED = QtCore.pyqtSignal()
DOWN_RELEASED = QtCore.pyqtSignal()
LEFT_RELEASED = QtCore.pyqtSignal()
RIGHT_RELEASED = QtCore.pyqtSignal()
VIEW_RELEASED = QtCore.pyqtSignal()
MENU_RELEASED = QtCore.pyqtSignal()
AXIS_MOVED = QtCore.pyqtSignal(float, float, float, float, float, float)
def __init__(self, parent):
super().__init__(parent)
pygame.init()
pygame.joystick.init()
self.joystick = pygame.joystick.Joystick(0)
self.joystick.init()
self.hat_prev = 0
def get_hat(self):
hat = self.joystick.get_hat(0)
val = 0
val += int(hat[1] == 1) << 0 # up
val += int(hat[1] == -1) << 1 # down
val += int(hat[0] == 1) << 2 # right
val += int(hat[0] == -1) << 3 # left
val, self.hat_prev = val - self.hat_prev, val
return val
def run(self) -> None:
while True:
for e in pygame.event.get():
if e.type == pygame.locals.JOYAXISMOTION:
self.AXIS_MOVED.emit(
self.joystick.get_axis(0), #left horizontal
self.joystick.get_axis(1), #left vertical
self.joystick.get_axis(2), #right horizontal
self.joystick.get_axis(3), #right vertical
self.joystick.get_axis(4), #left trigger
self.joystick.get_axis(5), #right triggr
)
elif e.type == pygame.locals.JOYHATMOTION:
h = self.get_hat()
if h > 0:
if (h >> 0) & 1: self.UP_PRESSED.emit()
if (h >> 1) & 1: self.DOWN_PRESSED.emit()
if (h >> 2) & 1: self.RIGHT_PRESSED.emit()
if (h >> 3) & 1: self.LEFT_PRESSED.emit()
else:
if (abs(h) >> 0) & 1: self.UP_RELEASED.emit()
if (abs(h) >> 1) & 1: self.DOWN_RELEASED.emit()
if (abs(h) >> 2) & 1: self.RIGHT_RELEASED.emit()
if (abs(h) >> 3) & 1: self.LEFT_RELEASED.emit()
elif e.type == pygame.locals.JOYBUTTONDOWN:
if e.button == 0: self.A_PRESSED.emit()
if e.button == 1: self.B_PRESSED.emit()
if e.button == 2: self.X_PRESSED.emit()
if e.button == 3: self.Y_PRESSED.emit()
if e.button == 4: self.L_PRESSED.emit()
if e.button == 5: self.R_PRESSED.emit()
if e.button == 6: self.VIEW_PRESSED.emit()
if e.button == 7: self.MENU_PRESSED.emit()
elif e.type == pygame.locals.JOYBUTTONUP:
if e.button == 0: self.A_RELEASED.emit()
if e.button == 1: self.B_RELEASED.emit()
if e.button == 2: self.X_RELEASED.emit()
if e.button == 3: self.Y_RELEASED.emit()
if e.button == 4: self.L_RELEASED.emit()
if e.button == 5: self.R_RELEASED.emit()
if e.button == 6: self.VIEW_RELEASED.emit()
if e.button == 7: self.MENU_RELEASED.emit()
if __name__ == '__main__':
app = QtWidgets.QApplication(sys.argv)
w = Mainwindow()
w.show()
app.exec()