Skip to content

Latest commit

 

History

History
677 lines (599 loc) · 32.7 KB

211230.md

File metadata and controls

677 lines (599 loc) · 32.7 KB

Day 84

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

Chapter 3

아이템 획득 시 점수 올리기

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

class WindowClass(QWidget):
    def __init__(self):
        super().__init__()

        self.key = None
        self.img_bg = [ # 맵 칩
            QImage('Python_workspace\python_game\image\dot_eat\chip00.png'),
            QImage('Python_workspace\python_game\image\dot_eat\chip01.png'),
            QImage('Python_workspace\python_game\image\dot_eat\chip02.png'),
            QImage('Python_workspace\python_game\image\dot_eat\chip03.png')
        ]
        self.img_pen = [ # 캐릭터 이미지
            QImage('Python_workspace\python_game\image\dot_eat\pen00.png'), # 위쪽
            QImage('Python_workspace\python_game\image\dot_eat\pen01.png'),
            QImage('Python_workspace\python_game\image\dot_eat\pen02.png'),
            QImage('Python_workspace\python_game\image\dot_eat\pen03.png'), # 아래쪽
            QImage('Python_workspace\python_game\image\dot_eat\pen04.png'),
            QImage('Python_workspace\python_game\image\dot_eat\pen05.png'),
            QImage('Python_workspace\python_game\image\dot_eat\pen06.png'), # 왼쪽
            QImage('Python_workspace\python_game\image\dot_eat\pen07.png'),
            QImage('Python_workspace\python_game\image\dot_eat\pen08.png'),
            QImage('Python_workspace\python_game\image\dot_eat\pen09.png'), # 오른쪽
            QImage('Python_workspace\python_game\image\dot_eat\pen10.png'),
            QImage('Python_workspace\python_game\image\dot_eat\pen11.png')
        ]

        self.map_data = [ # 미로 데이터
            [0, 1, 1, 1, 1, 0, 0, 1, 1, 1, 1, 0],
            [0, 2, 3, 3, 2, 1, 1, 2, 3, 3, 2, 0],
            [0, 3, 0, 0, 3, 3, 3, 3, 0, 0, 3, 0],
            [0, 3, 1, 1, 3, 0, 0, 3, 1, 1, 3, 0],
            [0, 3, 2, 2, 3, 0, 0, 3, 2, 2, 3, 0],
            [0, 3, 0, 0, 3, 1, 1, 3, 0, 0, 3, 0],
            [0, 3, 1, 1, 3, 3, 3, 3, 1, 1, 3, 0],
            [0, 2, 3, 3, 2, 0, 0, 2, 3, 3, 2, 0],
            [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
        ]
        
        self.DIR_UP = 0 # 캐릭터 방향 정의 변수(위쪽)
        self.DIR_DOWN = 1 # 캐릭터 방향 정의 변수(아래쪽)
        self.DIR_LEFT = 2 # 캐릭터 방향 정의 변수(왼쪽)
        self.DIR_RIGHT = 3 # 캐릭터 방향 정의 변수(오른쪽)
        self.ANIMATION = [0, 1, 0, 2]

        self.tmr = 0
        self.score = 0 # 점수

        self.pen_x = 60 # 펜펜의 X 좌표
        self.pen_y = 60 # 펜펜의 Y 좌표
        self.pen_d = 0 # 펜펜의 방향
        self.pen_a = 0 # 펜펜의 이미지 번호

        self.initUI()
        timer = QTimer(self)
        timer.setInterval(100)
        timer.timeout.connect(self.main_proc)
        timer.start()

    def initUI(self):
        self.setFixedSize(720, 540) # 창 크기 고정
        self.setWindowTitle('Eat candy')

    def check_wall(self, cx, cy, di, dot): # 지정한 방향에 벽 존재 여부 확인 함수
        chk = False # chk에 False 대입
        if di == self.DIR_UP: # 위쪽일 경우
            mx = int(cx / 60) # mx와 my에 리스트의 좌상 방향 확인용 값 대입
            my = int((cy - dot) / 60)
            if self.map_data[my][mx] <= 1: # 좌상
                chk = True # chk에 True 대입
            mx = int((cx + 59) / 60) # 리스트의 우상 방향 확인용 값 대입
            if self.map_data[my][mx] <= 1: # 우상
                chk = True # chk에 True 대입

        if di == self.DIR_DOWN: # 아래쪽일 경우
            mx = int(cx / 60) # mx와 my에 리스트의 좌하 방향 확인용 값 대입
            my = int((cy + 59 + dot) / 60)
            if self.map_data[my][mx] <= 1: # 좌하
                chk = True
            mx = int((cx + 59) / 60) # 리스트의 우하 방향 확인용 값 대입
            if self.map_data[my][mx] <= 1: # 우하
                chk = True

        if di == self.DIR_LEFT: # 왼쪽일 경우
            mx = int((cx - dot) / 60) # mx와 my에 리스트의 좌상 방향 확인용 값 대입
            my = int(cy / 60)
            if self.map_data[my][mx] <= 1: # 좌상
                chk = True
            my = int((cy + 59) / 60) # 리스트의 좌하 방향 확인용 값 대입
            if self.map_data[my][mx] <= 1: # 좌하
                chk = True

        if di == self.DIR_RIGHT: # 오른쪽일 경우
            mx = int((cx + 59 + dot) / 60) # mx와 my에 리스트의 우상 방향 확인용 값 대입
            my = int(cy / 60)
            if self.map_data[my][mx] <= 1: # 우상
                chk = True
            my = int((cy + 59) / 60) # 리스트의 우하 방향 확인용 값 대입
            if self.map_data[my][mx] <= 1: # 우하
                chk = True
        return chk # chk 값 반환

    def move_penpen(self): # 펜펜(캐릭터) 이동 함수
        if self.key == Qt.Key_Up: # 위쪽 방향키를 눌렀다면
            self.pen_d = self.DIR_UP # 펜펜의 방향을 위쪽으로
            if self.check_wall(self.pen_x, self.pen_y, self.pen_d, 20) == False: # 해당 방향이 벽이 아니라면
                self.pen_y = self.pen_y - 20 # 위쪽으로 이동
        if self.key == Qt.Key_Down: # 아래쪽
            self.pen_d = self.DIR_DOWN # 펜펜의 방향을 아래쪽으로
            if self.check_wall(self.pen_x, self.pen_y, self.pen_d, 20) == False:
                self.pen_y = self.pen_y + 20
        if self.key == Qt.Key_Left: # 왼쪽
            self.pen_d = self.DIR_LEFT # 펜펜의 방향을 왼쪽으로
            if self.check_wall(self.pen_x, self.pen_y, self.pen_d, 20) == False:
                self.pen_x = self.pen_x - 20
        if self.key == Qt.Key_Right: # 오른쪽
            self.pen_d = self.DIR_RIGHT # 펜펜의 방향을 오른쪽으로
            if self.check_wall(self.pen_x, self.pen_y, self.pen_d, 20) == False:
                self.pen_x = self.pen_x + 20
        self.pen_a = self.pen_d * 3 + self.ANIMATION[self.tmr % 4] # 펜펜 애니메이션(이미지) 번호 계산
        mx = int(self.pen_x / 60) # mx, my에 펜펜이 있는 위치의 리스트로 확인할 값 대입
        my = int(self.pen_y / 60)
        if self.map_data[my][mx] == 3: # 사탕 위치에 들어가면
            self.score = self.score + 100 # 점수 추가
            self.map_data[my][mx] = 2 # 사탕 삭제

    def main_proc(self):
        self.tmr = self.tmr + 1 # 타이머 값 증가
        self.move_penpen()
        self.update()

    def draw_text(self, qp, txt, x, y, siz, col): # 그림자를 포함한 문자열을 표시
        qp.setFont(QFont('Times New Roman', siz, QFont.Bold)) # 폰트 정의
        qp.setPen(Qt.black) # 문자열 그림자(2픽셀 어긋나게 검게 표시)
        qp.drawText(x + 2, y + 2, txt)
        qp.setPen(col) # 지정한 색으로 문자열 표시
        qp.drawText(x, y, txt)

    def paintEvent(self, e):
        qp = QPainter()
        qp.begin(self)
        
        for y in range(9):
            for x in range(12):
                qp.drawImage(QRect(x * 60, y * 60, 60, 60), self.img_bg[self.map_data[y][x]]) # 맵 칩으로 미로 그리기

        qp.drawImage(QRect(self.pen_x, self.pen_y, 60, 60), self.img_pen[self.pen_a]) # PenPen 표시
        self.draw_text(qp, 'SCORE ' + str(self.score), 100, 30, 30, Qt.white)
        qp.end()
    
    def keyPressEvent(self, e):
        self.key = e.key()

    def keyReleaseEvent(self, e):
        self.key = None

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

펜펜의 좌표를 맵 칩 크기로 나눠서, 2차원 리스트 map_data의 인덱스를 구하여 그 위치가 어느 칸인지 확인하는 것이다.

그 결과 사탕이 있는 칸의 값인 3이면 점수를 올리고, 사탕이 없는 바닥인 2로 값을 변경하여 사탕을 없앤다.

적 등장시키기

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.key = None
        self.img_bg = [ # 맵 칩
            QImage('Python_workspace\python_game\image\dot_eat\chip00.png'),
            QImage('Python_workspace\python_game\image\dot_eat\chip01.png'),
            QImage('Python_workspace\python_game\image\dot_eat\chip02.png'),
            QImage('Python_workspace\python_game\image\dot_eat\chip03.png')
        ]
        self.img_pen = [ # 캐릭터 이미지
            QImage('Python_workspace\python_game\image\dot_eat\pen00.png'), # 위쪽
            QImage('Python_workspace\python_game\image\dot_eat\pen01.png'),
            QImage('Python_workspace\python_game\image\dot_eat\pen02.png'),
            QImage('Python_workspace\python_game\image\dot_eat\pen03.png'), # 아래쪽
            QImage('Python_workspace\python_game\image\dot_eat\pen04.png'),
            QImage('Python_workspace\python_game\image\dot_eat\pen05.png'),
            QImage('Python_workspace\python_game\image\dot_eat\pen06.png'), # 왼쪽
            QImage('Python_workspace\python_game\image\dot_eat\pen07.png'),
            QImage('Python_workspace\python_game\image\dot_eat\pen08.png'),
            QImage('Python_workspace\python_game\image\dot_eat\pen09.png'), # 오른쪽
            QImage('Python_workspace\python_game\image\dot_eat\pen10.png'),
            QImage('Python_workspace\python_game\image\dot_eat\pen11.png')
        ]
        self.img_red = [
            QImage('Python_workspace/python_game/image/dot_eat/red00.png'),
            QImage('Python_workspace/python_game/image/dot_eat/red01.png'),
            QImage('Python_workspace/python_game/image/dot_eat/red02.png'),
            QImage('Python_workspace/python_game/image/dot_eat/red03.png'),
            QImage('Python_workspace/python_game/image/dot_eat/red04.png'),
            QImage('Python_workspace/python_game/image/dot_eat/red05.png'),
            QImage('Python_workspace/python_game/image/dot_eat/red06.png'),
            QImage('Python_workspace/python_game/image/dot_eat/red07.png'),
            QImage('Python_workspace/python_game/image/dot_eat/red08.png'),
            QImage('Python_workspace/python_game/image/dot_eat/red09.png'),
            QImage('Python_workspace/python_game/image/dot_eat/red10.png'),
            QImage('Python_workspace/python_game/image/dot_eat/red11.png')
        ]

        self.map_data = [ # 미로 데이터
            [0, 1, 1, 1, 1, 0, 0, 1, 1, 1, 1, 0],
            [0, 2, 3, 3, 2, 1, 1, 2, 3, 3, 2, 0],
            [0, 3, 0, 0, 3, 3, 3, 3, 0, 0, 3, 0],
            [0, 3, 1, 1, 3, 0, 0, 3, 1, 1, 3, 0],
            [0, 3, 2, 2, 3, 0, 0, 3, 2, 2, 3, 0],
            [0, 3, 0, 0, 3, 1, 1, 3, 0, 0, 3, 0],
            [0, 3, 1, 1, 3, 3, 3, 3, 1, 1, 3, 0],
            [0, 2, 3, 3, 2, 0, 0, 2, 3, 3, 2, 0],
            [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
        ]
        
        self.DIR_UP = 0 # 캐릭터 방향 정의 변수(위쪽)
        self.DIR_DOWN = 1 # 캐릭터 방향 정의 변수(아래쪽)
        self.DIR_LEFT = 2 # 캐릭터 방향 정의 변수(왼쪽)
        self.DIR_RIGHT = 3 # 캐릭터 방향 정의 변수(오른쪽)
        self.ANIMATION = [0, 1, 0, 2]

        self.tmr = 0
        self.score = 0 # 점수

        self.pen_x = 60 # 펜펜의 X 좌표
        self.pen_y = 60 # 펜펜의 Y 좌표
        self.pen_d = 0 # 펜펜의 방향
        self.pen_a = 0 # 펜펜의 이미지 번호

        self.red_x = 600 # 레드의 X 좌표
        self.red_y = 420 # 레드의 Y 좌표
        self.red_d = 0 # 레드의 방향
        self.red_a = 0 # 레드의 이미지 번호

        self.initUI()
        timer = QTimer(self)
        timer.setInterval(100)
        timer.timeout.connect(self.main_proc)
        timer.start()

    def initUI(self):
        self.setFixedSize(720, 540) # 창 크기 고정
        self.setWindowTitle('Add Enemy')

    def check_wall(self, cx, cy, di, dot): # 지정한 방향에 벽 존재 여부 확인 함수
        chk = False # chk에 False 대입
        if di == self.DIR_UP: # 위쪽일 경우
            mx = int(cx / 60) # mx와 my에 리스트의 좌상 방향 확인용 값 대입
            my = int((cy - dot) / 60)
            if self.map_data[my][mx] <= 1: # 좌상
                chk = True # chk에 True 대입
            mx = int((cx + 59) / 60) # 리스트의 우상 방향 확인용 값 대입
            if self.map_data[my][mx] <= 1: # 우상
                chk = True # chk에 True 대입

        if di == self.DIR_DOWN: # 아래쪽일 경우
            mx = int(cx / 60) # mx와 my에 리스트의 좌하 방향 확인용 값 대입
            my = int((cy + 59 + dot) / 60)
            if self.map_data[my][mx] <= 1: # 좌하
                chk = True
            mx = int((cx + 59) / 60) # 리스트의 우하 방향 확인용 값 대입
            if self.map_data[my][mx] <= 1: # 우하
                chk = True

        if di == self.DIR_LEFT: # 왼쪽일 경우
            mx = int((cx - dot) / 60) # mx와 my에 리스트의 좌상 방향 확인용 값 대입
            my = int(cy / 60)
            if self.map_data[my][mx] <= 1: # 좌상
                chk = True
            my = int((cy + 59) / 60) # 리스트의 좌하 방향 확인용 값 대입
            if self.map_data[my][mx] <= 1: # 좌하
                chk = True

        if di == self.DIR_RIGHT: # 오른쪽일 경우
            mx = int((cx + 59 + dot) / 60) # mx와 my에 리스트의 우상 방향 확인용 값 대입
            my = int(cy / 60)
            if self.map_data[my][mx] <= 1: # 우상
                chk = True
            my = int((cy + 59) / 60) # 리스트의 우하 방향 확인용 값 대입
            if self.map_data[my][mx] <= 1: # 우하
                chk = True
        return chk # chk 값 반환

    def move_penpen(self): # 펜펜(캐릭터) 이동 함수
        if self.key == Qt.Key_Up: # 위쪽 방향키를 눌렀다면
            self.pen_d = self.DIR_UP # 펜펜의 방향을 위쪽으로
            if self.check_wall(self.pen_x, self.pen_y, self.pen_d, 20) == False: # 해당 방향이 벽이 아니라면
                self.pen_y = self.pen_y - 20 # 위쪽으로 이동
        if self.key == Qt.Key_Down: # 아래쪽
            self.pen_d = self.DIR_DOWN # 펜펜의 방향을 아래쪽으로
            if self.check_wall(self.pen_x, self.pen_y, self.pen_d, 20) == False:
                self.pen_y = self.pen_y + 20
        if self.key == Qt.Key_Left: # 왼쪽
            self.pen_d = self.DIR_LEFT # 펜펜의 방향을 왼쪽으로
            if self.check_wall(self.pen_x, self.pen_y, self.pen_d, 20) == False:
                self.pen_x = self.pen_x - 20
        if self.key == Qt.Key_Right: # 오른쪽
            self.pen_d = self.DIR_RIGHT # 펜펜의 방향을 오른쪽으로
            if self.check_wall(self.pen_x, self.pen_y, self.pen_d, 20) == False:
                self.pen_x = self.pen_x + 20
        self.pen_a = self.pen_d * 3 + self.ANIMATION[self.tmr % 4] # 펜펜 애니메이션(이미지) 번호 계산
        mx = int(self.pen_x / 60) # mx, my에 펜펜이 있는 위치의 리스트로 확인할 값 대입
        my = int(self.pen_y / 60)
        if self.map_data[my][mx] == 3: # 사탕 위치에 들어가면
            self.score = self.score + 100 # 점수 추가
            self.map_data[my][mx] = 2 # 사탕 삭제

    def move_enemy(self): # 레드(적) 이동 함수
        speed = 10 # 레드의 이동 속도
        if self.red_x % 60 == 0 and self.red_y % 60 == 0: # 칸의 정확한 위치에 있는 경우
            self.red_d = random.randint(0, 3) # 무작위로 방향 변경
        if self.red_d == self.DIR_UP: # 레드가 위쪽을 향한 경우
            if self.check_wall(self.red_x, self.red_y, self.red_d, speed) == False: # 해당 방향이 벽이 아니라면
                self.red_y = self.red_y - speed # 이동
        if self.red_d == self.DIR_DOWN:
            if self.check_wall(self.red_x, self.red_y, self.red_d, speed) == False:
                self.red_y = self.red_y + speed
        if self.red_d == self.DIR_LEFT:
            if self.check_wall(self.red_x, self.red_y, self.red_d, speed) == False:
                self.red_x = self.red_x - speed
        if self.red_d == self.DIR_RIGHT:
            if self.check_wall(self.red_x, self.red_y, self.red_d, speed) == False:
                self.red_x = self.red_x + speed
        self.red_a = self.red_d * 3 + self.ANIMATION[self.tmr % 4] # 레드의 애니메이션 번호 계산

    def main_proc(self):
        self.tmr = self.tmr + 1 # 타이머 값 증가
        self.move_penpen()
        self.move_enemy()
        self.update()

    def draw_text(self, qp, txt, x, y, siz, col): # 그림자를 포함한 문자열을 표시
        qp.setFont(QFont('Times New Roman', siz, QFont.Bold)) # 폰트 정의
        qp.setPen(Qt.black) # 문자열 그림자(2픽셀 어긋나게 검게 표시)
        qp.drawText(x + 2, y + 2, txt)
        qp.setPen(col) # 지정한 색으로 문자열 표시
        qp.drawText(x, y, txt)

    def paintEvent(self, e):
        qp = QPainter()
        qp.begin(self)
        
        for y in range(9):
            for x in range(12):
                qp.drawImage(QRect(x * 60, y * 60, 60, 60), self.img_bg[self.map_data[y][x]]) # 맵 칩으로 미로 그리기

        qp.drawImage(QRect(self.pen_x, self.pen_y, 60, 60), self.img_pen[self.pen_a]) # PenPen 표시
        qp.drawImage(QRect(self.red_x, self.red_y, 60, 60), self.img_red[self.red_a])
        self.draw_text(qp, 'SCORE ' + str(self.score), 100, 30, 30, Qt.white)
        qp.end()
    
    def keyPressEvent(self, e):
        self.key = e.key()

    def keyReleaseEvent(self, e):
        self.key = None

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

이제 적인 빨간색 펭귄이 미로안을 돌아다닌다. 하지만 펜펜을 추격하지 않고, 접촉해도 아무 일도 일어나지 않는다.

타이틀, 클리어, 게임 오버

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.key = None
        self.img_title = QImage('Python_workspace/python_game/image/dot_eat/title.png')
        self.img_bg = [ # 맵 칩
            QImage('Python_workspace\python_game\image\dot_eat\chip00.png'),
            QImage('Python_workspace\python_game\image\dot_eat\chip01.png'),
            QImage('Python_workspace\python_game\image\dot_eat\chip02.png'),
            QImage('Python_workspace\python_game\image\dot_eat\chip03.png')
        ]
        self.img_pen = [ # 캐릭터 이미지
            QImage('Python_workspace\python_game\image\dot_eat\pen00.png'), # 위쪽
            QImage('Python_workspace\python_game\image\dot_eat\pen01.png'),
            QImage('Python_workspace\python_game\image\dot_eat\pen02.png'),
            QImage('Python_workspace\python_game\image\dot_eat\pen03.png'), # 아래쪽
            QImage('Python_workspace\python_game\image\dot_eat\pen04.png'),
            QImage('Python_workspace\python_game\image\dot_eat\pen05.png'),
            QImage('Python_workspace\python_game\image\dot_eat\pen06.png'), # 왼쪽
            QImage('Python_workspace\python_game\image\dot_eat\pen07.png'),
            QImage('Python_workspace\python_game\image\dot_eat\pen08.png'),
            QImage('Python_workspace\python_game\image\dot_eat\pen09.png'), # 오른쪽
            QImage('Python_workspace\python_game\image\dot_eat\pen10.png'),
            QImage('Python_workspace\python_game\image\dot_eat\pen11.png')
        ]
        self.img_red = [
            QImage('Python_workspace/python_game/image/dot_eat/red00.png'),
            QImage('Python_workspace/python_game/image/dot_eat/red01.png'),
            QImage('Python_workspace/python_game/image/dot_eat/red02.png'),
            QImage('Python_workspace/python_game/image/dot_eat/red03.png'),
            QImage('Python_workspace/python_game/image/dot_eat/red04.png'),
            QImage('Python_workspace/python_game/image/dot_eat/red05.png'),
            QImage('Python_workspace/python_game/image/dot_eat/red06.png'),
            QImage('Python_workspace/python_game/image/dot_eat/red07.png'),
            QImage('Python_workspace/python_game/image/dot_eat/red08.png'),
            QImage('Python_workspace/python_game/image/dot_eat/red09.png'),
            QImage('Python_workspace/python_game/image/dot_eat/red10.png'),
            QImage('Python_workspace/python_game/image/dot_eat/red11.png')
        ]
        
        self.DIR_UP = 0 # 캐릭터 방향 정의 변수(위쪽)
        self.DIR_DOWN = 1 # 캐릭터 방향 정의 변수(아래쪽)
        self.DIR_LEFT = 2 # 캐릭터 방향 정의 변수(왼쪽)
        self.DIR_RIGHT = 3 # 캐릭터 방향 정의 변수(오른쪽)
        self.ANIMATION = [0, 1, 0, 2]

        self.idx = 0
        self.tmr = 0
        self.score = 0 # 점수
        self.candy = 0 # 각 스테이지에 있는 사탕 수

        self.pen_x = 0 # 펜펜의 X 좌표
        self.pen_y = 0 # 펜펜의 Y 좌표
        self.pen_d = 0 # 펜펜의 방향
        self.pen_a = 0 # 펜펜의 이미지 번호

        self.red_x = 0 # 레드의 X 좌표
        self.red_y = 0 # 레드의 Y 좌표
        self.red_d = 0 # 레드의 방향
        self.red_a = 0 # 레드의 이미지 번호

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

    def initUI(self):
        self.setFixedSize(720, 540) # 창 크기 고정
        self.setWindowTitle('PenPen\'s Maze Adventure')
        self.set_stage()
        self.set_chara_pos()

    def set_stage(self): # 스테이지 데이터 설정 함수
        self.map_data = [ # 미로 데이터
            [0, 1, 1, 1, 1, 0, 0, 1, 1, 1, 1, 0],
            [0, 2, 3, 3, 2, 1, 1, 2, 3, 3, 2, 0],
            [0, 3, 0, 0, 3, 3, 3, 3, 0, 0, 3, 0],
            [0, 3, 1, 1, 3, 0, 0, 3, 1, 1, 3, 0],
            [0, 3, 2, 2, 3, 0, 0, 3, 2, 2, 3, 0],
            [0, 3, 0, 0, 3, 1, 1, 3, 0, 0, 3, 0],
            [0, 3, 1, 1, 3, 3, 3, 3, 1, 1, 3, 0],
            [0, 2, 3, 3, 2, 0, 0, 2, 3, 3, 2, 0],
            [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
        ]
        self.candy = 32 # 사탕 수

    def set_chara_pos(self): # 캐릭터 시작 위치 설정 함수
        self.pen_x = 60 # 펜펜의 시작 x, y 좌표 대입
        self.pen_y = 60
        self.pen_d = self.DIR_DOWN # 펜펜의 방향을 아래로
        self.pen_a = 3 # 펜펜 그림 번호 대입
        self.red_x = 600 # 레드의 시작 x, y 좌표 대입
        self.red_y = 420
        self.red_d = self.DIR_DOWN # 레드의 방향을 아래로
        self.red_a = 3 # 레드 그림 번호 대입

    def check_wall(self, cx, cy, di, dot): # 지정한 방향에 벽 존재 여부 확인 함수
        chk = False # chk에 False 대입
        if di == self.DIR_UP: # 위쪽일 경우
            mx = int(cx / 60) # mx와 my에 리스트의 좌상 방향 확인용 값 대입
            my = int((cy - dot) / 60)
            if self.map_data[my][mx] <= 1: # 좌상
                chk = True # chk에 True 대입
            mx = int((cx + 59) / 60) # 리스트의 우상 방향 확인용 값 대입
            if self.map_data[my][mx] <= 1: # 우상
                chk = True # chk에 True 대입

        if di == self.DIR_DOWN: # 아래쪽일 경우
            mx = int(cx / 60) # mx와 my에 리스트의 좌하 방향 확인용 값 대입
            my = int((cy + 59 + dot) / 60)
            if self.map_data[my][mx] <= 1: # 좌하
                chk = True
            mx = int((cx + 59) / 60) # 리스트의 우하 방향 확인용 값 대입
            if self.map_data[my][mx] <= 1: # 우하
                chk = True

        if di == self.DIR_LEFT: # 왼쪽일 경우
            mx = int((cx - dot) / 60) # mx와 my에 리스트의 좌상 방향 확인용 값 대입
            my = int(cy / 60)
            if self.map_data[my][mx] <= 1: # 좌상
                chk = True
            my = int((cy + 59) / 60) # 리스트의 좌하 방향 확인용 값 대입
            if self.map_data[my][mx] <= 1: # 좌하
                chk = True

        if di == self.DIR_RIGHT: # 오른쪽일 경우
            mx = int((cx + 59 + dot) / 60) # mx와 my에 리스트의 우상 방향 확인용 값 대입
            my = int(cy / 60)
            if self.map_data[my][mx] <= 1: # 우상
                chk = True
            my = int((cy + 59) / 60) # 리스트의 우하 방향 확인용 값 대입
            if self.map_data[my][mx] <= 1: # 우하
                chk = True
        return chk # chk 값 반환

    def move_penpen(self): # 펜펜(캐릭터) 이동 함수
        if self.key == Qt.Key_Up: # 위쪽 방향키를 눌렀다면
            self.pen_d = self.DIR_UP # 펜펜의 방향을 위쪽으로
            if self.check_wall(self.pen_x, self.pen_y, self.pen_d, 20) == False: # 해당 방향이 벽이 아니라면
                self.pen_y = self.pen_y - 20 # 위쪽으로 이동
        if self.key == Qt.Key_Down: # 아래쪽
            self.pen_d = self.DIR_DOWN # 펜펜의 방향을 아래쪽으로
            if self.check_wall(self.pen_x, self.pen_y, self.pen_d, 20) == False:
                self.pen_y = self.pen_y + 20
        if self.key == Qt.Key_Left: # 왼쪽
            self.pen_d = self.DIR_LEFT # 펜펜의 방향을 왼쪽으로
            if self.check_wall(self.pen_x, self.pen_y, self.pen_d, 20) == False:
                self.pen_x = self.pen_x - 20
        if self.key == Qt.Key_Right: # 오른쪽
            self.pen_d = self.DIR_RIGHT # 펜펜의 방향을 오른쪽으로
            if self.check_wall(self.pen_x, self.pen_y, self.pen_d, 20) == False:
                self.pen_x = self.pen_x + 20
        self.pen_a = self.pen_d * 3 + self.ANIMATION[self.tmr % 4] # 펜펜 애니메이션(이미지) 번호 계산
        mx = int(self.pen_x / 60) # mx, my에 펜펜이 있는 위치의 리스트로 확인할 값 대입
        my = int(self.pen_y / 60)
        if self.map_data[my][mx] == 3: # 사탕 위치에 들어가면
            self.score = self.score + 100 # 점수 추가
            self.map_data[my][mx] = 2 # 사탕 삭제
            self.candy = self.candy - 1 # 사탕 개수 감소

    def move_enemy(self): # 레드(적) 이동 함수
        speed = 10 # 레드의 이동 속도
        if self.red_x % 60 == 0 and self.red_y % 60 == 0: # 칸의 정확한 위치에 있는 경우
            self.red_d = random.randint(0, 6) # 무작위로 방향 변경
            if self.red_d >= 4: # 난수가 4 이상인 경우 주인공 추적
                if self.pen_y < self.red_y: # 펜펜이 위쪽에 있다면
                    self.red_d = self.DIR_UP # 레드의 방향을 위쪽으로
                if self.pen_y > self.red_y:
                    self.red_d = self.DIR_DOWN
                if self.pen_x < self.red_x:
                    self.red_d = self.DIR_LEFT
                if self.pen_x > self.red_x:
                    self.red_d = self.DIR_RIGHT
        if self.red_d == self.DIR_UP: # 레드가 위쪽을 향한 경우
            if self.check_wall(self.red_x, self.red_y, self.red_d, speed) == False: # 해당 방향이 벽이 아니라면
                self.red_y = self.red_y - speed # 이동
        if self.red_d == self.DIR_DOWN:
            if self.check_wall(self.red_x, self.red_y, self.red_d, speed) == False:
                self.red_y = self.red_y + speed
        if self.red_d == self.DIR_LEFT:
            if self.check_wall(self.red_x, self.red_y, self.red_d, speed) == False:
                self.red_x = self.red_x - speed
        if self.red_d == self.DIR_RIGHT:
            if self.check_wall(self.red_x, self.red_y, self.red_d, speed) == False:
                self.red_x = self.red_x + speed
        self.red_a = self.red_d * 3 + self.ANIMATION[self.tmr % 4] # 레드의 애니메이션 번호 계산
        if abs(self.red_x - self.pen_x) <= 40 and abs(self.red_y - self.pen_y) <= 40: # 펜펜과 접촉했는지 판단해서 접촉했다면
            self.idx = 2 # 게임 오버 처리로 이동
            self.tmr = 0

    def main_proc(self):
        self.tmr = self.tmr + 1 # 타이머 값 증가
        if self.idx == 0: # 인덱스 0 처리(타이틀 화면)
            if self.key == Qt.Key_Space: # [SPACE]키를 누르면
                self.score = 0 # 점수 0 대입
                self.set_stage() # 스테이지 데이터 세트
                self.set_chara_pos() # 각 캐릭터 시작 위치 설정
                self.idx = 1 # 인덱스를 1로 변경, 게임 시작
        if self.idx == 1: # 인덱스 1 처리(게임 플레이)
            self.move_penpen() # 펜펜 이동
            self.move_enemy() # 레드 이동
            if self.candy == 0: # 사탕을 모두 먹었다면
                self.idx = 4 # 스테이지 클리어 처리로 이동
                self.tmr = 0
        if self.idx == 2: # 인덱스 2 처리(게임 오버)
            if self.tmr == 50:
                self.idx = 0 # 타이틀 화면으로 이동
        if self.idx == 4: # 인덱스 4 처리(스테이지 클리어)
            if self.tmr == 50:
                self.idx = 0 # 타이틀 화면으로 이동

        self.update()

    def draw_text(self, qp, txt, x, y, siz, col): # 그림자를 포함한 문자열을 표시
        qp.setFont(QFont('Times New Roman', siz, QFont.Bold)) # 폰트 정의
        qp.setPen(Qt.black) # 문자열 그림자(2픽셀 어긋나게 검게 표시)
        qp.drawText(x + 2, y + 2, txt)
        qp.setPen(col) # 지정한 색으로 문자열 표시
        qp.drawText(x, y, txt)

    def paintEvent(self, e):
        qp = QPainter()
        qp.begin(self)
        
        for y in range(9):
            for x in range(12):
                qp.drawImage(QRect(x * 60, y * 60, 60, 60), self.img_bg[self.map_data[y][x]]) # 맵 칩으로 미로 그리기

        qp.drawImage(QRect(self.pen_x, self.pen_y, 60, 60), self.img_pen[self.pen_a]) # PenPen 표시
        qp.drawImage(QRect(self.red_x, self.red_y, 60, 60), self.img_red[self.red_a]) # 레드 표시
        self.draw_text(qp, 'SCORE ' + str(self.score), 100, 30, 30, Qt.white) # 점수 표시

        if self.idx == 0: # 타이틀 화면 표시
            qp.drawImage(QRect(35, 100, self.img_title.width(), self.img_title.height()), self.img_title)
            if self.tmr % 10 < 5: # 문자열이 깜빡거리는 처리
                self.draw_text(qp, 'Press SPACE !', 250, 380, 30, Qt.yellow)

        if self.idx == 2: # 게임 오버 문자 표시
            self.draw_text(qp, 'GAME OVER', 200, 270, 40, Qt.red)

        if self.idx == 4: # 스테이지 클리어 문자 표시
            self.draw_text(qp, 'STAGE CLEAR', 180, 270, 40, QColor(255, 170, 170))

        qp.end()
    
    def keyPressEvent(self, e):
        self.key = e.key()

    def keyReleaseEvent(self, e):
        self.key = None

if __name__ == "__main__":
    app = QApplication(sys.argv)
    main_W = WindowClass()
    main_W.show()
    sys.exit(app.exec_())
idx 값 처리
0 타이틀 화면
- 스페이스키를 누르면 각 변수에 초기값 대입, idx를 1로 변경
1 게임 플레이 중 화면
- 펜펜 이동, 레드 이동 수행
- 레드 처리 중 펜펜과 접촉했다면 idx를 2로 변경
- 사탕을 모두 주우면 idx를 4로 변경
2 게임 오버 화면
- 약 5초간 대기한 뒤 idx 0으로 이동
4 게임 클리어 화면
- 약 5초간 대기한 뒤 idx 0으로 이동

이제 레드가 펜펜을 추적할 수도 있게 되었다.

if self.red_x % 60 == 0 and self.red_y % 60 == 0: # 칸의 정확한 위치에 있는 경우
    self.red_d = random.randint(0, 6) # 무작위로 방향 변경
    if self.red_d >= 4: # 난수가 4 이상인 경우 주인공 추적
        if self.pen_y < self.red_y: # 펜펜이 위쪽에 있다면
            self.red_d = self.DIR_UP # 레드의 방향을 위쪽으로
        if self.pen_y > self.red_y:
            self.red_d = self.DIR_DOWN
        if self.pen_x < self.red_x:
            self.red_d = self.DIR_LEFT
        if self.pen_x > self.red_x:
            self.red_d = self.DIR_RIGHT

0, 1, 2, 3, 4, 5, 6 중에서 하나를 뽑는데 0, 1, 2, 3인 경우에는 상하좌우 방향으로 바꾸고 4, 5, 6인 경우에는 펜펜이 어디있는지 확인하고 펜펜이 있는 방향으로 향한다.

펭귄의 이미지는 60 * 60 픽셀이지만 접촉 판정 부분을 보면 40픽셀로 되어 있는 것을 볼 수 있는데 이렇게 한 이유는 60픽셀로 하면 눈으로 볼 때는 닿지 않았는데 닿았다고 판정하기 때문이다.
그렇기에 40으로 판정해서 유저가 납득할 수 있는 히트박스를 지정해야 한다.