Skip to content

Latest commit

 

History

History
467 lines (378 loc) · 16.4 KB

220111.md

File metadata and controls

467 lines (378 loc) · 16.4 KB

Day 92

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

Chapter 9

유사 3D로 도로 그리기 pt. 1

멀리 있을수록 폭이 짧아지는 판 표시

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

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

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

    def initUI(self):
        self.setFixedSize(800, 600) # 창 크기 고정
        self.setWindowTitle('Draw road')
        self.setStyleSheet('background:blue')

    def initattr(self):
        self.BORD_COL = [Qt.white, QColor(192, 208, 224), Qt.gray]

    def paintEvent(self, e):
        qp = QPainter()
        qp.begin(self)
        qp.setBrush(Qt.darkGreen)
        qp.drawRect(0, 300, 800, 600)

        for i in range(1, 25):
            w = i * 33                  # 판의 폭을 변수 w에 대입
            h = 12                      # 판의 높이를 변수 h에 대입
            x = 400 - w / 2             # 판을 그릴 X 좌표를 변수 x에 대입
            y = 288 + i * h             # 판을 그릴 Y 좌표를 변수 y에 대입
            col = self.BORD_COL[i % 3]  # 판의 색을 변수 col에 대입
            qp.setBrush(col)
            qp.drawRect(x, y, w, h)     # (x, y) 위치에 폭 w, 높이 h인 판 그림

        qp.end()

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

멀리 있는 물체의 폭이 짧아졌지만 3차원 공간에서는 높이도 짧아져야만 한다.

높이 변화시키기

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

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

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

    def initUI(self):
        self.setFixedSize(800, 600) # 창 크기 고정
        self.setWindowTitle('Draw road')
        self.setStyleSheet('background:blue')

    def initattr(self):
        self.BORD_COL = [Qt.white, QColor(192, 208, 224), Qt.gray]

    def paintEvent(self, e):
        qp = QPainter()
        qp.begin(self)
        qp.setBrush(Qt.darkGreen)
        qp.drawRect(0, 300, 800, 600)

        h = 2   # 첫 번째 판의 높이를 변수 h에 대입
        y = 300 # 첫 번째 판의 Y 좌표를 변수 y에 대입

        for i in range(1, 24):
            w = i * i * 1.5             # 판의 폭을 계산해서 w에 대입
            x = 400 - w / 2
            col = self.BORD_COL[i % 3]
            qp.setBrush(col)
            qp.drawRect(x, y, w, h)
            y = y + h                   # 다음 판의 Y 좌표를 계산해서 y에 대입
            h = h + 1                   # 다음 판의 높이를 계산해서 h에 대입

        qp.end()

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

이전 것과 비교해보면 이번 것은 화면에서 깊이가 느껴지는 것을 알 수 있다.

반복 시 변수 i를 제곱해서 폭 값을 계산하여 i * i는 훨씬 큰 값이 되어 가까운 판일수록 폭이 점점 커진다.

유사 3D로 도로 그리기 pt. 2

drawPolygon()을 사용하면 다각형을 그릴 수 있다.

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

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

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

    def initUI(self):
        self.setFixedSize(800, 600) # 창 크기 고정
        self.setWindowTitle('Draw road')
        self.setStyleSheet('background:blue')

    def initattr(self):
        self.BORD_COL = [Qt.white, QColor(192, 208, 224), Qt.gray]

    def paintEvent(self, e):
        qp = QPainter()
        qp.begin(self)
        qp.setBrush(Qt.darkGreen)
        qp.drawRect(0, 300, 800, 600)

        h = 2
        y = 300

        for i in range(1, 24):
            uw = i * i * 1.5                 # 판의 위쪽 폭을 계산해서 uw에 대입
            ux = 400 - uw / 2                # 판의 위쪽 X좌표를 계산해서 ux에 대입
            bw = (i + 1) * (i + 1) * 1.5     # 판의 아래쪽 폭을 계산해서 bw에 대입
            bx = 400 - bw / 2                # 판의 아래쪽 X좌표를 계산해서 bx에 대입
            col = self.BORD_COL[i % 3]
            qp.setBrush(col)
            points = [                       # 다각형을 그릴 때 사용할 좌표
                QPoint(ux, y),
                QPoint(ux + uw, y),
                QPoint(bx + bw, y + h),
                QPoint(bx, y + h)
            ]
            qp.drawPolygon(QPolygon(points)) # 주어진 좌표들로 다각형을 그림
            y = y + h
            h = h + 1

        qp.end()

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

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

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

    def initUI(self):
        self.setFixedSize(800, 600) # 창 크기 고정
        self.setWindowTitle('Draw road')
        self.setStyleSheet('background:blue')

    def initattr(self):
        self.BORD_COL = [Qt.white, QColor(192, 208, 224), Qt.gray]
        self.key = None

    def paintEvent(self, e):
        qp = QPainter()
        qp.begin(self)
        qp.setBrush(Qt.darkGreen)
        qp.drawRect(0, 300, 800, 600)
        self.draw_text(qp, '위쪽, 왼쪽, 오른쪽 방향키를 눌러 주십시오')

        if self.key == Qt.Key_Up:
            self.draw_road(qp, 0)
        if self.key == Qt.Key_Left:
            self.draw_road(qp, -10)
        if self.key == Qt.Key_Right:
            self.draw_road(qp, 10)
        

        qp.end()
    
    def draw_road(self, qp, di):
        h = 24
        y = 600 - h

        for i in range(23, 0, -1):
            uw = (i - 1) * (i - 1) * 1.5
            ux = 400 - uw / 2 + di * (23 - (i - 1)) # 판의 위쪽 X 좌표를 계산해서 ux에 대입
            bw = i * i * 1.5
            bx = 400 - bw / 2 + di * (23 - i)       # 판의 아래쪽 X 좌표를 계산해서 bx에 대입
            col = self.BORD_COL[i % 3]
            qp.setBrush(col)
            points = [
                QPoint(ux, y),
                QPoint(ux + uw, y),
                QPoint(bx + bw, y + h),
                QPoint(bx, y + h)
            ]
            qp.drawPolygon(QPolygon(points))
            h = h - 1                               # 다음 판의 높이를 계산해서 h에 대입
            y = y - h                               # 다음 판의 Y 좌표를 계산해서 y에 대입

    def draw_text(self, qp, text):
        qp.setPen(QPen(Qt.white))
        fm = QFontMetrics(QFont())                                      # QFontMetrics는 인수로 준 폰트로 문자열이나 문자의 크기를 알아냄
        qp.drawText(400 - fm.boundingRect(text).width() / 2, 100, text) # fm.boundingRect(text).width()를 하면 이 문자열의 픽셀 수를 알아낼 수 있다.

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

왼쪽, 오른쪽, 위쪽 방향키를 누르면 누르는 방향에 따른 도로가 표시된다.

draw_road() 함수는 휘어지는 방향을 인수로 받는다.

휘어진 도로를 그릴 때 유의해야할 점이 있다.

  1. 도로는 가까운 쪽부터 멀어지는 방향으로 그린다.
  2. 휘어진 도로를 만들 때 멀리 있는 판일수록 X 좌표를 많이 기울어지게 한다.

가까운 쪽부터 멀어지는 방향으로 도로의 판을 그리는 편이 도로의 굽은 정도를 계산하기 쉬워 이번에는 이렇게 그리지만 실제 게임을 제작할 때는 먼 쪽부터 가까워지는 방향으로 그릴 필요가 있다.

ux와 bx 변수에 휘어진 정도를 계산하여 값이 대입된다.

di가 양의 값이면 오른쪽 커브, 음의 값이면 왼쪽 커브, 0이면 직선 도로가 되도록 계산한다.

draw_road() 함수에 인수 10을 주고 실행하면 가장 가까운 판은 i 값이 23일 때 그리므로 아랫변의 X 좌표에 들어가는 값은 10 * (23 - 23) = 0이 되어 좌표가 미끄러지지 않는다.
2번째 판, 3번째 판으로 갈수록 어긋나는 값이 순차적으로 커지게 되어 가장 먼 판의 췻변은 10 * (23 - (1 - 1))로 230픽셀 옆으로 미끄러져 표시된다.

도로 기복 표현하기 pt. 1

오르막을 올라갈 때는 일반적으로 도로의 앞쪽을 보기 어려워지며, 시야가 나빠지고, 내리막을 내려갈 때는 훨씬 앞까지 보이고 시야가 좋아진다.

이를 표현하려면 오르막에서는 길 끝에 있는 판이 움푹 들어가도록 그리고, 내리막에서는 길 끝에 있는 판이 위로 떠오르는 듯 그리면 된다.

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

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

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

    def initUI(self):
        self.setFixedSize(800, 600)
        self.setWindowTitle('Draw road')
        self.setStyleSheet('background:blue')

    def initattr(self):
        self.BORD_COL = [Qt.white, QColor(192, 208, 224), Qt.gray]
        self.key = None

    def paintEvent(self, e):
        qp = QPainter()
        qp.begin(self)
        qp.setBrush(Qt.darkGreen)
        qp.drawRect(0, 300, 800, 600)
        self.draw_text(qp, '위쪽, 아래쪽 방향키를 눌러 주십시오')

        if self.key == Qt.Key_Up:       # 내리막
            self.draw_road(qp, 0, -5)
        if self.key == Qt.Key_Down:     # 오르막
            self.draw_road(qp, 0, 5)
        

        qp.end()
    
    def draw_road(self, qp, di, updown):
        h = 24
        y = 600 - h

        for i in range(23, 0, -1):
            uw = (i - 1) * (i - 1) * 1.5
            ux = 400 - uw / 2 + di * (23 - (i - 1))
            bw = i * i * 1.5
            bx = 400 - bw / 2 + di * (23 - i)
            col = self.BORD_COL[i % 3]
            qp.setBrush(col)
            points = [
                QPoint(ux, y),
                QPoint(ux + uw, y),
                QPoint(bx + bw, y + h),
                QPoint(bx, y + h)
            ]
            qp.drawPolygon(QPolygon(points))
            h = h - 1
            y = y - h + updown  # 다음 판의 Y 좌표를 계산해서 y에 대입

    def draw_text(self, qp, text):
        qp.setPen(QPen(Qt.white))
        fm = QFontMetrics(QFont())
        qp.drawText(400 - fm.boundingRect(text).width() / 2, 100, text)

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

위쪽 방향키를 누르면 판의 Y 좌표를 위로 미끄러뜨린 내리막 상태가 되고, 아래쪽 방향키를 누르면 판의 Y 좌표를 아래로 미끄러뜨린 오르막 상태가 표시된다.

도로 기복 표현하기 pt. 2

삼각함수를 사용해 도로의 끝을 지평선에 맞출 수 있다.

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

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

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

    def initUI(self):
        self.setFixedSize(800, 600)
        self.setWindowTitle('Draw road')
        self.setStyleSheet('background:blue')

    def initattr(self):
        self.BORD_COL = [Qt.white, QColor(192, 208, 224), Qt.gray]
        self.key = None
        self.updown = [0] * 24                                      # 판의 Y 좌표를 미끄러뜨린 값을 대입할 리스트
        for i in range(23, -1, -1):                                 # 반복, i는 23 ~ -1까지 1씩 감소
            self.updown[i] = math.sin(math.radians(180 * i / 23))   # 삼각함수로 미끄러뜨린 값을 updown에 대입


    def paintEvent(self, e):
        qp = QPainter()
        qp.begin(self)
        qp.setBrush(Qt.darkGreen)
        qp.drawRect(0, 300, 800, 600)
        self.draw_text(qp, '위쪽, 아래쪽 방향키를 눌러 주십시오')

        if self.key == Qt.Key_Up:       # 오르막
            self.draw_road(qp, 0, -50)
        if self.key == Qt.Key_Down:     # 내리막
            self.draw_road(qp, 0, 50)
        

        qp.end()
    
    def draw_road(self, qp, di, ud):
        h = 24
        y = 600 - h

        for i in range(23, 0, -1):
            uw = (i - 1) * (i - 1) * 1.5
            ux = 400 - uw / 2 + di * (23 - (i - 1))
            uy = y + int(self.updown[i - 1] * ud)       # 판의 윗변 Y 좌표를 계산해서 uy에 대입
            bw = i * i * 1.5
            bx = 400 - bw / 2 + di * (23 - i)
            by = y + h + int(self.updown[i] * ud)   # 판의 아랫변 Y 좌표를 계산해서 by에 대입
            col = self.BORD_COL[i % 3]
            qp.setBrush(col)
            points = [
                QPoint(ux, uy),
                QPoint(ux + uw, uy),
                QPoint(bx + bw, by),
                QPoint(bx, by)
            ]
            qp.drawPolygon(QPolygon(points))
            h = h - 1
            y = y - h

    def draw_text(self, qp, text):
        qp.setPen(QPen(Qt.white))
        fm = QFontMetrics(QFont())
        qp.drawText(400 - fm.boundingRect(text).width() / 2, 100, text)

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

도로 앞을 지평선에 맞추기 위해서는 도로를 그릴 때 sin() 함수를 사용해 이 파형과 같이 판의 Y 좌표를 변화시킨다.

sin 값은 0도일 때 0, 90도일 때 최댓값인 1, 180도일 때 다시 0이 된다. 가장 가까운 판부터 먼 판까지 0도부터 180도의 sin() 함수 값을 Y 좌표에 더해서 그려나간다.

updown에 대입하는 부분을 보자.

self.updown = [0] * 24                                      # 판의 Y 좌표를 미끄러뜨린 값을 대입할 리스트
for i in range(23, -1, -1):                                 # 반복, i는 23 ~ -1까지 1씩 감소
    self.updown[i] = math.sin(math.radians(180 * i / 23))   # 삼각함수로 미끄러뜨린 값을 updown에 대입

radians() 명령에 작성한 각도는 180 * i / 23으로 이는 i 값이 23일 때 180이라는 의미이다. i가 1증가할 때마다 약 8도씩 줄고, i가 0일 때 0도가 된다.

이 계산식으로 180도부터 0도까지의 값을 계산해 이를 updown에 대입한다.

이렇게 구한 값으로 uy와 by, 사다리꼴 윗변과 아랫변의 Y 좌표에 인수로 전달한 기복의 크기 ud를 곱한 값을 더해준다.

ud의 값이 양수인 경우에는 내리막을 그리고 음수인 경우에는 파형의 상하가 역전 되므로 오르막을 그리게 된다.

이 ud 값을 바꾸면 기복을 크거나 작게 바꿀 수 있다.