본문 바로가기
-----ETC-----/C++ 게임 개발 시리즈

[C++ 게임 개발 시리즈] Day 11: 타일 맵과 맵 에디터 사용법

by cogito21_cpp 2024. 8. 1.
반응형

타일 맵과 맵 에디터 사용법

타일 맵은 작은 이미지를 배열하여 큰 지도를 만드는 기술입니다. 게임 개발에서 주로 배경이나 맵 디자인에 사용됩니다. 오늘은 타일 맵을 생성하고, 이를 관리하기 위한 맵 에디터 사용법을 학습하겠습니다.

타일 맵 기본 개념

타일 맵은 여러 개의 작은 이미지를 사용하여 큰 맵을 구성합니다. 각 타일은 동일한 크기를 가지며, 타일 맵은 2차원 배열로 표현됩니다.

  1. 타일(Tile): 맵을 구성하는 기본 단위입니다. 타일은 주로 정사각형이나 직사각형 모양을 가집니다.
  2. 타일셋(Tileset): 여러 타일 이미지를 하나의 큰 이미지로 모아 놓은 것입니다.
  3. 타일 맵(Tile Map): 타일을 배열하여 큰 지도를 만든 것입니다. 2차원 배열로 표현됩니다.

SFML을 사용한 타일 맵 구현

타일셋 이미지

먼저, 타일셋 이미지를 준비합니다. 타일셋 이미지는 여러 타일이 하나의 이미지 파일에 모여 있는 형태입니다. 각 타일의 크기와 타일셋 이미지의 크기를 알고 있어야 합니다.

타일 맵 데이터

타일 맵은 2차원 배열로 표현됩니다. 각 타일의 인덱스를 저장하는 배열을 사용하여 타일 맵을 정의합니다.

#include <SFML/Graphics.hpp>
#include <SFML/Window.hpp>
#include <SFML/System.hpp>
#include <iostream>
#include <vector>

int main() {
    // 창 생성
    sf::RenderWindow window(sf::VideoMode(800, 600), "Tile Map Example");

    // 타일 텍스처 로드
    sf::Texture tileTexture;
    if (!tileTexture.loadFromFile("tileset.png")) {
        std::cerr << "Error loading texture" << std::endl;
        return -1;
    }

    // 타일 맵 데이터 (0, 1, 2는 타일 인덱스를 의미)
    const int tileSize = 32;
    const int mapWidth = 10;
    const int mapHeight = 10;
    int mapData[mapHeight][mapWidth] = {
        {0, 0, 1, 1, 2, 2, 1, 1, 0, 0},
        {0, 1, 2, 2, 1, 1, 2, 2, 1, 0},
        {1, 2, 2, 1, 0, 0, 1, 2, 2, 1},
        {2, 2, 1, 0, 0, 0, 0, 1, 2, 2},
        {1, 2, 1, 0, 0, 0, 0, 1, 2, 1},
        {0, 1, 2, 0, 0, 0, 0, 2, 1, 0},
        {0, 0, 1, 1, 2, 2, 1, 1, 0, 0},
        {1, 1, 2, 2, 1, 1, 2, 2, 1, 1},
        {2, 2, 1, 1, 0, 0, 1, 1, 2, 2},
        {1, 1, 0, 0, 1, 1, 0, 0, 1, 1}
    };

    // 타일 맵 생성
    sf::VertexArray tileMap(sf::Quads, mapWidth * mapHeight * 4);
    for (int i = 0; i < mapHeight; i++) {
        for (int j = 0; j < mapWidth; j++) {
            int tileIndex = mapData[i][j];
            int tu = tileIndex % 3; // 타일셋 가로 타일 수
            int tv = tileIndex / 3; // 타일셋 세로 타일 수

            sf::Vertex* quad = &tileMap[(i * mapWidth + j) * 4];
            quad[0].position = sf::Vector2f(j * tileSize, i * tileSize);
            quad[1].position = sf::Vector2f((j + 1) * tileSize, i * tileSize);
            quad[2].position = sf::Vector2f((j + 1) * tileSize, (i + 1) * tileSize);
            quad[3].position = sf::Vector2f(j * tileSize, (i + 1) * tileSize);

            quad[0].texCoords = sf::Vector2f(tu * tileSize, tv * tileSize);
            quad[1].texCoords = sf::Vector2f((tu + 1) * tileSize, tv * tileSize);
            quad[2].texCoords = sf::Vector2f((tu + 1) * tileSize, (tv + 1) * tileSize);
            quad[3].texCoords = sf::Vector2f(tu * tileSize, (tv + 1) * tileSize);
        }
    }

    // 게임 루프
    while (window.isOpen()) {
        sf::Event event;
        while (window.pollEvent(event)) {
            if (event.type == sf::Event::Closed)
                window.close();
        }

        // 화면 지우기
        window.clear();

        // 타일 맵 그리기
        sf::RenderStates states;
        states.texture = &tileTexture;
        window.draw(tileMap, states);

        // 화면에 그리기
        window.display();
    }

    return 0;
}

 

맵 에디터 사용법

맵 에디터는 타일 맵을 쉽게 생성하고 수정할 수 있는 도구입니다. 대표적인 맵 에디터로는 Tiled가 있습니다. 다음은 Tiled를 사용하여 타일 맵을 생성하고, 이를 SFML에서 사용하는 방법입니다.

Tiled 설치 및 사용

  1. Tiled 설치:
  2. 타일셋 추가:
    • Tiled를 실행하고, "New Tileset"을 선택합니다.
    • 타일셋 이미지를 선택하고, 타일 크기를 설정합니다 (예: 32x32).
  3. 타일 맵 생성:
    • "New Map"을 선택하고, 맵의 크기와 타일 크기를 설정합니다.
    • 타일셋을 사용하여 맵을 디자인합니다.
  4. 맵 저장:
    • 맵을 .tmx 파일로 저장합니다.

Tiled 맵을 SFML에서 사용

TMX 파일 파싱

.tmx 파일을 파싱하여 SFML에서 사용할 수 있도록 합니다. 이를 위해 TinyXML2 라이브러리를 사용할 수 있습니다.

  1. TinyXML2 설치:
  2. TMX 파일 파싱 코드:
#include <SFML/Graphics.hpp>
#include <SFML/Window.hpp>
#include <SFML/System.hpp>
#include <tinyxml2.h>
#include <iostream>
#include <vector>

bool loadTileMap(const std::string& filename, sf::VertexArray& tileMap, sf::Texture& tileTexture) {
    tinyxml2::XMLDocument doc;
    if (doc.LoadFile(filename.c_str()) != tinyxml2::XML_SUCCESS) {
        std::cerr << "Error loading TMX file" << std::endl;
        return false;
    }

    tinyxml2::XMLElement* map = doc.FirstChildElement("map");
    int mapWidth = map->IntAttribute("width");
    int mapHeight = map->IntAttribute("height");
    int tileSize = map->IntAttribute("tilewidth");

    tinyxml2::XMLElement* tileset = map->FirstChildElement("tileset");
    std::string textureFile = tileset->FirstChildElement("image")->Attribute("source");

    if (!tileTexture.loadFromFile(textureFile)) {
        std::cerr << "Error loading texture" << std::endl;
        return false;
    }

    tinyxml2::XMLElement* layer = map->FirstChildElement("layer");
    tinyxml2::XMLElement* data = layer->FirstChildElement("data");

    std::vector<int> tileData;
    for (tinyxml2::XMLElement* tile = data->FirstChildElement("tile"); tile != nullptr; tile = tile->NextSiblingElement("tile")) {
        tileData.push_back(tile->IntAttribute("gid") - 1);
    }

    tileMap.setPrimitiveType(sf::Quads

);
    tileMap.resize(mapWidth * mapHeight * 4);

    for (int i = 0; i < mapHeight; ++i) {
        for (int j = 0; j < mapWidth; ++j) {
            int tileIndex = tileData[i * mapWidth + j];
            int tu = tileIndex % (tileTexture.getSize().x / tileSize);
            int tv = tileIndex / (tileTexture.getSize().x / tileSize);

            sf::Vertex* quad = &tileMap[(i * mapWidth + j) * 4];
            quad[0].position = sf::Vector2f(j * tileSize, i * tileSize);
            quad[1].position = sf::Vector2f((j + 1) * tileSize, i * tileSize);
            quad[2].position = sf::Vector2f((j + 1) * tileSize, (i + 1) * tileSize);
            quad[3].position = sf::Vector2f(j * tileSize, (i + 1) * tileSize);

            quad[0].texCoords = sf::Vector2f(tu * tileSize, tv * tileSize);
            quad[1].texCoords = sf::Vector2f((tu + 1) * tileSize, tv * tileSize);
            quad[2].texCoords = sf::Vector2f((tu + 1) * tileSize, (tv + 1) * tileSize);
            quad[3].texCoords = sf::Vector2f(tu * tileSize, (tv + 1) * tileSize);
        }
    }

    return true;
}

int main() {
    // 창 생성
    sf::RenderWindow window(sf::VideoMode(800, 600), "Tile Map from Tiled");

    sf::VertexArray tileMap;
    sf::Texture tileTexture;

    if (!loadTileMap("map.tmx", tileMap, tileTexture)) {
        std::cerr << "Failed to load tile map" << std::endl;
        return -1;
    }

    // 게임 루프
    while (window.isOpen()) {
        sf::Event event;
        while (window.pollEvent(event)) {
            if (event.type == sf::Event::Closed)
                window.close();
        }

        // 화면 지우기
        window.clear();

        // 타일 맵 그리기
        sf::RenderStates states;
        states.texture = &tileTexture;
        window.draw(tileMap, states);

        // 화면에 그리기
        window.display();
    }

    return 0;
}

결론

오늘은 타일 맵의 기본 개념과 이를 SFML을 사용하여 구현하는 방법을 학습했습니다. 또한, Tiled 맵 에디터를 사용하여 타일 맵을 생성하고, 이를 SFML에서 사용하는 방법을 알아보았습니다. 질문이나 추가적인 피드백이 있으면 언제든지 댓글로 남겨 주세요. 내일은 "Day 12: 2D 물리 엔진 기초 (Box2D)"에 대해 학습하겠습니다.

반응형