이것저것 잡동사니
[예제 코드] PyQt5을 사용한 조이스틱(Joystick) 클래스 본문
반응형
파이썬으로 게임을 만들면서 조이스틱을 구현할 필요가 있어 만들게 되었다. 마우스 왼쪽 버튼을 드래그함으로써 조이스틱을 조작할 수 있다. 마우스를 떼면 조이스틱이 원점으로 되돌아간다.
측정 값
1. 강도(Strength) : 0~1의 살수값을 가지며 조이스틱이 가운데에 있을 때 0, 가장자리에 있을 때 1이다.
2. 방향(Direction) : 오른쪽 방향을 0으로 하여 반시계방향으로 증가하도록 측정된다.
실행 결과
소스코드
깃헙 링크 : Github
본 포스트보다 깃헙의 코드가 더 최신코드입니다.
GitHub - bsiyoung/PyQt5-Joystick: Simple Joystick with PyQt5
Simple Joystick with PyQt5. Contribute to bsiyoung/PyQt5-Joystick development by creating an account on GitHub.
github.com
Joystick.py
import sys
import math
from PyQt5.QtWidgets import QApplication, QWidget
from PyQt5.QtWidgets import QLabel
from PyQt5.QtGui import QPainter, QBrush, QPen
from PyQt5.QtCore import Qt
class Joystick(QWidget):
def __init__(self):
super().__init__()
self.window_title = 'Joystick'
self.window_min_size = [200, 200]
self.wnd_fit_size = 400
self.window_size = [self.wnd_fit_size, self.wnd_fit_size]
self.circle_margin_ratio = 0.1
self.circle_diameter = int(self.window_size[0] * (1 - self.circle_margin_ratio * 2))
self.stick_diameter_ratio = 0.1
self.stick_diameter = int(self.circle_diameter * self.stick_diameter_ratio)
self.is_mouse_down = False
self.stick_pos = [0, 0]
self.strength = 0
self.stat_label_margin = 10
self.stat_label = QLabel(self)
self.init_ui()
def init_ui(self):
self.setWindowTitle(self.window_title)
self.setMinimumSize(self.window_min_size[0], self.window_min_size[1])
self.resize(self.window_size[0], self.window_size[1])
self.stat_label.setAlignment(Qt.AlignLeft)
self.stat_label.setGeometry(self.stat_label_margin, self.stat_label_margin,
self.window_min_size[0] - self.stat_label_margin * 2,
self.window_min_size[0] - self.stat_label_margin * 2)
font = self.stat_label.font()
font.setPointSize(10)
self.setMouseTracking(True)
self.show()
def resizeEvent(self, event):
self.wnd_fit_size = min(self.width(), self.height())
self.circle_diameter = int(self.wnd_fit_size * (1 - self.circle_margin_ratio * 2))
self.stick_diameter = int(self.circle_diameter * self.stick_diameter_ratio)
def _draw_outer_circle(self, painter):
painter.setPen(QPen(Qt.black, 2, Qt.SolidLine))
circle_margin = int(self.wnd_fit_size * self.circle_margin_ratio)
painter.drawEllipse(circle_margin, circle_margin,
self.circle_diameter, self.circle_diameter)
def _draw_sub_lines(self, painter):
painter.setRenderHint(QPainter.Antialiasing)
painter.setPen(QPen(Qt.lightGray, 1, Qt.DashLine))
num_sub_line = 6
for i in range(num_sub_line):
theta = math.pi / 2 - math.pi * i / num_sub_line
x0 = int(self.wnd_fit_size / 2 - self.circle_diameter / 2 * math.cos(theta))
y0 = int(self.wnd_fit_size / 2 - self.circle_diameter / 2 * math.sin(theta))
x1 = int(self.wnd_fit_size / 2 - self.circle_diameter / 2 * math.cos(theta + math.pi))
y1 = int(self.wnd_fit_size / 2 - self.circle_diameter / 2 * math.sin(theta + math.pi))
painter.drawLine(x0, y0, x1, y1)
def _draw_sub_circles(self, painter):
painter.setPen(QPen(Qt.lightGray, 1, Qt.DashLine))
num_sub_circle = 4
for i in range(num_sub_circle):
sub_radius = int(self.circle_diameter / 2 * (i + 1) / (num_sub_circle + 1))
sub_margin = int(self.wnd_fit_size / 2 - sub_radius)
painter.drawEllipse(sub_margin, sub_margin, sub_radius * 2, sub_radius * 2)
# Draw Inner(Joystick) Circle
painter.setBrush(QBrush(Qt.black, Qt.SolidPattern))
stick_margin = [int(self.wnd_fit_size / 2 + self.stick_pos[0] - self.stick_diameter / 2),
int(self.wnd_fit_size / 2 - self.stick_pos[1] - self.stick_diameter / 2)]
painter.drawEllipse(stick_margin[0], stick_margin[1], self.stick_diameter, self.stick_diameter)
def paintEvent(self, event):
painter = QPainter(self)
# Draw Outer(Main) Circle
self._draw_outer_circle(painter)
# Draw Sub Lines
self._draw_sub_lines(painter)
# Draw Sub Circles
self._draw_sub_circles(painter)
# Change Status Label Text (Angle In Degree)
strength = self.get_strength()
angle = self.get_angle(in_deg=True)
if angle < 0:
angle += 360
self.stat_label.setText('Strength : {:.2f} \nDirection : {:.2f}°'.format(strength, angle))
def mouseMoveEvent(self, event):
# Move Stick Only When Mouse Left Button Pressed
if self.is_mouse_down is False:
return
# Window Coordinate To Cartesian Coordinate
pos = event.pos()
stick_pos_buf = [pos.x() - self.wnd_fit_size / 2, self.wnd_fit_size / 2 - pos.y()]
# If Cursor Is Not In Available Range, Correct It
if self._get_strength(stick_pos_buf) > 1.0:
theta = math.atan2(stick_pos_buf[1], stick_pos_buf[0])
radius = (self.circle_diameter - self.stick_diameter) / 2
stick_pos_buf[0] = radius * math.cos(theta)
stick_pos_buf[1] = radius * math.sin(theta)
self.stick_pos = stick_pos_buf
self.repaint()
def mousePressEvent(self, event):
if event.button() == Qt.LeftButton:
self.is_mouse_down = True
def mouseReleaseEvent(self, event):
if event.button() == Qt.LeftButton:
self.is_mouse_down = False
self.stick_pos = [0, 0]
self.repaint()
# Get Strength With Argument
def _get_strength(self, stick_pos):
max_distance = (self.circle_diameter - self.stick_diameter) / 2
distance = math.sqrt(stick_pos[0] * stick_pos[0] + stick_pos[1] * stick_pos[1])
return distance / max_distance
# Get Strength With Current Stick Position
def get_strength(self):
max_distance = (self.circle_diameter - self.stick_diameter) / 2
distance = math.sqrt(self.stick_pos[0] * self.stick_pos[0] + self.stick_pos[1] * self.stick_pos[1])
return distance / max_distance
def get_angle(self, in_deg=False):
angle = math.atan2(self.stick_pos[1], self.stick_pos[0])
if in_deg is True:
angle = angle * 180 / math.pi
return angle
Joystick_Test.py
조이스틱 클래스를 테스트하기 위한 코드이다. 조이스틱 window의 freezing을 방지하기 위해 QThread를 사용한다. 파이썬의 기본 threading 라이브러리를 사용하면 안 된다.
import sys
import time
from PyQt5.QtWidgets import QApplication
from PyQt5.QtCore import QThread
from Joystick import Joystick
class Thread(QThread):
def __init__(self, parent):
super().__init__(parent)
self.parent = parent
def run(self):
for i in range(100):
time.sleep(0.5)
strength = joystick.get_strength()
angle = joystick.get_angle(in_deg=True)
print('Strength : {:.2f} | Angle : {:.2f}°'.format(strength, angle))
app = QApplication(sys.argv)
joystick = Joystick.Joystick()
th = Thread(joystick)
th.start()
sys.exit(app.exec_())
반응형
'컴퓨터공학 > 예제 코드' 카테고리의 다른 글
[예제 코드] openpyxl 기초 사용법 완벽 정리 (0) | 2023.10.23 |
---|---|
[예제 코드] hyperopt를 사용한 LightGBM의 hyperparameter tuning (0) | 2022.10.05 |
[예제 코드] 분류 모델에 사용되는 성능 평가 지표 (sklearn 사용) (0) | 2022.07.21 |
[예제 코드/Python] 바이낸스 API를 이용한 암호화폐 선물 가격 데이터 얻기 (0) | 2022.06.11 |
[예제 코드/C언어] 윈도우 명령 프롬프트 크기 변경&고정하기 (0) | 2022.06.11 |
Comments