Skip to content

Latest commit

 

History

History
1527 lines (1375 loc) · 73.3 KB

211231.md

File metadata and controls

1527 lines (1375 loc) · 73.3 KB

Day 85

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

Chapter 4

여러 스테이지 조합하기

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.stage = 1 # 스테이지 수
        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.red_sx = 0 # 레드 시작 위치 X 좌표
        self.red_sy = 0 # 레드 시작 위치 Y 좌표
        
        self.map_data = []

        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): # 스테이지 데이터 설정 함수
        if self.stage == 1:
            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 # 사탕 수
            self.red_sx = 600
            self.red_sy = 420

        if self.stage == 2:
            self.map_data = [
                [0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0],
                [0, 2, 2, 2, 3, 3, 3, 3, 2, 2, 2, 0],
                [0, 3, 3, 0, 2, 1, 1, 2, 0, 3, 3, 0],
                [0, 3, 3, 1, 3, 3, 3, 3, 1, 3, 3, 0],
                [0, 2, 1, 3, 3, 3, 3, 3, 3, 1, 2, 0],
                [0, 3, 3, 0, 3, 3, 3, 3, 0, 3, 3, 0],
                [0, 3, 3, 1, 2, 1, 1, 2, 1, 3, 3, 0],
                [0, 2, 2, 2, 3, 3, 3, 3, 2, 2, 2, 0],
                [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
            ]
            self.candy = 38
            self.red_sx = 600
            self.red_sy = 60
        
        if self.stage == 3:
            self.map_data = [
                [0, 1, 0, 1, 0, 1, 1, 1, 1, 1, 1, 0],
                [0, 2, 1, 3, 1, 2, 2, 3, 3, 3, 3, 0],
                [0, 2, 2, 2, 2, 2, 2, 3, 3, 3, 3, 0],
                [0, 2, 1, 1, 1, 2, 2, 1, 1, 1, 1, 0],
                [0, 2, 2, 2, 2, 3, 3, 2, 2, 2, 2, 0],
                [0, 1, 1, 2, 0, 2, 2, 0, 1, 1, 2, 0],
                [0, 3, 3, 3, 1, 1, 1, 0, 3, 3, 3, 0],
                [0, 3, 3, 3, 2, 2, 2, 0, 3, 3, 3, 0],
                [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
            ]
            self.candy = 23
            self.red_sx = 600
            self.red_sy = 420

    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 = self.red_sx # 레드의 시작 x, y 좌표 대입
        self.red_y = self.red_sy
        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.stage = 1
                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 == 30:
                if self.stage < 3:
                    self.stage = self.stage + 1
                    self.set_stage()
                    self.set_chara_pos()
                    self.idx = 1
                else:
                    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) # 점수 표시
        self.draw_text(qp, 'STAGE ' + str(self.stage), 440, 30, 30, QColor(191, 255, 0)) # 스테이지 수 표시

        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: # 스테이지 클리어 문자 표시
            if self.stage < 3:
                self.draw_text(qp, 'STAGE CLEAR', 180, 270, 40, QColor(255, 170, 170))
            else:
                self.draw_text(qp, 'ALL STAGE CLEAR!', 80, 270, 40, QColor(210, 168, 212))

        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_())

스테이지의 모든 사탕을 주우면 다음 스테이지로 넘어간다. 최종 스테이지인 3 스테이지를 클리어하면 타이틀 화면으로 돌아간다.

set_stage 함수는 stage 값에 따라 map_data에 미로 데이터를 대입한다. 추가로 게임 시작시 레드의 좌표를 red_sx, red_sy에 대입한다.

캐릭터의 남은 수명 적용하기

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'),
            QImage('Python_workspace\python_game\image\dot_eat\pen_face.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.stage = 1 # 스테이지 수
        self.score = 0 # 점수
        self.candy = 0 # 각 스테이지에 있는 사탕 수
        self.life = 3 # 펜펜의 목숨 수

        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.red_sx = 0 # 레드 시작 위치 X 좌표
        self.red_sy = 0 # 레드 시작 위치 Y 좌표
        
        self.map_data = []

        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): # 스테이지 데이터 설정 함수
        if self.stage == 1:
            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 # 사탕 수
            self.red_sx = 600
            self.red_sy = 420

        if self.stage == 2:
            self.map_data = [
                [0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0],
                [0, 2, 2, 2, 3, 3, 3, 3, 2, 2, 2, 0],
                [0, 3, 3, 0, 2, 1, 1, 2, 0, 3, 3, 0],
                [0, 3, 3, 1, 3, 3, 3, 3, 1, 3, 3, 0],
                [0, 2, 1, 3, 3, 3, 3, 3, 3, 1, 2, 0],
                [0, 3, 3, 0, 3, 3, 3, 3, 0, 3, 3, 0],
                [0, 3, 3, 1, 2, 1, 1, 2, 1, 3, 3, 0],
                [0, 2, 2, 2, 3, 3, 3, 3, 2, 2, 2, 0],
                [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
            ]
            self.candy = 38
            self.red_sx = 600
            self.red_sy = 60
        
        if self.stage == 3:
            self.map_data = [
                [0, 1, 0, 1, 0, 1, 1, 1, 1, 1, 1, 0],
                [0, 2, 1, 3, 1, 2, 2, 3, 3, 3, 3, 0],
                [0, 2, 2, 2, 2, 2, 2, 3, 3, 3, 3, 0],
                [0, 2, 1, 1, 1, 2, 2, 1, 1, 1, 1, 0],
                [0, 2, 2, 2, 2, 3, 3, 2, 2, 2, 2, 0],
                [0, 1, 1, 2, 0, 2, 2, 0, 1, 1, 2, 0],
                [0, 3, 3, 3, 1, 1, 1, 0, 3, 3, 3, 0],
                [0, 3, 3, 3, 2, 2, 2, 0, 3, 3, 3, 0],
                [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
            ]
            self.candy = 23
            self.red_sx = 600
            self.red_sy = 420

    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 = self.red_sx # 레드의 시작 x, y 좌표 대입
        self.red_y = self.red_sy
        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.stage = 1
                self.score = 0 # 점수 0 대입
                self.life = 3 # 남은 목숨 3 대입
                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 == 1:
                self.life = self.life - 1 # 잔여 수 1 감소
            if self.tmr == 30:
                if self.life == 0: # 잔여 수가 0이면
                    self.idx = 3 # 게임 오버 처리로 이동
                    self.tmr = 0
                else: # 그렇지 않으면
                    self.set_chara_pos() # 캐릭터를 초기 위치로 이동
                    self.idx = 1 # 다시 플레이
        if self.idx == 3: # 인덱스 3 처리(게임 오버)
            if self.tmr == 50:
                self.idx = 0 # 타이틀 화면으로 이동
        if self.idx == 4: # 인덱스 4 처리(스테이지 클리어)
            if self.tmr == 30:
                if self.stage < 3:
                    self.stage = self.stage + 1
                    self.set_stage()
                    self.set_chara_pos()
                    self.idx = 1
                else:
                    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) # 점수 표시
        self.draw_text(qp, 'STAGE ' + str(self.stage), 440, 30, 30, QColor(191, 255, 0)) # 스테이지 수 표시

        for i in range(self.life): # 펜펜의 남은 목숨 수만큼 반복
            qp.drawImage(QRect(60 + i * 50, 500, self.img_pen[12].width(), self.img_pen[12].height()), self.img_pen[12]) # 펜펜의 남은 목숨 표시

        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, 'MISS', 295, 270, 40, Qt.red)

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

        if self.idx == 4: # 스테이지 클리어 문자 표시
            if self.stage < 3:
                self.draw_text(qp, 'STAGE CLEAR', 180, 270, 40, QColor(255, 170, 170))
            else:
                self.draw_text(qp, 'ALL STAGE CLEAR!', 80, 270, 40, QColor(210, 168, 212))

        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_())

life라는 변수로 펜펜의 남은 목숨 수를 관리한다. 만약 레드에게 닿았다면 idx를 2로 변경한다.

idx 2에서는 life를 감소시키고, 0이되면 게임 오버인 idx 3으로 이동 시키고, 남은 목숨이 있다면 set_chara_pos 함수를 통해 캐릭터 좌표를 시작 위치로 되돌리고 게임을 다시 시작한다.

새로운 적 등장시키기

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'),
            QImage('Python_workspace\python_game\image\dot_eat\pen_face.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.img_kuma = [
            QImage('Python_workspace\python_game\image\dot_eat\kuma00.png'),
            QImage('Python_workspace\python_game\image\dot_eat\kuma01.png'),
            QImage('Python_workspace\python_game\image\dot_eat\kuma02.png')
        ]
        self.pen_face_width = self.img_pen[12].width()
        self.pen_face_height = self.img_pen[12].height()
        self.kuma_width = self.img_kuma[0].width()
        self.kuma_height = self.img_kuma[0].height()
        
        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.stage = 1 # 스테이지 수
        self.score = 0 # 점수
        self.candy = 0 # 각 스테이지에 있는 사탕 수
        self.life = 3 # 펜펜의 목숨 수

        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.red_sx = 0 # 레드 시작 위치 X 좌표
        self.red_sy = 0 # 레드 시작 위치 Y 좌표

        self.kuma_x = 0 # 쿠마의 X, Y 좌표
        self.kuma_y = 0
        self.kuma_d = 0 # 쿠마의 방향
        self.kuma_a = 0 # 쿠마의 이미지 번호
        self.kuma_sx = 0 # 쿠마의 시작 X, Y 좌표
        self.kuma_sy = 0
        self.kuma_sd = 0 # 시작 시 쿠마의 방향
        
        self.map_data = []

        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): # 스테이지 데이터 설정 함수
        if self.stage == 1:
            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 # 사탕 수
            self.red_sx = 600
            self.red_sy = 420
            self.kuma_sd = -1 # 쿠마가 출현하지 않음

        if self.stage == 2:
            self.map_data = [
                [0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0],
                [0, 2, 2, 2, 3, 3, 3, 3, 2, 2, 2, 0],
                [0, 3, 3, 0, 2, 1, 1, 2, 0, 3, 3, 0],
                [0, 3, 3, 1, 3, 3, 3, 3, 1, 3, 3, 0],
                [0, 2, 1, 3, 3, 3, 3, 3, 3, 1, 2, 0],
                [0, 3, 3, 0, 3, 3, 3, 3, 0, 3, 3, 0],
                [0, 3, 3, 1, 2, 1, 1, 2, 1, 3, 3, 0],
                [0, 2, 2, 2, 3, 3, 3, 3, 2, 2, 2, 0],
                [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
            ]
            self.candy = 38
            self.red_sx = 600
            self.red_sy = 60
            self.kuma_sx = 300
            self.kuma_sy = 240
            self.kuma_sd = self.DIR_LEFT
        
        if self.stage == 3:
            self.map_data = [
                [0, 1, 0, 1, 0, 1, 1, 1, 1, 1, 1, 0],
                [0, 2, 1, 3, 1, 2, 2, 3, 3, 3, 3, 0],
                [0, 2, 2, 2, 2, 2, 2, 3, 3, 3, 3, 0],
                [0, 2, 1, 1, 1, 2, 2, 1, 1, 1, 1, 0],
                [0, 2, 2, 2, 2, 3, 3, 2, 2, 2, 2, 0],
                [0, 1, 1, 2, 0, 2, 2, 0, 1, 1, 2, 0],
                [0, 3, 3, 3, 1, 1, 1, 0, 3, 3, 3, 0],
                [0, 3, 3, 3, 2, 2, 2, 0, 3, 3, 3, 0],
                [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
            ]
            self.candy = 23
            self.red_sx = 600
            self.red_sy = 420
            self.kuma_sx = 300
            self.kuma_sy = 240
            self.kuma_sd = self.DIR_RIGHT

    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 = self.red_sx # 레드의 시작 x, y 좌표 대입
        self.red_y = self.red_sy
        self.red_d = self.DIR_DOWN # 레드의 방향을 아래로
        self.red_a = 3 # 레드 그림 번호 대입

        self.kuma_x = self.kuma_sx
        self.kuma_y = self.kuma_sy
        self.kuma_d = self.kuma_sd
        self.kuma_a = 0

    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 move_enemy2(self): # 쿠마 이동 함수
        speed = 5
        if self.kuma_sd == -1: # kuma_sd 값이 -1이면 함수 이탈
            return
        if self.kuma_d == self.DIR_UP: # 쿠마가 위쪽을 향한 경우
            if self.check_wall(self.kuma_x, self.kuma_y, self.kuma_d, speed) == False: # 해당 방향이 벽이 아니라면
                self.kuma_y = self.kuma_y - speed # 이동
            else: # 벽이라면
                self.kuma_d = self.DIR_DOWN # 아래쪽으로 방향 변경
        elif self.kuma_d == self.DIR_DOWN:
            if self.check_wall(self.kuma_x, self.kuma_y, self.kuma_d, speed) == False:
                self.kuma_y = self.kuma_y + speed
            else:
                self.kuma_d = self.DIR_UP
        elif self.kuma_d == self.DIR_LEFT:
            if self.check_wall(self.kuma_x, self.kuma_y, self.kuma_d, speed) == False:
                self.kuma_x = self.kuma_x - speed
            else:
                self.kuma_d = self.DIR_RIGHT
        elif self.kuma_d == self.DIR_RIGHT:
            if self.check_wall(self.kuma_x, self.kuma_y, self.kuma_d, speed) == False:
                self.kuma_x = self.kuma_x + speed
            else:
                self.kuma_d = self.DIR_LEFT
        self.kuma_a = self.ANIMATION[self.tmr % 4] # 레드의 애니메이션 번호 계산
        if abs(self.kuma_x - self.pen_x) <= 40 and abs(self.kuma_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.stage = 1
                self.score = 0 # 점수 0 대입
                self.life = 3 # 남은 목숨 3 대입
                self.set_stage() # 스테이지 데이터 세트
                self.set_chara_pos() # 각 캐릭터 시작 위치 설정
                self.idx = 1 # 인덱스를 1로 변경, 게임 시작
        if self.idx == 1: # 인덱스 1 처리(게임 플레이)
            self.move_penpen() # 펜펜 이동
            self.move_enemy() # 레드 이동
            self.move_enemy2()
            if self.candy == 0: # 사탕을 모두 먹었다면
                self.idx = 4 # 스테이지 클리어 처리로 이동
                self.tmr = 0
        if self.idx == 2: # 인덱스 2 처리(적에게 당한 처리)
            if self.tmr == 1:
                self.life = self.life - 1 # 잔여 수 1 감소
            if self.tmr == 30:
                if self.life == 0: # 잔여 수가 0이면
                    self.idx = 3 # 게임 오버 처리로 이동
                    self.tmr = 0
                else: # 그렇지 않으면
                    self.set_chara_pos() # 캐릭터를 초기 위치로 이동
                    self.idx = 1 # 다시 플레이
        if self.idx == 3: # 인덱스 3 처리(게임 오버)
            if self.tmr == 50:
                self.idx = 0 # 타이틀 화면으로 이동
        if self.idx == 4: # 인덱스 4 처리(스테이지 클리어)
            if self.tmr == 30:
                if self.stage < 3:
                    self.stage = self.stage + 1
                    self.set_stage()
                    self.set_chara_pos()
                    self.idx = 1
                else:
                    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]) # 레드 표시
        if self.kuma_sd != -1: # kuma_sd 값이 -1이 아니면 쿠마 표시
            qp.drawImage(QRect(self.kuma_x, self.kuma_y, self.kuma_width, self.kuma_height), self.img_kuma[self.kuma_a])
        self.draw_text(qp, 'SCORE ' + str(self.score), 100, 30, 30, Qt.white) # 점수 표시
        self.draw_text(qp, 'STAGE ' + str(self.stage), 440, 30, 30, QColor(191, 255, 0)) # 스테이지 수 표시

        for i in range(self.life): # 펜펜의 남은 목숨 수만큼 반복
            qp.drawImage(QRect(60 + i * 50, 500, self.pen_face_width, self.pen_face_height), self.img_pen[12]) # 펜펜의 남은 목숨 표시

        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, 'MISS', 295, 270, 40, Qt.red)

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

        if self.idx == 4: # 스테이지 클리어 문자 표시
            if self.stage < 3:
                self.draw_text(qp, 'STAGE CLEAR', 180, 270, 40, QColor(255, 170, 170))
            else:
                self.draw_text(qp, 'ALL STAGE CLEAR!', 80, 270, 40, QColor(210, 168, 212))

        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_())

변경 및 추가된 부분 : move_enemy2, paintEvent, set_chara_pos, set_stage

스테이지 2부터 쿠마가 나타나서 좌우로 왕복을 계속한다. move_enemy2가 쿠마의 이동 함수인데 보면 벽에 부딪히면 kuma_d에 반대 방향의 값을 대입해 같은 경로를 왕복하게 한다.

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'),
            QImage('Python_workspace\python_game\image\dot_eat\pen_face.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.img_kuma = [
            QImage('Python_workspace\python_game\image\dot_eat\kuma00.png'),
            QImage('Python_workspace\python_game\image\dot_eat\kuma01.png'),
            QImage('Python_workspace\python_game\image\dot_eat\kuma02.png')
        ]
        self.img_ending = QImage('Python_workspace\python_game\image\dot_eat\ending.png')
        self.pen_face_width = self.img_pen[12].width()
        self.pen_face_height = self.img_pen[12].height()
        self.kuma_width = self.img_kuma[0].width()
        self.kuma_height = self.img_kuma[0].height()
        
        self.DIR_UP = 0 # 캐릭터 방향 정의 변수(위쪽)
        self.DIR_DOWN = 1 # 캐릭터 방향 정의 변수(아래쪽)
        self.DIR_LEFT = 2 # 캐릭터 방향 정의 변수(왼쪽)
        self.DIR_RIGHT = 3 # 캐릭터 방향 정의 변수(오른쪽)
        self.ANIMATION = [0, 1, 0, 2]
        self.BLINK = ['#fff', '#ffc', '#ff8', '#fe4', '#ff8', '#ffc']

        self.idx = 0
        self.tmr = 0
        self.stage = 1 # 스테이지 수
        self.score = 0 # 점수
        self.candy = 0 # 각 스테이지에 있는 사탕 수
        self.life = 3 # 펜펜의 목숨 수

        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.red_sx = 0 # 레드 시작 위치 X 좌표
        self.red_sy = 0 # 레드 시작 위치 Y 좌표

        self.kuma_x = 0 # 쿠마의 X, Y 좌표
        self.kuma_y = 0
        self.kuma_d = 0 # 쿠마의 방향
        self.kuma_a = 0 # 쿠마의 이미지 번호
        self.kuma_sx = 0 # 쿠마의 시작 X, Y 좌표
        self.kuma_sy = 0
        self.kuma_sd = 0 # 시작 시 쿠마의 방향
        
        self.map_data = []

        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): # 스테이지 데이터 설정 함수
        if self.stage == 1:
            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 # 사탕 수
            self.red_sx = 600
            self.red_sy = 420
            self.kuma_sd = -1 # 쿠마가 출현하지 않음

        if self.stage == 2:
            self.map_data = [
                [0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0],
                [0, 2, 2, 2, 3, 3, 3, 3, 2, 2, 2, 0],
                [0, 3, 3, 0, 2, 1, 1, 2, 0, 3, 3, 0],
                [0, 3, 3, 1, 3, 3, 3, 3, 1, 3, 3, 0],
                [0, 2, 1, 3, 3, 3, 3, 3, 3, 1, 2, 0],
                [0, 3, 3, 0, 3, 3, 3, 3, 0, 3, 3, 0],
                [0, 3, 3, 1, 2, 1, 1, 2, 1, 3, 3, 0],
                [0, 2, 2, 2, 3, 3, 3, 3, 2, 2, 2, 0],
                [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
            ]
            self.candy = 38
            self.red_sx = 600
            self.red_sy = 60
            self.kuma_sx = 300
            self.kuma_sy = 240
            self.kuma_sd = self.DIR_LEFT
        
        if self.stage == 3:
            self.map_data = [
                [0, 1, 0, 1, 0, 1, 1, 1, 1, 1, 1, 0],
                [0, 2, 1, 3, 1, 2, 2, 3, 3, 3, 3, 0],
                [0, 2, 2, 2, 2, 2, 2, 3, 3, 3, 3, 0],
                [0, 2, 1, 1, 1, 2, 2, 1, 1, 1, 1, 0],
                [0, 2, 2, 2, 2, 3, 3, 2, 2, 2, 2, 0],
                [0, 1, 1, 2, 0, 2, 2, 0, 1, 1, 2, 0],
                [0, 3, 3, 3, 1, 1, 1, 0, 3, 3, 3, 0],
                [0, 3, 3, 3, 2, 2, 2, 0, 3, 3, 3, 0],
                [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
            ]
            self.candy = 23
            self.red_sx = 600
            self.red_sy = 420
            self.kuma_sx = 300
            self.kuma_sy = 240
            self.kuma_sd = self.DIR_RIGHT

        if self.stage == 4:
            self.map_data = [
                [0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0],
                [0, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 0],
                [0, 3, 0, 3, 3, 1, 3, 0, 3, 0, 3, 0],
                [0, 3, 1, 0, 3, 3, 3, 0, 3, 1, 3, 0],
                [0, 3, 3, 0, 1, 1, 1, 0, 3, 3, 3, 0],
                [0, 3, 0, 1, 3, 3, 3, 1, 3, 1, 1, 0],
                [0, 3, 1, 3, 3, 1, 3, 3, 3, 3, 3, 0],
                [0, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 0],
                [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ,0]
            ]
            self.candy = 50
            self.red_sx = 120
            self.red_sy = 240
            self.kuma_sx = 480
            self.kuma_sy = 240
            self.kuma_sd = self.DIR_UP

        if self.stage == 5:
            self.map_data = [
                [0, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0],
                [0, 2, 0, 3, 3, 3, 3, 3, 3, 3, 3, 0],
                [0, 2, 0, 3, 0, 1, 3, 3, 1, 0, 3, 0],
                [0, 2, 0, 3, 0, 3, 3, 3, 3, 0, 3, 0],
                [0, 2, 1, 3, 1, 1, 3, 3, 1, 1, 3, 0],
                [0, 2, 2, 3, 3, 3, 3, 3, 3, 3, 3, 0],
                [0, 2, 1, 1, 1, 1, 2, 1, 1, 1, 1, 0],
                [0, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 0],
                [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
            ]
            self.candy = 40
            self.red_sx = 600
            self.red_sy = 420
            self.kuma_sx = 360
            self.kuma_sy = 180
            self.kuma_sd = self.DIR_RIGHT

    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 = self.red_sx # 레드의 시작 x, y 좌표 대입
        self.red_y = self.red_sy
        self.red_d = self.DIR_DOWN # 레드의 방향을 아래로
        self.red_a = 3 # 레드 그림 번호 대입

        self.kuma_x = self.kuma_sx
        self.kuma_y = self.kuma_sy
        self.kuma_d = self.kuma_sd
        self.kuma_a = 0

    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 move_enemy2(self): # 쿠마 이동 함수
        speed = 5
        if self.kuma_sd == -1: # kuma_sd 값이 -1이면 함수 이탈
            return
        if self.kuma_d == self.DIR_UP: # 쿠마가 위쪽을 향한 경우
            if self.check_wall(self.kuma_x, self.kuma_y, self.kuma_d, speed) == False: # 해당 방향이 벽이 아니라면
                self.kuma_y = self.kuma_y - speed # 이동
            else: # 벽이라면
                self.kuma_d = self.DIR_DOWN # 아래쪽으로 방향 변경
        elif self.kuma_d == self.DIR_DOWN:
            if self.check_wall(self.kuma_x, self.kuma_y, self.kuma_d, speed) == False:
                self.kuma_y = self.kuma_y + speed
            else:
                self.kuma_d = self.DIR_UP
        elif self.kuma_d == self.DIR_LEFT:
            if self.check_wall(self.kuma_x, self.kuma_y, self.kuma_d, speed) == False:
                self.kuma_x = self.kuma_x - speed
            else:
                self.kuma_d = self.DIR_RIGHT
        elif self.kuma_d == self.DIR_RIGHT:
            if self.check_wall(self.kuma_x, self.kuma_y, self.kuma_d, speed) == False:
                self.kuma_x = self.kuma_x + speed
            else:
                self.kuma_d = self.DIR_LEFT
        self.kuma_a = self.ANIMATION[self.tmr % 4] # 레드의 애니메이션 번호 계산
        if abs(self.kuma_x - self.pen_x) <= 40 and abs(self.kuma_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.stage = 1
                self.score = 0 # 점수 0 대입
                self.life = 3 # 남은 목숨 3 대입
                self.set_stage() # 스테이지 데이터 세트
                self.set_chara_pos() # 각 캐릭터 시작 위치 설정
                self.idx = 1 # 인덱스를 1로 변경, 게임 시작
        if self.idx == 1: # 인덱스 1 처리(게임 플레이)
            self.move_penpen() # 펜펜 이동
            self.move_enemy() # 레드 이동
            self.move_enemy2()
            if self.candy == 0: # 사탕을 모두 먹었다면
                self.idx = 4 # 스테이지 클리어 처리로 이동
                self.tmr = 0
        if self.idx == 2: # 인덱스 2 처리(적에게 당한 처리)
            if self.tmr == 1:
                self.life = self.life - 1 # 잔여 수 1 감소
            if self.tmr == 30:
                if self.life == 0: # 잔여 수가 0이면
                    self.idx = 3 # 게임 오버 처리로 이동
                    self.tmr = 0
                else: # 그렇지 않으면
                    self.set_chara_pos() # 캐릭터를 초기 위치로 이동
                    self.idx = 1 # 다시 플레이
        if self.idx == 3: # 인덱스 3 처리(게임 오버)
            if self.tmr == 50:
                self.idx = 0 # 타이틀 화면으로 이동
        if self.idx == 4: # 인덱스 4 처리(스테이지 클리어)
            if self.tmr == 30:
                if self.stage < 5:
                    self.stage = self.stage + 1
                    self.set_stage()
                    self.set_chara_pos()
                    self.idx = 1
                else:
                    self.idx = 5 # 엔딩 처리로 이동
                    self.tmr = 0
        if self.idx == 5: # 엔딩 처리
            if self.tmr == 300:
                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]) # 레드 표시
        if self.kuma_sd != -1: # kuma_sd 값이 -1이 아니면 쿠마 표시
            qp.drawImage(QRect(self.kuma_x, self.kuma_y, self.kuma_width, self.kuma_height), self.img_kuma[self.kuma_a])
        self.draw_text(qp, 'SCORE ' + str(self.score), 100, 30, 30, Qt.white) # 점수 표시
        self.draw_text(qp, 'STAGE ' + str(self.stage), 440, 30, 30, QColor(191, 255, 0)) # 스테이지 수 표시

        for i in range(self.life): # 펜펜의 남은 목숨 수만큼 반복
            qp.drawImage(QRect(60 + i * 50, 500, self.pen_face_width, self.pen_face_height), self.img_pen[12]) # 펜펜의 남은 목숨 표시

        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, 'MISS', 295, 270, 40, Qt.red)

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

        if self.idx == 4: # 스테이지 클리어 문자 표시
            if self.stage < 5:
                self.draw_text(qp, 'STAGE CLEAR', 180, 270, 40, QColor(255, 170, 170))
            else:
                self.draw_text(qp, 'ALL STAGE CLEAR!', 80, 270, 40, QColor(210, 168, 212))

        if self.idx == 5: # 엔딩 표시
            if self.tmr < 60:
                xr = 8 * self.tmr # 타원의 반지름 계산
                yr = 6 * self.tmr
                qp.setPen(Qt.black)
                qp.setBrush(Qt.black)
                qp.drawEllipse(360 - xr, 270 - yr, xr * 2, yr * 2) # 점점 커지는 타원 그리기
            else:
                qp.setBrush(Qt.black)
                qp.drawRect(-1, -1, 721, 541) # 화면을 검은 색으로 칠함
                qp.drawImage(QRect(240, 220, 240, 160), self.img_ending) # 엔딩 화면 표시
                self.draw_text(qp, 'Congratulations!', 185, 160, 40, QColor(self.BLINK[self.tmr % 6])) # 반짝거리는 'Congratulations!' 문자 표시

        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_())

변경 및 추가된 부분 : 스테이지 추가, main_proc에 엔딩 처리 추가, paintEvent에 엔딩 표시 부분 추가