하드코딩으로 액터를 배치하던 방식을 개선해, 텍스트 파일에 작성된 맵 데이터를 읽어 게임 월드를 자동으로 생성하는 구조를 구현했다.
1. 맵 데이터 준비
##########
#........#
#........#
#..p..b..#
#.....b..#
#........#
#........#
#......t.#
#......t.#
##########
각 문자는 하나의 타일 또는 액터를 의미한다.
# : 벽
. : 바닥
p : 플레이어
b : 박스
t : 목표 지점
2. 하드코딩 방식의 문제
기존에는 코드에서 액터를 직접 생성해야 했다.
SpawnActor<Wall>(Vector2(0, 0));
SpawnActor<Wall>(Vector2(1, 0));
SpawnActor<Ground>(Vector2(1, 1));
이 방식은 맵이 커질수록 코드가 길어지고, 스테이지를 변경할 때마다 소스 코드를 수정해야 하는 문제가 있다.
3. GameLevel의 역할
TestLevel 대신에 GameLevel로 변경, 게임 시작 레벨이다.
class GameLevel : public Level
{
public:
virtual void OnInitialized() override;
virtual void Draw() override;
private:
void LoadMap(const std::string& filename);
private:
int targetScore = 0;
bool isGameClear = false;
};
주요 멤버의 역할은 다음과 같다.
- targetScore : 맵에 배치된 목표 지점 개수
- isGameClear : 게임 클리어 여부
- OnInitialized() : 레벨 초기화 시 맵 로드
- LoadMap() : 파일을 읽고 액터 생성
레벨 초기화 시 맵을 로드한다.
void GameLevel::OnInitialized()
{
LoadMap("Map.txt");
}
그리고 Main.cpp 에서는 GameLevel을 생성하도록 변경한다.
Engine::AddNewLevel<GameLevel>();
4. 맵 파일 읽기
맵 파일은 std::ifstream을 이용해 읽는다.
void GameLevel::LoadMap(const std::string& filename)
{
std::ifstream file(filename, std::ios::binary);
if (file.is_open() == false)
{
return;
}
file.seekg(0, std::ios::end);
const std::streamsize fileSize = file.tellg();
file.seekg(0, std::ios::beg);
std::string buffer(static_cast<size_t>(fileSize), '\0');
file.read(buffer.data(), fileSize);
// buffer 파싱
}
파일 전체를 하나의 문자열로 읽은 다음, 문자를 하나씩 순회하며 맵을 해석한다.
5. 문자 단위 파싱과 좌표 계산
맵 데이터를 읽을 때 현재 위치를 나타내는 x,y 좌표를 사용한다.
Vector2 position;
for (const char mapCharacter : buffer)
{
if (mapCharacter == '\r')
{
continue;
}
if (mapCharacter == '\n')
{
position.x = 0;
position.y++;
continue;
}
// 현재 문자 해석
position.x++;
}
Windows의 줄바꿈은 보통 \r\n 으로 구성된다.
\r을 무시하지 않으면 줄바꿈마다 x좌표가 한 칸 증가하게된다. 그 결과 다음 줄의 액터 위치가 어긋날 수 있다.
6. 문자와 액터 매핑
각 문자는 switch문을 사용해 액터 생성 로직으로 변환한다.
switch (mapCharacter)
{
case '#':
AddActor<Wall>(position);
break;
case '.':
AddActor<Ground>(position);
break;
case 'p':
AddActor<Ground>(position);
AddActor<Player>(position);
break;
case 'b':
AddActor<Ground>(position);
AddActor<Box>(position);
break;
case 't':
AddActor<Target>(position);
targetScore++;
break;
}
문자와 액터의 매핑 구조는 다음과 같다.
문자생성 액터
| # | Wall |
| . | Ground |
| p | Ground + Player |
| b | Ground + Box |
| t | Target |
7. 플레이어와 박스에 Ground를 함께 생성하는 이유
플레이어와 박스는 바닥 위에서 배치되는 이동 오브젝트다. 하나의 문자만 보고 플레이어나 박스만 생성하면 바닥이 존재하지 않게 된다.
렌더링 순서는 sortingOrder로 조정한다.
- Ground : 낮은 순서
- Box : 중간 순서
- Player : 높은 순서
8. 기본 액터 클래스 구성
클래스표시 문자색상정렬 순서
| Wall | # | 기본 색상 | 기본 |
| Ground | 빈 문자열 | 기본 색상 | 기본 |
| Player | P | 초록색 | 5 |
| Box | B | 빨간색 | 3 |
| Target | T | 파란색 | 기본 |
각 액터에는 기존 강의에서 구현한 커스텀 RTTI매크로도 적용한다.
class Player : public Actor
{
TYPE_DECLARATIONS(Player, Actor)
};'엔진프레임워크 만들기' 카테고리의 다른 글
| C++ 엔진프레임워크 - 10 커스텀 타입 시스템(RTTI) (0) | 2026.09.21 |
|---|---|
| C++ 엔진프레임워크 - 9 엔진설정 로드 시스템 (0) | 2026.09.15 |
| C++ 엔진프레임워크 - 8 2중 버퍼링 - 2 (0) | 2026.09.15 |
| C++ 엔진프레임워크 - 7 2중 버퍼링 (0) | 2026.09.10 |
| C++ 엔진프레임워크 - 6 기본 렌더링 2 (0) | 2026.09.09 |































