soma0sd

코딩 & 과학 & 교육

Pygame: 등축투영 타일의 좌표

반응형

오늘은 등축 투영(isometric) 타일배치를 위한 좌표계를 구현해봅니다.

직교 좌표계를 등축투영 좌표계로 변환

작교좌표계와 등축 투영 좌표계

하향식 게임에서 사용하는 직교좌표계 타일에서 왼쪽 위가 (0, 0) 좌표일 때, 이 타일 좌표계를 그대로 회전시켜 등축투영(isometric) 좌표계로 변환하면 그림처럼 왼쪽 끝이 (0. 0) 좌표계가 됩니다. 하지만 이대로 타일을 배치하면 문제가 발생합니다.

등축투영 좌표계의 랜더링 문제

타일을 x가 0인 지점부터 차례대로 채운다고 하면 랜더링 하는 순서로 인해 그림과 같은 문제가 생깁니다. 원점을 꼭대기 부분으로 바꾸지 않으면 계산이 편하긴 하겠지만 랜더링 순서로 인해 맵에 벽이나 언덕 타일이 있는 경우 심각한 문제가 발생합니다.

타일셋 이미지

예제용 타일셋 이미지

예제를 작성하기 전에 타일셋 이미지를 프로젝트 디렉토리 내부의 asset/tileset_base.png에 저장합니다.

코딩: 타일셋 클래스

from itertools import product
from typing import TYPE_CHECKING, List

import pygame

if TYPE_CHECKING:
    from pygame import Surface

class Tileset:
    """타일셋 관리 클래스"""

    def __init__(self, path: str, size=(64, 64), margin=0, spacing=0):
        """클래스 초기화

        Args:
            path: 타일셋 이미지 경로
            size: 한 타일의 크기
            margin: 타일 이미지 여백
            spacing: 타일 사이의 간격
        """
        self.__tile = []  # 타일 리스트

        image = pygame.image.load(path).convert_alpha()
        image.set_colorkey((0, 0, 0))
        rect = image.get_rect()
        x0 = y0 = margin
        w, h = rect.size
        dx, dy = size[0] + spacing, size[1] + spacing
        for y, x in product(range(y0, h, dy), range(x0, w, dx)):
            _tile = pygame.Surface(size, pygame.SRCALPHA)
            _tile.blit(image, (0, 0), (x, y, *size))
            self.__tile.append(_tile)

    @property
    def tile(self) -> List["Surface"]:
        """타일 목록"""
        return self.__tile

Tileset.tile[{타일번호}]로 타일 이미지를 불러올 수 있습니다. 예시 이미지는 여백도 없고 타일간 경계도 없지만, 에셋 스토어 등에서 타일 이미지를 불러오는 경우 여백이나 타일 경계선이 있는 경우가 간혹 있어서 클래스 초기화에 고려하였습니다.

코딩: 타일맵 클래스

class Tilemap:
    """타일맵 관리 클래스"""

    def __init__(self, tileset, grid=(64, 32), size=(5, 5)):
        """클래스 초기화

        Args:
            tileset: 기본 타일셋
            size: 타일을 배치하는 그리드의 크기
            size: 타일맵의 크기
        """
        self.__grid = grid
        self.__tileset = tileset
        self.__size = size

    def set_tile(self, display: "Surface", pos=(0, 0), idx=0, tileset=None):
        """타일 배치 메서드

        Args:
            display: 타일을 배치할 표면
            pos: 타일 좌표
            idx: 배치 타일의 종류
            tileset: 배치할 타일셋 (없으면 기본 타일셋)
        """
        tileset: Tileset = tileset if tileset != None else self.__tileset
        _x, _y = pos
        _w, _h = self.__grid
        cx = display.get_rect().centerx - (_w * 0.5)
        cy = display.get_rect().centery - (_h * self.__size[1] * 0.5)
        iso_x = -_x * _w * 0.5 + _y * _w * 0.5
        iso_y = _x * _h * 0.5 + _y * _h * 0.5
        display.blit(tileset.__tile[idx], (cx + iso_x, cy + iso_y))

타일맵에 타일을 배치하기 위한 클래스입니다.

코딩: 프로그램 작성

import sys
from itertools import product
from typing import TYPE_CHECKING, List

import pygame
from pygame.locals import DOUBLEBUF, QUIT

if TYPE_CHECKING:
    from pygame import Surface

if __name__ == "__main__":
    """메인 프로그램

    타일을 좌표 순서대로 배치
    """
    pygame.init()
    display = pygame.display.set_mode((640, 480), DOUBLEBUF)
    tileset = Tileset("asset/tileset_base.png")
    tilemap = Tilemap(tileset, (64, 32), (7, 7))
    clock = pygame.time.Clock()

    # 초기 타일 배치
    for _pos in product(range(7), range(7)):
        tilemap.set_tile(display, _pos, 0)

    tile_idx = 0
    while True:
        tile_idx = (tile_idx + 1) % (len(tileset.__tile) - 1)
        for _pos in product(range(7), range(7)):
            tilemap.set_tile(display, _pos, tile_idx)
            for event in pygame.event.get():
                if event.type == QUIT:
                    pygame.quit()
                    sys.exit()
            pygame.display.flip()
            clock.tick(15)

이 스크립트를 실행하면 타일을 좌표 순서대로 변경하는 애니메이션을 실행합니다.

반응형
태그:

댓글

End of content

No more pages to load