Skip to content

Latest commit

 

History

History
383 lines (311 loc) · 14.9 KB

220103.md

File metadata and controls

383 lines (311 loc) · 14.9 KB

Day 87

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

Chapter 4

맵 에디터 제작하기 1 추가 내용

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

def clickable(widget): # 클릭 이벤트가 없는 위젯에 클릭 이벤트를 추가해줌
    # clicked라는 시그널을 생성하고 이벤트 중 마우스 클릭 이벤트가 발생하면 해당 이벤트 발생 위치를 파악해서 그 위치가 오브젝트 안이라면 click signal을 emit함
    class Filter(QObject):
    
        clicked = pyqtSignal()
        
        def eventFilter(self, obj, event):
        
            if obj == widget:
                if event.type() == QEvent.MouseButtonRelease:
                    if obj.rect().contains(event.pos()):
                        self.clicked.emit()
                        return True
            
            return False
    
    filter = Filter(widget)
    widget.installEventFilter(filter)
    return filter.clicked

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

        self.chip = 0 # 선택한 맵 칩 번호를 대입할 변수
        self.map_data = [] # 미로 데이터를 대입할 리스트
        for i in range(9):
            self.map_data.append([2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2]) # 리스트 초기화

        self.img = [
            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.initUI()
        self.draw_map()
        self.draw_chip()

    def initUI(self):
        self.setFixedSize(820, 560)
        self.map_label = QLabel(self) # 맵을 표시할 라벨
        self.map_label.move(10, 10)
        self.map_label.resize(720, 540)

        self.chip_label = QLabel(self) # 칩을 표시할 라벨
        self.chip_label.move(740, 10)
        self.chip_label.resize(60, 540)
        self.setWindowTitle('Map editor')

        # clikcable(이벤트를 발생시킬 객체).connect(발생시킬 함수)
        # 파라미터를 보낼 떄는 lambda 함수 사용
        clickable(self.map_label).connect(self.set_map)
        clickable(self.chip_label).connect(self.select_chip)

    def mousePressEvent(self, e): # 마우스 클릭 이벤트
        self.mx = int(e.x() / 60) # 리스트 인덱스 계산
        self.my = int(e.y() / 60)

    def draw_map(self): # 맵 그리기
        pixmap = QPixmap(self.map_label.size()) # map_label 크기의 pixmap 생성
        qp = QPainter(pixmap) # 페인터 객체 생성
        for y in range(9):
            for x in range(12):
                qp.drawImage(QRect(60 * x, 60 * y, 60, 60), self.img[self.map_data[y][x]]) # 맵 데이터를 토대로 맵 칩으로 미로를 그림
        qp.end()
        self.map_label.setPixmap(pixmap) # 라벨에 pixmap에 그린 이미지를 설정

    def draw_chip(self): # 칩 선택창 그리기
        pixmap = QPixmap(self.chip_label.size()) # chip_label 크기의 pixmap 생성
        qp = QPainter(pixmap)
        for i in range(len(self.img)):
            qp.drawImage(QRect(0, i * 60, 60, 60), self.img[i]) # 맵 칩을 그림
        qp.setPen(QPen(Qt.red, 3))
        qp.drawRect(1, 1 + 60 * self.chip, 57, 57) # 선택한 칩에 테두리 표시
        qp.end()
        self.chip_label.setPixmap(pixmap)

    def set_map(self): # 미로 내 칩 배치 함수
        if 0 <= self.mx and self.mx <= 11 and 0 <= self.my and self.my <= 8: # 클릭한 위치가 미로 범위라면
            self.map_data[self.my][self.mx] = self.chip
            self.draw_map()

    def select_chip(self):
        if 0 <= self.my and self.my < len(self.img):
            self.chip = self.my
            self.draw_chip()

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

몇 가지 추가 내용을 찾다가 클릭 이벤트가 없는 위젯에 클릭 이벤트를 추가해주는 부분을 발견했다.

def clickable(widget): # 클릭 이벤트가 없는 위젯에 클릭 이벤트를 추가해줌
    # clicked라는 시그널을 생성하고 이벤트 중 마우스 클릭 이벤트가 발생하면 해당 이벤트 발생 위치를 파악해서 그 위치가 오브젝트 안이라면 click signal을 emit함
    class Filter(QObject):
    
        clicked = pyqtSignal()
        
        def eventFilter(self, obj, event):
        
            if obj == widget:
                if event.type() == QEvent.MouseButtonRelease:
                    if obj.rect().contains(event.pos()):
                        self.clicked.emit()
                        return True
            
            return False
    
    filter = Filter(widget)
    widget.installEventFilter(filter)
    return filter.clicked

이 클래스인데 우선 clicked라는 시그널을 생성하고 이벤트 중 마우스 클릭 이벤트가 발생하면 해당 이벤트 발생 위치를 파악해서 그 위치가 오브젝트 안이라면 click signal을 emit하는 클래스다.

# clikcable(이벤트를 발생시킬 객체).connect(발생시킬 함수)
# 파라미터를 보낼 떄는 lambda 함수 사용
clickable(self.map_label).connect(self.set_map)
clickable(self.chip_label).connect(self.select_chip)

사용 방법도 간단했다. 이 클래스를 이용하면 mousePressEvent에 if문이 들어가지 않아도 된다.

맵 에디터 제작하기 2

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

def clickable(widget): # 클릭 이벤트가 없는 위젯에 클릭 이벤트를 추가해줌
    # clicked라는 시그널을 생성하고 이벤트 중 마우스 클릭 이벤트가 발생하면 해당 이벤트 발생 위치를 파악해서 그 위치가 오브젝트 안이라면 click signal을 emit함
    class Filter(QObject):
    
        clicked = pyqtSignal()
        
        def eventFilter(self, obj, event):
        
            if obj == widget:
                if event.type() == QEvent.MouseButtonRelease:
                    if obj.rect().contains(event.pos()):
                        self.clicked.emit()
                        return True
            
            return False
    
    filter = Filter(widget)
    widget.installEventFilter(filter)
    return filter.clicked

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

        self.chip = 0 # 선택한 맵 칩 번호를 대입할 변수
        self.map_data = [] # 미로 데이터를 대입할 리스트
        for i in range(9):
            self.map_data.append([2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2]) # 리스트 초기화

        self.img = [
            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.initUI()
        self.draw_map()
        self.draw_chip()

    def initUI(self):
        self.setFixedSize(820, 760)
        self.map_label = QLabel(self) # 맵을 표시할 라벨
        self.map_label.move(10, 10)
        self.map_label.resize(720, 540)

        self.chip_label = QLabel(self) # 칩을 표시할 라벨
        self.chip_label.move(740, 10)
        self.chip_label.resize(60, 540)
        self.setWindowTitle('Map editor')

        self.print_btn = QPushButton('데이터 출력', self) # 데이터 출력 버튼
        self.print_btn.setFont(QFont('Times New Roman', 16))
        self.print_btn.move(400, 560)

        self.output_tb = QTextBrowser(self) # 데이터 출력 필드
        self.output_tb.move(10, 560)

        # clikcable(이벤트를 발생시킬 객체).connect(발생시킬 함수)
        # 파라미터를 보낼 떄는 lambda 함수 사용
        clickable(self.map_label).connect(self.set_map)
        clickable(self.chip_label).connect(self.select_chip)

        self.print_btn.clicked.connect(self.put_data)

    def mousePressEvent(self, e): # 마우스 클릭 이벤트
        self.mx = int(e.x() / 60) # 리스트 인덱스 계산
        self.my = int(e.y() / 60)

    def draw_map(self): # 맵 그리기
        pixmap = QPixmap(self.map_label.size()) # map_label 크기의 pixmap 생성
        qp = QPainter(pixmap) # 페인터 객체 생성
        for y in range(9):
            for x in range(12):
                qp.drawImage(QRect(60 * x, 60 * y, 60, 60), self.img[self.map_data[y][x]]) # 맵 데이터를 토대로 맵 칩으로 미로를 그림
        qp.end()
        self.map_label.setPixmap(pixmap) # 라벨에 pixmap에 그린 이미지를 설정

    def draw_chip(self): # 칩 선택창 그리기
        pixmap = QPixmap(self.chip_label.size()) # chip_label 크기의 pixmap 생성
        qp = QPainter(pixmap)
        for i in range(len(self.img)):
            qp.drawImage(QRect(0, i * 60, 60, 60), self.img[i]) # 맵 칩을 그림
        qp.setPen(QPen(Qt.red, 3))
        qp.drawRect(1, 1 + 60 * self.chip, 57, 57) # 선택한 칩에 테두리 표시
        qp.end()
        self.chip_label.setPixmap(pixmap)

    def set_map(self): # 미로 내 칩 배치 함수
        if 0 <= self.mx and self.mx <= 11 and 0 <= self.my and self.my <= 8: # 클릭한 위치가 미로 범위라면
            self.map_data[self.my][self.mx] = self.chip
            self.draw_map()

    def select_chip(self): # 맵 칩 선택 함수
        if 0 <= self.my and self.my < len(self.img):
            self.chip = self.my
            self.draw_chip()

    def put_data(self): # 데이터를 출력하는 함수
        c = 0 # 사탕 수를 세는 변수
        self.output_tb.clear() # 텍스트 출력 부분의 문자 모두 삭제
        for y in range(9):
            for x in range(12):
                self.output_tb.insertPlainText(str(self.map_data[y][x]) + ',') # 출력 부분에 데이터 삽입
                if self.map_data[y][x] == 3: # 사탕 수 계산
                    c = c + 1
            self.output_tb.insertPlainText('\n') # 줄바꿈
        self.output_tb.insertPlainText('candy = ' + str(c)) # 사탕 수 삽입

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

추가 및 변경된 부분 : 데이터 출력 버튼 및 출력 필드 추가, put_data 함수 추가 및 버튼에 연결

맵 에디터로 맵을 만들고 데이터 출력 버튼을 클릭하면 맵 데이터가 출력된다.

Chapter 5

Pygame으로 이미지 그리기

import pygame
import sys

img_galaxy = pygame.image.load('Python_workspace\python_game\Chapter5\image\galaxy.png')
def main():
    pygame.init()
    pygame.display.set_caption('Pygame 사용법')
    screen = pygame.display.set_mode((960, 720))
    clock = pygame.time.Clock()

    while True:
        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                pygame.quit()
                sys.exit()
            if event.type == pygame.KEYDOWN:
                if event.key == pygame.K_F1:
                    screen = pygame.display.set_mode((960, 720), pygame.FULLSCREEN)
                if event.key == pygame.K_F2 or event.key == pygame.K_ESCAPE:
                    screen = pygame.display.set_mode((960, 720))
            
        screen.blit(img_galaxy, [0, 0])
        pygame.display.update()
        clock.tick(30)

if __name__ == '__main__':
    main()

F1을 누르면 전체 화면, F2나 ESC를 누르면 원래대로 변한다.
screen.blit(이미지 변수, [x 좌표][y 좌표])로 사용한다.

이미지 회전, 확대, 축소하기

import pygame
import sys

img_galaxy = pygame.image.load('Python_workspace\python_game\Chapter5\image\galaxy.png')
img_sship = pygame.image.load('Python_workspace\python_game\Chapter5\image\starship.png')

def main():
    pygame.init()
    pygame.display.set_caption('Pygame 사용법')
    screen = pygame.display.set_mode((960, 720))
    clock = pygame.time.Clock()
    ang = 0

    while True:
        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                pygame.quit()
                sys.exit()
            if event.type == pygame.KEYDOWN:
                if event.key == pygame.K_F1:
                    screen = pygame.display.set_mode((960, 720), pygame.FULLSCREEN)
                if event.key == pygame.K_F2 or event.key == pygame.K_ESCAPE:
                    screen = pygame.display.set_mode((960, 720))

        screen.blit(img_galaxy, [0, 0])
        
        ang = (ang + 1) % 360 # 회전 각도 증가
        img_rz = pygame.transform.rotozoom(img_sship, ang, 1.0) # 회전한 우주선 이미지 생성
        x = 480 - img_rz.get_width() / 2 # 표시할 x 좌표 계산
        y = 360 - img_rz.get_height() / 2 # 표시할 Y 좌표 계산
        screen.blit(img_rz, [x, y])

        pygame.display.update()
        clock.tick(30)

if __name__ == '__main__':
    main()

회전인 rotate와 확대/축소인 scale은 표시 속도를 우선하기 때문에 확대/축소 혹은 회전 후 이미지가 거칠게 보일 수 있는데 이는 rotozomm을 사용하면 이미지를 부드럽게 표시할 수 있다.

rotozoom(이미지, 회전각, 확대비율)로 사용한다.

표시할 x와 y 좌표를 계산할 때 윈도우의 중심 좌표 (480, 360)을 사용한다. 회전시킨 이미지의 폭의 절반을 480에서 뺀 값을 x, 높이의 절반을 360에서 뺀 값을 y 좌표로 지정했기 때문에 항상 화면 중앙에 표시된다.

동시에 키 입력하기

import pygame
import sys

WHITE = (255, 255, 255)
BLACK = (0, 0, 0)
BROWN = (192, 0, 0)
GREEN = (0, 128, 0)
BLUE = (0, 0, 255)

def main():
    pygame.init()
    pygame.display.set_caption("Pygame 사용법")
    screen = pygame.display.set_mode((960, 720))
    clock = pygame.time.Clock()
    font = pygame.font.Font(None, 80)

    while True:
        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                pygame.quit()
                sys.exit()

        screen.fill(BLACK)

        key = pygame.key.get_pressed()
        txt1 = font.render("UP{}  DOWN{}".format(key[pygame.K_UP], key[pygame.K_DOWN]), True, WHITE, GREEN)
        txt2 = font.render("LEFT{}  RIGHT{}".format(key[pygame.K_LEFT], key[pygame.K_RIGHT]), True, WHITE, BLUE)
        txt3 = font.render("SPACE{}  Z{}".format(key[pygame.K_SPACE], key[pygame.K_z]), True, WHITE, BROWN)
        screen.blit(txt1, [200, 100])
        screen.blit(txt2, [200, 300])
        screen.blit(txt3, [200, 500])

        pygame.display.update()
        clock.tick(10)

if __name__ == '__main__':
    main()

key = pygame.key.get_pressed()로 키 상태를 key에 대입한다. 키를 누른 경우에는 key[pygame.키보드 상수] 값이 1이 된다.