창작에 관련된 질문이나 간단한 팁, 예제를 올리는 곳
2편 : 기본적인 캐릭터 움직임
일단 강좌를 시작하기 전에 2가지 사항을 이야기 하겠습니다.
사실 안 읽으셔도 별로 상관없습니다.
/*------------------------------------ 주석인 셈입니다. -----------------------------------------------------------
1. 흥크립트 자동 대화창 기능에서 메모리 누수 현상이 발견되었습니다
치명적인 것은 아니지만 집의 메모리가 128MB이하라면 곤란할 수도?
처음에는 매 출력마다 대화창을 자동으로 크게에 맞게 그리는 식이었는데
속도 낭비가 너무 심해서 메모리를 할당해 미리 그린 후 빠르게 출력하게 했습니다.
그런데 어디가 잘못되었는지 계속해서 조금씩 메모리가 낭비되더군요.
1) 어쨌든 강좌를 하다보면 이것 저것 라이브러리의 문제점이 발견될지도 모르고
2) 아직은 대화창에 관련된 강좌는 하지 않을 것이고
3) 저 메모리 누수로 컴퓨터 다운 시키려면 상당히 대규모 게임을 만들어야 할 테니 발생할 확률도 적고
4) 자동대화창 기능을 그냥 없애고 싶지 않아서 손 볼려는 데, 시간이 꽤 걸릴 듯
이런 이유로 일단은 덮어 두겠습니다.
2. 과거에 문D라이브 강좌를 한 적이 있습니다.
1편
http://blog.naver.com/kfgenius.do?Redirect=Log&logNo=130014444939
2편
http://blog.naver.com/kfgenius.do?Redirect=Log&logNo=130014480937
3편
http://blog.naver.com/kfgenius.do?Redirect=Log&logNo=130014567046
4편
http://blog.naver.com/kfgenius.do?Redirect=Log&logNo=130014707401
대화창 클래스(CTextDlg) 빼고는 지금 버전에 그대로 적용되니
안 해보신 분은 이걸 연습해 보시는 것도 좋습니다.
//------------------------------------------------------------------------------------------------------------------------*/
그럼 다시 본론으로 돌아와서 더블 드래곤 같은 액션 게임 만들기 입니다.
Source2.zip
이걸 받아서 MoonD.sln이 있는 폴더에 풉니다.
하위 폴더 만드시지 마시고 그 자리에 풀어 주십시오.
원래는 Mayday님이 만드신 무투를 그대로 구현하는 게 쉬울 것 같아서 그걸 보여 드리려고 했는데
알고보니 그래픽 파일이 모두 xyz으로 되어 있어서 일단 접어두고,
이 샘플 소스로 하겠습니다. 여기 배경은 알만툴 것이고 그림은 독고진님이 그리셨던 건데...
뭐 설마 이거 가지고 뭐라고 하시겠습니까?
Mayday님은 혹시 괜찮다면 무투 소스 좀 보내주세요.
무투 그대로 재현하는 것을 강좌 3부터 해보도록 하죠.
일단 소스를 어디에 적어야 하나?
강좌1에 있었던 Sample에서 MoonD.sln을 더블클릭해서 프로젝트를 엽시다.
일단 main.cpp로 가야하는데 아무것도 안 떠 있다면...
화면 왼쪽이나 오른쪽 구석에 있을 솔루션 탐색기를 선택해서
main.cpp를 더블 클릭하십시오.
만약 솔루션 탐색기가 안 보이면 보기 메뉴에서 - 솔루션 탐색기를 선택하십시오.
VC++ 툴을 다루는 것은 그다지 어렵지 않으니 잘 익혀두십시오.
제가 하나하나 설명하기에 지면 낭비가 심할 듯 싶군요.
자, 이제 main.cpp가 열렸다면 고쳐야 할 부분은 친절하게도 주석 (일종의 설명으로 컴파일때는 처리되지 않고
코드를 작성할 때만 보여서 코드의 이해를 돕는 문장)이 달려있습니다.
//////////////////////////////////////////////////
//메인코드
//////////////////////////////////////////////////
int main(char* arg[])
{
//창생성
if(!MainInitialize("Sample", TRUE, FALSE, window_mode))return 0;
//윈도우창이동
if(window_mode)
{
jdd->OnMove(100, 100);
SetCursor(LoadCursor(0, IDC_ARROW));
}
//메인실행
while(!GetKey(vkey_esc))
{
if(!ProcessMessage())break;
//이곳에게임을만듭니다.
jdd->Render();
}
//정리하고끝내기
jdd->DeleteFont(global_font);
return 0;
}
바로 빨간 저 부분이지요.
저 주석은 지워버리고 새로운 코드를 적어봅시다.
팩맨 만들어 보신 분 있다면 이건 중복이겠지만 복습치고 해봅시다.
int main(char* arg[])
{
//창생성
if(!MainInitialize("Sample", TRUE, FALSE, window_mode))return 0;
//윈도우창이동
if(window_mode)
{
jdd->OnMove(100, 100);
SetCursor(LoadCursor(0, IDC_ARROW));
}
jdd->LoadPicture("배경", "던젼6.jpg", NULL, true);
jdd->LoadPicture("걷기1", "w1.gif", NULL, true);
jdd->LoadPicture("걷기2", "w2.gif", NULL, true);
jdd->LoadPicture("걷기3", "w3.gif", NULL, true);
//메인실행
while(!GetKey(vkey_esc))
{
if(!ProcessMessage())break;
jdd->DrawPicture(backbuffer, "배경", 0, 0, NULL);
jdd->DrawPicture(backbuffer, "걷기1", 100, 100, NULL);
jdd->Render();
}
//정리하고끝내기
jdd->DeleteFont(global_font);
return 0;
}
간단하게 그림을 읽어 들이고(LoadPicture) 그림을 출력(DrawPictrue)하는 겁니다.
각각 어떻게 쓰는지 봅시다.
jdd->LoadPicture("그림 이름", "파일명", NULL, true);
식으로 씁니다.
뒤에 NULL과 true는 신경 끄시고 그냥 저렇게만 적어 주면 됩니다.
아마 저걸 알 필요는 없을 겁니다.
ID값은 프로그램 내에서 저 그림에 부여하는 이름입니다.
나중에 저 그림을 부를 때 쓸 이름이죠.
그리고 뒤에는 파일명, 그림파일의 지금 현재 작업 폴더로 부터 상대 경로를 적어주면 됩니다.
절대 경로 적어주셔도 먹힙니다.
단, 다른 컴퓨터에 다른 폴더가 된다면 작동 안 하니까 절대 경로는 쓰지 마십시오.
어쨌든 위에서는 "던젼.jpg"라는 그림을 "배경"이란 이름을 붙여 불렀군요.
여기서 또 하나!
게임의 메인 루프는 while의 안입니다.
while(!GetKey(vkey_esc))
{
(이 안)
}
LoadPicture 명령은 반드시 이 밖에서, 루프가 시작되기 전에 해야합니다.
루프는 보통 1초에 100번 도는데 로드 같은 작업을 1초에 100번 시키면 난리납니다.
그러니까 루프 들어가기 전에 딱 한번!
그럼 이번엔 불러 들인 그림을 출력해 봅시다.
jdd->LoadPicture(backbuffer, "그림 이름", X좌표, Y좌표, NULL);
역시 다른 부분은 신경 끄고 파란 부분만 봅시다.
눈치 있으신 분은 알겠지만 해당하는 X좌표와 Y좌표에 불러 들인 그림을 찍는 겁니다.
지금 게임에서는 "배경"이란 그림을 X:0 Y:0 에 찍고 있는 겁니다.
"걷기1"이란 그림은 X:100 Y:100 에 찍고요.
제대로 하셨다면 아래와 같이 출력 될 겁니다.
다음은 캐릭터를 이동시켜 봅시다.
지금은 100, 100에 무조건 찍게 되어 있습니다.
이걸 바꿔야 겠죠?
변수라고 아십니까? 모르는 분들도 있을 테니 설명을 하겠습니다.
변수는 간단히 변하는 수입니다.
지금 코드에 적어놓은 100, 100은 무슨 일이 있어도 변하지 않습니다.
프로그램을 잡고 온갖 난리를 떨어도 100, 100은 100,100입니다.
그럼 어떻게 해야 할까?
이렇게 해야 합니다.
jdd->DrawPicture(backbuffer, "걷기1", x, y, NULL);
숫자 대신 x와 y라고 적었습니다.
이게 변수입니다.
그런데 이렇게 쓰고 실행시켜 보면 에러가 날겁니다.
왜냐면 C++에는 선언이 필요하기 때문이죠.
while이 시작되기 전에 변수를 선언해줍시다.
int x = 100;
int y = 100;
int란 정수란 뜻입니다. integer죠.
'변수 x는 내가 정수로 쓸텐데 일단 초기값은 100이다.' 라고 선언하는 겁니다.
프로그램에게 선언하는 거죠. 나 이렇게 쓸거야 하고.
이것만 넣고 테스트 해봐야 같을 테니 소스를 이렇게 바꿔봅시다.
선언부분도 포함해 두었으니 이걸 보고 아까 코드를 수정해 봅시다.
//////////////////////////////////////////////////
//메인코드
//////////////////////////////////////////////////
int main(char* arg[])
{
//창생성
if(!MainInitialize("Sample", TRUE, FALSE, window_mode))return 0;
//윈도우창이동
if(window_mode)
{
jdd->OnMove(100, 100);
SetCursor(LoadCursor(0, IDC_ARROW));
}
jdd->LoadPicture("배경", "던젼6.jpg", NULL, true);
jdd->LoadPicture("걷기", "w1.gif", NULL, true);
jdd->LoadPicture("걷기", "w2.gif", NULL, true);
jdd->LoadPicture("걷기", "w3.gif", NULL, true);
int x = 100;
int y = 100;
//메인실행
while(!GetKey(vkey_esc))
{
if(!ProcessMessage())break;
if(GetKey(vkey_up,1))y-=5;
if(GetKey(vkey_down,1))y+=5;
if(GetKey(vkey_left,1))x-=5;
if(GetKey(vkey_right,1))x+=5;
jdd->DrawPicture(backbuffer, "배경", 0, 0, NULL);
jdd->DrawPicture(backbuffer, "걷기", x, y, NULL);
jdd->Render();
}
//정리하고끝내기
jdd->DeleteFont(global_font);
return 0;
}
이렇게 고친 후 실행 해 보면 키 조작에 의해서 상하좌우로 이동하는 캐릭터를 보실 수 있습니다.
일단 GetKey부터 설명해야 겠군요.
GetKey(키ID, 연속입력 간격)
키 ID는 반드시 지정된 것을 써야 인식을 합니다.
약 90여개가 있지만 사실 그 키들을 다 쓸 일은 없을테고 나올 때마다 설명하기로 하죠.
처음부터 다 알고 싶은 분은 donglib.h에서 vkey로 검색해 보시길.
다 정의되어 있습니다.
아무튼 해당 키ID의 입력이 들어오면 GetKey는 '참'이란 값을 돌려 줍니다.
'참'이란 값을 돌려 주다니? 무슨 소리... 라고 아직 C++의 표현에 익숙하지 않으신 분들을 일단 넘어갑시다.
나중에 if 이야기하면서 설명하겠습니다.
다음 값인 연속입력 간격에 대해 설명하겠습니다.
일단 저 값을 안 적고 GetKey(vkey_up)으로 한번 바꿔 보십시오.
그러면 위쪽 화살표를 누른 순간에만 인식을 하고 그 후로는 안 먹힙니다.
손을 뗀 후 다시 눌렀을 때 먹힙니다.
이동 할 때마다 따다다닥 눌러 댈 순 없으니 꾹 누르고 있으면 그냥 움직이게 하고 싶겠죠?
그럴 때는 저 값을 적어줍니다. 1이라고 적으면 1/100초를 기다렸다가 다시 확인해본다는 겁니다.
1/100가 지나서 다시 확인해봤는데도 누르고 있는 상태라면 해당 이벤트를 한 번 더 일으킵니다.
확실히 익히시고 싶으시면 저 값을 바꿔 보십시오. 마이너스 값은 의미 없습니다.
단, -1은 특별 값으로 -1을 적으면 한 번 인식한 후 뗐다가 다시 누를 때까지 인식하지 않습니다.
아무 것도 안 적었을 때와 같은 값이죠.
그럼 if를 볼까요? 게임에서는 if가 가장 중요합니다.
조건문 없이는 게임이 아닌 영화가 되겠지요. 뭐 잡설은 그만하고.
if(GetKey(vkey_up, 1))y-=5;
풀어서 설명해 보겠습니다.
만약 GetKey에서 '참'이란 값을 주면 y를 5 감소(y-=5)시켜라.
...란 뜻입니다.
아까전에 참, 거짓을 이야기 했는데 그것은 이 if를 위한 것이지요.
if는 돌아온 참이면 그 다음에 적힌 코드를 실행하고
거짓이라면 그냥 쌩까 버립니다.
그런 녀석이지요.
GetKey는 위쪽 화살표(vkey_up)를 눌렀을 때만 참이란 값을 돌려 주므로
위쪽 화살표를 누르지 않는 한 y-=5는 실행되지 않는 것이죠.
마찬가지로 밑에는 아래쪽 화살표(vkey_down), 왼쪽 화살표(vkey_left), 오른쪽 화살표(vkey_right)에 의한 반응들입니다.
어느 정도 이해하셨는지 몰라도 모르시는 것 있으시면 나중에 물으시도록 하십시오.
일단 지금은 캐릭터가 그냥 화면을 벗어나 버릴 겁니다.
그걸 막아야 겠죠.
그걸 위해 이 코드를 추가합니다.
if(x<0)x=0;
if(x>SCREEN_X-19)x=SCREEN_X-19;
if(y<0)y=0;
if(y>SCREEN_Y-29)y=SCREEN_Y-29;
어디에 추가해야 되고 무슨 뜻인지는 과제로 남기겠습니다.
만약 캐릭터가 화면을 빠져 나가지 않는다면 성공한 것입니다.
잘 봤습니다. 비베로 구현해 본 것이라 쉬웠구요.
다만 GetKey를 Getkey로 쓰는 바람에 빌드가 안되어 고생 좀 했습니다.
jdd->LoadPicture("배경", "던젼.jpg", NULL, true);은
jdd->LoadPicture("배경", "던젼6.jpg", NULL, true);이었구요.
3강은 언제쯤 되나요?