타일맵 툴 만들기 / 타일맵 파일 저장,로드기능 만들기

2025. 4. 8. 23:35원티드_언리얼RPG 2기/WinApi

 

타일맵 툴 만들기

 

타일맵 에디터에서 위치와 인덱스 계산은 크게 두 부분으로 나눌 수 있다

  1. 샘플 타일 영역에서의 선택 - 사용자가 배치할 타일을 고르는 부분
  2. 메인 맵 영역에서의 배치 - 선택한 타일을 실제 맵에 배치하는 부분

각 문제를 더 작은 단계로 분해해보자

샘플 타일 영역 처리 단계

  1. 영역 확인: 마우스가 샘플 타일 영역 안에 있는지 확인
  2. 상대 좌표 계산: 샘플 타일 영역 내에서의 마우스 위치 계산
  3. 타일 인덱스 계산: 상대 좌표를 타일 크기로 나누어 타일 인덱스 계산

메인 맵 영역 처리 단계

  1. 영역 확인: 마우스가 메인 맵 영역 안에 있는지 확인
  2. 타일 위치 계산: 메인 맵 내에서 마우스가 위치한 타일 좌표 계산
  3. 타일 정보 업데이트: 계산된 위치에 선택된 타일 정보 적용

윈도우좌표계는 좌측상단기준으로 (0,0)이다. 따라서 마우스 위치로 상대좌표를 계산하려면

상대영역 사각형의 left 를뺀값이 x좌표 , 사각형의 top을 뺀 값이 y좌표이다. 

 

if (PtInRect(&rcSampleTile, g_ptMouse))  // 샘플 타일 영역 확인
{
    if (KeyManager::GetInstance()->IsOnceKeyDown(VK_LBUTTON))
    {
        int posX = g_ptMouse.x - rcSampleTile.left;  // 상대 X 좌표 계산
        int posY = g_ptMouse.y - rcSampleTile.top;   // 상대 Y 좌표 계산
        selectedTile.x = posX / TILE_SIZE;  // X 인덱스 계산
        selectedTile.y = posY / TILE_SIZE;  // Y 인덱스 계산
    }
}
else if (PtInRect(&rcMain, g_ptMouse))  // 메인 맵 영역 확인
{
    if (KeyManager::GetInstance()->IsStayKeyDown(VK_LBUTTON))
    {
        int posX = g_ptMouse.x;  // 절대 X 좌표
        int posY = g_ptMouse.y;  // 절대 Y 좌표
        int tileX = posX / TILE_SIZE;  // 타일 X 위치 계산
        int tileY = posY / TILE_SIZE;  // 타일 Y 위치 계산
        tileInfo[tileY * TILE_X + tileX].frameX = selectedTile.x;  // 선택된 타일의 X 인덱스 적용
        tileInfo[tileY * TILE_X + tileX].frameY = selectedTile.y;  // 선택된 타일의 Y 인덱스 적용
    }
}

 

마우스 위치가 샘플타일 영역에 있고 왼쪽버튼을 누르면 if문이 실행, 메인영역에있고 마우스 왼쪽버튼이 눌려있는 상태면 else if문이 실행된다. 

 

 

 

타일맵 파일 저장,로드기능 만들기

 

 

Windows 환경에서의 파일 입출력은 크게 두 가지 방식으로 할 수 있다:

  1. C++ 스트림(fstream) 사용
  2. Windows API 사용

2주차 강의에 C++에서 제공하는 라이브러리 <fstream>, <ostream> 에서 파일입출력을 관리하는 실습을 했다. 

이번에는 WinApi에서 제공하는 파일입출력함수를 써보려고 한다.

 

Windows API 파일 입출력 기본 단계:

  1. 파일 열기/생성: CreateFile() 함수 사용
  2. 데이터 읽기/쓰기: ReadFile()/WriteFile() 함수 사용
  3. 파일 닫기: CloseHandle() 함수 사용

 

CreateFile함수의 원형을 보자

HANDLE CreateFile(
    LPCTSTR lpFileName,           // 파일 이름
    DWORD dwDesiredAccess,        // 접근 모드 (읽기/쓰기)
    DWORD dwShareMode,            // 공유 모드
    LPSECURITY_ATTRIBUTES lpSecurityAttributes, // 보안 속성
    DWORD dwCreationDisposition,  // 생성 방식
    DWORD dwFlagsAndAttributes,   // 파일 속성
    HANDLE hTemplateFile          // 템플릿 파일 핸들
);

CreateFile은 파일 Create, Open, Write 등의 동작을 할 때 호출되는 함수로 리턴형이 HANDLE이다.

 

주요 매개변수:

lpFileName: 파일 경로 및 이름

dwDesiredAccess:

  • GENERIC_READ: 읽기 전용
  • GENERIC_WRITE: 쓰기 전용 

dwCreationDisposition:

  • CREATE_ALWAYS: 항상 새 파일 생성, 기존 파일 덮어쓰기
  • OPEN_EXISTING: 기존 파일 열기

 

Write / Read file함수의 원형

BOOL WriteFile(
    HANDLE hFile,                 // 파일 핸들
    LPCVOID lpBuffer,             // 쓸 데이터 버퍼
    DWORD nNumberOfBytesToWrite,  // 쓸 바이트 수
    LPDWORD lpNumberOfBytesWritten, // 실제 쓴 바이트 수
    LPOVERLAPPED lpOverlapped     // 비동기 작업용 (보통 NULL)
);

BOOL ReadFile(
    HANDLE hFile,                 // 파일 핸들
    LPVOID lpBuffer,              // 읽어올 버퍼
    DWORD nNumberOfBytesToRead,   // 읽을 바이트 수
    LPDWORD lpNumberOfBytesRead,  // 실제 읽은 바이트 수
    LPOVERLAPPED lpOverlapped     // 비동기 작업용 (보통 NULL)
);

 

 

파일을 저장하는 Save()함수

void TilemapTool::Save()
{
    // 파일 생성
    HANDLE hFile = CreateFile(
        L"TileMapData.dat", GENERIC_WRITE, 0, NULL,
        CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
    if (hFile == INVALID_HANDLE_VALUE)
    {
        MessageBox(g_hWnd, TEXT("파일 생성 실패"), TEXT("경고"), MB_OK);
        return;
    }
    DWORD dwByte = 0;
    // 데이터 쓰기
    WriteFile(hFile, tileInfo, sizeof(tileInfo), &dwByte, NULL);
    // 파일 닫기
    CloseHandle(hFile);
}


 TileMapData.dat 라는 이름으로 기존파일을 덮어쓰는 쓰기전용 파일을 만들었다.  

 

저장된 파일을 불러오는 Load()함수

 

void TilemapTool::Load()
{    
    // 파일 열기
    HANDLE hFile = CreateFile(
        L"TileMapData.dat", GENERIC_READ, 0, NULL,
        OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
    if (hFile == INVALID_HANDLE_VALUE)
    {
        MessageBox(g_hWnd, TEXT("파일 열기 실패"), TEXT("경고"), MB_OK);
        return;
    }
    DWORD dwByte = 0;
    // 데이터 읽기
    ReadFile(hFile, tileInfo, sizeof(tileInfo), &dwByte, NULL);
    // 파일 닫기
    CloseHandle(hFile);
}

 

TileMapData.dat 라는 이름의 기존파일을 여는 읽기전용 파일을 만들었다.  

궁금증: Load함수는 이미 저장된 파일을 읽어오는데 CreateFile()을 해야할까?

 

정답: Windows API에서 파일을 다룰 때는 모든 파일 작업(읽기/쓰기 모두)이 파일을 먼저 "열어야" 가능, 그리고 파일을 여는 함수가 바로 CreateFile()이다.

 

CreateFile()은 이름과 다르게 새 파일 생성/ 기존 파일 열기/ 디바이스 열기(파일 뿐아니라 장치도 열수있다) 다양한 기능을 할 수 있다. 즉 파일을 생성한다기보다 파일 핸들을 생성하는 함수라고 생각하는게 좋다. 파일을 읽으려면 먼저 파일에 접근할 수 있는 "핸들(손잡이)"이 필요한데, 그 핸들을 얻는 과정이 CreateFile()인 것이다.