2002년도에 적은 글입니다.

--------------------------------------------

회사 다닐 때부터 BREW로 만든 프로그램의 전체 흐름을 한번 정리 하고 싶었는데 그때는 시간도 별로 없었고, 회사 사퇴 후 할려고 마음만 먹고 있다가 현재 회사 그만두었고 지금 제가 반 폐인 생화을 하고 있는 관계로 새벽에 잠이 안왔어 제가 만든 게임을 기초로 대충 정리 한것입니다… 제 실력이 미천하고 경험이 없다 보니 틀린 부분도 있을거고 제가 한게
정석이 아니니 필요 하신분은 약간 참고로 하면 좋지 않을까 생각됩니다.

이 글을 보기전에 BREW 프로그래밍을 안 해보신분은
http://devsupport.magicn.com/devsite/lecture/lecture_list.asp?index=lec&board_id=2
갔어 먼저 튜트리얼 부분을 보시면 많은 도움이 되실겁니다.




-        프로그램의 초기화 부분

  BREW의 경우 세부 사항에 관한 자료가 없어 저도 세부 사항에 대해서 아는 것이 거의 없는데 프로그램이 초기 시작부분을 보면 Windows의 COM 부분과 거의 흡사합니다. BREW의 라이브러의 등록 및 해제도 COM 사용과 비슷하고..그래서 전 BREW는 COM을 따라하지 않았나 생각하고 있습니다. BREW 자체가 컴포넌트 식으로 만들어진 것 같더군요.먼저 BREW에서는 프로그래밍시 처음 코딩하는 부분이 바로 아래의 코더 입니다. 보통 다른 프로그램도 거의 밑의 부분에 관해서는 비슷할 거 라고 생각됩니다.

예)
…….
// 이 매크로는 데브피아에 올라온 자료를 보고 좋은 것 같아 저도 그 자료를 토대로 그 대
// 사용한 것입니다. 그러니 매크로는 사용해도 되고 안해도 되는거죠..
// 밑의 매크로가 이해가 되지 않는분이라면 그냥 무시하세요…
// 이벤트 메세지를 MFC의 메세지 맵의 인터페이스 처럼 하기 위한 메크로
#define BEGIN_EVENT_MAP(_e_) \

switch(_e_) {
#define ON_EVENT(_ec_,_f_) \
                        case _ec_ : { \
                                return _f_##(pi,wParam,dwParam); \
                        }
#define END_EVENT_MAP() \
                        default: break; \
                        }
/*-------------------------------------------------------------------
게임의 이벤트 메세지를 받는 콜백 함수
-------------------------------------------------------------------*/
static boolean Game_HandleEvent(IApplet * pi, AEEEvent eCode, uint16 wParam, uint32 dwParam);


// 게임이 시작하면 이 함수가 가장먼저 실행됩니다.
// 게임에서 사용할 구조체인 PLAYER와 핸들 이벤트를 BREW의 프로그램 매니저에 등록하고
// 게임 시작시의 호출하는 함수와 게임 종료시에 호출할 함수를 등록합니다.
//===========================================================================
int AEEClsCreateInstance(AEECLSID ClsId,IShell * pIShell,IModule * po,void ** ppObj)
{
   *ppObj = NULL;
               
   if(ClsId == AEECLSID_TENNIS)
   {
      if(AEEApplet_New(sizeof(PLAYER), ClsId, pIShell,po,(IApplet**)ppObj,
         (AEEHANDLER)Tennis_HandleEvent,(PFNFREEAPPDATA)Game_Free)
         == TRUE)
      {
                // 게임이 실행되면 가장 먼저 실행됩니다.
                 if( Game_Init((IApplet*)*ppObj) )
                 {
                         return (AEE_SUCCESS);

}
                 else {
                         IAPPLET_Release((IApplet*)*ppObj);
                         return EFAILED;
                 }
      }
   }
   return (EFAILED);
}



// 게임에서 사용하는 이벤트를 등록하는 부분입니다.
// 이 부분을 제가 위에 정의한 매크로를 사용했어 다른분들과 좀 다를겁니다.
// 이벤트 등록은 MFC를 해보신은 메시지 맵을 아실건데 그것과 같다고 보시면 됩니다.
// BREW에서 지원하는 여러 이벤트 중 필요한 이벤트를 등록하고 그 이벤트가 일어날 때
// 해야 될 동작을 코딩하면 되죠…
/////////////////////////////////////////////////////////////////////////////////////
// ------------------------------- Tennis_HandleEvent()
static boolean Game_HandleEvent(IApplet * pi, AEEEvent eCode, uint16 wParam, uint32 dwParam)//
{  
   BEGIN_EVENT_MAP(eCode)
                ON_EVENT(EVT_APP_START,OnStart)                // 게임이 시작되면        
                ON_EVENT(EVT_APP_STOP,OnStop)                // 게임이 멈추면        
                ON_EVENT(EVT_APP_SUSPEND,OnSuspend)        // 게임의 일시중지 일 때
                ON_EVENT(EVT_APP_RESUME,OnResume)   // 게임을 일지중지에서 재 시작
                ON_EVENT(EVT_KEY,OnKey)                    // 키를 이벤트        
                ON_EVENT(EVT_KEY_PRESS,OnKeyPress)  //         키를 누르면..
   END_EVENT_MAP()

        return FALSE;

} // End Tennis_HandleEvent()

// 위의 이벤트 등록 부분을 매크로를 사용하지 않고 코딩하면 아래와 같아 집니다.

static boolean Game_HandleEvent(IApplet* pi, AEEEvent eCode, uint16 wParam, uint32 dwParam)
{
      PLAYER *pMe = (PLAYER*)pi;
      switch (eCode)
      {
           case EVT_APP_START:
              ………..
..............
              return(TRUE);
           case EVT_APP_STOP:
………………
              return TRUE;
case EVT_APP_SUSPEND:
…………
………..
           default:
              break;
     }
      return FALSE;
}

위의 이벤트 등록 함수를 부분 중 EVT_KEY, EVT_KEY_PRESS가 있는데 이 둘의 명확한 차이는 저도 잘 모르고 있습니다. 다만 핸드폰의 키를 누르면 EVT_KEY이벤트가 일어나고 그후 연속하여 EVT_KEY_PRESS가 발생합니다. 키를 누르면 위의 이벤트 둘다 순서대로 발생합니다.
저의 경의 EVT_KEY는 게임에서 이름 입력을 위한 텍스트 컨트롤 사용시에 이 이벤트를 사용했고 그거 이외의 모든 키 이벤트는 EVT_KEY_PRESS에서 처리하게 만들었습니다.

위 코드가 하는 것은 게임에서 사용하는 구조체와 이벤트 등록과 프로그램 실행이 가장 먼저 해야될 부분이 들어가 있습니다.
그리고 위에서 제가 게임에서 사용하는 구조체 PLAYER를 등록한다고 했는데 이 PLAYER는 게임에서 지속적으로 사용할 모든 변수를 들을 가지고 있는 구조체를 뜻합니다.

구조체 이름은 당근 프로그래머가 임의로 정하면 됩니다. 전에 제가 올린 글에 이야기 했듯이  BREW는 전역변수를 지원하지 않습니다. 그래서 이런 구조체를 만든 후 이 구조체를 등록하고 각 함수에서는 이 구조체를 포인터로 argument로 전달하여 사용합니다.
이 구조체를 여타 함수에서 사용할려면 꼭 함수의 argument로 전달하므로 거의 모든 함수마다 이 구조체를 사용하는 것을 볼수 있을겁니다. 1회성이 아닌 변수는 모두 이 구조체에 선언 하여 사용하면 됩니다.

예)
typedef struct __PLAYER
{
        AEEApplet                m_AEE; // 이 변수는 꼭 있어야 되는 것입니다.
        AECHAR                        *pName;                
        AECHAR                        *pText;                
        ITextCtl                *pTextBox;        
        IMenuCtl                *pSoftMenu;        
        ISoundPlayer                *pSoundPlayer;                
        IGraphics                *pIGraphics;        
        KOURTINFO        CourtInfo; // 타 구조체를 변수로  사용 할수도 있습니다.
        boolean                        bServeShot;        
        byte                        MenurtY;                
        byte                        MenuGap;                
        boolean                        bWalfFirst;        
        byte                        SeleNum;                
        byte                        ComPntry;        
        byte                        OldTos;                
        byte                        Rall;
…………
}PLAYER
참고로 위에서 제가 byte를 사용한게 있는데 제가 처음에는 메모리 조금이라도 아껴 볼려고 int 보다는 byte를 사용했는데 뒤에 생각해보니 쓸데 없는짓을 했더군요…
먼저 핸드폰의 메모리가 컬러 정도라면(보통 게임들이 칼라폰을 기준으로 만드니까요..) 메모리도 좀 넉넉하고 핸드폰의 ARM CPU가 32비트라서 byte를 사용하면 이게 연속적으로 32비트가 되게 선언되었으면 상관 없지만 그렇지 않다면 byte를 사용해도 컴파일시 자동으로 int로 바뀌어 버릴거고 특히 GVM으로 포팅시 GVM은 byte를 지원하지 않으니 byte를 모두 int로 바꾸어야 되는 일이 발생하더군요..그러니 전 byte 보다는 int를 사용하는걸 추천합니다.



-        게임의 시작
게임이 시작하면 먼저 위의 코드 중 AEEClsCreateInstance()에서 등록한 Game_Init()가
실행하고 그 뒤로 EVT_START 이벤트에서 정의한 함수가 실행됩니다.

Game_Init() 게임에서 사용하는 각종 변수들을 초기화 하는걸로 사용하면 됩니다.
예)
boolean Game_Init(IApplet *pApp)
{
        PLAYER *pPlayer = (PLAYER*)pApp;

        pPlayer->pSoftMenu = NULL;
        pPlayer->pText = NULL;                
        pPlayer->pSoundPlayer = NULL;        
       
        Init_Name(pPlayer);                
        ………………
}

EVT_START는 윈도우 프로그래밍으로 보면 WM_CREAT와 같다고 보시면 됩니다.
위의 Game_Init()다음으로 실행되는 함수입니다. 저는 EVT_START에 OnStart()
함수가 실행되도록 위에서 코딩 했으니 다음의 함수가 실행됩니다.
예)
boolean OnStart(IApplet* pi, uint16 wParam, uint32 dwParam) ///////////////////////////////////////
{
        …………….
        PLAYER *pPlayer = (PLAYER*)pi;

        // KTF 지정 이미지 로드
        LoadIntroImage(pPlayer);
…….
        ……..
        // 인트로 그림을 그린다.
        IIMAGE_Draw( pPlayer->Imaeg.Intro, 0, 0);
        IDISPLAY_Update(pPlayer->m_AEE.m_pIDisplay);
        return TRUE;
} // End OnStart()

이 함수 게임이 실행되면 가장 먼저 나오는 이 게임의 인트로 이미지로 메모리에 로드한 후 그걸 폰의 LCD에 나타나게 하고 있습니다.

PLAYER *pPlayer = (PLAYER*)pi;
이벤트 함수의 argument는 BREW에서 지정된 방식으로 해야되기 때문에 위와 같이 자신이 정의한 구조체에 맞게 형변환을 해야 됩니다.

LoadIntroImage(pPlayer);
이 함수는 리소스 파일에서 그림 파일을 불러 온 후 메모리에 할당하는 함수입니다.
BREW는 리소스 파일을 지원하므로 텍스트나 그림등은 리소스 파일에 등록하여 사용하면 좋
습니다.

boolean LoadIntroImage(PLAYER *pPlayer)
{
        // 메모리가 할당 되어 있으면 해제시킨다.
        if( pPlayer->Image.Intro != NULL )
        {
                IIMAGE_Release(pPlayer->Image.Intro);
                pPlayer->Image.Intro = NULL;
        }

 // 리소스에서 원하는 그림 리소스를 불러온후 메모리에 할당한다.
                pPlayer->Image.Intro = ISHELL_LoadResImage(pPlayer->m_AEE.m_pIShell,
TENNIS_RES_FILE,IDB_INTRO);
       
        // 메모리 할당이 실패하면…
        if( pPlayer->Image.Intro == NULL ) return FALSE;

        return TRUE;
}
저는 이미지의 메모리 할당 및 해제는 서로 짝으로 함수로 만들어 사용했습니다.
void ImageIntroRelease(PLAYER *pPlayer)
{
        if( pPlayer->Image.Intro != NULL )
        {
                IIMAGE_Release(pPlayer->Image.Intro);
                pPlayer->Image.Intro = NULL;
        }
}
저의 경우는 코딩 할 때 코드의 유지보수성을 높이기 위해 가능한 함수를 만들어 코딩을 했습니다. 그런 결과 제 코드를 보면 거의 모든 것이 함수 위주로 되어 있으면 함수의 길이가 주석을 포함하여 최대 한 화면을 넘어서지 않도록 했으면 불가피 하게 4-7개 정도만 한 화면을 넘어서는 길이의 함수를 사용했습니다.

참고로 Game_Init()와 OnStart() 다음에는 게임이 종료할 때 실행 할 함수인 Game_Free()
도 있습니다. 이 함수도 AEEClsCreateInstance()에서 등록합니다.
예)
void Game_Free(PLAYER *pPlayer) /////////////////////////////////////////////////////////////
{
        ………………………
         ImageTextRelease(pPlayer);
         ImageWorldMapRelease(pPlayer);
         ImageArrowRelease(pPlayer);
         ImageKTFRelease(pPlayer);
         ImageMapTextRelease(pPlayer);
         ImageSoftMenuRelease(pPlayer);
         ImageCloseRelease(pPlayer);
         …………..
        // 텍스트 컨트롤을 해제한다.
        if( pPlayer->pTextBox != NULL )
        {
                ITEXTCTL_Release(pPlayer->pTextBox);   pPlayer->pTextBox = NULL;
        }

IDISPLAY_SetColor(pPlayer->m_AEE.m_pIDisplay, CLR_USER_BACKGROUND,RGB_WHITE);
        IDISPLAY_SetColor(pPlayer->m_AEE.m_pIDisplay,CLR_USER_TEXT,RGB_BLACK);

}
보통 이 함수에서는 게임에서 포인터로 할당한 변수들을 검사하여 메모리가 할당 되어 있으면 해제를 하게 하면되고 게임에서 기본 텍스트나 Background 칼라값을 변경하였다면 꼭 원래의 칼라로 되돌려야 된다.. 만약 되돌리지 않으면 이 게임 말고 다른 게임에서 이 게임에서 지정한 칼라 값이 적용 될수도 있기 때문이다.

-        이벤트 처리
게임 시작 후 인트로 그림이 출력하면 그 다음으로 아무키나 누르면 KTF 지정 그림이 출력하게 해야 되기 때문에 이 부분은 EVT_KEYPRESS에서 처리하게 해야됩니다.
저의 경우는 OnKeyPress()를 구현하면 되니까 아래와 같습니다.
예)
boolean OnKeyPress(IApplet* pi, uint16 wParam, uint32 dwParam)
{
PLAYER *pPlayer = (PLAYER*)pi;
uint16 keytype = wParam;

        …………………………………
        // 게임의 현재 상태에 따라 키에 따른 다른 동작을 한다.
        switch( pPlayer->Game_State )
        {
        case INTRO:        // 인트로 그림출력 상태
                KEY_INTRO(pPlayer);        
                break;
        case INTRO_KTF: // KTF 지정 그림 출력 상태                                
                KEY_INTRO_KTF(pPlayer);        
                break;
        case MAINMENU:        // 게임의 메인 메뉴 출력 상태                        
                KEY_GAME_MAINMENU(pPlayer, keytype);        
                break;                                                                        case OPTION:  // 게임에서 메인메뉴 중 옵션을 선택하여 옵션 선택 화면이
                      // 출력되어 있는 상태
                KEY_GAME_OPTION(pPlayer, keytype);        
                break;                                                                        …………………
        ……………………
        }

        return TRUE;

}
위 코드와 같이 저는 게임의 각 상태에 따라 pPlayer->Game_State에 현재의 상태를 지정해 놓아 OnStart()에서 pPlayer->Game_State = INTRO 로 하고 인트로 그림을 출력하게 했는데 그 상태에서 아무 키를 누르면 KEY_INTRO(pPlayer) 함수가 실행되어 인트로 그림을 지우고 KTF 지정 그림을 리소스에서 불러오고 그 그림을 출력하고
pPlayer->Game_State = INTRO_KTF로 지정합니다.
이런식으로 게임을 상태를 상세하게 정의 해 놓고 각 단계마다 현재 상태를 지정하고 그 상태에서 키 입력에 따른 행동을 구현하면됩니다.
그리고 각 상태 때 마다 동일한 키 입력이 다른 구현을 해야되므로
KEY_GAME_MAINMENU(pPlayer, keytype) 함수등을 사용하여 현재 게임 상황에 따라 구현 해야될 사항을 구현한다.

예)
인트로 그림을 메모리 해제를 한후 KTF 지정 이미지를 리소스에서 로드 한 후 글니다.
그리고 현재 상태를 지정한다.
void KEY_INTRO(PLAYER *pPlayer)
{
        // 게임의 인트로를 메모리 헤제한다.
        ImageIntroRelease(pPlayer);

        ……………………..
        IIMAGE_Draw(pPlayer->Img.KTF,0,0); // KTF 지정 이미지를 그린다.

pPlayer-> Game_State = INTRO_KTF; // 현재 게임 상태를 지정한다.

        ……………………
        IDISPLAY_Update(pPlayer->m_AEE.m_pIDisplay);
}


게임의 메인 메뉴 선택화면에서 up,down,선택 키에 따른 처리를 한다.
        아래의 코드를 보면 if문을 사용했는데 속도 향상을 위해서는 조건문이 2개 이상일 때에는 switch문이나 함수포인터를 사용하는 것이 빠릅니다.

void KEY_GAME_MAINMENU(PLAYER *pPlayer,uint16 keytype)
{
        if( keytype == AVK_UP )
        {
                ……………………..
                GAME_MAINMENU_KEY_UP(pPlayer);                
                …………………………                        
                return;
        }
       
        if( keytype == AVK_DOWN )
        {
                GAME_MAINMENU_KEY_DOWN(pPlayer);
                ………………………
                return;
        }
       
        if( keytype == AVK_SELECT )
        {
                MainMenu_Select(pPlayer);
        }
}        




-        타이머 사용
BREW에서는 타이머를 하나만 지원하고 타이머를 호출한 후 타이머 함수가 실행된후 다시 타이머를 호출해야 연속적으로 타이머를 발생 시킬 수 있습니다.

ISHELL_SetTimer(pPlayer->m_AEE.m_pIShell,300,(PFNNOTIFY)Render,pPlayer);

위와 같이 타이머를 지정하며 0.3초후에 Render() 함수를 호출합니다.

Void Render(PLAYER *pPlayer)
{
…………
…………
ISHELL_SetTimer(pPlayer->m_AEE.m_pIShell,300,(PFNNOTIFY)Render,pPlayer);
}
위와 같이 Render 함수를 0.3초 마다 계속 호출하고 싶으면 Render() 함수에 위와 같이
다시 타이머를 셋팅 해야 됩니다.

타이머가 중요한 이유는 보통 게임에서 본 게임으로 들어가기전의 각종 옵션 선택등의 메뉴부분의 처리는 키 이벤트등으로 처리하면 되지만 본 게임의 루틴이 들어가 있는 함수는 타이머로 셋팅해야 됩니다. 예를 들어 축구 게임을 만든다면 축구 경기를 하기전의 팀선택등은 키 이벤트등을 이용하다가 경기에 들어가면 경기가 끝날 때 까지의 처리는 위의 Render()와 같은 함수를 만들어 이 함수를 타이머로 지정하면 계속적으로 호출하게 하면 되는거죠.. 연속적인 타이머 호출로 while()문과 같은 루프 기능을 구현하는거죠.
SDK에 보면 타이머 셋팅을 최소 150 이상은 하라고 되어 있는데 제 경험이나 주위의 이야기를 들어보면 0.05초까지도 셋팅에 문제 없습니다. 다만 0.05초로 셋팅을 해도 위의 Render() 함수의 경우 본 게임의 루틴이 들어가 있고 시간을 가장 많이 잡아먹는 화면 출력 부분이 있어 Render()가 0.05초마다는 불러지지는 않습니다.

신고
by 흥배 2009.03.21 02:59
| 1 |