본문 바로가기
-----ETC-----/C++로 배우는 게임 엔진 개발

[C++로 배우는 게임 엔진 개발] Day 11: 타일맵 렌더링

by cogito21_cpp 2024. 8. 1.
반응형

타일맵 렌더링

오늘은 SDL2를 사용하여 타일맵을 렌더링하는 방법을 학습하겠습니다. 타일맵은 게임 맵을 작은 타일들로 구성하여 효율적으로 렌더링하는 방법입니다. 이를 통해 큰 맵을 쉽게 관리하고 렌더링할 수 있습니다.

1. 타일맵의 개요

타일맵은 작은 타일들을 배열하여 게임 맵을 구성합니다. 각 타일은 일정한 크기의 사각형으로, 타일맵 배열의 각 요소는 타일의 종류를 나타냅니다.

2. 타일맵 클래스 설계

타일맵을 관리하기 위해 TileMap 클래스를 설계합니다. 이 클래스는 타일맵 배열을 저장하고, 타일맵을 렌더링하는 역할을 합니다.

 

헤더 파일 작성

include/TileMap.h 파일을 생성하고 다음과 같이 작성합니다.

#ifndef TILEMAP_H
#define TILEMAP_H

#include <SDL2/SDL.h>
#include <vector>

class TileMap {
public:
    TileMap(int tileWidth, int tileHeight, int mapWidth, int mapHeight);
    ~TileMap();

    bool LoadTileSet(const char* filePath, SDL_Renderer* renderer);
    void SetTile(int x, int y, int tileID);
    void Render(SDL_Renderer* renderer);

private:
    int tileWidth;
    int tileHeight;
    int mapWidth;
    int mapHeight;
    SDL_Texture* tileSet;
    std::vector<std::vector<int>> map;
};

#endif // TILEMAP_H

 

소스 파일 작성

src/TileMap.cpp 파일을 생성하고 다음과 같이 작성합니다.

#include "TileMap.h"
#include <iostream>

TileMap::TileMap(int tileWidth, int tileHeight, int mapWidth, int mapHeight)
    : tileWidth(tileWidth), tileHeight(tileHeight), mapWidth(mapWidth), mapHeight(mapHeight), tileSet(nullptr) {
    map.resize(mapHeight, std::vector<int>(mapWidth, 0));
}

TileMap::~TileMap() {
    if (tileSet) {
        SDL_DestroyTexture(tileSet);
    }
}

bool TileMap::LoadTileSet(const char* filePath, SDL_Renderer* renderer) {
    SDL_Surface* tempSurface = IMG_Load(filePath);
    if (tempSurface == nullptr) {
        std::cerr << "IMG_Load Error: " << IMG_GetError() << std::endl;
        return false;
    }

    tileSet = SDL_CreateTextureFromSurface(renderer, tempSurface);
    SDL_FreeSurface(tempSurface);

    if (tileSet == nullptr) {
        std::cerr << "SDL_CreateTextureFromSurface Error: " << SDL_GetError() << std::endl;
        return false;
    }

    return true;
}

void TileMap::SetTile(int x, int y, int tileID) {
    if (x >= 0 && x < mapWidth && y >= 0 && y < mapHeight) {
        map[y][x] = tileID;
    }
}

void TileMap::Render(SDL_Renderer* renderer) {
    for (int y = 0; y < mapHeight; ++y) {
        for (int x = 0; x < mapWidth; ++x) {
            int tileID = map[y][x];
            SDL_Rect srcRect = { (tileID % (tileSet->w / tileWidth)) * tileWidth, (tileID / (tileSet->w / tileWidth)) * tileHeight, tileWidth, tileHeight };
            SDL_Rect destRect = { x * tileWidth, y * tileHeight, tileWidth, tileHeight };
            SDL_RenderCopy(renderer, tileSet, &srcRect, &destRect);
        }
    }
}

3. 게임 엔진 수정

타일맵 클래스를 사용하여 게임 맵을 생성하고 렌더링합니다.

 

헤더 파일 수정

include/GameEngine.h 파일을 수정하여 TileMap 클래스를 포함합니다.

#ifndef GAMEENGINE_H
#define GAMEENGINE_H

#include <SDL2/SDL.h>
#include <SDL2/SDL_image.h>
#include "TileMap.h"

class GameEngine {
public:
    GameEngine();
    ~GameEngine();
    bool Initialize(const char* title, int width, int height);
    void Run();
    void Shutdown();

private:
    SDL_Window* window;
    SDL_Renderer* renderer;
    TileMap* tileMap;
    bool isRunning;
    Uint32 frameStart;
    int frameTime;

    void HandleEvents();
    void Update();
    void Render();
};

#endif // GAMEENGINE_H

 

소스 파일 수정

src/GameEngine.cpp 파일을 다음과 같이 수정합니다.

#include "GameEngine.h"
#include <iostream>

const int FPS = 60;
const int FRAME_DELAY = 1000 / FPS;

GameEngine::GameEngine()
    : window(nullptr), renderer(nullptr), tileMap(nullptr), isRunning(false), frameStart(0), frameTime(0) {}

GameEngine::~GameEngine() {
    Shutdown();
}

bool GameEngine::Initialize(const char* title, int width, int height) {
    if (SDL_Init(SDL_INIT_VIDEO) != 0) {
        std::cerr << "SDL_Init Error: " << SDL_GetError() << std::endl;
        return false;
    }

    window = SDL_CreateWindow(title, SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, width, height, SDL_WINDOW_SHOWN);
    if (window == nullptr) {
        std::cerr << "SDL_CreateWindow Error: " << SDL_GetError() << std::endl;
        return false;
    }

    renderer = SDL_CreateRenderer(window, -1, SDL_RENDERER_ACCELERATED | SDL_RENDERER_PRESENTVSYNC);
    if (renderer == nullptr) {
        std::cerr << "SDL_CreateRenderer Error: " << SDL_GetError() << std::endl;
        return false;
    }

    if (!(IMG_Init(IMG_INIT_PNG) & IMG_INIT_PNG)) {
        std::cerr << "IMG_Init Error: " << IMG_GetError() << std::endl;
        return false;
    }

    tileMap = new TileMap(32, 32, 25, 20); // 타일 크기, 맵 크기 설정
    if (!tileMap->LoadTileSet("path/to/your/tileset.png", renderer)) {
        return false;
    }

    // 타일맵 설정 (예시)
    for (int y = 0; y < 20; ++y) {
        for (int x = 0; x < 25; ++x) {
            tileMap->SetTile(x, y, (x + y) % 4); // 타일 ID를 설정 (예시)
        }
    }

    isRunning = true;
    return true;
}

void GameEngine::Run() {
    while (isRunning) {
        frameStart = SDL_GetTicks();

        HandleEvents();
        Update();
        Render();

        frameTime = SDL_GetTicks() - frameStart;
        if (FRAME_DELAY > frameTime) {
            SDL_Delay(FRAME_DELAY - frameTime);
        }
    }
}

void GameEngine::HandleEvents() {
    SDL_Event event;
    while (SDL_PollEvent(&event)) {
        if (event.type == SDL_QUIT) {
            isRunning = false;
        }
        if (event.type == SDL_KEYDOWN) {
            switch (event.key.keysym.sym) {
                case SDLK_ESCAPE:
                    isRunning = false;
                    break;
                // 여기에 추가 입력 처리를 구현합니다.
                default:
                    break;
            }
        }
    }
}

void GameEngine::Update() {
    // 게임 상태 업데이트 로직을 여기에 추가합니다.
}

void GameEngine::Render() {
    // 화면을 검은색으로 지우기
    SDL_SetRenderDrawColor(renderer, 0, 0, 0, 255);
    SDL_RenderClear(renderer);

    // 타일맵 렌더링
    tileMap->Render(renderer);

    // 렌더링 결과를 화면에 출력
    SDL_RenderPresent(renderer);
}

void GameEngine::Shutdown() {
    if (tileMap) {
        delete tileMap;
        tileMap = nullptr;
    }
    if (renderer) {
        SDL_DestroyRenderer(renderer);
        renderer = nullptr;
    }
    if (window) {
        SDL_DestroyWindow(window);
        window = nullptr;
    }
    IMG_Quit();
    SDL_Quit();
}

4. 타일셋 이미지 준비

타일맵을 렌더링하기 위해 타일셋 이미지를 준비합니다. 예제에서는 path/to/your/tileset.png 경로에 타일셋 이미지를 배치합니다. 이 경로를 실제 이미지 파일 경로로 변경해야 합니다.

5. 프로젝트 빌드 및 실행

  1. Visual Studio에서 CMake 프로젝트 열기:
    • Visual Studio를 실행하고, File -> Open -> CMake...를 선택합니다.
    • GameEngine 디렉토리를 선택하여 프로젝트를 엽니다.
  2. SDL2_image 라이브러리 설치:
  3. SDL2_image 라이브러리를 프로젝트에 설정합니다.
  4. 프로젝트 빌드:
    • Visual Studio 상단의 Build 메뉴에서 Build All을 선택하여 프로젝트를 빌드합니다.
  5. 프로젝트 실행:
    • Debug 메뉴에서 Start Without Debugging을 선택하여 프로그램을 실행합니다.
    • 윈도우 창이 생성되고, 타일맵이 화면에 렌더링됩니다.

마무리

오늘은 SDL2를 사용하여 타일맵을 렌더링하는 방법을 학습했습니다. 타일맵을 사용하여 게임 맵을 생성하고 렌더링하는 기초를 배웠습니다. 다음 단계에서는 간단한 물리 엔진을 구현하여 게임 내에서 물리적인 상호작용을 처리하는 방법을 학습하겠습니다.

질문이나 추가적인 피드백이 있으면 언제든지 댓글로 남겨 주세요.

Day 12 예고

다음 날은 "간단한 물리 엔진 구현 (AABB 충돌)"에 대해 다룰 것입니다. 간단한 충돌 감지와 물리적인 상호작용을 처리하는 방법을 배워보겠습니다.

반응형