게임으로 배우는 파이썬 Part 1 Chapter 4 :PyGame
Programming Language/Python

게임으로 배우는 파이썬 Part 1 Chapter 4 :PyGame

728x90

PyGame은 파이썬 게임용 라이브러리이다. PyGame을 사용하면 윈도를 만들어 자유롭게 그릴 수 있다. 마우스나 키보드 입력도 받는다. 게임에서 편리하게 사용할 수 있는 명령도 풍부하다.

 

1 : 윈도 표시                                                                                       

먼저, 윈도를 화면에 표시한다.

"""justwindow.py"""
import sys
import pygame
from pygame.locals import QUIT

pygame.init()
# (widht, height)
SURFACE = pygame.display.set_mode((400, 300))
# window title
pygame.display.set_caption("Just Window")


def main():
    """main routine"""
    while True:
        SURFACE.fill((255, 255, 255))

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


if __name__ == '__main__':
    main()

위 파일을 싱행하면 다음과 같은 윈도가 표시된다.

크기는 폭 400, 높이 300 픽셀, 윈도 타이틀에 'Just Window' 라고 표시된다.

 

윈도는 화면에 표시되며, 그 위에서 마우스가 클릭되거나 마우스가 움직이거나 키보드가 눌리는 등 여러 가지 현상이 발생한다. 이러한 사실과 현상을 이벤트라고 한다. 이벤트는 발생하면 이벤트 큐라는 곳에 저장된다. 큐(queue)는 대기 행렬이며, 프로그램에서는 큐의 맨 앞에서 이벤트를 꺼내고 그 이벤트의 종류에 따라서 적절한 처리를 해 나간다.

 

이처럼 윈도를 가진 애플리케이션은 이벤트 큐에 쌓인 이벤트를 차례로 처리하는 작업을 반복하는데 이 반복을 메인 루프라고 한다. 파이썬뿐만 아니라 윈도를 가진 앱의 대부분은 이같은 구조로 동작한다.

 

import sys
import pygame
from pygame.locals import QUIT

이 세 줄은 모듈을 임포트하기 위한 것이다. sys 시스템 모듈pygame 모듈을 임포트 하고 있다. pygame에서는 여러 가지 상수를 사용하는데, 그것들은 pygame.locals 모듈에 정의되어 있다.

 

pygame.init()는 pygame 묘듈을 초기화하는 함수이다. pygame을 사용하는 앱에서는 처음으로 호출해야 한다.

 

SURFACE = pygame.display.set_mode((400, 300))에서는 크기를 지정해서 윈도를 작성하고 그 것을 변수 SURFACE에 저장한다.

 

pygame.display.set_caption("Just Window")로 윈도의 타이틀을 설정한다.

 

def main():부터가 함수 main의 선언이다. 파일의 마지막에 __name__ == '__main__'라는 if문이 있는 것으로 알 수 있듯이 이 파일부터 시작되었을 때 main 함수가 실행된다. main 함수는 전체가 while True:블록으로 둘러싸여 있다. 이 while 문이 메인 루프이다. 종료 처리가 이뤄질 때까지 루프를 계속한다.

 

while 블록 안에서는 먼저 SURFACE.fill((255, 255, 255))를 호출한다. 변수 SURFACE는 윈도이다. fill은 채운다는 의미이며, 그 윈도를 흰색 (255, 255, 255)로 칠한다. PyGame에서는 세 가지의 숫자 튜플(R 빨간색, G 녹색, B 파란색)로 색상을 지정한다. 각각의 요소는 0부터 255까지의 범위로 지졍한다.

(0, 0, 0) 검정색
(255, 255, 255) 흰색
(255, 0, 0) 빨간색
(0, 255, 0) 녹색
(0, 0, 255) 파란색
(255, 255, 0) 노란색

 

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

for event in pygame.event.get():은 이벤트 큐에서 이벤트를 읽는 명령이다. 읽혀진 이벤트는 변수 event에 저장된다. 다음은 그 event의 종류에 따라 적절한 처리를 한다. 위의 코드는 종료 이벤트를 검출했을 때 프로그램을 종료한다는 처리만 한다. 종료 이벤트는 윈도의 닫기 버튼을 눌렀을 때 등에 발생한다. 이벤트의 종류가 QUIT일 때, pygame.quit()로 PyGame의 초기화를 해제하고, sys.exit()로 프로그램을 종료한다.

 

pygame.display.update()는 프로그램 내에서 그린 내용을 반영시키는 명령이다. 이 명령을 실행하지 않으면 그린 내용이 화면에 반영되지 않으므로 잊지 말고 호출해야 한다.

 

2 : 타이머                                                                                            

메인 루프는 전속력으로 실행을 반복한다.

CPU 사용률을 보면 상당히 높은 수치인 것을 확인할 수 있다.

 

""" fps_test1.py """
import sys
import pygame
from pygame.locals import QUIT

pygame.init()
SURFACE = pygame.display.set_mode((400, 300))

def main():
    """ main routine """
    sysfont = pygame.font.SysFont(None, 36)
    counter = 0

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

        counter += 1
        SURFACE.fill((0, 0, 0))
        count_image = sysfont.render("count is {}".format(counter), True, (255, 255, 255))
        SURFACE.blit(count_image, (50, 50))
        pygame.display.update()


if __name__ == '__main__':
    main()

메인 루프 안에서 카운터 counter를 1씩 증가시키고 그 값을 화면에 표시한다.

매우 빠른 속도로 카운터가 증가하는 모습을 볼 수 있다.

또한 현재 CPU 이용률은 10.2%이다.

 

사람의 눈은 빠른 속도에는 쫓아갈 수 없으므로 너무 빠른 속도의 루프는 불필요하다.

또한 CPU의 부하를 줄이기 위해서도 프레임을 그릴 때 휴식을 취하는 것이 일반적이다.

 

PyGame에는 일정 프레임 레이트를 실현하기 위해서 적절히 휴식을 취하는 명령이 있다.

""" fps_test2.py """
import sys
import pygame
from pygame.locals import QUIT

pygame.init()
SURFACE = pygame.display.set_mode((400, 300))
FPSCLOCK = pygame.time.Clock()

def main():
    """ main routine """
    sysfont = pygame.font.SysFont(None, 36)
    counter = 0

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

        counter += 1
        SURFACE.fill((0, 0, 0))
        count_image = sysfont.render("count is {}".format(counter), True, (255, 255, 255))
        SURFACE.blit(count_image, (50, 50))
        pygame.display.update()
        FPSCLOCK.tick(10)


if __name__ == '__main__':
    main()

FPSCLOCK = pygame.time.Clock()로 클럭 객체를 작성하고 , 변수 FPSCLOCK에 저장한다. 메인 루프 안에 FPSCLOCK.tick(10)이라고 기술하면 딱 1초에 10회 루프가 실행되도록 적절한 휴식을 만들어 준다.

 

카운터가 1초에 10 증가해 가는 것을 확인할 수 있다.

CPU 이용률도 크게 떨어지는 것을 확인할 수 있다.

 

3 : PyGame의 문서                                                                            

공식문서

 

Pygame Front Page — pygame v2.1.1 documentation

Pygame, as of 1.9, has a camera module that allows you to capture images, watch live streams, and do some basic computer vision. This tutorial covers those use cases.

www.pygame.org

PyGame만이 아니라 컴퓨터 관련 정보의 대부분은 영어로 공개되어 있다.

스스로 필요한 정보를 찾는 것도 중요한 스킬이다.

 

4 : 각종 그리기                                                                                    

PyGame으로 직사각형이나 원, 도형 등을 그릴 때는 draw 클래스의 메서드를 사용한다.

 

좌표계

화면에 여러 가지 그려 가는데 어떤 내용을 그리려고 해도 어디에 그릴까하는 위치 지정이 필수이다.

그만큼 좌표계를 올바르게 이해하는 것은 매우 중요하다.

PC의 좌표계는 학교에서 배운 좌표계와 다르므로 주의해야 한다.

  • 학교에서 배운 좌표계 = 가로가 X축(오른쪽이 양수), 세로가 Y출(위가 양수), 중앙이 원점
  • PC의 좌표계 = 가로가 X축(오른쪽이 양수), 세로가 Y출(아래가 양수), 화면 왼쪽 위가 원점

 

Rect

PyGame에서 직사각형(위치와 크기)을 지정할 때 사용하는 클래스이다.

1. Rect 객체 만드는 방법

Rect(left, top, width, height)
Rect((left, top), (width, height))

left는 왼쪽 가장자리의 x 좌표, top은 위쪽 끝의 y 좌표, width는 폭, height는 높이이다.

 

일단 객체를 작성하면 여러 가지 프로퍼티로 접근할 수 있다.

Rect 클래스에서 이용할 수 있는 프로퍼티는 다음과 같다.

x, y, top, left, bottom, right
topleft, bottomleft, topfight, bottomright
midtop, midleft, midbottom, midright
center, centerx, centery
size, width, height, w, h

 

Rect 객체를 작성하고, 프로퍼티에 접근하는 예이다.

>>> r = Rect(30, 20, 60, 40)
>>> r.center
(60, 40)
>>> r.bottomleft
(30, 60)
>>> r.width
60
>>> r.bottom
60

 

Rect 클래스의 이점은 각종 프로퍼티에 접근할 수 있는 것만이 아니다.

여러 가지 메서드를 이용할 수 있다.

copy( ) Rect 객체를 복제한다
move(x, y) (x, y)만큼 이동한 Rect를 반환한다. 자신은 이동하지 않는다.
move_ip(x, y) 자신(Rect)을 (x, y)만큼 이동한다.
inflate(x, y) 현재값에서 (x, y)만큼 크기를 변경한 Rect를 반환한다.
inflate_ip(x, y) 자신의 사이즈를 (x, y)만큼 변경한다.
union(Rect) 자신과 인수의 Rect를 포함하는 최소 Rect를 반환한다.
contains(Rect) 인수의 Rect를 포함하는지 아닌지 여부를 반환한다.
collidepoint(x, y) (x, y)라는 점이 자신에게 포함되는지 아닌지 여부를 반환한다.
colliderect(Rect) Rect와 자신에게 겹침이 있는지 없는지(충돌)를 반환한다.

비슷한 이름의 메서드로 _ip가 붙어 있는 것이 있다. 이것은 in-place라는 의미로 자신이 변환하는 걸 의미한다. _ip가 붙지 않은 것은 자신의 정보를 바탕으로 다른 Rect를 작성해서 반환한다. 자신은 위치도 크기도 변화하지 않는다. 헷갈리지 않도록 조심해야 한다.

>>> r = Rect(10, 20, 30, 40)
>>> r.move(50, 60)
<rect(60, 80, 30, 40)>
>>> r
<rect(10, 20, 30, 40)>
>>> r.move_ip(50, 60)
>>> r
<rect(60, 80, 30, 40)>

Rect 클래스의 메서드를 효과적으로 활용함으로써 프로그램이 훨씬 간단하게 되는 경우도 많다.

꼭, 한 번 문서를 살펴보고 어떤 방법이 있는지 알아보는 것이 좋다.

 

2. 직사각형

화면에 그리려면 draw 클래스의 메서드를 사용한다.

직사각형을 그리는 메서드 rect의 정의는 다음과 같다.

직사각형을 나타내는 Rect, 직사각형을 그리는 rect, 헷갈리기 쉽기 때문에 혼동하지 않도록 주의해야 한다.

rect(Surface, color, Rect, width=0) -> Rect
Surface: 그리는 대상이 되는 화면 (Surface 객체)
color: 색
Rect: 직사각형의 위치와 크기
width: 선 폭 (생략할 때는 빈틈없이 칠한다)
""" draw_rect1.py """
import sys
import pygame
from pygame.draw import rect
from pygame.locals import QUIT, Rect


pygame.init()
SURFACE = pygame.display.set_mode((400, 300))
FPSCLOCK = pygame.time.Clock()


def main():
    """ main routine """

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

        
        SURFACE.fill((255, 255, 255))

        # 빨간색: 직사각형(꽉 채워 칠한다)
        pygame.draw.rect(SURFACE, (255, 0, 0), (10, 20, 100, 50))

        # 빨간색: 직사각형(선 굵기 3)
        pygame.draw.rect(SURFACE, (255, 0, 0), (150, 10, 100, 30), 3)

        # 녹색: 직사각형
        pygame.draw.rect(SURFACE, (0, 255, 0), ((100, 80), (80, 50)))

        # 파란색: 직사각형 Rect 객체
        rect0 = Rect(200, 60, 140, 80)
        pygame.draw.rect(SURFACE, (0, 0, 255), rect0)

        # 노란색: 직사각형, Rect 객체
        rect1 = Rect((30, 160), (100, 50))
        pygame.draw.rect(SURFACE, (255, 255, 0), rect1)

        pygame.display.update()
        FPSCLOCK.tick(3)


if __name__ == '__main__':
    main()

 

3. 원

circle(Surface, color, pos, radius, width=0) -> Rect
Surface: 그리는 대상이 되는 화면 (Surface 객체)
color: 색
pos: 중심 좌표
radius: 반경
width: 선 폭 (생략 시는 빈틈없이 칠한다)
""" draw_circle.py """
import sys
import pygame
from pygame.locals import QUIT, Rect

pygame.init()
SURFACE = pygame.display.set_mode((400, 300))
FPSCLOCK = pygame.time.Clock()


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

        SURFACE.fill((255, 255, 255))

        # 빨간색: 꽉 채워 칠한다
        pygame.draw.circle(SURFACE, (255, 0, 0), (50, 50), 20)
        # 빨간새기 굵기10
        pygame.draw.circle(SURFACE, (255, 0, 0), (150, 50), 20, 10)

        # 녹색: 반경10
        pygame.draw.circle(SURFACE, (0, 255, 0), (50, 150), 10)
        # 녹색: 반경20
        pygame.draw.circle(SURFACE, (0, 255, 0), (150, 150), 20)
        # 녹색: 반경30
        pygame.draw.circle(SURFACE, (0, 255, 0), (250, 150), 30)

        pygame.display.update()
        FPSCLOCK.tick(3)


if __name__ == '__main__':
    main()

 

4. 타원

중심과 반경이 아니라 타원에 외접하는 직사각형을 지정함으로서 좌표를 지정한다.

정사각형을 지정하면 원이 된다.

ellipse(Surface, color, Rect, width=0) -> Rect
Surface: 그리는 대상이 되는 화면 (Surface 객체)
color: 색
Rect: 타원에 외접하는 직사각형의 위치와 크기
width: 선 폭 (생략할 때는 빈틈없이 칠한다)
""" draw_ellipse.py """
import sys
import pygame
from pygame.locals import QUIT


pygame.init()
SURFACE = pygame.display.set_mode((400, 250))
FPSCLOCK = pygame.time.Clock()


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

        SURFACE.fill((255, 255, 255))

        # 빨간색
        pygame.draw.ellipse(SURFACE, (255, 0, 0), (50, 50, 140, 60))
        pygame.draw.ellipse(SURFACE, (255, 0, 0), (250, 30, 90, 90))

        # 녹색
        pygame.draw.ellipse(SURFACE, (0, 255, 0), (50, 150, 110, 60), 5)
        pygame.draw.ellipse(SURFACE, (0, 255, 0), ((250, 130), (90, 90)), 20)

        pygame.display.update()
        FPSCLOCK.tick(3)


if __name__ == '__main__':
    main()

 

5. 선

line(Surface, color, start_pos, end_pos, width=1) -> Rect
Surface: 그리는 대상이 되는 화면 (Surface 객체)
color: 색
start_pos: 시작점
end_pos: 도착점
width: 선 폭
""" draw_line1.py """
import sys
import pygame
from pygame.locals import QUIT


pygame.init()
SURFACE = pygame.display.set_mode((400, 220))
FPSCLOCK = pygame.time.Clock()


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

        SURFACE.fill((255, 255, 255))

        # 빨간색: 가로줄
        pygame.draw.line(SURFACE, (255, 0, 0), (10, 80), (200, 80))

        # 빨간색: 가로줄(굵기 15)
        pygame.draw.line(SURFACE, (255, 0, 0), (10, 150), (200, 150), 15)

        # 녹색: 세로줄
        pygame.draw.line(SURFACE, (0, 255, 0), (250, 30), (250, 200))

        # 파란색: 빗금(굵기 10)
        start_pos = (300, 30)
        end_pos = (380, 200)
        pygame.draw.line(SURFACE, (0, 0, 255), start_pos, end_pos, 10)

        pygame.display.update()
        FPSCLOCK.tick(3)


if __name__ == '__main__':
    main()

 

for 문과 합하면 격자 모양의 무늬를 간단하게 그릴 수 있다.

""" draw_line2.py """
import sys
import pygame
from pygame.locals import QUIT

pygame.init()
SURFACE = pygame.display.set_mode((400, 300))
FPSCLOCK = pygame.time.Clock()


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

        SURFACE.fill((0, 0, 0))

        # 하얀색: 세로줄
        for xpos in range(0, 400, 25):
            pygame.draw.line(SURFACE, 0xFFFFFF, (xpos, 0), (xpos, 300))

        # 하얀색: 가로줄
        for ypos in range(0, 300, 25):
            pygame.draw.line(SURFACE, 0xFFFFFF, (0, ypos), (400, ypos))

        pygame.display.update()
        FPSCLOCK.tick(3)


if __name__ == '__main__':
    main()

 

line은 두 점 사이를 잇는 하나의 직석을 긋는 메서드다.

lines 메서드를 사용하면 여러 개의 점을 잇는 선을 그릴 수 있다.

lines(Surface, color, closed, pointlist, width=1) -> Rect
Surface: 그리는 대상이 되는 화면 (Surface 객체)
color: 색
closed: 시작점을 마지막 점에 이을지 여부
pointlist: 점의 리스트
width: 선 폭
""" draw_lines0.py """
import sys
import random
import pygame
from pygame.locals import QUIT

pygame.init()
SURFACE = pygame.display.set_mode((400, 300))
FPSCLOCK = pygame.time.Clock()


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

        SURFACE.fill((0, 0, 0))

        pointlist = []
        for _ in range(10):
            xpos = random.randint(0, 400)
            ypos = random.randint(0, 300)
            pointlist.append((xpos, ypos))

        pygame.draw.lines(SURFACE, (255, 255, 255), True, pointlist, 5)

        pygame.display.update()
        FPSCLOCK.tick(3)


if __name__ == '__main__':
    main()

 

난수로 10개의 점을 생성하고 그 점을 선으로 연결하고 있다.

 

6. 폴리곤

폴리곤은 다각형을 의미하는 말이다.

여러 개의 정점을 이어서 복잡한 형태를 표현할 수 있다.

polygon(Surface, color, pointlist, width=0) -> Rect
Surface: 그리는 대상이 되는 화면 (Surface 객체)
color: 색
pointlist: 점의 리스트
width: 선 폭(0일 때는 꽉 채워 칠한다)
""" draw_polygon.py """
import sys
from math import e, sin, cos, radians
import pygame
from pygame.locals import QUIT

pygame.init()
SURFACE = pygame.display.set_mode((400, 300))
FPSCLOCK = pygame.time.Clock()


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

        SURFACE.fill((0, 0, 0))

        pointlist0, pointlist1 = [], []
        for theta in range(0, 360, 72):
            rad = radians(theta)
            pointlist0.append((cos(rad)*100 + 100, sin(rad)*100 + 150))
            pointlist1.append((cos(rad)*100 + 300, sin(rad)*100 + 150))

        pygame.draw.lines(SURFACE, (255, 255, 255), True, pointlist0)
        pygame.draw.polygon(SURFACE, (255, 255, 255), pointlist1)

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


if __name__ == '__main__':
    main()

728x90

이미지

선이나 원 그리기이미지나 문자 출력은 크게 다르다. 선이나 원은 좌표나 크기를 지정만 하면 그 결과가 화면에 제시된다. 한편, 이미지나 문자는 많은 점의 집합이다. 선, 원과 같은 명령으로 그릴 수 있는 내용이 아니다. 그래서 이미지나 문자는 일단 Surface라는 영역에 그리고, 그 Surface를 화면에 복사하는 수순으로 표시된다.

 

이미지 파일을 로드하는 방법은 다음과 같다.

load(filename) -> Surface
filename: 이미지 파일

반환값으로 Surface 객체가 반환된다.

화면 전체를 나타내는 다른 Surface에 이 Surface를 복사해서 그린다.

Surface 복사는 blit 명령으로 실행한다.

blit(source, dest, area=None, special_flags=0) -> Rect
source: 원본이 되는 Surface
dest: 복사하는 좌표(왼쪽 위)
area: 복사하는 영역(일부만 그릴 때)
special_flags: 복사할 때의 연산 방법

복사할 곳과 원본 모두 Surface 객체이므로 혼동하지 않도록 주의해야 한다.

""" draw_image1.py """
import sys
import pygame
from pygame.locals import QUIT

pygame.init()
SURFACE = pygame.display.set_mode((400, 300))
FPSCLOCK = pygame.time.Clock()


def main():
    """ main routine """
    logo = pygame.image.load("pythonlogo.jpg")

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

        SURFACE.fill((225, 225, 225))

        # 왼쪽 위의 (20, 50) 위치에 로고를 그린다.
        SURFACE.blit(logo, (20, 50))

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


if __name__ =='__main__':
    main()

 

이미지(서플리션)

blit 인수에 영역을 지정해서 원래 이미지의 일부만을 그릴 수도 있다.

""" draw_image_subregion1.py """
import sys
import pygame
from pygame.locals import QUIT, Rect

pygame.init()
SURFACE = pygame.display.set_mode((400, 200))
FPSCLOCK = pygame.time.Clock()


def main():
    """ main routine """
    logo = pygame.image.load("pythonlogo.jpg")

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

        SURFACE.fill((225, 225, 225))
        SURFACE.blit(logo, (0, 0))
        SURFACE.blit(logo, (250, 50), Rect(50, 50, 100, 100))

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


if __name__ =='__main__':
    main()

 

후반의 게임에서는 다음과 같은 이미지를 사용한다.

이 이미지를 사용해서 여러 개의 캐릭터를 그리는 샘플은 다음과 같다.

""" draw_image_subregion2.py """
import sys
import pygame
from pygame.locals import QUIT, Rect, KEYDOWN, K_LEFT, K_RIGHT

pygame.init()
pygame.key.set_repeat(5, 5)
SURFACE = pygame.display.set_mode((300, 200))
FPSCLOCK = pygame.time.Clock()


def main():
    """main routine"""
    strip = pygame.image.load("strip.png")
    images = []

    for index in range(9):
        image = pygame.Surface((24, 24))
        image.blit(strip, (0, 0), Rect(index * 24, 0, 24, 24))
        images.append(image)

    counter = 0
    pos_x = 100

    while True:
        for event in pygame.event.get():
            if event.type == QUIT:
                pygame.quit()
                sys.exit()
            elif event.type ==KEYDOWN:
                if event.key == K_LEFT:
                    pos_x -= 5
                elif event.key == K_RIGHT:
                    pos_x += 5

        SURFACE.fill((0, 0, 0))

        SURFACE.blit(images[counter % 2 + 0], (50, 50))
        SURFACE.blit(images[counter % 2 + 2], (100, 50))
        SURFACE.blit(images[counter % 2 + 4], (150, 50))
        SURFACE.blit(images[counter % 2 + 6], (200, 50))
        counter += 1

        SURFACE.blit(images[8], (pos_x, 150))

        pygame.display.update()
        FPSCLOCK.tick(5)


if __name__ == '__main__':
    main()

 

 

프로그램의 전반 부분에서 strip.png로부터 일부 영역을 잘라내 image에 저장하고, 그 image를 Surface에 복사한다.

후반부에서는 여러 Surface를 전환해서 그려 애니메이션적인 효과를 연출한다.

 

이미지(회전)

이미지를 회전하려면 transform 클래스rotate 메서드를 사용한다.

반환값은 새로운 이미지의 Surface 객체이다.

rotate(Surface, angle) -> Surface
Surface: 회전 대상이 되는 Surface
angle: 회전각

이미지를 회전해서 그리려면 일단 회전한 이미지를 작성하고, 그 이미지를 blit에 복사하는 수순을 밟는다.

""" draw_image3.py """
import sys
import pygame
from pygame.locals import QUIT

pygame.init()
SURFACE = pygame.display.set_mode((400, 300))
FPSCLOCK = pygame.time.Clock()


def main():
    """main routine"""
    logo = pygame.image.load("pythonlogo.jpg")
    theta = 0

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

        theta += 1

        SURFACE.fill((225, 225, 225))

        # 로고를 회전하고, 왼쪽 위가 (100, 30)인 위치에 로고를 그린다
        new_logo = pygame.transform.rotate(logo, theta)
        SURFACE.blit(new_logo, (100, 30))

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


if __name__ == '__main__':
    main()

 

여기에서 중요한 것은 회전함으로써 이미지의 가로 세로 크기가 변화한다는 것이다.

이 때문에 이미지의 왼쪽 위 좌표를 고정하고 그리면 이미지의 회전 중심이 어긋나고 만다.

 

게임에 따라서는 이미지의 중심을 축으로 회전할 때가 많다.

""" draw_image4.py """
import sys
import pygame
from pygame.locals import QUIT

pygame.init()
SURFACE = pygame.display.set_mode((400, 300))
FPSCLOCK = pygame.time.Clock()


def main():
    """main routine"""
    logo = pygame.image.load("pythonlogo.jpg")
    theta = 0

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

        theta += 1

        SURFACE.fill((225, 225, 225))

        # 로고를 회전하고, 중심이 (200, 150)인 위치에 로고를 그린다
        new_logo = pygame.transform.rotate(logo, theta)
        rect = new_logo.get_rect()
        rect.center = (200, 150)
        SURFACE.blit(new_logo, rect)

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


if __name__ == '__main__':
    main()

 

new_logo = pygame.transform.rotate(logo, theta)
rect = new_logo.get_rect()
rect.center = (200, 150)
SURFACE.blit(new_logo, rect)

핵심은 이 네 줄이다. transform.rotate로 회전 후의 이미지를 작성한다. 이 이미지가 차지하는 직사각형을 get_rect() 메서드로 구한다. 이 직사각형의 프로퍼티에 중심 축의 좌표를 설정하고, 그 Rect를 두 번째 인수에 지정해서 blit를 호출한다.

 

문자

문자 출력은 이미지 출력과 비슷한다. font 객체를 작성하고, render 메서드를 사용해 문자의 비트맵(Surface)를 작성하고, 이미지처럼 blit를 사용해 화면에 복사한다.

 

폰트 작성은 SysFont 명령을 사용한다.

반환 값은 Font 객체이다.

pygame.font.SysFont(name, size, bold=False, italic=False) -> Font
name: 폰트명, 기본 폰트를 사용하려면 None을 지정
size: 폰트 크기
bold: 굵은체인지 아닌지, 생략할 때는 False
italic: 이탤릭인지 아닌지, 생략할 때는 False

 

폰트 그리기에는 render 메서드를 사용한다.

반환값은 Surface 객체이다.

render(text, antialias, color, background=None) -> Surface
text: 그리는 텍스트
antialias: 안티앨리언스(윤곽을 부드럽게)
color: 색
background: 배경색
""" draw_text1.py """
import sys
import pygame
from pygame.font import SysFont
from pygame.locals import QUIT

pygame.init()
SURFACE = pygame.display.set_mode((400, 200))
FPSCLOCK = pygame.time.Clock()


def main():
    sysfont = pygame.font.SysFont(None, 72)
    message = sysfont.render("Hello Python", True, (0, 128, 128))
    message_rect = message.get_rect()
    message_rect.center = (200, 100)

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

        SURFACE.fill((255, 255, 255))
        SURFACE.blit(message, message_rect)
        pygame.display.update()
        FPSCLOCK.tick(3)


if __name__ == '__main__':
    main()

 

출력된 결과는 문자지만 Surface를 다루는 방법은 이미지와 다르지 않다.

이미지와 같이 회전할 수 있다.

다음은 회전과 줌을 동시에 하는 rotozoom 메서드이다.

rotozoom(Surface, angle, scale) -> Surface
Surface: 회전과 줌을 하는 Surface
angle: 회전각
scale: 줌 배율
""" draw_text2.py """
import sys
import pygame
from pygame.locals import QUIT

pygame.init()
SURFACE = pygame.display.set_mode((400, 300))
FPSCLOCK = pygame.time.Clock()


def main():
    sysfont = pygame.font.SysFont(None, 72)
    message = sysfont.render("Hello Python", True, (0, 128, 128))
    message_rect = message.get_rect()
    theta = 0
    scale = 1

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

        SURFACE.fill((255, 255, 255))
        theta += 5
        scale = (theta % 360) / 180
        tmp_msg = pygame.transform.rotozoom(message, theta, scale)
        tmp_rect = tmp_msg.get_rect()
        tmp_rect.center = (200, 150)
        SURFACE.blit(tmp_msg, tmp_rect)
        pygame.display.update()
        FPSCLOCK.tick(10)


if __name__ == '__main__':
    main()

 

 

이벤트

게임에서는 무언가를 입력한다.

모든 이벤트는 이벤트 큐에서 꺼내지만 그 type 프로퍼티를 보고 이벤트의 종류를 구별할 수 있다.

 

1. 마우스 클릭

마우스 누르기는 MOUSEBUTTONDOWN이다.

""" draw_circle_onclick.py """
import sys
import pygame
from pygame.locals import QUIT, MOUSEBUTTONDOWN

pygame.init()
SURFACE = pygame.display.set_mode((400, 300))
FPSCLOCK = pygame.time.Clock()


def main():
    """ main routine """
    mousepos = []

    while True:
        SURFACE.fill((255, 255, 255))

        for event in pygame.event.get():
            if event.type == QUIT:
                pygame.quit()
                sys.exit()
            elif event.type == MOUSEBUTTONDOWN:
                mousepos.append(event.pos)

		# 아래의 for 문과 같은 결과를 얻을 수 있다.
        # for i, j in mousepos:
        #     pygame.draw.circle(SURFACE, (0, 255, 0), (i, j), 5)
        for pos in mousepos:
            pygame.draw.circle(SURFACE, (0, 255, 0), pos, 5)

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


if __name__ == '__main__':
    main()

 

 

마우스 좌표는 event.pos로 구할 수 있다. 이 값은 (x, y)로 된 튜플이다. append 메서드를 사용해서 그 튜플을 리스트 mousepos에 추가한다. 나머지는 for 문으로 하나 하나 좌표를 꺼내고 circle 로 원을 그린다.

 

2. 마우스의 이동

마우스의 입력은 MOUSEBUTTONDOWN, 마우스의 이동은 MOUSEMOTION, 마우스 해제는 MOUSEBUTTONUP이다.

이러한 이벤트를 사용해서 마우스 궤적을 그린다.

""" draw_line_onmousemove.py """
import sys
import pygame
from pygame.constants import SRCALPHA
from pygame.locals import QUIT, MOUSEBUTTONDOWN, MOUSEMOTION, MOUSEBUTTONUP

pygame.init()
SURFACE = pygame.display.set_mode((400, 300))
FPSCLOCK = pygame.time.Clock()


def main():
    """ main routine """
    mousepos = []
    mousedown = False

    while True:
        for event in pygame.event.get():
            if event.type == QUIT:
                pygame.quit()
                sys.exit()
            elif event.type == MOUSEBUTTONDOWN:
                mousedown = True
            elif event.type == MOUSEMOTION:
                if mousedown:
                    mousepos.append(event.pos)
            elif event.type == MOUSEBUTTONUP:
                mousedown = False
                mousepos.clear()

        SURFACE.fill((255, 255, 255))
        if len(mousepos) > 1:
            pygame.draw.lines(SURFACE, (255, 0, 0), False, mousepos)
            # pygame.draw.aalines(SURFACE, (255, 0, 0), False, mousepos, False)

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


if __name__ == '__main__':
    main()

 

MOUSEBUTTONDOWN으로 mousedown을 True, MOUSEBUTTONUP으로 mousedown을 False로 한다. 이로써 mousedown이 True일 때는 마우스가 눌리게 된다.

 

마우스를 누르는 중에 마우스가 이동하면 mousepos.append(event.pos)로 그 좌표를 리스트에 저장한다. 좌표의 개수가 두 개 이상이 되었을 때 lines 메서드로 궤적을 그린다.

 

aalines 메서드를 사용하면 더욱 매끄러운 선을 그릴 수 있다.

aalines(Surface, color, closed, pointlist, blend=1) -> Rect
Surface: 그리는 대상이 되는 Surface
color: 색
closed: 시작점과 마지막점을 이을지 여부
pointlist: 좌표 리스트
blend: 섞는지 아닌지

마지막 인수 blend를 True로 하면 단순한 덮어쓰기가 아니라 섞은 상태로 그리기가 진행된다.

True면 부드러운 선, False면 부드럽지 않은 선을 그릴 수 있다.

또한 aalines 메서드를 사용하면 width 설정이 불가능하다.

무조건 width가 1인 선만 그릴 수 있다.

 

blend=True

 

blend=False

3. 키 누르기

키 누르기는 KEYDOWN 이벤트로 판별한다.

어떤 키가 눌렸는지는 이벤트의 key 프로퍼티를 보면 알 수 있다.

키 코드는 pygame.locals에 정의되어 있다.

""" draw_image_onkeydown.py """
import sys
import pygame
from pygame.image import load
from pygame.locals import QUIT, KEYDOWN, K_LEFT, K_RIGHT, K_UP, K_DOWN

pygame.init()
pygame.key.set_repeat(5, 5)
SURFACE = pygame.display.set_mode((400, 300))
FPSCLOCK = pygame.time.Clock()


def main():
    """ main routine """
    logo = pygame.image.load("pythonlogo.jpg")
    pos = [200, 150]
    while True:
        for event in pygame.event.get():
            if event.type == QUIT:
                pygame.quit()
                sys.exit()
            elif event.type == KEYDOWN:
                if event.key == K_LEFT:
                    pos[0] -= 5
                elif event.key == K_RIGHT:
                    pos[0] += 5
                elif event.key == K_UP:
                    pos[1] -= 5
                elif event.key == K_DOWN:
                    pos[1] += 5

        pos[0] = pos[0] % 400
        pos[1] = pos[1] % 300

        # SURFACE.fill((225, 225, 225))

        rect = logo.get_rect()
        rect.center = pos
        SURFACE.blit(logo, rect)

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


if __name__ == '__main__':
    main()

상하좌우 키를 누르면 로고가 이동한다.

pygame.key.set_repeat(5, 5)는 키를 누르고 뗄 때 이벤트를 정기적으로 발생시키기 위한 것이다.

 

elif event.type == KEYDOWN:
	if event.key == K_LEFT:
		pos[0] -= 5
	elif event.key == K_RIGHT:
		pos[0] += 5
	elif event.key == K_UP:
		pos[1] -= 5
	elif event.key == K_DOWN:
		pos[1] += 5

이번 코드의 핵심 부분이다. event.typeKEYDOWN이라면 키가 입력된 것이다. 그때는 어느 키가 눌렀는지 event.key를 조사한다. 그 값에 따라서 이미지의 중심 좌표 pos를 증감한다.

 

pos[0] = pos[0] % 400
pos[1] = pos[1] % 300

이 코드는 이미지의 끝에 도달했을 때 반대측에서부터 되돌아오기 위한 처리이다.

 

화면을 다시 그리기

SURFACE.fill((...))를 통해 화면 전체를 클리어(배경색으로 빈틈없이 채운다)하고 대상이 되는 요소를 전부 그리는 처리를 프레임마다 반복했다.

프레임마다 배경을 빈틈없이 채우지 않으면 이전 프레임에서 그린 내용이 남기 때문이다.

 

화면상에서 이미지 변화가 적은 경우, 프레임마다 다시 그리는 건 불필요한 작업이다. 이동이 있던 곳 만을 기억해 놓고 그 곳 만을 기억해 놓고 그 곳 만을 다시 쓰는 처리를 하면 처리 효율이 향상된다. 그러나 이번 샘플에서는 간결함이 우선시되어 프레임마다 배경을 클리어하는 기법으로 처리되었다.

728x90