모바일 맞고 게임3-1(각각의 메뉴구현)
DevSource/맞고 2012. 12. 27. 18:50 |각각의 메뉴 구현.
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: // 게임 평가. showEvaluateGame(); // 게임 평가를 보여준다. 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 showEvaluateGame() { evaluategame = new EvaluateGame(midlet); evaluategame.savePreCanvas(this); // 현재 캔버스(메뉴) 저장. midlet.setCurrentDisplay(evaluategame); // 게임 평가하기 화면을 보여준다. } /** * 게임 문의를 보여준다. */ private void showAboutCompany() { aboutcompany = new AboutCompany(midlet); aboutcompany.savePreCanvas(this); // 현재 캔버스(메뉴) 저장. midlet.setCurrentDisplay(aboutcompany); // 게임 보내기 화면을 보여준다. } |
그냥 단순히 각각의 서브메뉴에 해당되는 객체를 생성하고,
나중에 다시 돌아올때를 위해 현재의 Canvas를 저장하고,
서브 메뉴의 Canvas를 보여주는 간단한 코드입니다.
휴... 이걸로 메인 메뉴의 구현이 모두 끝났네요.
'DevSource > 맞고' 카테고리의 다른 글
모바일 맞고 게임4-2(Room 클래스 살펴보기) (0) | 2012.12.27 |
---|---|
모바일 맞고 게임4-1(맞고 게임의 기본 인터페이스 구현) (0) | 2012.12.27 |
모바일 맞고 게임강좌3-3(나머지 서브 메뉴 구현하기) (0) | 2012.12.27 |
모바일 맞고 게임 강좌2 (0) | 2012.12.27 |
모바일 맞고 게임 강좌1 (0) | 2012.12.27 |