[python][사과게임 매크로 만들기] 4. GUI 기반 사과게임 매크로
- 개요
명령 프롬프트창(CLI)에서 동작하는 사과게임 매크로를 만들어보았으니
이제 GUI(그래픽 유저 인터페이스)로 사과게임 매크로를 만들어봅시다.
파이썬에서 GUI를 만들어주는 다양한 라이브러리 중 PyQt5를 사용했습니다.
이전 포스트에서 CLI 기반 사과게임 매크로를 만들 때 클래스를 활용해서 만든 것처럼 이번에도 클래스를 활용합니다.
전체 코드는 github에 올려두었고, GUI를 만들 때 중요한 내용만 포스트에서 설명합니다.
파이썬 코드를 실행하면 프로그램이 켜집니다.
- PyQt5로 GUI 만들기
pip install PyQt5
PyQt5으로 GUI를 만들 때 실제 코드에서는 아래와 같은 작업을 해주어야 합니다.
1. 윈도우 창 만들기
말그대로 윈도우 창을 만들며 height와 width를 지정합니다.
"""
App 클래스를 생성하되 Qwidget 클래스를 상속 받습니다.
setWindowTitle은 프로그램 상단의 이름,
resize는 너비, 높이를 설정합니다.
앞으로 이 클래스에 main_layout을 추가하고
이 안에 패널, 패널 안에 버튼을 추가합니다.
"""
from PyQt5.QtWidgets import QApplication, QWidget, QVBoxLayout, QHBoxLayout, QLabel, QFrame, QPushButton
class App(QWidget):
def __init__(self):
super().__init__()
self.setWindowTitle('사과게임 매크로')
self.resize(450, 400)
2. 패널 만들기(구역 분리)
Qwidget이 프로그램 영역이고, 이 안에 메인 레이아웃을 잡은 뒤 구역을 분리합시다.
구역을 분리해야 여러 오브젝트를 배치하기 편합니다.
html/css를 이용하여 다른 웹페이지 화면을 따라 만들 때 먼저 div 태그로 구역을 어떻게 분리할까 고민하는 것과 비슷한 맥락이죠.
3. 구역 내 기능 구현
구역을 나눴으니 안에 기능을 구현합니다.
말이 거창해서 기능 구현이지 버튼을 만든다거나, 글씨를 쓴다거나, 혹은 마우스를 올리면 아래에 선택창이 나온다거나 그런 기능을 만듭니다.
- 화면 및 동작 설정: 도면
발표 파일을 만들기 전에 어떤 내용으로 무슨 그림을 넣을지 고민하는 것처럼
GUI의 화면 구성은 어떠하면 좋을지, 각각 기능은 어떻게 동작하면 좋을지 프로그램의 구조를 고민합시다.
1. 구역 분리
먼저 특정한 크기의 윈도우 창을 만들었다 생각하고 그 안에서 구역을 분리해야합니다.
저는 총 3개의 구역을 만들 계획입니다.
첫번째 구역의 이름은 BUTTON으로 매크로 동작을 위해 누르는 버튼을 담는 구역입니다.
두번째 구역의 이름은 INFO_TEXT로 예상점수가 몇 점인지 알려주는 것처럼 정보를 제공하는 구역입니다.
마지막 구역의 이름은 STATUS로 사과게임에서 보이는 10x17의 숫자배열을 보여주고, 매크로 동작시 숫자가 어떻게 변하는지 보여주는 구역입니다.
2. 버튼 동작
첫번째 구역에서 버튼을 만들다고 했습니다.
버튼은 총 4개를 만들 계획입니다.
PLAY 버튼: 사과게임의 스타트 버튼을 누르고, 10x17의 숫자배열을 STATUS 구역에 보여주고, 예상 점수, (예상 점수 달성에 필요한)총 이동횟수, (매크로가 움직인) 실제 이동 횟수를 STATUS 구역에 보여줍니다.
MACRO 버튼: 매크로를 시작하는 버튼입니다. 매크로가 진행되면서 STATUS에서 사과가 없어진 숫자를 _로 대체하고 실제 이동횟수를 업데이트합니다.
RESET/PLAY 버튼: 게임 내 리셋을 누르고 플레이 버튼을 누릅니다. 위의 PLAY 버튼과 같은 기능에 리셋 버튼을 누르는 기능만 추가되었습니다.
EXIT 버튼: 게임을 끕니다.
- 패널와 버튼을 만드는 코드
PyQt5에서 패널과 버튼을 만드는 함수는 정해져있지만
실제 코드로 패널과 버튼을 만드는 방법은 다양합니다.
저는 dictionary에 패널과 버튼 정보를 담고
setup 함수와 create 함수로 패널과 버튼을 생성했습니다.
변수 앞에 self가 적혀있는데 클래스로 작성한 코드를 그대로 복사 붙여넣기해서 그렇습니다.
1. 패널 생성 코드
panel_dict라는 딕셔너리를 선언하고 패널을 생생합니다.
BUTTON, INFO_TEXT, STATUS라는 key 이름에 각각 패널시 생성에 필요한 값을 value에 넣습니다.
"""
만들어진 패널은 self.panels에 담습니다.
만들어진 패널의 값을 업데이트할 때 이 변수로 패널에 접근합니다.
"""
self.panels = {}
"""
panel_dict는 panel을 만들 때 필요한 정보를 저장했습니다.
이 때 key는 패널의 이름, value에는 패널 선언 때 필요한 정보입니다.
QHBoxLayout()은 패널 내에서 요소를 선언할 시 배치가 horizontal한 방향,
QVBOxLayout()은 vertical한 방향으로 됩니다.
"""
self.panel_dict = {
'BUTTON':{
'bg-color': 'lightblue',
'height': 25,
'text': '',
'align': QHBoxLayout()
},
'INFO_TEXT':{
'bg-color': 'lightblue',
'height': 50,
'text': '',
'align': QVBoxLayout()
},
'STATUS':{
'bg-color': 'lightblue',
'height': 300,
'text': '',
'align': QVBoxLayout()
}
}
"""
setup_panel()은 말그대로 패널은 셋업하는 함수로
panel_dict의 key와 value를 create_panel에 넣어 패널을 생성하고,
main_layout(윈도우 창) 안에 배치합니다.
"""
self.setup_panel()
def setup_panel(self):
main_layout = QVBoxLayout()
for key, value in self.panel_dict.items():
self.panels[key] = self.create_panel(value)
main_layout.addWidget(self.panels[key])
self.setLayout(main_layout)
def create_panel(self, value):
panel = QFrame()
panel.setStyleSheet(f'background-color: {value['bg-color']}')
panel.setFixedHeight(value['height'])
layout = value['align']
layout.setContentsMargins(0, 0, 0, 0)
label = QLabel(value['text'])
layout.addWidget(label)
panel.setLayout(layout)
panel.label = label
return panel
2. 버튼 생성 코드
패널과 유사하게 버튼을 위한 딕셔너리를 선언합니다.
"""
패널과 마찬가지로 self.buttons는 만들어진 버튼을 담는 변수입니다.
지금 프로그램에서는 버튼 자체의 정보를 업데이트를 할 필요는 없지만 나중에
필요해질 수도 있으니 self.buttons 변수에 버튼 정보를 담습니다.
"""
self.buttons = {}
"""
버튼을 만들기 위한 기본 정보를 button_dict에 담습니다.
4개의 버튼을 만듭니다.
PLAY 버튼: 플레이 버튼을 누르고 매크로 실행을 위한 세팅
MACRO 버튼: 매크로 실행
RESET/PLAY 버튼: 리셋 버튼을 누르고 플레이 버튼을 누른 뒤 매크로 실행을 위한 세팅
EXIT 버튼: 프로그램 종료
panel_name은 버튼을 해당 패널 이름에 넣겠다는 것이고,
모든 버튼은 BUTTON 패널에 생성됩니다.
function_name은 버튼을 누를시 실행되는 함수입니다.
idx의 버튼의 순서대로 idx가 낮은 순서대로 왼쪽에서 오른쪽으로 배치됩니다.
"""
self.button_dict = {
'PLAY':{
'panel_name':'BUTTON',
'function_name': self.play,
'width': 100,
'height':25,
'idx': 0
},
'MACRO':{
'panel_name':'BUTTON',
'function_name': self.do_applegame,
'width': 100,
'height':25,
'idx': 1
},
'RESET/PLAY':{
'panel_name':'BUTTON',
'function_name': self.reset_and_play,
'width': 100,
'height':25,
'idx': 2
},
'EXIT':{
'panel_name':'BUTTON',
'function_name': self.macro_exit,
'width': 100,
'height':25,
'idx': 3
},
}
self.setup_button()
def setup_button(self):
for key, value in self.button_dict.items():
self.button_dict[key] = self.create_button(key, value)
def create_button(self, key, value):
button = QPushButton(key) # 버튼 객체 생성
button.setFixedWidth(value['width']) # 버튼 너비 설정
button.setFixedHeight( value['height']) # 버튼 높이 설정
# 버튼을 누를 떄(pressed) 나오는 색을 설정합니다(html/css 문법과 유사).
button.setStyleSheet("""
QPushButton:pressed {
background-color: gray;
color: black;
}""")
button.clicked.connect(value['function_name']) # 버튼을 누를 때 연결되는 함수
self.panels[value['panel_name']].layout().insertWidget(value['idx'], button) # 패널에 버튼 배치
return button
- 함수 실행에 따른 패널 정보 업데이트
PLAY, RESET/PLAY 버튼을 누르면 매크로 실행을 위한 준비를 합니다.
사과게임 화면에 나온 10x17의 숫자를 STATUS 패널에 나타낼 것이고,
실제 매크로 수행시 마우스로 숫자가 없어지면 STATUS를 업데이트 합니다.
INFO_TEXT 패널에는 예상점수와 이동횟수 정보를 업데이트합니다.
패널의 정보 업데이트가 코드의 여러 곳에서 되므로 포스트에서는 STATUS 패널 업데이트 부분만 예로 들어 설명하겠습니다.
앞에서도 말했지만 전체 코드는 github 링크에 있습니다. 전체 코드 github 링크
"""
get_process 함수는 매크로 실행을 위한 세팅을 하는 함수입니다.
get_numpy()에서 사과게임의 10x17의 숫자 배치를 self.num2d에 저장하게 되는데
self.num2d를 string으로 변환해서 STATUS 패널에 보여줍니다.
self.num2d의 숫자를 행렬처럼 배치해야하므로
np_str 선언하는 과정을 거치고,
self.panels['STATUS']의 정보를 업데이트 합니다.
"""
def get_process(self):
try:
self.get_numpy()
self.get_mouse_pos()
np_str = '\n'.join([' '.join(map(str, row)) for row in self.num2d])
self.set_yhxw()
self.panels['STATUS'].label.setText(np_str)
self.panels['STATUS'].label.setAlignment(Qt.AlignCenter) # 중앙 정렬 (수평 및 수직)
self.panels['STATUS'].label.setStyleSheet("font-family: Arial; font-size: 20px;")
except:
self.panels['STATUS'].label.setText('문제 발생')
self.panels['STATUS'].label.setAlignment(Qt.AlignCenter) # 중앙 정렬 (수평 및 수직)
self.panels['STATUS'].label.setStyleSheet("font-family: Arial; font-size: 20px;")
- CLI 매크로 프로그램과의 유사성
CLI에서 만든 여러 함수를 거의 그대로 사용하되(함수 이름도 좀 바꿈) 키보드 입력으로 함수 실행이 되던 것을 버튼 클릭으로 함수 실행이 되게 바꾸었습니다.
버튼 클릭으로 동작하게 바꾸면서 함수도 살짝 수정했는데 이건 전체 코드를 보면서 확인해주세요.
- 매크로 실행
실제 사과게임 매크로 프로그램을 사용한 유투브 영상입니다.