지피껨 만들기

_

_


지피껨 만들기.. (1)

샬롬~ 정말로 올만에 글 쓰는 아샬군입니다.

무언가 게임 제작 관련 글을 쓰려고 했는데... 실력과 시간 등의 문제로 인해 한동안 글을 못 썼습니다. TT ( 가장 최근에 제대로 쓴게.... TC로 게임 만들기...인가 -\-;;; )

일단 GP32의 성공 가능성 여부를 떠나서 (가격이... 털썩) 누구나 GP32용 게임을 만들 수 있도록 SDK를 공개한 점이 저를 매우 자극했고... 그리하여 이렇게 글부터 써봅니다.

자, 글부터 쓴다는 건 무슨 소리냐... 제가 아직 GP32용 게임을 만들 수 있는 녀석이 아니라는거죠. ( 방금 SDK를 받아서 방금 글 쓰는 짓을 하니... -_-; )

움하핫.... -_-;;;;

이런저런 이야기는 일단 뒤로 하고... GP32 게임 제작을 다같이(저포함-_-) 시작해보죠.

1. GPSDK를 구하라!

예, 그런 겁니다.

SDK는 Software Development Kit의... 중얼중얼..

예, 그런 거죠.

http://www.gameparkdev.co.kr/

여기로 간 후에... GP32 Developers라고 뜨죠? 아마추어 개발자 사이트의 Korean을 눌러서.... 들어갑시다. PDS를 누르고... 1번 자료인 [SDK] GP32 SDK all-in-one version (work.zip)을 받습니다.

................

예, 뻔한 이야기 해서 죄송합니다. -_-;;;

그리고 중요한 거 하나만 더 받죠. 5번 자료 [Tool] GP32 Developer Tool (gpdev.exe)을 받는 겁니다!! +_+ 자, 일단 이 둘만 있으면... 가장 기본적인 짓을 할 수 있습니다. 바로 Example 컴파일 & 실행....이라는 거죠. -_-;;;

2. Example들을 컴파일하자!

잔소리 많은 것보다는 일단 (직접 코딩은 안하지만-_-) 실행 파일을 떨궈보죠.

원래 GP32에서 돌아가게 하려면 복잡한 짓을 해야 하지만 (안해봤지만 복잡할 듯-_-) 그냥 PC에서 에뮬레이팅을 하는 건 대단히 간단합니다. 무언가 에뮬레이터로 실행하는 것도 아니고.... 그냥 VC++에서 (아참, 저는 Visual C++ 6.0을 사용합니다. ) Build하면 떨어지는 EXE 파일을 실행하면 그게 곧 에뮬레이팅.....이라는 진리가 되죠. 쿨럭...

자, 위에서 받은 work.zip을 압축을 풉니다. 대충 work라는 폴더에 풀었다고 칩.시.다... ( 그냥 압축을 풀면 폴더 4개가 떨어지니까.. 폴더 하나를 만들어서 안에 풀자구요. )

work/examples로 들어가면 ( 저는 보다는 /을 사용합니다. 개인적 취향. 불만이냐? ) examples.dsw라는 Workspace 파일이 있으니까.. 그걸 열면... 예제들이 쭈욱 들어있죠.

자자, 그럼 ex001을 Active Project로 잡은 후, F5(RUN)을 해주죠. GP32 모양이 뜨죠?

여기까지 하셨으면.. 이제 거의 다 된겁니다. (뭐가!!) 가볍게 GP32(모양을 한 녀석-_-) 위에 있는 OPEN을 눌러주죠. 헉, 갑자기 가상 SMC 파일을 요구하는군요. 존나 C8이라고 한번 외쳐주고, 아까 위에서 받은 gpdev.exe를 실행합니다. ( 뭐, 존나 C8은 안외쳐도 상관은 없습니다. Fuck up도 무관... )

아까 PDS에 들어갔을 때 모든 자료를 받은 분은 아시겠지만... GP32의 툴들은... 쉣...의 경지에 이르렀습니다. 특히 GpAppManager.exe 같은 건 정말 무아지경에 이르게 하죠.

뭐, 그렇다고 치고.. gpdev.exe로 돌아가서... 메뉴 달랑 하나뿐입니다. Utilites의 SMC Manager를 고릅니다. 무언가 뜨죠. File의 Create Virtual SMC를 고릅시다. 예, 이것이 바로 가상 SMC 파일을 만드는 겁니다. vendor하고 size를 고르는 게 있는데... vendor는 그냥 Samsung을 골랐고 (삼성을 좋아하지는 않지만, 국산이니-_-) size는 적당한 크기로 아무거나 고르세요.... ( 저는 16M로 고르고... 파일 이름은 test.smc로 골랐습니다. )

자, 다시 아까 그 '열기' 창으로 돌아가서... test.smc를 골라서 엽시다. ( 폴더는 '내문서'가 디폴트니까.. 저는 test.smc도 '내문서'에 두었습니다. ) 오오, 존나 무언가 뜨지 않습니까? 16bit mode로 전환하려면 SELECT를 누르라는군요. 여기서 SELECT는 SPACE입니다.

여기서 잠깐 짚고 넘어가면.. 방향키는 그냥 방향키로 누르면 되구여... A버튼은 M, B버튼은 N, R버튼은 B, L버튼은 V입니다. ( 대개 ZXCV를 쓰는데.... 여기선 VBNM이네요. 쩝. ) SELECT는 SPACE를 누르면 되고, START는 ENTER입니다.

하여튼.... 드디어 실행은 했습니다. 스스로 대견스럽지 않습니까 -_-;;;;;;;;

다른 예제들은 work/examples/readme-k.txt를 보시면 설명이 나와있습니다. 개인적으로는 ex008이나 ex009 정도만 보셔도 윤곽이 잡히실 거라고 믿습니다. ^^ 아, 그리고.. Virtual SMC를 자동으로 읽는 게 있을텐데... 방금 SDK를 만진 관계로.... 잘 모르겠네요... -_-

일단은... 이정도만 하고... 다음에는 실제로 무언가 삽질을 좀 해보도록 하죠.

--2002.04.10 아샬

P.S. 존나 당연한 이야기를 마치 대단한 것처럼 써서 죄송합니다... -_-;;; 다음에는... 좀 도움이 되는 이야기들로 채워보죠.. 쿨럭...


지피껨 만들기.. (2)

샬롬~

우우우, 이런... 저번에 실수를 했더군요. ex001이 아니라 제가 실행한 것은 ex000이었습니다. -_-

ex001은 단순히 사각형 하나 출력하는 거지, 16bit 변환 이야기는 없었습니다. TT 예, 저의 실수입니다... 목숨만은... -\-

여담이지만.. ex001을 보시면... 상당히 짧습니다. 소스가.. 적당한 매력이지요.. ^^

3. 움직이는 사각형

이번에는 실제로 코딩을.... 후후후.... 일단 간단히 움직이는 사각형을 구현하도록 하죠. 물론, 사용자의 입력에 따라서 움직여야 하겠죠? Surface니 Flip이니 하는 개념은....... 제가 예전에 (몇년 전이지?-_-) 쓴 'TC로 게임 만들기'를 참조함이.. -_- ( DOS 시절 게임 만드신 분이라면.. Page라는 개념을 아실 겁니다. 그거죠. T_T )

자, 일단..... VC++에서 GP32용 Project를 만들기 위해서는.. 상당히 귀찮은 작업이 필요한데... work/docs/dev_ko/index.htm을 보시면... 아니, 바로 가는 걸로 찾아보죠. ^^ work/docs/dev_ko/vc/project.htm을 보시면 됩니다.

그런데.... 하여튼.. 저는 귀찮아서.. work/project/template를 바로 이용하겠습니다. 원래는 이걸 가지고 복사해서 삽질하라고 써있는데.. 귀찮아서..... -_-

win32 application project 생성해주듯이.. GP32 application project 생성하는 것도 만들면 좋은데....... 나중에 시간이 나면.... 해보도록 하겠습니다만... 제가 약속은 잘 안지키는 놈이라서..... -_- 예, 그런 거죠.. 쿨럭.. 털썩.. 부들......

work/project/template/win32에 들어가면.. template.dsw가 있습니다. 그걸 더블 클릭을 하면.. 무언가 나오죠. 화면에 사각형을 출력하는 예제인데... 아래의 소스로 교체를 해봅시다. ^^

#include 'gpdef.h'
#include 'gpgraphic.h'
#include 'gpmain.h'

GPDRAWSURFACE gpDraw;

void GpMain( void *arg )
{
    GpLcdSurfaceGet( &gpDraw, 0 );
    GpSurfaceSet( &gpDraw );

    if ( !( GPC_LCD_ON_BIT & GpLcdStatusGet() ) )   GpLcdEnable();

    GpRectFill( NULL, &gpDraw, 0, 0, gpDraw.buf_w, gpDraw.buf_h, 0x00 );
    GpRectFill( NULL, &gpDraw, 120, 60, 80, 80, 0xFF );
}

원래 예제는 Surface 하나짜리라서... 좀 바꿨습니다. 이건 쓸데 없는 건 일단 최소화를 해본 거죠.. ^^ ( 과연 최소화가 맞나? -_- ) 일단 사각형을 움직이기 전에 위의 소스부터 분석하고..... 사각형을 움직여보죠... 후후후... +_+

GP32 Application의 main은 GpMain입니다. Win32 Application의 main이 WinMain인 것과 마찬가지....

맨 첨의 GPDRAWSURFACE gpDraw;를 봅시다. 이게 바로 DOS 시절의 Page 개념인 Surface를 정의하는 겁니다. 그냥 화면...의 개념으로 보시면 됩니다. 예, 간단하죠? ^^

GpLcdSurfaceGet이란 놈은.... LCD Surface, 그냥 LCD 화면을 얻는 거죠. 위에 정의한 gpDraw란 변수는 그냥 정의만 된거지.. 아직 아무 값도 안가지는 거잖아요. 실제로 쓸 수 있게 Surface Get을 때려주는 거죠.

자, 그 담에 실제로 화면을 세팅을 해주는 건... GpSurface Set입니다. ( 이미 눈치까신 분이 있겠지만.. GP32 API는... Gp가 앞에 붙습니다. ^^ )

if ( !( GPC_LCD_ON_BIT & GpLcdStatusGet() ) )   GpLcdEnable();

이 녀석은... 실제로 LCD 화면을 사용하는 건데.... (Enable) 앞에 있는 조건은.. LCD 쓸 수 있는지 체크 정도죠. 실제로는 이게 없어도 되는데 (Default가 Enable이거든요^^) 그냥 넣었습니다.

실제로 GP32 기계에선 어떻게 되는지 모르니까... -_- 여기까지 했으면.. 준비는 끝난 겁니다. 실제로 여기까지만 하고 실행해도 하얀 화면이 뜨죠. ^^ 그런데.. 아무 것도 안그리면 좀 썰렁하니..... 일단 화면을 검게 지우고 상자를 하나 그려보죠.

GpRectFill( NULL, &gpDraw, 0, 0, gpDraw.buf_w, gpDraw.buf_h, 0x00 );

이건 (0,0)부터 화면 크기만한 상자를 그리는 겁니다. 즉, 화면을 0x00(black)으로 지우는 거죠. ^^

GpRectFill( NULL, &gpDraw, 120, 60, 80, 80, 0xFF );

이건 0xFF(white)로 상자를 그려주는 겁니다. 후후후... 대단히 간단합니다. ^^ 예, 그런 거죠. ^^

그럼 이번엔 실제로 키보드의 입력을 받아서 움직이게 해볼까요? ^^

#include 'gpdef.h'
#include 'gpgraphic.h'
#include 'gpmain.h'

GPDRAWSURFACE gpDraw;

void GpMain( void *arg )
{
    unsigned char keydata;  // 키보드 입력을 받기 위한 녀석...

    int x = 120;    // 상자의 X 좌표
    int y = 60;`    // 상자의 Y 좌표

    GpLcdSurfaceGet( &gpDraw, 0 );
    GpSurfaceSet( &gpDraw );

    if ( !( GPC_LCD_ON_BIT & GpLcdStatusGet() ) )   GpLcdEnable();

    while( 1 )  // 루프를 돌려보죠~ +_+
    {
        keydata = GpKeyGet();   // 키 입력을 받을까요?

        if( keydata & GPC_VK_UP )   y--;    // UP
        if( keydata & GPC_VK_DOWN ) y++;    // DOWN
        if( keydata & GPC_VK_LEFT ) x--;    // LEFT
        if( keydata & GPC_VK_RIGHT )    x++;    // RIGHT

        GpRectFill( NULL, &gpDraw, 0, 0, gpDraw.buf_w, gpDraw.buf_h, 0x00 );
        GpRectFill( NULL, &gpDraw, x, y, 80, 80, 0xFF );
    }
}

아까의 소스와 달라진 부분에는 주석이 붙어있습니다. 참고하시고~ ^^ 자, 이번에는 키 입력을 받는 게 추가가 됐는데... 매우 간단합니다. 제가 뭐라고 부연 설명을 하는게 어색할 정도죠. -_-

여기까지 하면.. 우리의 목적이 일단 달성된 거 같은데... 실행해보면 조금 이상하죠? 상자가 상당히 깜빡거립니다. +_+

왜 그런가하고 식음을 전폐하고 고민을 하니.... 화면을 지우고 상자를 그리기 때문에 그런 거죠. 화면을 지울 때 상자가 함께 지워지고, 상자를 다시 그리고.. 당연히 깜빡거리는 거 아니겠습니까... ^^

그런데... 그게 당연하다고 존나 깜빡거리는 걸 게임이라고 내놓으려니.. 자존심이 허락하지 않네요. ^^;;; 그럼 어찌할까요..... 설명하기 귀찮으니.. 일단은 아래의 소스로 살짝 수정을 해볼까요?

#include 'gpdef.h'
#include 'gpgraphic.h'
#include 'gpmain.h'

GPDRAWSURFACE gpDraw[2];    // Surface를 2개로 잡을까여? ^^
int nflip = 0;          // Surface Flip을 위한 변수

void GpMain( void *arg )
{
    unsigned char keydata;

    int x = 120;
    int y = 60;

    GpLcdSurfaceGet( &gpDraw[0], 0 );   // 자자, 0번 Surface
    GpLcdSurfaceGet( &gpDraw[1], 1 );   // 이번에는 1번 Surface

    GpSurfaceSet( &gpDraw[0] );

    if ( !( GPC_LCD_ON_BIT & GpLcdStatusGet() ) )   GpLcdEnable();

    while( 1 )
    {
        keydata = GpKeyGet();

        if( keydata & GPC_VK_UP )       y--;
        if( keydata & GPC_VK_DOWN )     y++;
        if( keydata & GPC_VK_LEFT )     x--;
        if( keydata & GPC_VK_RIGHT )    x++;

        GpRectFill( NULL, &gpDraw[nflip], 0, 0, gpDraw[nflip].buf_w, gpDraw[nflip].buf_h, 0x00 );
        GpRectFill( NULL, &gpDraw[nflip], x, y, 80, 80, 0xFF );

        GpSurfaceFlip( &gpDraw[nflip] );    // Surface Flip을 감행!!

        nflip = ( nflip + 1 ) % 2;      // Flip을 위한 변수를 변신~
    }
}

움하하핫.......... 드디어 안깜빡이는 상자다......... TT 이제 이번 글은 접기로.. 쿨럭.. -\-

예, 간단히 설명을 하죠. 여기서 포인트는 Surface를 2개 만든다는 점과 Surface Flip이라는 녀석이죠. 아까 깜빡거리는 문제에 대한 해답을 생각해볼까요?

일단 문제가 되는 건.... 우리가 무언가 작업을 하는게-그림을 그리는게- 유저에게 보인다는 거죠. 그렇다면.... 그 화면을 두놈을 만들어서.... 한 화면을 보여주고.. 다른 화면에 그림을 그려서.. 순식간에 두 화면을 바꿔치면(Flip) 되지 않겠느냐.... 이거죠. ^^ 바로 그 개념입니다. 별로 어려운 건 없으니까요.. 그냥 쓱~하고 보시면 아항~하고 이해가 가실 겁니다. DOS 시절처럼 인터럽트 호출해서 13h모드 만들고.. 그런 짓은 없으니.. ( 아아, Plane Mode의 추억이여.. 털썩... -_-;;;;; )

일단... 이번에는 여기까지입니다. 특별한 설명이 없는 날림인 점... 대단히 죄송합니다. T_T 어짜피.. 이 글을 보시는 분들은.... 나름대로 게임 프로그래밍을 공부하신 분들이라고 가정하고... (퍽퍽) 다음에는.... 좀 더 심오한 삽질로 들어가도록 하겠습니다. ^^

--2002.04.10 아샬

P.S. 사실... 빨리 쓰려다 보니.. 좀 성의가 없네요. 오랜만에 쓰다보니.... 글 쓰는데.. 정성을 다하기가.. 어렵습니다. 죄송 T_T


지피껨 만들기.. (3)

샬롬~

이런 이런.... 실은 이번에는 좀 게임다운 녀석(?)을 만들기 위해 스프라이트 에디터와 맵 에디터를 물색하고 있었는데.. GP32에 맞추려니... DOS 시절의 녀석을 찾아봤고...

결론은.....

Win2K에선 안돌아간다.....였습니다. (좌절)

게다가... 오랜만에 DOS 시절 아마추어 게임들을 받아서 플레이를 시도했으나......... 말끔히 실패....... 재부팅까지 하는 놈도 있더군요. T_T

( 여담이지만.. 집에는 486DX2가 있습니다. +_+ 이런 일이 일어날 줄 알고... 모셔둔.... -_- 실은... 486DX2만 있습니다. T_T )

그래서... 무언가 툴을 이용하는 건.... 다음으로 미루도록 하고.. ^^;;;;;;

GP32에서 제공하는 기본적인 툴들을 활용하도록 하겠습니다. ^^

아참, 깜빡한 게 있는데.... GP32는 기본적으로 320x240에 8bit color입니다. 물론, 16bit color도 사용할 수 있습니다. ( 기본이 8bit color라는 거죠^^ )

그런데.. 특이한 점이.. 실제로 LCD가 16bit 컬러를 발현하기 때문에... DOS 시절의 8bit color와는 다르죠. T_T

( 원래는 RGB가 각각 64 단계였죠? 즉, 64x64x64 컬러인데... GP32는 RGB가 각각 5bit, 즉, 2^5=32 단계죠... 32x32x32 컬러니까.... 2x2x2=8분의 1로 줄어든 셈이죠. 문제는... 작업할 때는 RGB가 각각 64단계인데... GP32로 보면 32단계가 된다는 점입니다. 즉, 그래픽의 질적 차이가 두드러지겠죠. 물론.... 애뮬레이팅을 했을 때도 그런지는 잘 모르겠지만요-_- )

4. 캐릭터를 움직여보자~ ^^

으윽........

일단 신음 소리 먼저 냅니다. 실은 이거 하나 하려고 제가 삽질을 좀 많이 해서... -_-

위에서 툴이 없었다고 이야기를 했죠? 그래서.. 어떻게 할까.. 고민하다가.. 그냥 GPSDK 예제에 있는 그림을 바이너리 파일로 뽑아서 쓰기로 했죠.

그.런.데...

거기서 문제가 생긴 겁니다. 이미지의 가로 길이가 무조건 4의 배수여야 한다는 걸 몰랐었기 때문에.. 이상하게도 오른쪽에 남는 검은 띠로 졸라 고생을 했죠. T_T

하여튼... 그런 겁니다. -_-;;;;

일단, GPSDK 예제에 있는 그림을 바이너리로 뽑아보죠!!

#include <stdio.h>
#include 'img_background.c'
#include 'img_sprite.c'

void main()
{
    int width, height, size;
    FILE *fp;

    width = 320;
    height = 240;

    fp = fopen( 'back.img', 'wb' );
    fwrite( &amp;width, 2, 1, fp );
    fwrite( &amp;height, 2, 1, fp );
    if( height % 4 )    height += 4 - ( height % 4 );
    size = width * height;
    fwrite( img_back, size, 1, fp );
    fclose( fp );

    width = 80;
    height = 150;

    fp = fopen( 'char.img', 'wb' );
    fwrite( &amp;width, 2, 1, fp );
    fwrite( &amp;height, 2, 1, fp );
    if( height % 4 )    height += 4 - ( height % 4 );
    size = width * height;
    fwrite( img_char, size, 1, fp );
    fclose( fp );
}

맨 위의 img_background.c하고 img_sprite.c는 ex008이나 ex009에서 챙기면 됩니다. ^^

하여튼, Build & Run을 해주면 back.img하구 char.img라는 파일이 떨어지죠.

자, 그럼 그 두 녀석을 어케 쓸까여? ^^

이전까지는 test.smc( 다른 이름일 수도 있음. 그냥 Virtual SMC )가 아무 의미가 없었죠. 이제부터는... 여기에 이런 데이터, 저런 데이터를 넣는 겁니다. ( 그렇게 해도 되나? 맞겠지.. 뭐... -_- 역시... 정식 GP32 개발자가 아니라서 벌이는 뻘짓인가... )

gpdev.exe를 실행하고, SMC manager를 띄운 뒤... Open을 해서 test.smc를 엽니다. 잘 보면 창이 위/아래 둘인데.. 위가 SMC의 내용이고, 아래가 Local Disk의 내용이죠. 즉, 필요한 내용을 아래에서 위로 끌어서 저장할 수 있고, 반대로 아래로 끌어와 SMC에서 하드로 읽어올 수도 있죠.

일단 저는 gp:/game/data까지 폴더를 생성해서 거기에 데이터를 넣습니다. 폴더를 만들었으면.... back.img와 char.img가... 잘 들어갔죠?

자, 그럼 이번에 다루는 소스를 봅시다. 전보다는 조금 길지도... ^^

( 아차, 저번에 다룬 내용에 빼먹은 점을 지적합니다. 키 입력을 얻는 함수는 gpstdlib.h에 정의되어 있는데.. 그걸 include 안했더군요. T_T 컴파일 시에 warning이 뜨는데.. 급히 하느라 깜빡.. 죄송 ^^ )

#include 'gpdef.h'
#include 'gpstdio.h'
#include 'gpstdlib.h'
#include 'gpgraphic.h'
#include 'gpfont.h'
#include 'gpmain.h'

unsigned char keydata;

GPDRAWSURFACE gpDraw[2];
int nflip = 0;

ubyte *img_back = NULL;
ubyte *img_char = NULL;

ubyte *read_image( char *name )
{
    ubyte *image = NULL;
    int size, rcount;
    F_HANDLE h_file;

    GpFileGetSize( name, &amp;size );

    GpFileOpen( name, OPEN_R, &amp;h_file );
    image = gp_mem_func.malloc( size );
    GpFileRead( h_file, image, size, &amp;rcount );
    GpFileClose( h_file );

    return image;
}

void put_image( int x, int y, ubyte *image )
{
    if( image )
    {
        int width = *(word*)image;
        int height = *(word*)( image + 2 );
        GpBitBlt( NULL, &amp;gpDraw[nflip], x, y, width, height, image + 4, 0, 0, width, height );
    }
}

void put_sprite( int x, int y, ubyte *image )
{
    if( image )
    {
        int width = *(word*)image;
        int height = *(word*)( image + 2 );
        GpTransBlt( NULL, &amp;gpDraw[nflip], x, y, width, height, image + 4, 0, 0, width, height, 0xEF );
    }
}

int init()
{
    GpLcdSurfaceGet( &amp;gpDraw[0], 0 );
    GpLcdSurfaceGet( &amp;gpDraw[1], 1 );

    GpSurfaceSet( &amp;gpDraw[0] );

    if ( !( GPC_LCD_ON_BIT &amp; GpLcdStatusGet() ) )   GpLcdEnable();

    GpFatInit();

    img_back = read_image( 'gp:gamedataback.img' );
    img_char = read_image( 'gp:gamedatachar.img' );

    return 0;
}

void GpMain( void *arg )
{
    int x = 0, y = 0;

    if( init() )    return;

    while( 1 )
    {
        keydata = GpKeyGet();

        if( keydata &amp; GPC_VK_UP )   y--;
        if( keydata &amp; GPC_VK_DOWN ) y++;
        if( keydata &amp; GPC_VK_LEFT ) x--;
        if( keydata &amp; GPC_VK_RIGHT )    x++;

        put_image( 0, 0, img_back );
        put_sprite( x, y, img_char );

        GpTextOut( NULL, &amp;gpDraw[nflip], 0, 0, 'TEST +_+', 0xFF );

        GpSurfaceFlip( &amp;gpDraw[nflip++] );
        nflip %= 2;
    }
}

뭐, 조금 길다고 했지만... 그래봤자 얼마 안 길기 때문에.. -_-

일단, 기본적인 세팅은 init 함수로 빼버렸습니다. GpMain이 한결 깔끔해졌죠? ^^

init부터 살펴보면.... GpFatInit();란 놈이 추가됐죠. SMC FAT을 사용할 수 있게 세팅하는 거죠. 왜 그짓을 하냐구요? 우리의 그림 파일들이 SMC에 들어가있으니까... 그런 거죠. -_- 뭐, 나중에 게임 저장/불러오기 같은 거도 해야 하고... 쿨럭...^^

그 아래 있는게

    img_back = read_image( 'gp:gamedataback.img' );
    img_char = read_image( 'gp:gamedatachar.img' );

이 두놈인데.. read_image는.. put_image/put_sprite하고 함께 설명하죠.. 하여튼... 그림 파일을 읽어오는 건지는 아시겠죠? ^^

다시 GpMain으로 돌아가면... put_image하고 put_sprite를 사용하는 부분이랑 GpTextOut이 추가되어 있죠? ^^

GpTextOut은 글자를 출력하는 건데.. 그냥 보시면 아시겠지만.. 대단히 간단합니다. 그냥 저렇게 쓰면 되죠. -_-

숫자 등의 데이터를 출력하려면..

    char tmp_string[256];
    gp_str_func.sprintf( tmp_string, '%d', data );
    GpTextOut( NULL, &amp;gpDraw[nflip], x, y, tmp_string, 0xFF );

뭐, 이런 식으로 하면 되죠. 문자 출력 관련의 녀석을 쓰려면... gpfont.h를 include해야 하구요.. ^^

자, 그럼... 대망의 image 처리 부분을 보도록 하죠. 여기에는 File I/O와 Memory Management, Bitmap Blit 등이 복합적으로 쓰였는데요..

먼저, GP32에서 사용하는 Image Data Format을 보죠. PCX 등의 전통적인 파일들은... 우측으로 X, 아래로 Y의 좌표계를 가지죠. ( PC Screen 좌표계와 동일하죠^^ ) 하지만 BMP는 우측으로 X, 위로 Y인 수학에서 쓰이는 좌표계를 사용합니다.

여기까지는 좋.은.데...

문제는 GP32입니다.

이 녀석은 골때리게도... 위로 X 좌표, 오른쪽으로 Y 좌표를 가집니다. ( 화면의 X, Y와 혼동하지 마세요!! ) 즉, 데이터가 그림의 왼쪽 아래에서부터 올라오는 방식입니다. -_- ( 진짜 골때리지 않습니까.... 누가 생각한 건지.. -_- ) 게다가 그림의 가로 길이는 무조건 4의 배수여야 합니다. ( 이거 땜시 위에서 고생 쪼까 했죠. T_T )

뭐, 이건 나중에 직접 PCX나 BMP 등의 파일에서 데이터를 추출할 때 다시 보도록 하구요...

일단은 그렇게 뽑힌 데이터를 읽는 부분부터 보죠.

ubyte *read_image( char *name )
{
    ubyte *image = NULL;    // return용 Image Data Buffer
    int size, rcount;   // 파일 크기, 실제로 읽힌 크기
    F_HANDLE h_file;    // 파일 핸들. C의 FILE*과 동일하다고 보면.. 쿨럭.. ^^;

    GpFileGetSize( name, &amp;size );   // 파일 크기를 얻습니다~

    GpFileOpen( name, OPEN_R, &amp;h_file );    // 파일을 Read로 열고..
    image = gp_mem_func.malloc( size ); // 데이터 크기만큼 메모리를 할당합니다.
    GpFileRead( h_file, image, size, &amp;rcount ); // 파일에서 읽어오는 거죠.
    GpFileClose( h_file );          // 파일을 닫아줍니다^^

    return image;
}

간단히 주석으로 처리했습니다. 예, 그런 거죠^^

여기서 읽어오는 데이터의 형태를 알아볼까요? ^^

*.img의 File Format (제가 임의로 만든 포맷입니다. DOS 시절에 쓰던-_-)
    (2byte) - Image의 가로 길이 (width)
    (2byte) - Image의 세로 길이 (height)
    (nbyte) - 실제 Image 데이터.. ( n = width * height )

자, 이번에는 그 이미지 데이터를 출력하는 겁니다.

함수가 두개가 있는데..

위의 예제에선 투명 컬러를 0xEF로 설정을 했죠. 쉽게 말해서 0xEF인 점은 화면에 찍지 않는 거죠^^

아아, 별거 아닌 걸로 길게 쓰려니... 내가 힘드네.. -_-

다음에는... 좀 더 게임 같은 녀석을 만들어 보죠. 뭐, 총알 발사....를 하고 싶지만.. 총알 그림이 없으니.. 분신 발사... 같은 거를 하는 예제로... 후후후.. +_+

--2002.04.10 아샬


지피껨 만들기.. (4)

샬롬~

으으음... 아래에..... 외국 개발자분이..... 남기신.... 사이트에... 가봤습니다...

우찌하여.. 이런 일이...... 국내의 게임기... 국내보다 해외 커뮤니티가 더욱 강하다...라는... -_-

실제로 제가 여기서 다루고 있는 VC에서 적당히 GP32인 척하는 건 기계에 얹을 때나 아니면 컴파일러 상의 차이 등을 극복하기 힘든데... 그 친구들은... 기계 문제는 일단 접어두고 (SMC에 담으면 되니..) 아예 에뮬레이터를 만들어서 작업을 하더군요. !GeePee32라는 녀석이라든지... ^^;;;;;; 컴파일러도 최강의 컴파일러-라고 개인적으로 생각하는- gcc를 이용하고^^

뭐... 그런 겁니다.

개인적으로 Xbox에 대해.. 흥행이나 그런 여부를 떠나서 최강 스펙의 게임기라는데에 매력을 느끼듯이.. GP32에 대해서도 최강 스펙의 휴대용 게임기라는 점을 간과했던... 털썩..

예, 뭐.. 그런 거죠.. ^^;;;;;;;

GBA에서 나름대로 매력적이던 Wolf3D와 DOOM 등도 이미 GP32로 포팅 작업이 활발히 이뤄지고 있더군요. 심지어 GBA 에뮬레이터 for GP32까지 존재하다니 -_- Engineer 본능이나 Hacker 본능 등은... 일단은... 적당히 잠재우고... 실제로 하려는 건.... 게임 만들기니... 털썩^^

( 아, 갑자기 떠오른 궁금증이... GP32에서 float 같은 거 써도 되나 -_- 예전에-초창기- 게임파크 쪽 만났을 때는 안된다고 들은 거 같은데.. 고정 소수점...이라는 전설적인 방법을 써야 하나.... 최근에는 수정이 됐나....... 아까 그 사이트에 Spec이라고 나왔던 거 같은데.... 귀찮다 -_- )

5. 초날림 슈팅이란 무엇인가...

예, 벌써 5번째 내용이군요. 이번까지만 일단 글을 작성하고.... 일단은 지피껨 만들기 part I(거창하다-_-)은 접으려고 합니다.

사실... 별거는 아니고.. 무언가 비주얼적인 게 있어야 하는데....... 그런걸 마련하지 못해서.... -_- 주인공 크기만 좀 작아도 적 좀 나오고.... 그걸 쏘고.... 맞추고.... 그런 짓을 하는데.. 그것도 어렵고... -_-

그래서....... 예.전.에.만.든.게.임.의 데이터를 집에서 들고와 GP32용으로 포팅을 해보려고 합니다 -_- 설명하느라 마구 추가한 난잡한 소스를 정리도 좀 하구요. ^^;

예, 뭐... 그런거죠... 털썩... ( 여담이지만... 거의 회사에서 살기 때문에 -_- 데이터를 들고 오는건... 이번 주말........ 물론...... 잊을 수도 있기 때문에... 다음 내용이 언제부터 될 것이다...라고는 장담을 못하는 -_- 혹시 486 노트북 컴퓨터 소유하고 계신 분~ 저...저에게 넘기실 계획은... -_-;;;;;;;;;;;;;; 고전 게임을 휴대하면서 하고 싶어욧.... T_T )

자, 잡소리가 길었는데.. 일단 소스부터 보죠~ ( 결국... 이짓이냐 -_- )

#include 'gpdef.h'
#include 'gpstdio.h'
#include 'gpstdlib.h'
#include 'gpgraphic.h'
#include 'gpfont.h'
#include 'gpmain.h'

unsigned char keydata;

GPDRAWSURFACE gpDraw[2];
int nflip = 0;

ubyte *img_back = NULL;
ubyte *img_char = NULL;

#define IMG_WIDTH( x )  ( *(word*)(x) )
#define IMG_HEIGHT( x ) ( *(word*)( (x) + 2 ) )

#define LCD_WIDTH   320
#define LCD_HEIGHT  240

#define MAX_FIRE 5

typedef struct _FIRE
{
    int flag;
    int x, y;
}   FIRE;

ubyte *read_image( char *name )
{
    ubyte *image = NULL;
    int size, rcount;
    F_HANDLE h_file;

    GpFileGetSize( name, &amp;size );

    GpFileOpen( name, OPEN_R, &amp;h_file );
    image = gp_mem_func.malloc( size );
    GpFileRead( h_file, image, size, &amp;rcount );
    GpFileClose( h_file );

    return image;
}

void put_image( int x, int y, ubyte *image, int flip )
{
    if( image )
    {
        int width = *(word*)image;
        int height = *(word*)( image + 2 );
        if( !flip ) GpBitBlt( NULL, &amp;gpDraw[nflip], x, y, width, height, image + 4, 0, 0, width, height );
        else        GpBitLRBlt( NULL, &amp;gpDraw[nflip], x, y, width, height, image + 4, 0, 0, width, height );
    }
}

void put_sprite( int x, int y, ubyte *image, int flip )
{
    if( image )
    {
        int width = *(word*)image;
        int height = *(word*)( image + 2 );
        if( !flip ) GpTransBlt( NULL, &amp;gpDraw[nflip], x, y, width, height, image + 4, 0, 0, width, height, 0xEF );
        else        GpTransLRBlt( NULL, &amp;gpDraw[nflip], x, y, width, height, image + 4, 0, 0, width, height, 0xEF );
    }
}

int init()
{
    GpLcdSurfaceGet( &amp;gpDraw[0], 0 );
    GpLcdSurfaceGet( &amp;gpDraw[1], 1 );

    GpSurfaceSet( &amp;gpDraw[0] );

    if ( !( GPC_LCD_ON_BIT &amp; GpLcdStatusGet() ) )   GpLcdEnable();

    GpFatInit();

    img_back = read_image( 'gp:gamedataback.img' );
    img_char = read_image( 'gp:gamedatachar.img' );

    return 0;
}

void GpMain( void *arg )
{
    int i;
    int x = 0, y = 0;
    int n_tick, n_frame;
    int delta_tick;
    int fps = 0;
    char tmp_string[256];
    FIRE fire[MAX_FIRE];
    int press_a = 0;

    if( init() )    return;

    for( i = 0 ; i < MAX_FIRE ; i++ )
    {
        fire[i].flag = 0;
    }

    n_tick = GpTickCountGet();
    n_frame = 0;

    while( 1 )
    {
        delta_tick = GpTickCountGet() - n_tick;
        if( delta_tick >= 1000 )
        {
            fps = n_frame * 1000 / delta_tick;
            n_tick = GpTickCountGet();
            n_frame = 0;
        }
        n_frame++;

        keydata = GpKeyGet();

        if( keydata &amp; GPC_VK_UP )       y--;
        if( keydata &amp; GPC_VK_DOWN )     y++;
        if( keydata &amp; GPC_VK_LEFT )     x--;
        if( keydata &amp; GPC_VK_RIGHT )    x++;

        if( x < 0 ) x = 0;
        if( y < 0 ) y = 0;
        if( x > LCD_WIDTH - IMG_WIDTH( img_char ) ) x = LCD_WIDTH - IMG_WIDTH( img_char );
        if( y > LCD_HEIGHT - IMG_HEIGHT( img_char ) )   y = LCD_HEIGHT - IMG_HEIGHT( img_char );

        if( keydata &amp; GPC_VK_FA )
        {
            if( !press_a )
            {
                for( i = 0 ; i < MAX_FIRE ; i++ )
                {
                    if( !fire[i].flag )
                    {
                        fire[i].flag = 1;
                        fire[i].x = x;
                        fire[i].y = y;
                        break;
                    }
                }
                press_a = 1;
            }
        }
        else    press_a = 0;

        put_image( 0, 0, img_back, 0 );

        for( i = 0 ; i < MAX_FIRE ; i++ )
        {
            if( fire[i].flag )
            {
                put_sprite( fire[i].x, fire[i].y, img_char, 0 );

                fire[i].x++;
                if( fire[i].x >= LCD_WIDTH )    fire[i].flag = 0;
            }
        }

        put_sprite( x, y, img_char, 1 );

        gp_str_func.sprintf( tmp_string, '슈팅 테스트 [%dfps]', fps );
        GpTextOut( NULL, &amp;gpDraw[nflip], 0, 0, tmp_string, 0xFF );

        GpSurfaceFlip( &amp;gpDraw[nflip++] );
        nflip %= 2;
    }
}

나름대로... 개인적으로... 소스 보기 좋게 작성하는 편이라고 자부하기 때문에... 이정도에서..... (퍽퍽)

역시.. 간단한... 설명 정도는... 해야겠죠... -_-;;;; 일단 뭐뭐가 추가됐는지 보죠. (제가 추가한 순서대로 설명하렵니다~)

먼저 추가한 게 FPS(Frame Per Second)를 화면에 출력해주는 거죠.

    delta_tick = GpTickCountGet() - n_tick;
    if( delta_tick >= 1000 )
    {
        fps = n_frame * 1000 / delta_tick;
        n_tick = GpTickCountGet();
        n_frame = 0;
    }
    n_frame++;

GpTickCountGet()이란 함수가 쓰였는데.. VC++의 GetTickCount랑 같죠. Milli Second(철자 맞나?)를 쓰니까.. 1000이면 1초죠. 1초 이상의 시간이 흘렀으면.. Frame / Second를 해서 fps 값으로 넣는 겁니다.

아래에 숫자를 문자로 바꿔서 출력하는 건 저번에 설명했으니 패스~ ^^

그리고.. 전의 녀석은.. 화면 밖으로 튀어나갔는데.. 단순히 if문 4번 때려서 화면 밖으로 못 나가게 했죠. ^^;

    if( x < 0 ) x = 0;
    if( y < 0 ) y = 0;
    if( x > LCD_WIDTH - IMG_WIDTH( img_char ) ) x = LCD_WIDTH - IMG_WIDTH( img_char );
    if( y > LCD_HEIGHT - IMG_HEIGHT( img_char ) )   y = LCD_HEIGHT - IMG_HEIGHT( img_char );

( 굳이... 또 붙일 필요가 있었나.... 에라 모르겠다.. 인쇄 매체를 통해 쓰는 것도 아니니.. 지면 낭비닷!! +_+ )

그 담에.... 총알 관리가 있는데..

그보다 먼저 put_image/putsprite가 좀 바뀌었죠. 마지막에 flip이란 인자를 뒀는데.. 그게 켜지면(TRUE) 좌우를 뒤집는 거죠. GpBitBlt의 좌우를 뒤집는 건 GpBit'LR'Blt란 놈입니다. (Left-Right) 상하를 뒤집는 건 GpBit'UD'Blt라는 놈이죠. (Up-Down) 뭐... 그런 겁니다 -_- 원래 좌우 뒤집기는... 4의 배수 어쩌고가 있는데... 일단 무시하고 했습니다. 대충.. -_- 원래는 그러면 안되여~ 쿨럭... 제대로 된 좌우 뒤집기에 대해서는 work/examples/ex009를 참조하세여~ ( 거기선 Surface 하나에 뒤집힌 놈을 찍고 그 Surface를 Active Surface에 부어주죠. 여기서 Active Surface는.. 예전의 DOS 시절에... 작업 중인 페이지를 Active Page라고 불렀기 때문에.. 그냥 Active Surface라고 임의로 불러봤습니다. 코드로 말하면 gpDraw[nflip] 정도겠죠 -\- )

자, 그리고 잠깐 보여드리는.. 매우 간단한 Object Management가 있죠. ^^; 단순히 배열을 잡아서 flag 변수를 가지고 뭐 생성됐나 체크하는 원시적인 방법인데..

    #define MAX_FIRE 5

    typedef struct _FIRE
    {
        int flag;
        int x, y;
    }   FIRE;

    FIRE fire[MAX_FIRE];

예전에 나름대로 잘 썼던 방식이고.. 지금 봐도 특별한 문제는 없네요... ^^ ( 적어도 간단한 2D 게임에선.. ) 물론, 이것도 나름대로 효율을 주려고 돌면서 Search하고, 현재 갯수 넣고.. 뭐 그런 짓을 하지만.... 고작 최대 발사 가능 5발인 놈이 그짓 해서 뭐하겠습니까 -_-

그리고 포인트가... 연사 불가...라는 건데...

    if( keydata &amp; GPC_VK_FA )
    {
        // 발사!!
    }

이런식으로 하면 A 버튼을 누르고만 있어도 마구 총알이 나가죠. 게다가.. 제 컴터에선 800fps가 넘게 나오고 있기 때문에.... 초당 800발이 나가는 엽기적인 상황이.. -_- ( MAX_FIRE를 100000 정도로 바꿔보면 존나 느려지는 게임을 볼 수... 쿨럭.. ) 그래서.. 새로 버튼이 눌렸는지 체크.. 즉, 이전에 A 버튼이 눌리지 않았고 AND 이번에 A 버튼이 눌렸는지 체크하는 거죠. ^^

    if( keydata &amp; GPC_VK_FA )
    {
        if( !press_a )
        {
            // 발사 !!
            press_a = 1;
        }
    }
    else    press_a = 0;

총알 발사 처리 부분은 단순하게 했죠. for로 돌려서 flag가 꺼진 놈을 찾아서... 찾았으면 총알 등록하고... break!!

    for( i = 0 ; i < MAX_FIRE ; i++ )
    {
        if( !fire[i].flag )
        {
            fire[i].flag = 1;
            fire[i].x = x;
            fire[i].y = y;
            break;
        }
    }

총알 출력 및 움직임도 간단합니다. 만약 충돌 처리를 한다면.. 여기에 추가를 해줘야겠죠. ( 원래 일반적으로 출력 부분과 움직임 부분은 분리합니다. 이 예제는 간단하니.. 그냥 하나로 처리하죠. ^^ )

    for( i = 0 ; i < MAX_FIRE ; i++ )
    {
        if( fire[i].flag )
        {
            // 총알 그려주기~
            put_sprite( fire[i].x, fire[i].y, img_char, 0 );

            // 총알을 움직이고, 화면 밖으로 나가면 제거!!
            fire[i].x++;
            if( fire[i].x >= LCD_WIDTH )    fire[i].flag = 0;
        }
    }

룰루랄라 간단하죠? ^^ 자, 그럼.... 여기서 마치면... 뭔가 섭섭하니.. 이것과 별개로.. 하나만 더 적어볼께여.. 이번에는 완벽히 실행되는 녀석은 아니고.. 짧은 소스입니다. ^^;

6. 충돌. 꽝.

예, 충돌처리에 대해서 짧게 다루려고 합니다. 뭐, Per Pixel Collision Detect를 하려는 건 아니구요.. ^^ 단순한 '사.각.형.충.돌.체.크'를 하려고 합니다. 게임 프로그래밍을 해보신 분은 다 아시겠지만.. 모르시는 분-게임 플밍을 GP32로 처음 도전하는 분-을 위해... 후후후.. +_+

이론은 간단합니다. 그냥 오브젝트를 사각형(3D로 치면 AABB죠^^)으로 만들어서 두 사각형이 겹치는 건지 보는 겁니다. 이건 매우 간단하죠. ^^ 단지 그 사각형을 회전시키거나(3D로 치면 OBB...털썩) 그런 짓을 하면 실제로 수학을 적용시켜야 하기 때문에 그냥 우리가 쓰는 이미지 그대로 충돌에 사용하죠. ^^;;;;;;;; ( 원래 이미지가 사각형이죠? )

자, 두 사각형이 겹친 것은 어떻게 알 수 있을까요? 가장 설명하기 쉬운 방법이라면... 두 사각형이 겹치는 사각형을 구해서... 그 크기가 0이면-또는 그보다 작으면?- 충돌 안됨. 실제로 존재하면(크기>0) 충돌이죠...

그림으로 표현하면...

    +---------+
    | A             |
    |         +---+----+
    |         | C  |        |
    +-----+---+        |
               |              |
               |           B |
               +--------+

A와 B가 겹쳐서 생기는 사각형 C를 구하는 겁니다. ^^;;;;

자, 그럼 그 사각형 C는 어떻게 구할까요? 간단히 사각형의 left는... 두 사각형의 left 중 큰 값이 적용되면 되겠죠? ( 위의 그림을 보시면서 이해하세여~ ^^ ) 그리고... top도 마찬가지로 A, B 중 큰 top 값을 이용... right와 bottom은 반대로 작은 값을 이용하면 되죠. 즉, C의 영역(RECT를 써볼까요? ^^)을 구하면..

    C's rect.top    = MAX( A's rect.top,    B's rect.top );
    C's rect.bottom = MIN( A's rect.bottom, B's rect.bottom );
    C's rect.left   = MAX( A's rect.left,   B's rect.left );
    C's rect.right  = MIN( A's rect.right,  B's rect.right );

이렇게 되겠죠. ^^;;;

자, 그럼 이 사각형이 존재하는(실존하는?) 놈인지의 검사는 간단하죠? 세상에 right가 left보다 작은 사각형이 있나요? 아니면 top이 bottom보다 작은 사각형이 있나요? ^^; ( 값은 같아도 됩니다. right == left이고 top == bottom일 경우 점..이니까요 ^^ 그것도 크기 1짜리 사각형이죠. )

    if( C's rect.top <= C's rect.bottom &amp;&amp; C's rect.left <= C's rect.right )
    {
        // 충돌했드아!!!!!!!!!!
    }

뭐... 이런 겁니다. ^^ 나름대로 좀 잘 좀 해보려고 했는데.. 이상한가.. ^^;;;; 하여튼... 이런 식으로 처리가 되고.. 만약 Per Pixel Collision Detect를 하고 싶으면.. 그래도 일단 사각형 충돌 체크로 러프하게 처리하구요.. 겹쳐서 생기는 사각형 내의 픽셀들만 겹치는지 체크하는 게 좋겠죠? ^^

예, 위에 쓴 것처럼... 일단은 여기서 잠시 쉽니다. -_- ( 어제 쓰기 시작했잖아!! (버럭) ) 실은... 맨 위에 쓴... 그런 외국 사이트들을 보고... 국내 GP32 실정에 좌절을.......(이라고 해봤자 핑계다) 뭐, 이거랑 별도로 GP32용 Simple 3D Engine 등도 준비할 예정이니 ( 물론........... 안 만들 가능성이.... 무진장 높다 -_- ) 기대하지는 마시고..... 열심히 게임 만드세여~ ^^ 원래 제가 하고자 했던... GP32 SDK를 구해놓고도 뭘할지 헤매는 분을 위한 간단한 Start Up 정도의 역할은 나름대로 했다고 생각하기 때문에... 후후후... +_+

모두모두 즐작...하세여~

--2002.04.11 아샬

P.S. 마치.. 마치는 글 같군... -_-; 컴백...합니다.. 준비해서.......... 저 사라지는 거 아니에요. T_T



분류:프로그래밍