맵 에디터 작성 게임 프로그래밍

맵을 만들어 보자!!!!


1. 맵.

지금까지 만들어 본 에디터는 본편이 맵을 만들기 위한 전초과정이었습니다.
맵에디터는 모든 게임의 정보가 집적되어 있는 곳입니다.
바로 주인공이 뛰어다니고, 일정 이벤트를 겪고, 스토리를 진해하는 곳이기 때문입니다.
맵은 그럼으로써 게임에서 매우 중요한 위치를 가지고 있습니다.
맵에디터는 그래서 모든 에디터들의 중심되는 위치를 가지고있게 됩니다.
상용으로 나온 많은 게임제작툴을 보면 기본이 맵에디팅 화면인 것을 볼 수 있을겁니다.

맵은 왜 만드느냐? 하는 의문이 있는 분도 계실겁니다.
그냥 배경이라면, 배경용 그림을 스크립트에 의해 불러와서 이를 스크롤 시키면 끝나는 것이 아닌가? 하는 생각도 듭니다.
하지만, 맵은 단순히 그림만이 아니라는데 그 의미가 있겠습니다.
맵에 저장되는 정보의 대부분은 그림에 관한 부분입니다.
하지만, 충돌처리에 대한 부분과 주인공과 대화를 나눌 캐릭터에 대한 정보, 그리고 주인공이 겪을 이벤트에 대한 정보가 맵의 곳곳에 저장되게 됩니다. 이것은 게임내의 메인코드내에 적재할 수 있는 부분이 아닙니다.
확장성과 편집의 용이함을 위해서 에디터를 만들어 봅시다..^^
(* 프로그래머는 어떻게 보면 엄청 편한 직업입니다. 귀찮게 모든 데이터를 일일이 입력할 필요도 없고, 모든 맵을 만들거나 에디트 할 필요도 없이, 그냥 에디터를 만들어 주고, 그 포맷에 맞게 데이터들이 만들어져 오기만을 기다리면 되는거니까요..^^ 물론 RPG쪽이 특히 심한 경향이 있긴 합니다만..^^)

앞선 강좌에서 스프라이트를 배웠습니다.
스프라이트는 보통, 사람같은 캐릭터를 화면상에 출력할 때 사용되는 그림이지만, 맵에도 찍히게 됩니다. 이름하여 오브젝트란 개념으로 사용되어서 화면상에 찍히게 되지요.
맵에서 오브젝트라 함은..(저 혼자만의 정의일런지도 모르겟습니다만) 나무와같은 일정크기 이상의 높이를 갖는 물체를 뜻한다고 생각합니다. 또는 주인공캐릭터가 밀거나 당길수 있고, 이벤트에 의해 움직일수 있는 맵상의 물체를 오브젝트로 하는 사람도 있습니다. 하여간, 이 오브젝트는 스프라이트 형태로 화면에 출력되기 마련입니다.
그외에는 모든 것이 타일형태로써 존재합니다.
물론 타일기반 맵이라는 형태에서 뿐입니다.
요즘 보통게임들에서 많이 사용하는 방법은 타일조합에 의한 타일맵보다는 몇장의 큰그림들로 이루어진, 속칭 통짜맵이 사용되고 있습니다.
대표적인 예로 창세기전3나 발더스 게이트같은 류는 통짜맵을 사용한 경우입니다.
레이디안, Seal, 영웅전설 씨리즈, 또는 전략게임중 스타크래프트같은 류는 타일맵을 사용한 경우이죠.


* 타일조합방식의 장점 그리고 단점... ^^
타일조합방식은 무척 오래된 방식입니다. 가장 초창기 게임에서 그래픽을 사용한 게임이면 무조껀 타일조합방식을 썼습니다. 그때는 캐릭터 마져도 타일로 표현되던 시대라..^^
(어디가서 울티마 1~5까지 구해보세요. 캐릭터마져 움직이는 타일입니다. 크기도 똑같구.. --;)
역사가 긴만큼 사용상의 편리함이 있으리라 하지만, 원래 게임 만드는 사람들은 사용하기의 편리함보다는 기능의 많고 적음, 많은 표현이 가능한 점을 원하기 때문에 절대 편하지는 않습니다. 편하지 않다는 것이 단점이죠 뭐.^^
장점이라면, 적은 그림으로 꽤 다양한 맵을 구성할 수 있습니다.
그러면서 가림처리라든가, 계단 올라가기등, 상당히 구조적인 방법으로 그래픽을 표현 할 수 있습니다. 프로그래머들이 좋아하는 방법인데요...
계단을 정말 계단같이 올라가거나, 상자를 밟고 올라서거나(영웅전설3에서 가능하죠.), 상자를 밀어서 문을 막고, 집 옥상에서 뛰어내리고,, 하는 마치 3D게임을 보는 듯한 구조를 만들 수 있는 것이 타일맵 구조입니다. 단순타일구조가 아닌 높이가 있는 구조라는 것이 다르긴 하지만요.. 한마디로 타일맵의 장점은 그 막강한 확장성에 있습니다.
단점은, 그래픽에서 그만큼 그리기 힘들다는 것이죠. 모든 타일이 자연스럽게 연결되도록 제작하려면 상당한 노하우가 필요하기 때문입니다.
요즘은 이것을 극복한 방법으로 3D를 혼용해서 매핑식으로 하기도 한다고 합니다.
(녹스가 이런방법을 사용한 것으로 보이더군요)

*통짜맵(?) 방식의 장단점.
장점이라면, 일단, 그래픽파트가 편합니다. 그리고 좀더 화려하고 멋진 그래픽이 가능합니다.
타일방식의 답답함(?)에서 벗어나 부드럽고 유연한 그래픽을 표현 할 수있습니다.
단점이라면, 확장성이 전혀 없다는 것이겠죠. 통짜맵에는 추가정보다 들어가기 힘든 구조입니다.
계단을 올라가거나 하는 것도 일련의 뽀록(?)으로 속성처리등으로 하는 방식일뿐, 울티마나 여타 구조적인 맵을 사용하는 게임에서 보이는 맵은 절대 표현 불가능합니다.
단순 맵에서 나오는 모든 오브젝트는 그냥 그림일 뿐으로 표현될 뿐, 그 이상도 그 이하도 아닌 방식이 통짜맵 방식입니다.
프로그램입장에서 보면 편한대신 좀 답답하죠. 수정하기도 힘들고,, 맵에서 집에 조그만 탁자를 하나 놓으려고 해도 집전체를 다시 그려야 하니까요..(뭐 그래픽툴에서 한걸 불러와서 쓰긴 합니다만..)


2. 맵에 들어가는 것들.

앞서 설명한대로 여러 가지 확장성이 있는 타일방식과 그래픽이 화려한 통짜맵방식이 있는데, 제가 만들 에디터는 타일방식을 취합니다.
단, 확장은 여러분이 하시는 것이고, 전 여기서 에디터의 기본 프레임과 파일구조, 그리고 기본에 대한것만 강좌하도록 하겠습니다.^^
일단, 타일맵의 기본이 되는 것은 타일과 스프라이트입니다.
앞서 설명한대로, 오브젝트들은 대부분 스프라이트로 처리되고, 타일은 타일로 처리됩니다.
즉, 그래픽은 타일과 스프라이트가 사용됩니다.
그외에 기타정보가 추가된 것이 맵정보인데...
그렇다면 맵정보에는 무엇이 들어갈 수 있을까요?
먼저 가장 아래에 찍히는 타일정보가 들어갑니다. 이것을 '바닥' 이라고 부르겠습니다.
그리고 그위에 오브젝트와 캐릭터들이 찍힙니다.
오브젝트는 '오브젝트'
캐릭터에는 플레이어 캐릭터와 NPC가 포함되는데 통합해서 '캐릭터'라고 부르겠습니다.
캐릭터는 위에서 만든 애니메이션화일(txt화일)을 포함한 어떤 데이터를 가지는 그룹을 뜻합니다.
캐릭터에 대한 내용은 나중에 자세히 하도록 하고, 이번엔 일단 접어두죠.
그위에 가장 위에 찍히는 타일이 하나 더 있습니다. 이 타일은 특징적으로 투명색을 가지는 타일입니다. 보통 게임에서 집의 뒤를 돌아가면 집이 사람을 가리는 경우가 있을겁니다.
이런식으로 100% 사람을 가리는 경우는 타일을 사용하게 되는데, 이것을 '천정'이라고 부르겠습니다.
그외에 속성을 가져야 합니다. 간단한 속성으로는 갈수 있는 곳, 없는 곳이 있겠지만, 그 외에도 여러 가지 속성이 추가 될 수 있습니다.
건드리면 움직이는 문과 상자의 속성같은 것이 대표적인 예가 되겠네요.
또 무엇이 필요할까요? 흐음~~
이벤트가 필요하겠죠. 어떤 특정위치를 플레이어 캐릭터가 지나갈 때 해당 이벤트를 실행하도록 하는 겁니다.

그럼 대강의 표현식으로 표현해 보자면
struct
{
int 바닥
int 오브젝트
int 캐릭터
int 천장
char 속성
int 이벤트
};

이렇게 되겠네요~..
이런 정보가 x,y크기만큼 있겠죠.^^
타일의 크기는 32*32정도를 사용하고, 이 타일이 한화면에 20*15개 찍히면 한화면이 가득차게 됩니다.
보통 일반적인 게임에서 한번에 읽는 맵의 크기는 5*5정도의 화면분을 읽어들입니다.
즉 100*75정도의 맵을 생성해내겠죠.
여기서는 맵의 크기를 유동적으로 생성하는 부분을 배재하고 한정된 크기만으로 만들 수 있도록 하겠습니다. (유동적이 된다면 링크드 리스트를 두 번 사용해야 합니다. 코딩의 복잡함과 해석의 어려움은 정말... 그냥 고정된 크기만 만듭시다. 훌쩍.)
여기서 좀 더 확장한다면, 작은맵, 중간맵, 큰맵 정도로 세분화 된 맵을 만들어 낼 수 있을겁니다.



############# 타일을 만들자! #############################

그럼 먼저 맵에서 쓰일 타일을 만들어 봅시다.
타일은 32*32 또는 64*64등의 정방형 크기를 가지는 것이 보통입니다.
화면의 해상도가 640*480이므로 640과480의 공약수중 하나가 타일의 크기가 될겁니다.
저해상도 시절엔 보통 8*8 크기이고, 그래픽이 좀 뛰어난 게임들은 16*16의 타일을 사용했습니다.
요즘 타일은 보통 16*16이고, 조금 큰 타일을 사용하는 게임이 32*32입니다.
통짜맵은 640*480짜리 타일을 사용한다고 생각하심 되겠네요^^;;;;

그럼 타일을 만들어 봅시다.
타일을 만들기 위해서는 먼저 소스를 불러와야 합니다.
일반적으로 bmp화일을 부르는 것을 앞에서 알아봤으니, 이번에도 bmp화일을 불러서 화면의 일정영역을 잘라서 타일화 시키는 것을 만들어 보겠습니다.
타일은 하나의 bmp파일보다는 여러개의 bmp파일로 만드는 것이 보통입니다.
맵은 보통 5~10개정도의 화면을 차지하기 때문에 bmp파일로 3~4개정도 쓰이기 마련입니다.
그럼, 이 bmp화일들을 차례로 불러서 불러서 til이라는 확장자를 가지는 Tile파일을 만드는 간단한 컴파일러(?)를 만들어 보겠습니다.
기본적인 생김새는 이미 만들어본 스프라이트 에디터와 동일합니다.
파일의 내부적인 생김새마져도 똑같이 만들 생각입니다.
이미 Sprite객체에 PutTile이란 함수가 존재함을 아실 겁니다.
그리고 Dib객체내에 ImageCreate란 함수가 있음을 아실겁니다.
ImageCreate함수는 다른Dib객체를 받아서 그중의 Rect영역을 자신의 Bmp로 만드는 함수입니다.
그럼으로써, 하나의 bmp에서 여러개의 조각조각 타일로 나뉜 bmp화일을 생성할 수 있는겁니다.

그럼 CSprite객체중에 추가된 부분을 보시겠습니다.

void CSprite::LoadBmp(Sprite* Spr, CDib* pDib, RECT rect, DWORD ColorKeyL,
DWORD ColorKeyH)
{
bool Append=false;
if(!Spr)
{
Spr = new Sprite;
Append=true;
}
Spr->m_pDib->ImageCreate(pDib, rect);
입력된 Dib객체에서 rect영역만큼을 복사해서 새로운 bmp Dib를 생성합니다.

CreateSurface(Spr);
서피스를 생성합니다. (함수로 빠졌죠? ^^)
m_pDDraw->SetColorKeys(Spr->m_lpDS, ColorKeyL, ColorKeyH);
Spr->m_ColorKeyL=ColorKeyL;
Spr->m_ColorKeyH=ColorKeyH;

// 링크드 리스트에 추가한다.
if(Append)
{
Spr->m_AbsX = - Spr->m_pDib->GetWidth()/2;
Spr->m_AbsY = - Spr->m_pDib->GetHeight();
if(m_SprHead==NULL)
m_SprHead=Spr;
if(m_SprTail)
{
m_SprTail->m_Next = Spr;
Spr->m_Prev = m_SprTail;
}
m_SprTail = Spr;
}
나머지 부분은 동일합니다.
}

함수의 다형성을 사용해서 함수 이름은 그냥 동일하게 사용했습니다.
그것이 중요한 문제가 아니고... 타일이랑 스프라이트는 서로 그림이란 점에서 동일합니다. 그런면에서 저는 동일한 객체로써 생성하도록 했습니다.
타일도 요즘같아서는 크기가 일정한 스프라이트 정도로 확장되어서 사용되고 있습니다.
단, 출력 루틴중에 Sprite에서 사용되었던, 기준점 이동을 전혀 사용하지 않습니다.
타일은 따로 기준점이 필요없는 고정된 크기를 가지기 때문입니다.
타일자체로는 애니메이션등이 구현되기 힘들기 때문에(물론 이것을 하는 게임도 꽤 많습니다.) 기준점루틴이 빠져도 상관이 없는겁니다.
하지만, 스프라이트와 같은 구조가 되면서 필요없는 약간의 메모리 낭비가 있겠군요. 하지만, 이미지를 많이 사용하지 않는 이번 강좌의 예제에서는 별반 차이가 없을것이라고 생각됩니다.

나머지 부분은 Sprite에디터와 동일한 구성과 함수를 가지고 있습니다.
(상속을 받아서 할까 생각을 했을 정도로 같은 구조입니다. 흑.)
하지만, 다른 부분이 있죠. 바로 Bmp로드 부분과 화면 출력부분입니다.

void CTileEdit::OnLoadBitmap()
{
char Name[256];
wsprintf(Name,"untitled.bmp");
if(!m_pSprite)
{
m_pSprite = new CSprite(m_pDDraw);
}
스프라이트 객체(여기선 타일이죠)가 없으면 새로 생성합니다.
CSprite* pSprite = new CSprite(m_pDDraw);
임시 스프라이트 객체입니다. 읽기위해서만 사용됩니다.
RECT rect;
if(WinOpenFile(Name,"BMP 화일 *.bmp\0;*.bmp\0","BMP를 읽기"))
{
pSprite->LoadBmp(NULL, Name, 0, 0);
임시 스프라이트객체에 bmp화일을 읽습니다.
for(int j=0; jGetSprite(0)->m_pDib->GetHeight()/32; j++)
타일의 가로,세로 크기가 32,32이므로 그만큼 루프를 돕니다.
즉, 세로크기가 480이면 15번 루프를 돕니다.
{
for(int i=0; iGetSprite(0)->m_pDib->GetWidth()/32; I++)
가로크기도 마찬가지 640이면 20번 루프~
{
SetRect(&rect, i*32, j*32, i*32+32, j*32+32);
임시 스프라이트에서 복사될 영역을 설정합니다.
처음부터 가로로 잘리겠군요.
m_pSprite->LoadBmp(NULL,
pSprite->GetSprite(0)->m_pDib, rect, 0, 0);
실제 메모리에 복사합니다.
}
}
}
m_pSpr = m_pSprite->m_SprHead;
delete pSprite;
임시 스프라이트 객체를 소멸시킵니다.
OnPaint();
}


다음은 바뀐 화면 출력 부분입니다.

void CTileEdit::OnPaint()
{
m_pDDraw->FillSurface(m_pDDraw->GetSurfaceBack(), 0);
Sprite* pSpr = m_pSpr;
if(pSpr)
{
for(int j=0; j<10; j++)
for(int i=0; i<10; i++)
{
if(pSpr)
{
pSpr->PutTileEx(m_pDDraw->GetSurfaceBack(), i*32, j*32);
pSpr = pSpr->m_Next;
}
}
}
m_pDDraw->FlipSurfaces(15, 15);
}
처음부터 가로로 찍어나가는 함수입니다.
달라졌다지만, 별다르게 달라진 점은 없습니다.
















최종적으로 만들어진 타일 에디터입니다.
변변찮은 기능은 없지만, 여러개의 비트맵을 불러서 조각조각 타일로 만드는 기능을 가지고 있는 원초적인 타일에디터입니다.
흐음~ 좀 더 기능을 개선시킨다면, 중복타일 제거기능을 넣어보셔도 좋을겁니다.
즉, 여러개의 bmp 파일을 불렀을 경우 중복되는 그림이 있을 경우가 있을겁니다.
그런경우가 많을 수도 있고, 적을 수도 있는데, 이거은 그래픽하는 사람들이 얼마나 노가다를 해주느냐에 따라 다른겁니다.
제가 회사에서 만들었던 툴은 중복타일 제거기능을 가지고 있습니다.
기능의 구현은 간단합니다.
처음부터 맨마지막까지 문자열비교를 해서 완벽히 같으면 그 타일을 제거하는 겁니다.
물론 같은놈끼리 비교하는 닭짓을 하면 안되죠.. --;

소스는 중복되는 부분이 많아서 여기에 뿌리기 보다는 따로 다운받을 수 있도록 하겠습니다.


############# 그럼 계속해서 맵 에디터를 만들자!!! ##############

중간에 꼭 만들어야 하는 타일에디터를 빼먹어서 잠깐 타일에디터를 만들어 봤습니다.
타일이 왜 필요한지는 위에서 다 알아봤고요..
타일을 만들기까지했으니, 이번엔 맵에디터를 만들어 보겠습니다.
맵에디터는 일단 두 개의 윈도우를 생성시킬 겁니다.
왜냐? 한쪽은 소스이고, 한쪽은 만들어질 맵입니다.

실제적으로 화면에 보이는 부분을 만들기에 앞서, 구조체를 먼저 알아봅시다.,
위에서 잠깐 쓴 것에 따라서 만든것인데요...
struct Chip
{
int m_Badak;
int m_Object;
int m_Character;
int m_Chun;
char m_Attrib;
int m_Event;
};
이것이 타일하나에 대한 정보입니다.
물론 오브젝트와 캐릭터가 같은 타일위에 올라가서 찍힐수는 없을겁니다. 하지만, 가끔씩 뚫리는 오브젝트 같은 것이 존재 할 수도 있기 때문에 오브젝트와 캐릭터를 따로 분리해서 저장합니다.

위에서 일단 맵의 크기는 고정시킨다고 했죠?
맘대로 넓히고 줄일수 있는 구조를 일반맵에서 구현한다면, 이중링크드리스트를 사용해야해서 만들기 무척 까다롭습니다.
하지만 고정일때는 상대적으로 만들기 쉽죠.

Chip Data[dMapYSize][dMapXSize];

그래서 이렇게 선언했습니다.
dMapXSize와 dMapYSize 는 일단 100으로 define 되어있습니다.


그담에 알아볼 부분은 저장하는 부분입니다.
읽는 것은 저장의 반대이기 때문에 저장만 정확히 알면 바로 만들 수 있습니다.
void CMaps::SaveFile(char* fname)
{
FILE* fp;
fp = fopen(fname,"wb");

int temp = dMapXSize;
fwrite(&temp, sizeof(int), 1, fp);
temp = dMapYSize;
fwrite(&temp, sizeof(int), 1, fp);
for(int j=0; j for(int i=0; i {
fwrite(&m_Data[j][i], sizeof(struct Chip), 1, fp);
}
fwrite(m_TileName, 256, 1, fp);
fwrite(m_SpriteName, 256, 1, fp);
fclose(fp);
}
일단 맵의 확장성을 위해 맵의 가로, 세로크기를 저장합니다.
그리고 나서, Chip을 하나 하나 루프를 돌면서 저장합니다.
그후에 맵을 만드는데 사용된 타일화일의 이름과 스프라이트 파일의 이름을 저장합니다.
그렇게 되면, 모든 맵의 저장은 끝난 것입니다.

맵구조체를 선언하고 저장하는 루틴을 알아 보았습니다.
그러면 다음은 맵구조체를 상속받은 에디터를 알아보겟습니다.
에디터의 구조는 두 개의 창을 띄웁니다. 한 개의 창은 소스창이고 한 개의 창은 편집창입니다.

CWinApp 객체를 두 개 사용하여야 하고, CDirectDraw객체도 각 윈도우에 하나씩 만들어서 사용해야 합니다.

void CMapEdit::Init(HINSTANCE hinst, HWND hwnd, DLGPROC wProc1, DLGPROC wProc2)
{
//MessageBox(hwnd, "11","11",MB_OK);
m_TileEdit->m_hWnd =
CreateDialog((HINSTANCE)hinst, MAKEINTRESOURCE(IDD_TILE), hwnd, wProc2);
m_MapEdit->m_hWnd =
CreateDialog((HINSTANCE)hinst, MAKEINTRESOURCE(IDD_MAP), hwnd, wProc1);
ShowWindow(m_MapEdit->m_hWnd, SW_SHOW);
ShowWindow(m_TileEdit->m_hWnd, SW_SHOW);
wsprintf(m_MapEdit->m_szAppName,"Map Editer");
wsprintf(m_TileEdit->m_szAppName,"Tile Editer");
m_TileDDraw = new CDirectDraw();
m_MapDDraw = new CDirectDraw();
m_TileDDraw->DirectDrawInit(m_TileEdit->m_hWnd, dXSize, dYSize);
m_MapDDraw->DirectDrawInit(m_MapEdit->m_hWnd, dXSize, dYSize);
}

위의 함수는 두 개의 다이얼로그 기반 윈도우를 화면상에 띄우는 함수입니다.
간단하게 구현되었죠? 두 개의 다이렉트드로우 객체도 생성해서 각 윈도우에 하나씩 초기화 시켜주면 이 함수의 일은 다 한것입니다.

그럼 타일창에 타일을 찍는 함수를 알아볼까요?

void CMapEdit::TileOnPaint()
{
HDC hDc;
Sprite* Spr;
int i, j;
if(m_TileDDraw)
{
m_TileDDraw->FillSurface(m_TileDDraw->GetSurfaceBack(), 0);
화면을 검게 깨끗하게 칠합니다.
if(m_SrcMode == dTile)
{
if(m_Tile)
{
m_Tile->m_pDDraw = m_TileDDraw;
타일을 찍을 다이렉트 드로우 백버퍼를 지정합니다. 이번엔 타일윈도우에 생성된 다이렉트 드로우입니다.
Spr = m_Tile->GetSprite(m_CurTile);
현재의 타일을 받습니다.
for(j=0; j<10; j++)
{
for(i=0; i<15; i++)
{
if(Spr)
{
Spr->PutTileEx(m_TileDDraw->GetSurfaceBack(), i*32, j*32);
Spr = Spr->m_Next;
가로,세로로 루프를 돌면서 타일을 찍습니다.
}

}
}

m_TileDDraw->GetSurfaceBack()->GetDC(&hDc);
GDI를 사용해서 화면에 선을 긋기 위해 DC를 받아냅니다.
for(i = 0; i<10; i++)
{
MoveToEx(hDc, 0, i*32,NULL);
LineTo(hDc, 480, i*32);
}
for(i=0; i<15; i++)
{
MoveToEx(hDc, i*32, 0, NULL);
LineTo(hDc, i*32, 320);
}
가로,세로 선을 그려줍니다. 타일간의 경계선을 만들어 주는 겁니다.
m_TileDDraw->GetSurfaceBack()->ReleaseDC(hDc);
}
}
else
{
if(m_Object)
{
m_Object->m_pDDraw = m_TileDDraw;
m_Object->PutSpritesEx(m_CurObject, 240, 300);
}
만약 오브젝트 출력 모드라면 스프라이트를 출력합니다. 이때는 하나만 출력합니다.
}
m_TileDDraw->FlipSurfaces(10, 10);
}
타일모드와 오브젝트 모드를 구별해주는 체크박스를 화면에 표시해 줍니다.
if(m_SrcMode==dTile)
{
CheckDlgButton(m_TileEdit->m_hWnd, IDC_CHECK2, BST_CHECKED);
CheckDlgButton(m_TileEdit->m_hWnd, IDC_CHECK5,
BST_UNCHECKED);
}
else
{
CheckDlgButton(m_TileEdit->m_hWnd, IDC_CHECK2,
BST_UNCHECKED);
CheckDlgButton(m_TileEdit->m_hWnd, IDC_CHECK5, BST_CHECKED);
}
}



타일창의 모습은 위와 같습니다. Tile이란 버튼은 타일파일을 부를 때 사용하는 버튼이고, 아래의 Tile 체크박스와 Sprite체크박스는 소스가 타일인지 스프라이트인지를 결정할수 있도록 만들어진 체크박스입니다.
만약 위 그림처럼 Tile에 체크가 되어있다면, 타일을 불러서 타일을 편집창에 찍을 것이고,
Sprite가 체크되어있다면, 스프라이트를 불러서 편집창에 찍을겁니다.


편집창입니다.
Badak이 체크되어있으면, 바닥을 편집하고, Object가 체크되어있으면 오브젝트를 편집하게 됩니다.


void CMapEdit::OnTileClick(int xpos, int ypos)
{
if(xpos>10 && xpos < 480 && ypos > 10 && ypos <320 && m_Tile)
{
if(m_SrcMode == dTile)
{
xpos -=10;
ypos -=10;
xpos = xpos/32;
ypos = ypos/32;
m_ClipMode = m_SrcMode;
m_ClipData = m_CurTile+xpos+ypos*15;
}
else
{
m_ClipMode = m_SrcMode;
m_ClipData = m_CurObject;
}
}
}
핵심부분인... 마우스 클릭에 따라서 소스를 카피하는 부분입니다.
맵은 앞에서 말한대로 인덱스번호만을 저장하는 형식이기 때문에, 여기서도 메모리 버퍼에 타일인덱스번호만을 저장합니다. 또, 소스가 타일인지, 스프라이트인지를 결정하는 변수도 저장하게 됩니다.
타일일 경우 x좌표를 32로 나누고, y좌표를 32로 나눈후에 x+y*15+Cur라고 하면, 현재 클릭한 곳에 있는 타일의 번호를 알아낼 수 있습니다.

void CMapEdit::OnMapClick(int xpos, int ypos)
{
if(xpos>10 && xpos < 480 && ypos > 10 && ypos <320 && m_Tile)
{
xpos -=10;
ypos -=10;
xpos = xpos/32;
ypos = ypos/32;
if(xpos+m_CurXpos >= dMapXSize ||
ypos+m_CurYpos >= dMapYSize)return;
if(m_ClipMode==dTile && m_EditMode==dBadak )
{
m_Data[ypos+m_CurYpos][xpos+m_CurXpos].m_Badak =
m_ClipData;
}
if(m_ClipMode==dTile && m_EditMode==dChun)
{
m_Data[ypos+m_CurYpos][xpos+m_CurXpos].m_Chun =
m_ClipData;
}
if(m_ClipMode == dObject && m_EditMode == dObject)
{
m_Data[ypos+m_CurYpos][xpos+m_CurXpos].m_Object =
m_ClipData;
}
OnPaint();
}
}

메모리 버퍼에 들어간 타일번호 또는 스프라이트 번호를 편집창에 옮겨주는 함수입니다.
역시 마우스가 클릭되면, (WM_LBUTTONUP 메시지를 받으면), 해당좌표를 계산해서 맵에 찍게됩니다.

에공~ 대강 끝났습니다.
나머지는 대부분 앞에서 설명한 부분들입니다.
자세한 것은 소스를 조금 분석해 보시면 알게 될것입니다.
소스는 따로 올라갑니다.
그럼.
null

덧글

  • hyungyon 2004/05/11 17:47 # 답글

    너무길어서 읽을 맛이..-ㅂ -
  • ohmyzone 2004/07/06 15:40 # 답글

    스크랩 할래욤~!
  • zero2941 2004/10/12 11:14 # 답글

    질문있는데여~
    아이템은 어떻게 해여??
    2D도 아이템이 바뀌면 캐릭터 애니도 바뀌는 건 도대체 어떻게 하는 건지 알려주세요~^^
  • 貸兜避矢 2004/10/12 16:54 # 답글

    보통 아이템이 바뀌면 캐릭터자체가 바뀌게 됩니다. 2D는 애니메이션도 미리 그림으로 만들어 둘수 밖에 없기때문에, 그냥 교체하는거죠. 디아블로같은경우가 그렇습니다. 인터넷상에 나오는 툴로 데이터들을 보시면 알게 될겁니다.(거의 모든 조합을 다 만들어 두었더군요) 3D의 경우에는 좀 더 편하게 조합할 수 있습니다.
  • zero2941 2004/10/15 11:43 # 답글

    헉... 그렇다는 것은 엄청난 노가다를 ㅠㅠ 3d는 왜 좀더 편하게 조합할 수 있는 거예요??
  • 貸兜避矢 2004/10/15 13:52 # 답글

    3D는 캐릭터를 조합해서 출력하는것이 가능하기 때문이죠. 칼, 사람, 방패를 따로 만들어놓고 출력할때 조합해서 출력할 수 있습니다. 2D도 이론상으로는 가능하지만, 3D는 별다른 노력없이 가능하기때문이랄까요.. 하여간 3D를 조금 공부해보시면 압니다.
  • zero2941 2004/10/16 20:51 # 답글

    이론상으론 가능하다면 한번 시도를ㅎㅎ 근데 아직 c++이 서툴러서...
  • 貸兜避矢 2004/10/17 00:57 # 답글

    그정도 노력이면 3D를 공부하시는 편이 나을듯 합니다. 엄청난 노가다를 요구하기때문이죠.
  • zero2941 2004/10/17 19:22 # 답글

    헤헤^^;; 3d는 왠지 정이 안가서.. 지금 만드려는 것도 2d캐릭에 3d배경으로 만들 예정이예요
댓글 입력 영역