각각의 메뉴 구현.

Mobile 맞고 게임 제작 강좌 세번째 시간입니다.

오늘은 맞고 게임에서 메뉴 화면과, 그에 따른 서브 메뉴들을 한번 구현해 보는 시간을 갖도록 하겠습니다.


음... 지난시간에 만든 소스에 이어서 만들어지는 형식이므로, 먼저 지난시간의 소스를 준비해 주시고요.

오늘은 지난시간의 소스에 CyMenu라는 클래스를 하나 추가해 보겠습니다.


CyMenu 클래스는, 그야말로, 메뉴 화면의 정보를 세팅하고 화면에 그려주는 기능을 하는 클래스입니다.

일단 소스 코드를 먼저 살펴보고 하나하나 따로 설명을 드리도록 하겠습니다.

* CyMenu.java.

package common;

import javax.microedition.lcdui.*;
import com.skt.m.Graphics2D;

/**
* 메뉴를 화면에 그려준다.
*/


public class CyMenu {
/*
* 게임에서 쓰일 상수.
*/

private
final byte UPARROW = 0;
private final byte DOWNARROW = 1;

// 메뉴 항목 스프라이트의 폭과 높이.
private
final byte BACK_WIDTH = 80;
private
final byte BACK_HEIGHT = 100;

// 메뉴 항목 스프라이트의 폭과 높이.
private
final byte MENU_WIDTH = 68;
private
final byte MENU_HEIGHT = 14;

// 스크롤바의 폭과 높이.
private
final byte SCROLL_WIDTH;
private
final byte SCROLL_HEIGHT;

private
final byte FIRST; // 메뉴 항목의 제일 처음 인덱스.

public
final static byte GAME_START=0; // 게임 시작.
public
final static byte GALLERY=1; // 겔러리.
public
final static byte CONFIG=2; // 환경설정.
public
final static byte HELP=3; // 도움말.
public
final static byte SEND_GAME=4; // 선물 보내기.
public
final static byte EVAL‍_GAME=5; // 게임 평가.
public
final static byte ABOUT_COMPANY=6; // 게임 문의.
public
final static byte QUIT_GAME=7; // 게임 종료.

private
final byte LAST; // 메뉴 항목의 제일 마지막 인덱스.

/*
* 일반 변수.
*/

private b
yte curMenuIdx; // 현재 선택된 메뉴 인덱스.
private byte dispCnt; // 화면에 보여줄 최대 항목 갯수.
private
byte minDispMenuIdx; // 현재 화면에 보여지는 최소 메뉴 인덱스.
private
byte maxDispMenuIdx; // 현재 화면에 보여지는 최대 메뉴 인덱스.
byte itemInterval; // 메뉴 항목 사이의 간격.

Image imgBack; // 메뉴의 뒷배경 이미지.
Image sprMenu; // 메뉴 항목의 스프라이트 이미지.
Image sprScroll; // 스크롤 화살표 스프라이트 이미지.

private Canvas parent; // 메뉴를 그려줄 캔버스.
private short parentWidth; // 캔버스의 폭.
private short parentHeight; // 캔버스의 높이.

private short upScrollLeft;
private short upScrollTop;

private short downScrollLeft;
private short downScrollTop;

private short backLeft, backTop; // 배경 이미지의 위치.
private short menuLeft, menuTop; // 메뉴 스프라이트의 위치.

Graphics2D g2d;

/**
* Constructor.
* @param parent
* @param maxMenuItem
*/

public CyMenu(Canvas parent, int maxMenuItem)
{
this.parent = parent;

this.FIRST = 0;
this.LAST = (byte)(maxMenuItem - 1);

sprMenu = new Image(); // 최대 항목의 갯수 만큼 이미지 배열을 생성한다.

parentWidth = (short)parent.getWidth();
parentHeight = (short)parent.getHeight();

itemInterval = 0;
curMenuIdx = FIRST; // 초기 디폴트 선택 메뉴는 "게임 시작"

init(); // 초기화.

SCROLL_WIDTH = (byte)(sprScroll.getWidth()/2);
SCROLL_HEIGHT = (byte)sprScroll.getHeight();
}

/**
* 초기화.
*/

private
void init()
{
loadImages(); // 게임에 필요한 각종 이미지를 로드한다.

setItemInterval(2); // 메뉴 간의 간격 조정.
setDispCnt(4); // 한 화면에 보여줄 항목 갯수 설정.

// 배경이미지를 가운데 정렬.
setBackPos((parentWidth-BACK_WIDTH)/2,
(parentHeight-BACK_HEIGHT)/2);

// 메뉴항목 이미지의 위치 설정.
setMenuPos(backLeft+(BACK_WIDTH - MENU_WIDTH)/2,
backTop+25);

// 스크롤에 쓰이는 화살표 스프라이트 이미지 위치 설정.
setScrollBarPos();

minDispMenuIdx = 0;
maxDispMenuIdx = (byte)(getDispCnt());
}

/**
* 화면에 메뉴 화면을 그려준다.
* @param g
*/

public
void show(Graphics g)
{
// 메뉴의 배경 이미지를 화면 중앙에 그려준다.
if(imgBack != null)
g.drawImage(imgBack, backLeft, backTop, Graphics.LEFT | Graphics.TOP);

if(g2d == null)
g2d = Graphics2D.getGraphics2D(g);

drawMenuItem(); // 각각의 메뉴 항목을 그려준다.

// 현재 보여지는 메뉴의 제일 윗 항목이 처음이 아니라면,
if(minDispMenuIdx != FIRST)
drawScroll(g, UPARROW);

// 현재 보여지는 메뉴의 제일 윗 항목이 마지막이 아니라면,
if(maxDispMenuIdx != LAST)
drawScroll(g, DOWNARROW);
}

/**
* 화면에 메뉴를 표시해준다.
* @param beginIdx 화면에 보여지는 최소 인덱스.
* @param endIdx 화면에 보여지는 최대 인덱스.
*/

private
void drawMenuItem()
{
for(int i=minDispMenuIdx; i<=maxDispMenuIdx; i++)
{
// 현재 선택된 메뉴 항목이라면,
if(i == curMenuIdx)
g2d.drawImage(
menuLeft, menuTop + (i-minDispMenuIdx)*(MENU_HEIGHT+itemInterval),
sprMenu, MENU_WIDTH, i*MENU_HEIGHT,
MENU_WIDTH, MENU_HEIGHT, Graphics2D.DRAW_COPY );
else
g2d.drawImage(
menuLeft, menuTop + (i-minDispMenuIdx)*(MENU_HEIGHT+itemInterval),
sprMenu, 0, i*MENU_HEIGHT,
MENU_WIDTH, MENU_HEIGHT, Graphics2D.DRAW_COPY );
}
}

/**
* 스크롤 바를 그려 줌.
* @param g
* @param type 스크롤 바의 type.
*/

private
void drawScroll(Graphics g, int type)
{
Graphics2D g2d = Graphics2D.getGraphics2D(g);

if(type == UPARROW)
{ // 위쪽 화살표를 그려줌.
g2d.drawImage(upScrollLeft, upScrollTop, sprScroll,
0, 0, SCROLL_WIDTH, SCROLL_HEIGHT, Graphics2D.DRAW_COPY);
}
else /* if(type == DOWNARROW) */
{ // 아래쪽 화살표를 그려줌.
g2d.drawImage(downScrollLeft, downScrollTop, sprScroll,
SCROLL_WIDTH, 0, SCROLL_WIDTH, SCROLL_HEIGHT, Graphics2D.DRAW_COPY);
}
}

/**
* 메뉴에 필요한 각종 이미지
를 로드한다.
*/

private
void loadImages()
{
try {
// 메뉴의 뒷 배경 이미지 로드.
imgBack = Image.createImage("/image/menu/ImgBack.lbm");
// 메뉴 항목 스프라이트 이미지 로드.
sprMenu = Image.createImage("/image/menu/SprMenu.lbm");
// 스크롤시 사용될 화살표 스프라이트 이미지 로드.
sprScroll = Image.createImage("/image/menu/SprScroll.lbm");
} catch(Exception e) {}
}

/**
* 매뉴 배경 이미지의 위치 설정.
* @param x
* @param y
*/

private
void setBackPos(int x, int y)
{
backLeft = (short)x;
backTop = (short)y;
}

/**
* 매뉴 항목 스프라이트의 위치 설정.
* @param x
* @param y
*/

private
void setMenuPos(int x, int y)
{
menuLeft = (short)x;
menuTop = (short)y;
}

/**
* 스크롤 바 위치 설정.
*/

private
void setScrollBarPos()
{
upScrollLeft = (byte)(menuLeft + (MENU_WIDTH - SCROLL_WIDTH)/2);
upScrollTop = (byte)(menuTop-7);

downScrollLeft = upScrollLeft;
downScrollTop = (byte)(menuTop+(dispCnt*(MENU_HEIGHT+itemInterval)+MENU_HEIGHT)+2);
}

/**
* 메뉴항목들간의 사이 간격을 조절
* @param value 간격
*/

public
void setItemInterval(int interval)
{
itemInterval = (byte)interval;
}

/**
* 화면에 최대로 표시할 메뉴 요소 개수 설정
* @param itemCnt
*/

public
void setDispCnt(int itemCnt)
{
dispCnt = (byte)(itemCnt-1);
}

/**
* 화면에 최대로 표시할 메뉴 요소 개수 리턴.
* @return dispCnt;
*/

public
int getDispCnt()
{
return dispCnt;
}


/**
* 이전 항목으로 포커스 이동.
*/

public
void pre()
{
// 처음 항목이 선택된 상태라면,
if(curMenuIdx == FIRST) return;

// 화면에서 위로 넘어가면 감소.
if(minDispMenuIdx == curMenuIdx)
{
minDispMenuIdx--; // 현재 화면에 보여지는 최소 인덱스 - 1.
maxDispMenuIdx--; // 현재 화면에 보여지는 최대 인덱스 - 1.
}

curMenuIdx--; // 현재 메뉴 인덱스를 - 1.
}

/**
* 다음 항목으로 포커스 이동.
*/

public
void next()
{
// 마지막 항목이 선택된 상태라면,
if(curMenuIdx == LAST) return;

// 화면에서 아래로 넘어가면, 증가.
if(maxDispMenuIdx == curMenuIdx)
{
minDispMenuIdx++; // 현재 화면에 보여지는 최소 인덱스 + 1.
maxDispMenuIdx++; // 현재 화면에 보여지는 최대 인덱스 + 1.
}

curMenuIdx++; // 현재 메뉴 인덱스를 + 1
}

/**
* 현재 선택된 메뉴의 index를 알려준다.
* @return 현재 선택된index
*/

public
int getCurrentIndex()
{
return curMenuIdx;
}

/**
* 자원 해제.
*/

public
void cleanUp()
{
sprMenu = null;
parent = null;
imgBack = null;
sprScroll = null;
}
}


음... 일단 소스가 좀 길긴 하지만, 어려운 내용은 하나도 없네요.

먼저 생성자 부분을 보시면, 각종 변수/상수들을 초기화 해주고 있는걸 볼수 있습니다.

중요한 변수만 몇개 살펴보자면, parent라는 건.... 메뉴를 그려줄 Canvas이구요.

FIRST/LAST는, 각각 메뉴 항목의 첫번째 항목 인덱스와, 마지막 항목 인덱스를 저장할 상수입니다.


그리고 그다음 생성자 내에서 호출하게 되는 초기화 함수인 init()에서는,

메뉴 항목들의 이미지를 미리 로딩하고 메뉴의 위치를 정렬 합니다.

// 배경이미지를 가운데 정렬.
setBackPos((parentWidth-BACK_WIDTH)/2, (parentHeight-BACK_HEIGHT)/2);

// 메뉴항목 이미지의 위치 설정.
setMenuPos(backLeft+(BACK_WIDTH - MENU_WIDTH)/2, backTop+25);

먼저 배경이미지를 가운데 정렬 하고, 구해진 배경이미지의 위에서 메뉴 항목을 가운데 정렬 합니다.


그리고 그밖의 변수들은,

 

대충 메뉴 화면을 구현하면 위와 같은 화면이 생성되므로,

위의 그림을 바탕으로 변수들을 이해해 보시면 이해가 빠를라고 생각 됩니다.

보통 Left/Top 접미사를 갖는 변수들은 어떤 객체의 위치를 나타내는 변수,

Width/Height 접미사를 갖는 변수들은 어떤 객체의 크기를 나타내는 변수를 의미합니다.


또, minDispMenuIdx는 현재 화면상에 보여지는 메뉴 항목의 최저 인덱스,

maxDispMenuIdx는 현재화면상에 보여지는 메뉴 항목의 최대 인덱스를 의미하는데,

메뉴의 스크롤 기능을 위해서 이와 같은 변수를 두었습니다.



그다음 show() 함수에서 메뉴에 필요한 각 요소들을 그려주는데요.

// 메뉴의 배경 이미지를 화면 중앙에 그려준다.
if(imgBack != null)
g.drawImage(imgBack, backLeft, backTop, Graphics.LEFT | Graphics.TOP);

초기화시에 이미 배경 이미지는 가운데 정렬을 했으므로, 예전에 구한 위치에다가 그대로 그려줍니다.


그리고 다음에 나오는 getGraphics2D() 함수는,

Graphics 객체에서 Graphics2D 객체를 얻어오는 함수입니다.

g2d = Graphics2D.getGraphics2D(g);

단순히 Graphics2D 객체를 사용하기전에 위와 같이 호출해 주시면 Graphics2D 객체를 사용할 수 있습니다.

Graphics2D 객체는, 이미지의 특정 영역을 그려줄 수 있는 기능이 있기 때문에, 자주 사용됩니다.


이제 얻어온 g2d 객체를 이용해서 메뉴 항목을 그려주는 drawMenuItem() 함수를 호출하게 되는데요.

메뉴 항목은 다음과 같이, 하나의 이미지 파일 안에 저장이 되어 있습니다.

image/메뉴/SprMenu.lbm
 

이렇게 하나의 이미지로 되어 있다보니, 게임 시작 항목을 그려주고 싶다면,

g2d 객체를 이용해서 게임 시작 부분만을 잘라내어 화면상에 그려주어야 하겠지요.

drawMenuItem() 함수를 보면 그와 같은 작업이 구현되어 있습니다.

for(int i=minDispMenuIdx; i<=maxDispMenuIdx; i++)
{
// 현재 선택된 메뉴 항목이라면,
if(i == curMenuIdx)
g2d.drawImage(
menuLeft, menuTop + (i-minDispMenuIdx)*(MENU_HEIGHT+itemInterval),
sprMenu, MENU_WIDTH, i*MENU_HEIGHT,
MENU_WIDTH, MENU_HEIGHT, Graphics2D.DRAW_COPY );
else
g2d.drawImage(
menuLeft, menuTop + (i-minDispMenuIdx)*(MENU_HEIGHT+itemInterval),
sprMenu, 0, i*MENU_HEIGHT,
MENU_WIDTH, MENU_HEIGHT, Graphics2D.DRAW_COPY );
}

소스의 drawMenuItem 함수를 보면 위와 같이 구현이 되어 있는데,

minDispMenuIdx에서 maxDispMenuIdx까지 메뉴 항목을 출력해 줍니다.


Graphics2D의 drawImage() 함수는, 총 8개의 매개변수를 가지고 있습니다.

drawImage(화면(Canvas)상에 그려줄 x 좌표, y 좌표, 그려주고자 하는 Image 클래스,
어디서부터 그려줄 것인지 이미지 상의 x좌표, y좌표,
어디까지 그려줄 것인지 폭, 높이);

와 같이 구성 되어 있습니다.


우리가 parent Canvas의 5, 5에, 1번 메뉴인, 게임시작 메뉴를 그려주고 싶다면,

g2d.drawImage(5, 5, // Canvas의 5, 5에...
sprMenu, // sprMenu 클래스에 로드된 이미지 안에서,
0, 0, MENU_WIDTH, MENU_HEIGHT); // 0, 0부터 버튼 하나의 사이즈 만큼을 그려준다.



또, 5번 이미지를 읽어 오고 싶다면, MENU_HEIGHT을 5번 더한 높이에서부터 이미지를 그려주면 되겠죠.


그런데 만약 i가 curMenuIdx, 즉 현재 선택된 메뉴 항목이라면,

노란색 사각형안에 있는 글씨를 출력해 주어야 겠지요?

그러면, 간단히 x좌표에 MENU_WIDTH를 더한 부분 부터 이미지를 읽어 오면 됩니다.


그 외에는 모두 내부 또는 외부에서 클래스 내의 변수를 변경하고 호출하는 함수입니다.


마지막으로, 제일 끝 부분에 있는 pre() 함수와 next() 함수에서는

메뉴 항목의 포커스를 앞뒤로 이동시켜주는 함수인데,

스크롤 될 필요가 있다면, 즉, 화면 밖에 메뉴 항목이 더 남아 있다면,

minDispMenuIdx와 maxDispMenuIdx를 더하거나 빼주어 스크롤이 되게 해줍니다.



이제 메뉴를 출력하기 위한 CyMenu클래스를 완성했으므로,

지난 시간에 작성했던 소스에 CyMenu 클래스를 이용해서 메뉴를 달아보도록 하겠습니다.


먼저 메뉴를 그려주기 위해, 지난시간에 작성했던 MainMenu.java 파일에

메뉴를 출력하는 코드를 추가합니다.


지난시간에 작성했던 MainMenu 클래스의 처음 전역 변수 선언 부분에,


private CyMenu cymenu;


위와 같이 방금 전에 작성했떤 CyMenu 클래스를 선언하고,

MainMenu 클래스의 생성자 부분에서, 다음과 같이 cymenu 객체를 생성해 줍니다.

cymenu = new CyMenu(this, 8);

생성자에 준 두개의 인자는, MainMenu Canvas에다가 총 8개의 메뉴 항목을 표시하겠다.

라는 의미가 되겠지요.


그리고, 마지막으로 paint() 함수에, 지난 시간에는 로고 화면일때에만 그려줬지요.

이번시간에는 메뉴 화면을 그려주는 부분에,

cymenu.show(g);


와 같이 추가해 줍니다.


이제 메뉴를 표시하기 위한 준비는 모두 끝났습니다.

남은건, CyMenu 클래스에 작성해둔 함수들을 적적히 추가시켜주면 됩니다.

먼저, 지난 시간에 완성하지 못한 키 입력 부분에 메뉴 항목의 포커스를 이동시키는 코드를 추가 시켜보죠.

/**
* 키 입력 처리.
* @param keyCode
*/

public void keyPressed(int keyCode)
{
if(keyCode == KEY_CLR) // 취소 버튼을 누르면,
{
exitMidlet(); // 프로그램 종료.
return;
}

if(midlet.chkCurState(State.LOGO)) /* 현재 상태가 로고라면, */
{
/*
* 로고에서는 위에서 체크한 취소버튼을 제외하고,
* 아무 버튼이나 누르면, 메뉴로 넘어간다.
*/

midlet.setCurState(State.MENU); // 현재 상태를 MENU로 변경.

logo = null;
repaints();
}
else if(midlet.chkCurState(State.MENU)) /* 현재 상태가 메뉴라면, */
{
switch(keyCode)
{
case KEY_UP:
System.out.println("위쪽 화살표가 눌렸습니다.");
cymenu.pre();
repaints();
break;
case KEY_DOWN:
System.out.println("아래쪽 화살표가 눌렸습니다.");
cymenu.next();
repaints();
break;
case KEY_COMR:
case KEY_FIRE:
System.out.println("확인 키가 눌렸습니다.");

choiceMenu( cymenu.getCurrentIndex() ); // 선택된 메뉴에 해당하는 동작을 수행한다.
}
}
} // end public void keyPressed(int keyCode).

지난시간에는 키가 눌려지면, 단순히 Debug 창에 어떤 키가 눌렸다는 메시지만을 표시했었죠.

이번 시간에는, 위쪽 화살표를 누르면, 이전 항목으로 포커스를 이동하고,

아래쪽 화살표를 누르면, 다음 항목으로 포커스를 이동시켜 줍니다.

포커스가 이동된 후에는, repaints() 함수를 통해, 이동된 화면을 Canvas에 그려줍니다.


그리고 확인 키를 누르면, cymenu.getCurrentIndex() 함수를 이용해, 현재 선택된 메뉴 인덱스를 얻어와서,

그에 해당하는, 즉, 환경 설정을 선택하면, 환경 설정을, 게임 종료를 선택하면, 게임 종료와 같이,

선택된 항목에 따라 각각의 동작을 수행해 주어야 겠죠.


그러한 동작을 하는 것이 바로, choiceMenu() 함수입니다.

/**
* 선택된 메뉴의 해당 동작을 수행한다.

* @param menuIdx
*/
private void choiceMenu(int menuIdx)
{
switch(menuIdx)
{
case CyMenu.
GAME_START: // 게임 시작.
break;
case CyMenu.GALLERY: // 겔러리.
showGallery(); // 겔러리를 보여준다.
break;
case CyMenu.CONFIG: // 환경 설정.
showOption(); // 환경설정을 보여준다.
break;
case CyMenu.HELP: // 도움말.
showHelp(); // 도움말을 보여준다.
break;
case CyMenu.SEND_GAME: // 게임 보내기
.
showSendGame();
break;
case CyMenu.
EVAL‍_GAME: // 게임 평가.
showEval‍uateGame(); // 게임 평가를 보여준다.
break;
case CyMenu.ABOUT_COMPANY: // 게임 문의.
showAboutCompany(); // 게임 문의를 보여준다.
break;
case CyMenu.QUIT_GAME: // 게임 종료.
midlet.destroyApp(true); // 종료.
}
}

그러나, 아직은 서브 메뉴는 구현되지 않았으므로, 먼저 서브 메뉴를 구현한 뒤,

각각의 서브 메뉴에 해당하는 show 함수를 구현해 보도록 합시다.


위의 각 항목을 바탕으로 서브 메뉴 클래스를 모두 구현 하셨다면,

이제 각각의 메뉴에 해당하는 show 함수를 구현하여 주기만 하면 됩니다.

    /**
     * 겔러리를 보여준다.
     */
    private void showGallery()
   {
       gallery = new Gallery(midlet);
       gallery.savePreCanvas(this);
       midlet.setCurrentDisplay(gallery);
   }

    /**
     * 옵션을 보여준다.
     */
    private void showOption()
   {
       option = new Option(midlet);
       option.savePreCanvas(this);         // 현재 캔버스(메뉴) 저장.
       midlet.setCurrentDisplay(option);       // 옵션 화면을 보여준다.
   }

    /**
     * 도움말을 보여준다.
     */
    private void showHelp()
   {
       help = new Help(midlet);
       help.savePreCanvas(this);           // 현재 캔버스(메뉴) 저장.
       midlet.setCurrentDisplay(help); // 도움말 화면을 보여준다.
   }

    /**
     * 게임 보내기를 보여준다.
     */
    private void showSendGame()
   {
       sendgame = new SendGame(midlet);
       sendgame.savePreCanvas(this);       // 현재 캔버스(메뉴) 저장.
       midlet.setCurrentDisplay(sendgame); // 게임 보내기 화면을 보여준다.
   }

    /**
     * 게임 평가하기를 보여준다.
     */
    private void showEval‍uateGame()
   {
       eval‍uategame = new Eval‍uateGame(midlet);
       eval‍uategame.savePreCanvas(this);           // 현재 캔버스(메뉴) 저장.
       midlet.setCurrentDisplay(eval‍uategame); // 게임 평가하기 화면을 보여준다.
   }

    /**
     * 게임 문의를 보여준다.
     */
    private void showAboutCompany()
   {
       aboutcompany = new AboutCompany(midlet);
       aboutcompany.savePreCanvas(this);           // 현재 캔버스(메뉴) 저장.
       midlet.setCurrentDisplay(aboutcompany); // 게임 보내기 화면을 보여준다.
   }


그냥 단순히 각각의 서브메뉴에 해당되는 객체를 생성하고,

나중에 다시 돌아올때를 위해 현재의 Canvas를 저장하고,

서브 메뉴의 Canvas를 보여주는 간단한 코드입니다.


휴... 이걸로 메인 메뉴의 구현이 모두 끝났네요.

Posted by maysent
:

맞고 초기화 및 간단한 로고 출력 코드 작성

Mobile 맞고 게임 제작 강좌 두 번째 시간입니다.
오늘은 맞고 게임의 초기화 부분과 로고 출력 기능만이 있는 기본 프로그램을 만들어 보도록 하겠습니다.

먼저, 소스를 보도록 하지요.

[# twogo1p.Midlet.java]

package twogo1p;

import javax.microedition.lcdui.*;
import com.skt.m.*;

import common.State;

public class Midlet extends twogo.Midlet
{
private State state; // 현재 상태.
private byte curState; // 현재 게임의 상태.
private boolean bPause; // 현재 게임이 일시 정지 중인가.

private MainMenu mainMenu; // 메인 메뉴.

protected Display display;

public byte verSKVM; // SK-VM 의 버전이 저장될 변수.

/**
* Constructor.
*/

public Midlet()
{
state = new State();
mainMenu = new MainMenu(this, display);

turnOnLCD(); // LCD 조명 ON.

verSKVM = getSKVMVer();

if(isLowerSKVM12())
System.out.println(" # Warning : Your SK-VM Version is too low.");
}

/**
* 게임 초기화.
*/

public void initGame()
{
display = Display.getDisplay(this);
display.setCurrent(mainMenu); // mainMenu를 보여준다.

curState = state.LOGO; // 초기 상태는 LOGO.
}


public void startApp()
{
System.out.println(" # Info : App Started.");

initGame(); // 게임 초기화.
}

public void pauseApp() {}

public void destroyApp(boolean unconditional)
{
notifyDestroyed();
}


/**
* skvm version을 가져온다.
* @return
*/

private byte getSKVMVer()
{
return Byte.parseByte(System.getProperty("m.SK_VM"));
}

/**
* skvm version이 1.2 이하라면,
* @return
*/

boolean isLowerSKVM12()
{
return (verSKVM<12) ? true : false;
}

/**
* LCD 조명을 켜준다.
*/

public void turnOnLCD() {
Device.setKeyToneEnabled(false);
Device.setBacklightEnabled(false);
BackLight.on(0);
}

/**
* 현재 게임의 상태를 리턴한다.
* @return 현재 게임의 상태.
*/

public byte getCurState()
{
return curState;
}

/**
* 현재 게임의 상태를 변경한다.
* @param state
*/

public void setCurState(byte state)
{
curState = state;
}

/**
* 현재 상태가 해당 상태가 맞는지 체크한다.
* @param state
* @return
*/

public boolean chkCurState(byte state)
{
return (curState == state)? true : false;
}

/**
* 해당 Canvas를 보여준다.
* @param canvas 보여줄 캔버스.
*/

void setCurrentDisplay(Canvas canvas)
{
display.setCurrent(canvas);
}
} // end twogo1p.Midlet.


소스가 나름대로 길지만, 알고 보면 별것이 아닙니다.

위의 Midlet 함수를 보시면, 몇 가지 유용한 함수들이 있는데,

getSKVMVer() 함수는 현재 SKVM의 버전을 리턴 해 주는 함수이고,
turnOnLCD() 함수는 LCD의 조명을 켜주는 함수입니다.

아마, Mobile 게임에서 위의 두 함수는 상당히 자주 사용이 되리라 봅니다.(함수 이름은 달라질 수 있겠지요.)

그리고 Midlet 클래스의 생성자 부분에서는,

변수에 모두 메모리 할당을 해주고,

state = new State();
mainMenu = new MainMenu(this, display);

LCD 조명을 켜고, SKVM의 버전을 얻어옵니다.

turnOnLCD(); // LCD 조명 ON.
VerSKVM = getSKVMVer();

그리고 SK-VM의 버전이 1.2 이하라면, 아래와 같은 경고 메시지를 출력해 줍니다.

if(isLowerSKVM12())
System.out.println(" # Warning : Your SK-VM Version is too low.");

그 다음 맞고 게임이 실행 되고, startApp() 함수를 호출하게 되면, startApp() 함수에서는

initGame() 함수를 호출하여, 메뉴를 출력 하기 위한 초기화 작업을 해 줍니다.

initGame() 함수에서는 mainMenu를 화면에 출력 시켜주고,

display = Display.getDisplay(this);
display.setCurrent(mainMenu); // mainMenu를 보여준다.

현재 게임의 상태를 LOGO로 초기화 시켜 줍니다.

curState = state.LOGO; // 초기 상태는 LOGO.

여기서 사용된 State 클래스에는 단순히 상태를 나타내는 상수만이 정의 되어 있는 클래스로, 다음과 같습니다.

[# common.State.java]

package common;

/**
* 현재 상태와 관련된 상수들이 정의된 클래스.
*/

public class State {
public final int ERR = -1;

public final byte LOGO = 0;
public final byte MENU = 1;
public final byte GAME = 2;

public State() {}
} // end common.State.

보시는 바와 같이 State 클래스에는 단순히 현재 상태를 나타내는 각종 상수들이 정의 되어 있을 뿐이며, 그 외의 것은 아무것도 없습니다.

이제 기본적인 소스 골격은 모두 세워 놨으니, 실제로 로고 화면을 출력해 주는 MainMenu 클래스를 한번 보도록 하겠습니다.

이번에도 소스가 꽤 길지만, 역시 별로 어려울 것은 없는 소스입니다.

[# twogo1p.MainMenu.java]

package twogo1p;

import javax.microedition.lcdui.Image;
import javax.microedition.lcdui.Graphics;
import javax.microedition.lcdui.Canvas;
import javax.microedition.lcdui.Display;
import common.State;

/**
* 메인 메뉴
*/

class MainMenu extends Canvas implements Runnable
{
private State state;
private Midlet midlet;

private int width, height;
private byte menuState;

private Image logo;

private Display display;
private Thread thread;

/**
* Constructor.
* @param midlet
* @param display
*/

public MainMenu(Midlet midlet, Display display)
{
state = new State();

width = getWidth();
height = getHeight()+16;

this.midlet = midlet;
this.display = display;

try
{
logo = loadImage("/image/logo/logo.lbm");
} catch(Exception e)
{
System.out.println(e.toString());
}

thread = new Thread();
thread.start();
} // end public MainMenu(Midlet midlet, Display display).

public void run()
{
repaints();
}

/**
* 전체화면 repaint.
*/

public void repaints()
{
repaint(0, 0, width, height);
}

/**
* 메뉴 화면을 그려줌.
*/

public void paint(Graphics g)
{
g.setColor(0, 0, 0);
g.fillRect(0, 0, width, height);

if(midlet.chkCurState(state.LOGO))
{
// 현재 상태가 로고라면,
if(logo != null)
g.drawImage(logo, width/2, height/2, Graphics.VCENTER|Graphics.HCENTER);
}
else if(midlet.chkCurState(state.MENU))
{
// 현재 상태가 메뉴라면,
/*
* 메뉴 화면을 그려주는 코드 삽입.
*/

}
} // end public void paint(Graphics g).

/**
* 이미지를 읽어온다.
* @param path
* @return
* @throws Exception
*/

private Image loadImage(String path) throws Exception
{
Image img = null;

try

img = Image.createImage(path);
} catch(Exception e)
{
throw e;
}

return img;
}

/**
* 자원 해제.
*/

void cleanUp()
{
System.gc();
}

/**
* 프로그램을 종료한다.
*/

private void exitMidlet()
{
midlet.destroyApp(true);
}

/**
* 키 입력 처리.
* @param keyCode
*/

public void keyPressed(int keyCode)
{
if(keyCode == KEY_CLR) // 취소 버튼을 누르면,
{
exitMidlet(); // 프로그램 종료.
return;
}

if(midlet.chkCurState(state.LOGO))
{
// 현재 상태가 로고라면,

/*
* 로고에서는 위에서 체크한 취소버튼을 제외하고,
* 아무 버튼이나 누르면, 메뉴로 넘어간다.
*/

// 현재 상태를 MENU로 변경.
midlet.setCurState(state.MENU);

logo = null;
repaints();
}
else if(midlet.chkCurState(state.MENU))
{
switch(keyCode)
{
case KEY_UP:
System.out.println("위쪽 화살표가 눌렸습니다.");
break;
case KEY_DOWN:
System.out.println("아래쪽 화살표가 눌렸습니다.");
case KEY_COMR:
case KEY_FIRE:
System.out.println("확인 키가 눌렸습니다.");


}
}
} // end public void keyPressed(int keyCode).
} // end twogo1p.MainMenu.

이번에도 소스가 꽤 길지만, 역시 별로 어려울 것은 없는 소스입니다.

생성자에서는, Logo 화면에 출력될 그림을 로드 하고,

try
{
logo = loadImage("/image/logo/logo.lbm");
} catch(Exception e)
{
System.out.println(e.toString());
}

쓰레드를 시작 시킵니다.

thread = new Thread();
thread.start();

그리고 paint() 함수에서는 midlet.curState가 state.LOGO일때는, 로고 화면을,
State.MENU일때는 메뉴 화면을 출력시켜 줍니다.

그러나 오늘은 로고 화면만 구현 할 것이므로, 메뉴 화면을 그려주는 것은 포함되어있지 않습니다.


그리고 keyPressed() 함수에서 키 입력을 처리 할 때, 로고에서나 메뉴에서나 공통으로
취소 키(KEY_CLR)을 누르게 되면, 게임을 종료 할 수 있게 할 것 이므로,
취소 키만 따로 먼저 체크를 해 주고, LOGO에서는 취소키를 제외한 아무 키나 누르면 메뉴로 상태를 변경하고, 화면을 다시 그려주는 기능을 합니다.
MENU에서는 위, 아래 화살표와 확인 키가 사용이 되지만, 오늘은 메뉴를 구현하지 않았으므로, 그냥 디버그 창에 메시지만 출력이 되도록 했습니다.

오늘 작성한 소스를 SK-VM Emulator에서 돌려보면 다음과 같은 화면이 보일 것입니다.


로고 화면에서 아무 키나 누르면, 메뉴로 넘어가는데, 메뉴에 아무런 처리도 해주지 않았으므로, 그냥 검정색 화면만 나올 것입니다.

그때는 그냥 취소 키를 누르면 종료가 됩니다.


오늘 작성한 소스는 TwoGo#1.alz파일을 받아서 확인 해보시면 되겠구요.  <없음>

 

오늘 강좌는 여기까지 하도록 하겠습니다.

다음 시간에는 메뉴를 출력 해주고, 각각의 메뉴를 구현해 보도록 하겠습니다.

Posted by maysent
:

Mobile 맞고 게임 제작 강좌를 시작하며

Mobile 맞고 제작 강좌는 핸드폰 상에서 구현되는 맞고 게임을 구현하기 위한 강좌입니다.
맞고는 우리가 잘 알고 있는 고스톱을 2인 플레이의 기준에 맞게 변형시킨 게임입니다.

다음 시간부터 진행 될 전반적인 맞고 강좌의 목차는 다음과 같이 진행 될 것 입니다.

맞고 게임 구조 작성 및 게임 class 설계.
맞고 초기화 코드 및 로고 출력 구현.
각각의 메뉴 구현.
맞고 게임 방 만들기.
맞고의 룰 이해 및 게임 상수 정의.
맞고의 패 처리.
점수 처리 구현.
결과 출력 구현.
사운드 출력 구현.
디스플레이 요소 삽입.

대충 이러한 순서로 강좌는 진행 되게 될 것입니다.

강좌는 기본적으로 Eclipse 3.x과 SK-VM SDK 1.31로 작성되었으며,

컴파일러는 Eclipse 3.x 그 이후 버전이나, 또는 J-Builder와 같은 기타 IDE를 사용하셔도 무방하시리라 봅니다.

다음은 강좌에 사용될 프로그램들의 실행 화면입니다.

< SK-VM 1.3.1 > 화면

 

< Eclipse 3.0.0 > 화면

 


이번 시간에는 간단히 위에서 소개 드린 첫 번째 단계인 맞고 게임 구조 작성과 게임 class 설계를 한번 해볼까 합니다.

맞고 게임에 사용될 기본적인 클래스들은 다음과 같습니다.

[# Package : common]

Animation : 화면에 애니메이션을 출력할 때 사용되는 클래스.

BCollector : 숫자 배열을 관리하는 클래스.

Coord : x, y 좌표를 알고 있을 필요가 있을 때 사용되는 클래스.

CyDialog : 화면상에 표시되는 메시지, 알림창 등의 대화창을 표시해주는 클래스.

CyHelp : 도움말을 표시해 줄 때 사용되는 클래스.

CyItemList : 간단한 항목들을 표시할 때 사용되는 클래스.

CyMenu : 메뉴를 사용할 때 사용되는 클래스.

CyResMan : 이미지를 한번에 읽어와서 나중에 할당해 줄 때 사용되는 클래스.

DataManager : 게임에서 사용되는 변수나 기타 상태 값들을 핸드폰에 저장해 주는 기능의 클래스.

GameRoom : 게임방에 관련 된 사용자 등의 정보를 관리 해 주는 클래스.

GateMidlet : 모든 Midlet의 Parent로 기본적인 정보만을 가지고 있는 클래스.

Rect : 사각형의 좌표를 저장할 때 사용되는 클래스.

Room : 방에 관련된 기본적인 정보를 포함하고 관리하는 클래스.

Sleep : sleep에 관련된 모든 함수를 포함하고 있는 클래스.

SoundThread : 사운드를 출력할 때 사용되는 클래스.

Etc : 기타 게임에 필요한 함수들을 모아놓은 클래스.

[# Package : gostop]

GostopCard : 고스톱에서 사용되는 기본적인 카드 정보를 기억하고 있는 클래스.

GostopFloor : 고스톱 게임에서 바닥에 있는 게임의 상태를 알고 있는 클래스.

GostopRoom : 고스톱의 기본적인 룰을 적용시켜주는 클래스.

GostopView : 고스톱 화면에서 표시할 때 사용되는 repaint() 및 키 입력 부분을 담당하는 클래스.

ResultObj : 한 게임이 끝난 후, 게임 결과를 포함하고 있는 클래스.

[# Package : twogo]

Midlet : 기본적인 사운드 관련 정보를 포함하고 있는 클래스.


[# Package : twogo1p]

AboutCompany : 게임 문의 페이지를 보여주는 클래스.

Agent : 사용자 및 컴퓨터를 움직이는 클래스.

Eval‍uateGame : 게임 평가 페이지를 보여주는 클래스.

Gallery : 갤러리 메뉴를 표시하고 처리하는 클래스.

GameInfo : RMS에 게임 관련 정보를 넣어두고, 게임 시작할 때 가져 옴.

GameView : 맞고 1인용 게임을 보여주는 클래스.

Help : 도움말 페이지를 보여주는 클래스.

MainMenu : 메인 메뉴를 표시해주는 클래스.

Midlet : 맞고 Main Midlet 클래스.

Option : 소리 및 진동을 설정하는 옵션 페이지를 보여주는 클래스.

Room : 맞고 방에서 일어나는 모든 일을 담당하는 클래스.

SendGame : 게임 보내기 선물 페이지를 보여주는 클래스.

그리고 게임 상에서 각각의 클래스들은 다음과 같은 관계를 갖습니다.


위 그림에서 속이 빈 흰색 화살표는 포함 관계를 뜻하며, 속이 찬 검정색 화살표는 상속 관계를 뜻합니다.

먼저 메뉴와 관련된 클래스들의 경우에는, 각각 Canvas를 상속받은 클래스들이 6개 있고, MainMenu 클래스에서 해당 메뉴를 선택하면, 그에 해당하는 메뉴 클래스에 메뉴 할당을 하고 해당 화면을 보여주는 방식으로 작성될 것입니다.

게임과 관련된 클래스들의 경우, 기본적으로 Midlet 클래스에서 시작하며, 모든 디스플레이 적인 요소는 Canvas 클래스를 상속받은 GameView 클래스에서 모두 그려주는 방식으로 작성하게 될 것입니다.

그 외의 맞고 게임과 관련된 기본적인 클래스에 대해서 조금 자세히 정리를 해보면

[# Room 클래스에 구현될 함수]

public void init(Midlet midlet); // 초기화.

private void generateCards(); // 생성된 카드를 섞어주는 기능을 하는 함수.

private void distributeCards(); // 게임 시작 후, 카드를 플레이어들에게 나눠준다.

private void initTurnUp(); // 게임 초기 시작 시, 카드를 뒤집어 놓아주는 함수.

private void checkCanThrowCards(); // 낼 수 있는 카드인지 체크한다.

private boolean checkPresident(); // 플레이어의 패 중에 대통령(총통)이 있는지 체크.

private boolean checkFloorPresident(); // 바닥에 대통령(총통)이 있는지 체크.

Private boolean initUserMoney(); // 사용자 돈 초기화.

private void checkPresident(); // 사용자 돈 계산.

Private void showResult(); // 게임 결과를 보여줌.

protected void gameStarted(); // 게임 시작 함수.

void robPee(byte slot, byte robPee); // 피 뺏기.

protected void doRobPee(byte slot, byte amount); // 지정한 상대에게 피를 넘겨주기.

public byte getStockedCardatTop(); // 제일 위의 카드를 리턴.

boolean isGookJinInHand(byte slot); // 지정한 슬롯에 국진이 있는지 체크.

byte getOneOfTwoCard(boolean bWarnGodori, Boolean bWarnChungdan, boolean bWarnHongdan,boolean bWarnChodan)
// 두개의 카드중 하나의 카드를 선택할 때, 하나의 카드를 알려준다.

byte getSameCardInHand(byte slot, byte cardNum);
// 손에 동일한 카드가 몇 장있는지 리턴.

protected BCollector getHandCards(short slot); // 손에 있는 카드를 리턴.

[# GostopCard 클래스에 구현될 중요 함수]

public void init(short numAddCard2, short numAddCard3); // 초기화 함수.
private void initCardType(); // 카드 배열에 카드 타입을 초기화한다.
public byte getType(byte cardNum); // 해당 카드가 어떤 카드인지를 리턴한다.
public void addAdditionalCard(); // 추가로 쌍피를 설정할 때 사용한다.

[# GostopFloor 클래스에 구현될 중요 함수]

public void init(); // 초기화 함수.
public void initialTurnUp(byte cardnum); // 바닥에 패를 깐다.
public void addToFloor(byte cardnum); // 바닥에 빈자리를 찾아 카드를 놓는다.
public void removeFloorCard(byte cardnum); // 카드를 바닥에서 뺀다.
public void dropCard(byte cardnum); // 플레이어가 바닥에 카드를 그냥 내려 놓음.
public void matchCards(byte sourceCard, byte targetCard); // 카드를 붙여 내려놓음.
public void eatTriple(byte cardnum); // 싼 것을 먹으며 내려놓음.
public void bomb(byte card1, byte card2, byte card3, byte targetCard); // 폭탄을 내려놓음.
public void sSam(byte cardnum); // 뒤집었을때 뻑이 났을 경우,
public void turnUp(byte cardnum); // 카드를 한 장 뒤집는다.
public int checkFloorStatus(byte cardnum); // 바닥 상태를 체크한다.
public boolean getSak3(); // 싹슬이 인지 체크.



[# GostopView 클래스에 구현될 중요 함수]

public byte choiceTwocard(byte card1, byte card2); // 카드 둘 중 하나를 선택할 때,
public byte choiceGookjin(); // 국진을 선택할 때,
protected byte choiceShake(byte month); // 흔들기 여부 선택.
protected byte choiceBomb(byte month); // 폭탄 여부 선택.
protected byte choiceGostop(); // 고/스톱 선택.


물론, 앞으로 우리가 구현하게 될 함수들은 위의 함수들보다 더 많겠지만, 나름대로 중요하다고 생각되는 함수들만을 뽑아 본 것입니다.

다음 시간부터는 실제로 맞고 게임 제작에 들어가보도록 하겠습니다.

Posted by maysent
: