Skip to content

Latest commit

 

History

History
252 lines (221 loc) · 11.8 KB

211228.md

File metadata and controls

252 lines (221 loc) · 11.8 KB

Day 82

파이썬으로 배우는 게임 개발 실전편

Chapter 2

인덱스와 타이머

from PyQt5.QtWidgets import *
from PyQt5.QtGui import *
from PyQt5.QtCore import *
import sys

class WindowClass(QWidget):
    def __init__(self):
        super().__init__()
        self.fnt1 = QFont('Times New Roman', 20) # 작은 크기 폰트
        self.fnt2 = QFont('Times New Roman', 40) # 큰 크기 폰트
        self.idx = 0 # 인덱스 변수
        self.tmr = 0 # 타이머 변수
        self.center_text = '' # 중앙 문자열
        self.key_hint = '' # 키 힌트 문자열
        self.center_col = QColor(0, 0, 0) # 중앙 문자열 글자 색
        self.hint_col = QColor(0, 0, 0) # 키 힌트 문자열 글자 색
        self.key = None # 입력 키
        self.initUI()
        timer = QTimer(self)
        timer.setInterval(100)
        timer.timeout.connect(self.main_proc) # 100밀리초마다 main_proc 실행
        timer.start()

    def initUI(self):
        self.setGeometry(100, 100, 600, 400)
        self.setWindowTitle('Index and Timer')
        self.setStyleSheet('background:black')

    def main_proc(self):
        self.tmr = self.tmr + 1 # 타이머 값 증가
        if self.idx == 0: # 인덱스 0 처리(타이틀 화면)
            if self.tmr == 1: # 타이머 값이 1이면
                self.setStyleSheet('background:black') # 배경을 검은색으로
                self.center_text = '타이틀' # 중앙 문자열 설정
                self.key_hint = 'Press [SPACE] Key' # 키 힌트 문자열 설정
                self.center_col = QColor(0, 0, 0) # 중앙 문자열 글자 색 설정
                self.hint_col = QColor(204, 255, 0) # 키 힌트 문자열 글자 색 설정

            if self.key == Qt.Key_Space: # 스페이스 키를 눌렀다면
                self.setStyleSheet('background:blue')
                self.center_text = '게임 중' # 중앙 문자열 변경
                self.key_hint = '[E] 종료' # 키 힌트 문자열 변경
                self.center_col = QColor(0, 0, 0) # 중앙 문자열 글자 색 변경
                self.hint_col = QColor(255, 255, 0) # 키 힌트 문자열 글자 색 변경
                self.idx = 1 # 인덱스 값을 1로 변경
                self.tmr = 0 # 타이머 값을 0으로 변경

        if self.idx == 1: # 인덱스 1 처리(플레이 중 화면)
            if self.key == Qt.Key_E: # E 키를 눌렀다면
                self.setStyleSheet('background:maroon')
                self.center_text = 'GAME OVER'
                self.key_hint = ''
                self.center_col = QColor(0, 0, 0)
                self.hint_col = QColor(196, 52, 45)
                self.idx = 2 # 인덱스 값을 2로 변경
                self.tmr = 0 # 타이머 값을 0으로 변경

        if self.idx == 2: # 인덱스 2 처리(게임 오버 화면)
            if self.tmr == 30: # 타이머 값이 30일 때
                self.center_text = ''
                self.idx = 0 # 인덱스 값을 0으로 변경
                self.tmr = 0 # 타이머 값을 0으로 변경
        self.update()

    def keyPressEvent(self, e):
        self.key = e.key() # 입력된 키를 self.key에 저장

    def paintEvent(self, e):
        qp = QPainter()
        qp.begin(self)
        qp.setFont(self.fnt1)
        # 인덱스 값 표시
        qp.setPen(Qt.white)
        qp.drawText(150, 30, 'index {}'.format(self.idx))
        # 타이머 값 표시
        qp.setPen(Qt.cyan)
        qp.drawText(370, 30, 'timer {}'.format(self.tmr))
        # 키 힌트 문자열 표시
        qp.setPen(self.hint_col)
        qp.drawText(200, 300, self.key_hint)
        # 중앙 문자열 표시
        qp.setFont(self.fnt2)
        qp.setPen(self.center_col)
        qp.drawText(200, 150, self.center_text)

        qp.end()

if __name__ == "__main__":
    app = QApplication(sys.argv)
    main_W = WindowClass()
    main_W.show()
    sys.exit(app.exec_())

타이틀 화면에서 [SPACE]키를 누르면 게임 중 화면으로 변경되고, 게임 중 화면에서 [E]키를 누르면 게임 오버 화면으로 넘어가도록 인덱스와 타이머를 이용하여 프로그램을 만들었다.

미니 게임(유성 피하기)

from PyQt5.QtWidgets import *
from PyQt5.QtGui import *
from PyQt5.QtCore import *
import sys
import random

class WindowClass(QWidget):
    def __init__(self):
        super().__init__()
        self.fnt1 = QFont('Times New Roman', 24) # 작은 크기 폰트
        self.fnt2 = QFont('Times New Roman', 50) # 큰 크기 폰트
        self.idx = 0 # 인덱스 변수
        self.tmr = 0 # 타이머 변수
        self.score = 0 # 점수
        self.bg_pos = 0 # 배경 스크롤을 위한 좌표
        self.px = 240 # 우주선 좌표
        self.py = 540
        self.METEO_MAX = 30 # 화면에 흐르는 유성 수
        self.mx = [0] * self.METEO_MAX # 유성 좌표
        self.my = [0] * self.METEO_MAX
        self.key = None # 입력 받은 키
        self.status_text = '' # 타이틀 화면, 게임 오버 화면 텍스트
        self.status_col = QColor(0, 0, 0) # 타이틀 화면, 게임 오버 화면 텍스트 색
        self.start_text = '' # 타이틀 화면 키 힌트 텍스트
        self.start_text_col = QColor(158, 253, 56) # 타이틀 화면 키 힌트 텍스트 색
        self.img_player = [ # 우주선 이미지
            QImage('Python_workspace\python_game\image\meteor\starship0.png'),
            QImage('Python_workspace\python_game\image\meteor\starship1.png')
        ]
        self.img_bg = QImage('Python_workspace\python_game\image\meteor\cosmo.png') # 배경 이미지
        self.img_enemy = QImage('Python_workspace\python_game\image\meteor\meteo.png') # 유성 이미지

        self.initUI()
        timer = QTimer(self)
        timer.setInterval(50)
        timer.timeout.connect(self.main_proc) # 50밀리초마다 main_proc 실행
        timer.start()

    def initUI(self):
        self.setGeometry(100, 100, 480, 640)
        self.setWindowTitle('Meteor')

    def main_proc(self):
        self.tmr = self.tmr + 1 # 타이머 값 1씩 증가
        self.bg_pos = (self.bg_pos + 1) % 640 # 배경을 그릴 위치 계산
        if self.idx == 0: # 인덱스 0 처리(타이틀 화면)
            self.status_text = 'METEOR' # 타이틀 텍스트 설정
            self.status_col = QColor(231, 189, 66) # 타이틀 텍스트 색
            self.start_text = 'Press [SPACE] Key'

        if self.idx == 1: # 인덱스 1 처리(플레이 중 화면)
            self.score = self.score + 1 # 스코어 증가
            self.move_player() # 우주선 이동
            self.move_enemy() # 유성 이동
        
        if self.idx == 2: # 인덱스 2 처리(게임 오버 화면)
            self.move_enemy() # 유성 이동
            self.status_text = 'GAME OVER' # 게임 오버 텍스트 설정
            self.status_col = QColor(204, 0, 0) # 게임 오버 텍스트 색
            if self.tmr == 60: # 타이머 값이 60일 때
                self.idx = 0 # 인덱스 값 0으로 변경
                self.tmr = 0 # 타이머 값 0으로 변경
                self.status_text = '' # 타이틀 텍스트 비우기, 안 비우면 잠깐 GAME OVER와 METEOR이 겹쳐서 나타남

        self.update()

    def keyPressEvent(self, e): # 키 입력 이벤트
        if self.idx == 0 and e.key() == Qt.Key_Space: # 타이틀 화면에서 [SPACE] 키를 눌렀을 때
            self.status_text = '' # 타이틀 텍스트 제거
            self.start_text = '' # Press [SPACE] Key 텍스트 제거
            self.score = 0 # 점수 초기화
            self.px = 240 # 우주선 좌표 초기화
            self.init_enemy() # 유성 좌표 초기값 대입
            self.idx = 1 # 인덱스 값 1로 변경
        self.key = e.key() # 입력된 키를 self.key에 저장

    def keyReleaseEvent(self, e): # 키를 뗐을 때 실행
        self.key = '' # 입력된 키를 비움

    def paintEvent(self, e):
        qp = QPainter()
        qp.begin(self)
        # 위에서 아래로 흐르는 배경 연출(Walking dog의 응용)
        qp.drawImage(QRect(0, 0, 480, self.bg_pos), self.img_bg, QRect(0, 640 - self.bg_pos, 480, self.bg_pos))
        qp.drawImage(QRect(0, self.bg_pos, 480, 640 - self.bg_pos), self.img_bg, QRect(0, 0, 480, 640 - self.bg_pos))

        qp.setFont(self.fnt2)
        if self.idx == 2: # 게임 오버 화면 출력
            qp.setPen(self.status_col)
            qp.drawText(60, self.tmr * 4, self.status_text)
        elif self.idx == 0: # 타이틀 화면 출력
            qp.setPen(self.status_col)
            qp.drawText(120, 240, self.status_text)
        # Press [SPACE] Key 출력
        qp.setFont(self.fnt1)
        qp.setPen(self.start_text_col)
        qp.drawText(130, 480, self.start_text)
        # 점수 출력
        qp.setPen(Qt.white)
        qp.drawText(190, 30, 'SCORE {}'.format(self.score))
        # 게임 플레이 중 유성 이동 출력
        if self.idx >= 1:
            for i in range(self.METEO_MAX):
                qp.drawImage(QRect(self.mx[i], self.my[i], self.img_enemy.width(), self.img_enemy.height()), self.img_enemy)
        # 우주선 출력
        qp.drawImage(QRect(self.px, self.py, self.img_player[0].width(), self.img_player[0].height()), self.img_player[self.tmr % 2])

        qp.end()

    def hit_check(self, x1, y1, x2, y2): # 히트 체크
        if ((x1 - x2) * (x1 - x2) + (y1 - y2) * (y1 - y2) < 36 * 36):
            return True
        return False

    def init_enemy(self): # 유성 좌표의 초기 위치 지정 함수
        for i in range(self.METEO_MAX):
            self.mx[i] = random.randint(0, 480) # X 좌표를 난수로 결정
            self.my[i] = random.randint(-640, 0) # Y 좌표를 난수로 결정
    
    def move_enemy(self): # 유성 이동 함수
        for i in range(self.METEO_MAX):
            self.my[i] = self.my[i] + 6 + i / 5 # 유성 Y 좌표 업데이트
            if self.my[i] > 660: # Y 좌표가 660을 넘었다면
                self.mx[i] = random.randint(0, 480) # X 좌표를 난수로 결정
                self.my[i] = random.randint(-640, 0) # Y 좌표를 난수로 결정
            if self.idx == 1 and self.hit_check(self.px, self.py, self.mx[i], self.my[i]) == True: # 게임 중 우주선과 접촉했다면
                self.idx = 2 # 인덱스 값을 2로 변경
                self.tmr = 0 # 타이머 값을 0으로 변경

    def move_player(self): # 우주선 이동 함수
        if self.key == Qt.Key_Left and self.px > 30: # 왼쪽 키를 누른 상태에서 px > 30 이면
            self.px = self.px - 10 # px 값(X 좌표) 10 감소
        if self.key == Qt.Key_Right and self.px < 450: # 오른쪽 키를 누른 상태에서 px < 450 이면
            self.px = self.px + 10 # px 값(X 좌표) 10 증가

if __name__ == "__main__":
    app = QApplication(sys.argv)
    main_W = WindowClass()
    main_W.show()
    sys.exit(app.exec_())

QTimer를 사용하여 이번에도 쓰레드 사용 없이 미니 게임을 구현할 수 있었다.

이번에 알게 된 점 몇가지를 적어보자면 keyReleaseEvent에서 key를 초기화 시키지 않으면 해당 키가 계속 눌려있는 판정으로 계속 움직여 진다.
또한 keyPressEvent 안에 좌우 움직임을 구현하면 연속적으로 움직이는 것이 아니라 뚝뚝 끊겨서 움직여 진다.

배경 스크롤은 전에 Walking dog에서 응용한 것이다. 저번에는 가로로 스크롤이었지만 이번에는 세로로 스크롤일 뿐이어서 Walking dog의 x좌표 변동 부분을 y좌표 변동으로 바꿔주면 된다.

이번에 히트 체크 함수를 보면 sqrt를 사용하지 않고 우변의 36을 제곱한 것으로 표현했다.
이렇게 표현한 이유는 sqrt를 사용하지 않아 처리를 빠르게 되도록 하여, 게임이 쾌적하게 움직이도록 하기 위함이다.