먼저 twogo1p.Room 클래스는, gostop.GostopRoom 클래스로 부터 파생되고,

또, 이 GostopRoom 클래스는 common.GameRoom 클래스로부터 파생이 되며,

이 GameRoom 클래스는 User라는 클래스로 부터 파생 된 클래스 입니다.


즉, 가장 최상위 클래스가 User 클래스이니, User 클래스를 먼저 살펴 볼까요?


* User.java.

package common;

/**
 * 사용자의 정보를 담아 놓는 클래스
 */
public class User
{
    public final static byte NULL = -1;
    public final static byte PLAYER = 0;
    public final static byte COMPUTER = 1;

    public final static long DEFAULT_MONEY = 100000;

    private byte userType = NULL; // 유저 타입.
    private int numGame = 0; // 게임 횟수.  
    private int numWin = 0; // 이긴 횟수.       
    private long money = DEFAULT_MONEY; // 돈.

    /**
     * Constructor.
     */
    public User()
    {
        System.out.println("생성자 호출 됨.");
        userType = NULL;
        numGame = 0;
        numWin = 0;
        money = DEFAULT_MONEY;
    }

    /**
     * 사용자 정보를 설정한다.
     * @param numGame
     * @param numWin
     * @param mney
     */
    public void setUserInfo(int userType, int numGame, 
                             int numWin, long money)
    {
        this.userType = (byte)userType;
        this.numGame = numGame;
        this.numWin = numWin;
        this.money = money;
    }

    /**
     * 사용자 타입을 설정한다.
     * @param userType
     */
    public void setUserType(int userType)
    {
        this.userType = (byte)userType;
    }

    /**
     * 사용자의 money를 변경한다.
     * @param money
     * @return 변경된money
     */
    public long changeMoney(long money)
    {
        this.money += money;
        return this.money;
    }

    /**
     * 사용자의 money를 설정한다.
     * @param money
     */
    public void setMoney(long money)
    {
        this.money = money;
    }

    /**
     * 사용자의 money를 얻어온다.
     * @return 사용자money
     */
    public long getMoney()
    {
        return money;
    }

    /**
     * 사용자 타입을 얻어온다.
     * @return
     */
    public byte getUserType()
    {
        return userType;
    }
}

User 클래스는 그야말로, User 에 대한 정보를 관리해 주는 클래스입니다.

단순히 User에 대한 정보들을 set...함수로 변경하고, get...함수로 리턴해주는,

그런 단순한 기능 밖에 없는 클래스 입니다. 별다른 설명은 필요할 것 같지 않네요.


그럼, 다음으로 User 클래스를 상속받고 있는, GameRoom 클래스를 한번 살펴보도록 할까요.


* GameRoom.java.
package common;

/**
 * 게임방에 관련된 사용자등의 정보를 관리한다.
 */

public class GameRoom extends User
{
    public User[] user;

    /**
     * Constructor.
     */
    public GameRoom()
    {
        initUser(); // 사용자 정보 초기화.
    }

    /**
     * 사용자 정보를 초기화 한다.
     */
    private void initUser()
    {
        user = new User[2]; // User 클래스 두개 생성.
        user[PLAYER] = new User();
        user[COMPUTER] = new User();

        // 만약 유저 정보가 입력되어 있지 않다면,
        if(user[PLAYER].getUserType() == NULL)
        {
            /*
             * 각각의 정보를 초기화 한다.
             */
            user[PLAYER].setUserInfo(PLAYER, 0, 0, DEFAULT_MONEY);
            user[COMPUTER].setUserInfo(COMPUTER, 0, 0, DEFAULT_MONEY);
        }
    }


    /**
     * 플레이어의 돈을 리턴한다.
     * @return 플레이어의 돈.
     */
    public long getPlayerMoney()
    {
        return user[PLAYER].getMoney();
    }

    /**
     * 플레이어의 돈을 변경한다.
     * @param money 변경액.
     */
    public void changePlayerMoney(long money)
    {
        user[PLAYER].changeMoney(money);
    }


    /**
     * 컴퓨터의 돈을 리턴한다.
     * @return 컴퓨터의 돈.
     */
    public long getComMoney()
    {
        return user[COMPUTER].getMoney();
    }

    /**
     * 컴퓨터의 돈을 변경한다.
     * @param money 변경액.
     */
    public void changeComMoney(long money)
    {
        user[COMPUTER].changeMoney(money);
    }
}

GameRoom 클래스는 단순히 User 객체를 2개 생성에서, 컴퓨터의 것과 플레이어의 것으로 따로 나누어서 관리해 줍니다.

즉, 돈을 얻어 올때도, 단순히 getMoney()가 아니라, getPlayerMoney(), getComMoney()와 같이 나뉘어져있는거죠.

그리고 이 GameRoom 클래스를 상속 받고 있는 gostop.GostopRoom 클래스와 이것을 상속받은 twogo1p.Room 클래스에는,

지금은 아무런 기능도 구현이 되어있지 않은 빈 클래스입니다.

이 클래스들에 대한 구현은 다음 시간에 이루어지게 될 것입니다.

Posted by maysent
:
지난 시간까지는, 주로 메인 메뉴와 관련된 작업을 했었죠.

이번 시간부터는 이제 실제 게임 시작을 누른 그 순간부터 게임이 끝날 때까지의 게임과 관련된 내용을 프로그래밍 하게 될 것입니다.

먼저, 게임 시작을 누르면, 게임의 화면이 뜨도록 해주어야 하겠지요?


앞 시간에 열심히 만들었던, MainMenu.java 파일의 키입력 부분을 다음과 같이 변경해 줍니다.

* MainMenu.java
    /**
     * 선택된 메뉴의 해당 동작을 수행한다.
     * @param menuIdx
     */
    private void choiceMenu(int menuIdx)
    {
        switch(menuIdx)
        {
        case CyMenu.GAME_START: // 게임 시작.
            showGameStart(); // 게임 시작.
            break;
........
} } /** * 게임을 시작한다. */ private void showGameStart() { repaints(); serviceRepaints(); midlet.cleanUp(); // 자원 해제 후, midlet.enterGame(); // 게임 시작. }


또, Midlet.java에 enterGame 이라는 함수는 아직 정의를 안해 놓았었죠.

Midlet.java의 entetrGame 이라는 함수를 다음과 같이 정의 해줍니다.


* Midlet.java.
    /**
     * 게임을 시작한다.
     */
    void enterGame()
    {
        initGame();
        setCurrentDisplay(gameView);

        curState = State.GAME;
    }


이제, 게임 시작을 누르면, 게임 화면을 보여줄 준비는 모두 끝마쳤습니다.


이제 게임 진행시에 게임 화면을 그려줄 GameView 클래스를 만들어 봅시다.

GameView 클래스는, gostop 패키지의 GostopView 라는 클래스를 상속받지만,

지금 당장은 GostopView 클래스에 아무런 내용도 구현이 되어 있질 않으므로,

이번 강좌에서는 GostopView 클래스에대해서는 언급하지 않도록 하겠습니다.

그리고 한가지 더 중요한 클래스인 common 패키지의 CyResMan이라는 클래스가

GameView 클래스 내에 포함되어 있는데요.


SK-VM에서는 공통적인 생명 주기(?)를 갖는 이미지 파일들을 하나의 파일로 합쳐,

보다 효율적으로 관리 해 주기 위해 PNGLBM Mixer 라는 유틸리티를 하나 제공합니다.

아래는 PNGLBM Mixer의 실행 화면이구요.


LBM Mixer를 이용하면, LBM 파일들을 dat라는 확장자를 가지는 파일에 이미지들을 합쳐줍니다.

이렇게 이미지들을 합치게 되면, 각각의 LBM 파일에 포함되어 있는 이미지 정보 헤더들이

dat 파일로 합쳐지면서, 이미지 정보 헤더도 하나면 충분하므로, 용량 절감의 효과까지도 볼수있게 되겠지요.

실제로, LBM 파일 각각의 용량의 합보다 Mix된 dat 파일의 용량이 적은 것을 확인하실 수 있습니다.


이렇게 합쳐진 dat 파일을 불러와서 Image 클래스에 각각의 이미지 정보를 넣어주는 역할을 하는 클래스가,

바로 CyResMan 클래스입니다.

그럼 먼저 CyResMan 클래스를 잠깐 분석해 보고 가도록 하지요.

* CyResMan.java
package common;

import javax.microedition.lcdui.*;
import java.util.Hashtable;
import java.io.InputStream;

/**
 * 이미지를 한번에 읽어들여서 나중에 할당해줄때 사용한다. 
 */

public class CyResMan
{
    Hashtable resList = new Hashtable();

    byte[] byteImage;

    /**
     * mix해 놓은 이미지 파일을 지정해 전부 읽어 놓는다.
     * @param path
     */
    public void setImageData(String path)
    {
        try
        {
            InputStream is = null;
            is = getClass().getResourceAsStream(path);

            // byteImage를 null로 초기화 후,
            if(byteImage != null)
                byteImage = null;

            // dat 파일의 사이즈만큼을 읽어 오고,
            byteImage = new byte[is.available()];
            is.read(byteImage);

            // 파일을 닫는다.
            is.close();
        } catch(Exception e) {}
    }

    /**
     * 이미지를 읽어온다.
     * @param path
     * @return
     */
    public synchronized Image loadImage(String path)
    {
        // 동시에 2개의 thread가 읽는 경우가 있음.
        Image img = null;

        try
        {
            InputStream is = null;
            is = getClass().getResourceAsStream(path);

            // byteImage를 null로 초기화.
            if(byteImage != null)
                byteImage = null;

            // 해당 파일의 사이즈만큼을 읽어온다.
            byteImage = new byte[is.available()];
            is.read(byteImage);

            // 파일을 닫는다.
            is.close();

            // 메모리상에서 파일의 사이즈만큼을 읽어온다.
            img = Image.createImage(byteImage, 0, byteImage.length);

            byteImage = null;
        } catch(Exception e)
        {
            System.out.println("resMan.loadImage "+e.toString()+" "+path);
        }

        return img;
    }

    /**
     * 미리 읽어놓은 data에서 지정된 Offset과 length로 이미지를 만든다. 
     * @param name
     * @param off 오프셋.
     * @param len 이미지 사이즈.
     * @return 성공여부
     */
    public boolean addImage(String name, int off, int len)
    {
        try
        {
            /*
             * setImageData를 통해 미리 읽어 둔 .dat 파일에서,
             * offset 지점 부터, length 만큼을 읽어와 이미지를 만든다.
             */
            Image img = Image.createImage(byteImage, off, len);
            resList.put(name, img);
        } catch(Exception e)
        {
            // 이미지 생성 실패.
            return false;
        }

        // 이미지 생성 성공.
        return true;
    }

    /**
     * 자원 해제.
     */
    public void cleanUp()
    {
        byteImage = null;
        System.gc();
    }

    /**
     * 저장해 놓은 Image개체를 메모리에서 해제한다.
     * @param name
     */
    public void removeImage(String name)
    {
        Image img = (Image)resList.get(name);
        resList.remove(name);
        img = null;
    }

    /**
     * 읽어놓은 메모리상에서 Image개체를 가져온다.
     * @param name
     * @return name에 해당하는 image 개체
     */
    public Image getImage(String name)
    {
        Image img = (Image)resList.get(name);

        return img;
    }
}

흠... CyResMan 클래스의 setImagedata() 함수는, LBM 이미지를 믹스해 놓은 dat파일을 열어서,

dat 파일의 모든 내용을, byte형 배열인, byteImage에 기록을 해놓는 함수입니다.

setImageData() 함수를 이용해서, byteImage에 dat 파일의 정보를 기록한 후,

addImage() 함수를 통해서, 이미지의 크기 만큼을 읽어 이미지를 생성후, Hashtable에 기록합니다.

그 뒤, 필요할 때마다, getImage() 함수를 이용하면, 원하는 이미지를 Image 클래스에 저장할수 있게 되지요.


자, 그럼, 이제 GameView 클래스를 살펴보도록 할까요?

역시 먼저 GameView 클래스의 소스를 한번 보고나서 설명을 해드리도록 하겠습니다.

* GameView.java.
package twogo1p;

import javax.microedition.lcdui.*;
import java.io.InputStream;

import com.skt.m.Graphics2D;

import gostop.*;
import common.*;

/**
 * 맞고의 게임 화면을 그려준다.
 */
class GameView extends GostopView { // 폰트 사이즈 상수. private final int SMALLFONT_WIDTH = 4; private final int SMALLFONT_HEIGHT = 6; protected int totalWidth = 128, totalHeight = 150; //전체 게임 크기 private short cardWindowHeight = 0; private Image imgExitBtn, imgStartBtn; // 종료 버튼, 시작 버튼. // 플레이어와 컴퓨터의 아바타 얼굴 이미지. private Image imgPlayer, imgComputer; private Image imgFloorBg; // 바닥 중앙의 무늬 이미지. private Image imgBgPattern; // 배경 타일 패턴 이미지. private Image imgAqViewTable; // 획득 카드 화면의 사용자정보 출력 테이블. private Image imgAqViewOkBtn; // 획득 카드 화면의 확인 버튼. private Image imgAqViewIconID;// 획득 카드 화면의 ID 아이콘. private Image imgAqViewIconMoney; // 획득 카드 화면의 Money 아이콘. private Image imgSmallFont; // 작은 숫자 폰트 이미지. byte curTurn = User.NULL; // 현재 턴. byte curViewState = State.GAME_WAIT; // 현재 보여지는 화면의 상태. CyResMan resMan; Room room; Graphics2D g2d; Midlet midlet; Rect rectFloor; byte whoseAcquireCards = User.NULL; /** * Constructor. * @param midlet */ public GameView(Midlet midlet) { this.midlet = midlet; width = (short)getWidth(); height = (short)(getHeight()+16); resMan = new CyResMan(); room = new Room(); makeImage(); System.out.println(width + ", " + height); } /** * 합친 이미지 파일에서 각각의 이미지를 추출한다. */ private void makeImage() { // 합친 이미지 파일을 로드후, resMan.setImageData("/image/MixImage_1.dat"); // Mix 파일에서 각각의 이미지를 추출한다. resMan.addImage("0", 0, 654); // 남자 아바타 얼굴. resMan.addImage("1", 654, 684); // 여자 아바타 얼굴. resMan.addImage("2", 1338, 924); // 바닥의 타일 패턴. resMan.addImage("3", 2262, 7448); // 게임판 중앙의 장식 무늬. resMan.addImage("4", 9710, 204); // 폰트 #1. resMan.addImage("5", 9914, 304); // 폰트 #2. resMan.addImage("6", 10218, 21144); // 카드 스프라이트 (대). resMan.addImage("7", 31362, 5304); // 카드 스프라이트 (소). // 아바타 로드. imgPlayer = resMan.getImage("0"); imgComputer = resMan.getImage("1"); // 바닥 타일 및 배경 로드. imgBgPattern = resMan.getImage("2"); imgFloorBg = resMan.getImage("3"); // 폰트 로드. imgSmallFont = resMan.getImage("5"); initPos(); // 위치 초기화. } /** * 각 개체의 위치 설정. */ private void initPos() { rectFloor = new Rect(0,37,128,58); cardWindowHeight = (short)((totalHeight-imgFloorBg.getHeight())/2); } /** * 이미지 로드. * @param path * @return */ public synchronized Image loadImage(String path) { // 동시에 2개의 thread가 읽는 경우가 있음 Image img = null; try { byte[] byteImage; InputStream is = null; is = getClass().getResourceAsStream(path); byteImage = new byte[is.available()]; is.read(byteImage); is.close(); img = Image.createImage(byteImage, 0, byteImage.length); byteImage = null; } catch(Exception e) {} return img; } /** * 게임판의 배경을 화면에 그려준다. * @param g */ private void drawFloor(Graphics g) { g.setColor(204,153,255); g.fillRect(0,0,totalWidth,totalHeight); int clipx=0, clipy=0; int clipw=0, cliph=0; /* * 기본 클리핑 영역을 저장한다. */ clipx = g.getClipX(); clipy = g.getClipY(); clipw = g.getClipWidth(); cliph = g.getClipHeight(); g.setClip(0,0,totalWidth,totalHeight); /* * 타일 패턴을 화면에 깔아준다. */ for(int x=0;x<totalWidth;x+=imgBgPattern.getWidth()) { for(int y=0;y<totalHeight;y+=imgBgPattern.getHeight()) { g.drawImage(imgBgPattern, x, y, Graphics.LEFT|Graphics.TOP); } } if(curTurn == User.COMPUTER) { // 타일 배경을 그려준다. g.drawImage(imgFloorBg, width/2, rectFloor.top+6,
Graphics.HCENTER|Graphics.TOP); } else /* if(curTurn == PLAYER) */ { // 타일 배경을 그려준다. g.drawImage(imgFloorBg, width/2, rectFloor.top,
Graphics.HCENTER|Graphics.TOP); } /* * 저장해 둔 기본 클리핑 영역을 복원한다. */ g.setClip(clipx, clipy, clipw, cliph); } /** * 게임 대기화면을 그려준다. * @param g */ private void drawWaitingGame(Graphics g) { /* * Start 버튼이 로드되지 않았다면, 스타트버튼 이미지 로드. */ if(imgStartBtn == null) { imgStartBtn = loadImage("/image/startbtn.lbm"); } // Start 버튼을 그려준다. g.drawImage(imgStartBtn, width, height, Graphics.RIGHT|Graphics.BOTTOM); /* * Exit 버튼이 로드되지 않았다면, Exit 버튼 이미지 로드. */ if(imgExitBtn == null) { imgExitBtn = loadImage("/image/exitbtn.lbm"); } // Exit 버튼을 그려준다. g.drawImage(imgExitBtn, 0, height,Graphics.LEFT|Graphics.BOTTOM); // 각 플레이어의 정보 출력. g.setColor(182,219,255); /* * 컴퓨터의 정보 출력. */ // 아바타 얼굴 표시. g.drawImage(imgComputer, 28, 38, Graphics.TOP|Graphics.RIGHT); // 돈 표시. g.drawString("Computer", 30,38,Graphics.TOP|Graphics.LEFT); g.drawString(room.getComMoney()+" 원",
totalWidth-10,49,Graphics.TOP|Graphics.RIGHT); /* * 플레이어의 정보 출력. */ // 아바타 얼굴 표시. g.drawImage(imgPlayer, 28, 68, Graphics.TOP|Graphics.RIGHT); // 돈 표시. g.drawString("Player", 30,68,Graphics.TOP|Graphics.LEFT); g.drawString(room.getPlayerMoney()+" 원",
totalWidth-10, 81,Graphics.TOP|Graphics.RIGHT); } /** * 카드를 나눠주는 중의 화면을 그려준다. * @param g */ private void drawDistributeCards(Graphics g) { } /** * 게임 진행중의 화면을 그려준다. * @param g */ private void drawRunningGame(Graphics g) { } /** * 획득 카드 확인 화면을 그려준다. * @param g */ private void drawAcquireCards(Graphics g) { imgAqViewTable = loadImage("/image/aqviewp.lbm"); imgAqViewOkBtn = loadImage("/image/aqviewok.lbm"); imgAqViewIconID = loadImage("/image/iconid.lbm"); imgAqViewIconMoney = loadImage("/image/iconmoney.lbm"); // 화면 Clear 후, g.setColor(0,0,153); g.fillRect(0, 0, width, height); /* * ID/Money 테이블 출력. */ // ID/Money Border의 Fill 출력. g.setColor(255,200,255); g.fillRect(width-75,0,70,26); // 아이디 아이콘과 아이디 표시. g.setColor(0); g.drawImage(imgAqViewIconID, width-74, 3, Graphics.LEFT|Graphics.TOP); if(whoseAcquireCards == User.PLAYER) g.drawString("PLAYER", width-5, 1, Graphics.RIGHT|Graphics.TOP); else /* if(whoseAcquireCards == User.COMPUTER) */ g.drawString("COMPUTER", width-5, 1, Graphics.RIGHT|Graphics.TOP); // 돈 아이콘과 돈 표시. g.drawImage(imgAqViewIconMoney, width-74,15, Graphics.LEFT|Graphics.TOP); drawSmallNum(g,width-5, 16+1, room.getPlayerMoney(), Graphics.RIGHT); // ID/Money Border의 Outline 출력. g.setColor(0,146,170); g.drawRect(width-75,0,70,26); /* * User Info 테이블 출력. */ g.drawImage(imgAqViewTable, 83, 47+10, Graphics.LEFT|Graphics.TOP); drawSmallNum(g,(short)(113), (short)(60),
room.getPoint((int)whoseAcquireCards),
(short)Graphics.RIGHT); drawSmallNum(g,(short)(113), (short)(70),
room.getCntGo((int)whoseAcquireCards),
(short)Graphics.RIGHT); drawSmallNum(g,(short)(113), (short)(80),
room.getCntShake((int)whoseAcquireCards),
(short)Graphics.RIGHT); drawSmallNum(g,(short)(113), (short)(90),
room.getCntPuck((int)whoseAcquireCards),
(short)Graphics.RIGHT); /* * 확인 버튼 출력. */ g.drawImage(imgAqViewOkBtn, width, height,
Graphics.RIGHT|Graphics.BOTTOM); } /** * 결과 화면을 그려준다. * @param g */ private void drawResult(Graphics g) { } /** * 화면에 작은 숫자를 표시한다. * @param g * @param x 숫자의 x. * @param y 숫자의 y. * @param num 수. * @param anchor 왼쪽/오른쪽 정렬 선택. */ private void drawSmallNum(Graphics g, int x, int y, long num, int anchor) { int cnt; int posx = x; int posy = y; String strNum = String.valueOf(num); // 숫자를 문자열로 변경해 준다. int len = strNum.length(); // 문자열의 길이를 구한다. // 오른쪽 정렬일때, if(anchor==Graphics.RIGHT) { /* * 정확히 시작점부터 시작하기 위해, * 시작점에서 폰트의 폭을 빼준다. */ posx -= SMALLFONT_WIDTH; } // 숫자를 한자 한자 출력해 준다. if(anchor == Graphics.RIGHT) { // 오른쪽에서 왼쪽으로... for(cnt=len-1; cnt>=0; cnt--) { drawSmallFont(g, posx, posy, (strNum.charAt(cnt)-'0')); posx -= SMALLFONT_WIDTH; } } else if(anchor == Graphics.LEFT) { // 왼쪽에서 오른쪽으로... for(cnt=0; cnt<len; cnt++) { drawSmallFont(g, posx, posy, (strNum.charAt(cnt)-'0')); posx += SMALLFONT_WIDTH; } } } // end private void drawSmallNum(). private void drawSmallFont(Graphics g, int x, int y, int num) { g2d.drawImage(x, y, imgSmallFont, num*SMALLFONT_WIDTH, 0, SMALLFONT_WIDTH, SMALLFONT_HEIGHT, Graphics2D.DRAW_COPY); } /** * 화면에 게임 화면을 그려준다. */ public void paint(Graphics g) { if(g2d == null) g2d = Graphics2D.getGraphics2D(g); g.setColor(0); g.fillRect(0,0,width,height); drawFloor(g); // 게임판의 배경을 화면에 그려준다. System.out.println("현재 게임 상태 : " + curViewState); switch(curViewState) { case State.GAME_WAIT: // 게임 대기 중일때, drawWaitingGame(g); break; case State.GAME_DISTRIBUTECARDS: // 카드를 나눠주는 중일때, drawDistributeCards(g); break; case State.GAME_RUNNING: // 게임 진행 중일때, drawRunningGame(g); break; case State.GAME_ACQUIRECARDS: // 얻은 카드 확인 중일때, drawAcquireCards(g); break; case State.GAME_RESULT: // 게임 결과 출력 중일때, drawResult(g); break; } } /** * 메인 메뉴로 돌아감. */ private void enterMainMenu() { //room.gameOver(); //initGame(); midlet.enterMainMenu(); } }

소스가 깁니다. 그러나 역시, 핵심 코드만을 중점적으로 살펴본다면,

전혀 어려울 것이 없는 소스입니다.

먼저, 생성자를 보면, midlet 객체를 저장하고, width와 height을 저장하고,

각각의 클래스 객체들을 생성해 줍니다.


그리고, makeImage() 함수를 호출해 주는데요.

    /**
     * 합친 이미지 파일에서 각각의 이미지를 추출한다.
     */
    private void makeImage()
    {
        // 합친 이미지 파일을 로드후,
        resMan.setImageData("/image/MixImage_1.dat");

        // Mix 파일에서 각각의 이미지를 추출한다.
        resMan.addImage("0", 0, 654);       // 남자 아바타 얼굴.
        resMan.addImage("1", 654, 684);     // 여자 아바타 얼굴.
        resMan.addImage("2", 1338, 924);    // 바닥의 타일 패턴.            
        resMan.addImage("3", 2262, 7448);       // 게임판 중앙의 장식 무늬.
        resMan.addImage("4", 9710, 204);    // 폰트 #1.
        resMan.addImage("5", 9914, 304);    // 폰트 #2.
        resMan.addImage("6", 10218, 21144);     // 카드 스프라이트 (대).
        resMan.addImage("7", 31362, 5304);      // 카드 스프라이트 (소).

        // 아바타 로드.     
        imgPlayer = resMan.getImage("0");
        imgComputer = resMan.getImage("1");

        // 바닥 타일 및 배경 로드.
        imgBgPattern = resMan.getImage("2");
        imgFloorBg = resMan.getImage("3");

        // 폰트 로드.
        imgSmallFont = resMan.getImage("5");

        initPos(); // 위치 초기화.
    }

앞서 살펴본 CyResMan 클래스를 통해,

setImageData() 함수를 이용해서, dat 파일을 읽어오고,

addImage() 함수를 이용해서, 이미지들을 해쉬 테이블에 고루 대입 시켜줍니다.

마지막으로 getImage()를 이용해서, Hashtable의 이미지들을 Image 클래스로 추출할때에는,

addImage() 함수의 첫번째 인자로 주었던, " " 사이의 레이블을 통해 읽어올 수 있습니다.


그리고 그 다음으로 나오는 drawFloor() 함수는, 게임판의 뒷 배경을 그려주는 함수입니다.

    /**
     * 게임판의 배경을 화면에 그려준다.
     * @param g
     */
    private void drawFloor(Graphics g)
    {
        g.setColor(204,153,255);
        g.fillRect(0,0,totalWidth,totalHeight);

        int clipx=0, clipy=0;
        int clipw=0, cliph=0;
		
        /*
         * 기본 클리핑 영역을 저장한다.
         */
        clipx = g.getClipX();
        clipy = g.getClipY();
        clipw = g.getClipWidth();
        cliph = g.getClipHeight();

        g.setClip(0,0,totalWidth,totalHeight);
		
        /*
         * 타일 패턴을 화면에 깔아준다.
         */
        for(int x=0;x<totalWidth;x+=imgBgPattern.getWidth())
        {
            for(int y=0;y<totalHeight;y+=imgBgPattern.getHeight())
            {
                g.drawImage(imgBgPattern, x, y, Graphics.LEFT|Graphics.TOP);
            }
        }

        if(curTurn == User.COMPUTER)
        {
            // 타일 배경을 그려준다.
            g.drawImage(imgFloorBg, width/2, rectFloor.top+6,
Graphics.HCENTER|Graphics.TOP); } else /* if(curTurn == PLAYER) */ { // 타일 배경을 그려준다. g.drawImage(imgFloorBg, width/2, rectFloor.top,
Graphics.HCENTER|Graphics.TOP); } /* * 저장해 둔 기본 클리핑 영역을 복원한다. */ g.setClip(clipx, clipy, clipw, cliph); }


먼저 기본으로 지정되어져 있는 클리핑영역을 함부로 바꾸면 안되므로, 기본 클리핑 영역을 저장해줍니다.

기본 클리핑 영역을 저장해 놓고, 전체 화면의 크기인 totalWidth와 totalHeight 만큼을 클리핑 영역으로 지정해줍니다.

LCD가 큰 핸드폰의 경우, 게임의 화면이 한 화면에 다 보이게 되는데, 이때 이미지가 삐져나오면 지저분하게 되므로,

클리핑 영역을 지정해 주는것입니다.


그리고 그 다음 부분은 이중 for 루프를 이용해서 타일 패턴을 화면에 깔아주는 부분인데요.

타일패턴은... 아래와 같은 이미지입니다.



어차피 같은 무늬가 반복되는 배경화면을 큰 이미지로 저장하는 것 보다는,

반복되는 무늬 타일을 쫘르륵 깔아 주는 것이 훨씬 더 효율적이겠죠.

그래서, 이 배경 타일을 화면에 for 루프를 이용해서 깔아준 것입니다.


그리고 그 다음에 나오는,

    if(curTurn == User.COMPUTER)
    {
        // 타일 배경을 그려준다.
        g.drawImage(imgFloorBg, width/2, rectFloor.top+6,
Graphics.HCENTER|Graphics.TOP); } else /* if(curTurn == PLAYER) */ { // 타일 배경을 그려준다. g.drawImage(imgFloorBg, width/2, rectFloor.top,
Graphics.HCENTER|Graphics.TOP); }

위 부분은, 배경 타일을 화면에 깔아 준후, 화면 중앙에 있는 무늬를 출력해 주기 위한 부분인데요.

현재 턴이 플레이어 턴일때와, 컴퓨터 턴일때와, 중앙의 무늬의 위치가 다릅니다.

왜냐하면, 컴퓨터의 턴일때에는, 컴퓨터의 패 부분을 봐야 하기때문에, 컴퓨터의 패부분을 넓게,

플레이어의 턴일때에는, 플레이어의 패 부분을 봐야 하기때문에, 플레이어의 패 부분을 넓게 보여줘야하겠죠.

그래서 위와 같이 현재 턴에 따라 위치를 달리해서 그려주게 되는 것이지요.


그리고 그 뒤의 drawWaitingGame(), drawDistributeCards(), drawRunningGame(), drawAcquireCards(), drawResult()

이 5개의 함수는 모두, 게임 내의 각각의 상태 화면들을 그려줍니다.

즉, drawWaitingGame() 의 경우, 게임 시작 전 대기 화면을 그려주며,

drawDistributeCards() 의 경우, 카드를 나눠주는 동작을 그려주지만, 이번시간에는 구현하지 않겠습니다.

또, drawRunningGame() 의 경우, 게임이 진행중일때의 게임 화면을 그려주지만, 역시 이번시간에는 구현하지 않습니다.

그리고 drawAcquireCards() 의 경우, 게임 진행 중에, 왼쪽 화살표나 오른쪽 화살표를 누르면,

현재 플레이어와 컴퓨터가 어떤 패를 가지고 있는지 확인 하는 창이 나오는데, 그 때의 화면을 그려주는 함수입니다.

마지막으로 drawResult() 함수는 게임 종료 후, 게임의 결과를 보여주는 함수이지만, 역시 이번시간에는 구현하지 않습니다.


위에서 설명드린 5개의 함수들은 모두 그림파일을 읽어와서 해당 좌표에 출력해주는 일밖에 하지 않으므로,

따로 긴 설명은 드리지 않겠습니다.


그런데, 앞서 말씀드린 drawXXX() 함수들을 보면, 대부분 room 이라는 객체의 함수를 사용하고 있습니다.

room 객체는 Room 클래스의 객체로, 맞고 게임 중에 게임에서 다루어지는 변수나 각종 함수들을 포함하고 있습니다.


Room 클래스 살펴보기


그리고 다음으로 drawSmallFont() 라는 함수가 있습니다.

drawSmallFont는 인자로 받은 숫자를, 이미지 형식으로 출력해 주는 함수 입니다.

    /**
     * 화면에 작은 숫자를 표시한다.
     * @param g
     * @param x 숫자의 x.
     * @param y 숫자의 y.
     * @param num 수.
     * @param anchor 왼쪽/오른쪽 정렬 선택.
     */
    private void drawSmallNum(Graphics g, int x, int y, long num, int anchor)
    {
        int cnt;
        int posx = x;
        int posy = y;

        String strNum = String.valueOf(num); // 숫자를 문자열로 변경해 준다.
        int len = strNum.length(); // 문자열의 길이를 구한다.

        // 오른쪽 정렬일때, 
        if(anchor==Graphics.RIGHT)
        {
            /*
             * 정확히 시작점부터 시작하기 위해,
             * 시작점에서 폰트의 폭을 빼준다.
             */
            posx -= SMALLFONT_WIDTH;
        }

        // 숫자를 한자 한자 출력해 준다.
        if(anchor == Graphics.RIGHT)
        {
            // 오른쪽에서 왼쪽으로...
            for(cnt=len-1; cnt>=0; cnt--)
            {
                drawSmallFont(g, posx, posy, (strNum.charAt(cnt)-'0'));
                posx -= SMALLFONT_WIDTH;
            }
        }
        else if(anchor == Graphics.LEFT)
        {
            // 왼쪽에서 오른쪽으로...
            for(cnt=0; cnt<len; cnt++)
            {
                drawSmallFont(g, posx, posy, (strNum.charAt(cnt)-'0'));
                posx += SMALLFONT_WIDTH;
            }
        }
    } // end private void drawSmallNum().

가장 먼저, drawSmallNum() 함수의 윗부분을 보면, String 클래스의 valueOf()라는 함수를 통해서,

입력받은 정수형 데이터를 String 형으로 변경 해 주고 있습니다.

또 변경된 문자열 객체의 length() 함수를 통해, 문자열의 길이를 구해 len 이라는 변수에 저장을 해 주게 되죠.


    // 오른쪽 정렬일때, 
    if(anchor==Graphics.RIGHT)
    {
        /*
         * 정확히 시작점부터 시작하기 위해,
         * 시작점에서 폰트의 폭을 빼준다.
         */
        posx -= SMALLFONT_WIDTH;
    }

위의 부분은 오른쪽 정렬로 숫자를 출력할 경우, 정확히 기준점에서부터 폰트가 출력되도록 해주는 작업입니다.

Small Font의 이미지는, 다음과 같은 형식으로 저장이 되어 있습니다.



따라서, g2d 객체로 하나하나의 숫자를 출력해 주어야 하는데, g2d 객체에는, 오른쪽 정렬 기능이 없으므로,

오른쪽 정렬일때에는, 왼쪽으로 이미지의 폭만큼을 빼준뒤 부터 그려주어야 정확히 오른쪽 정렬이 가능 하겠죠?


그 다음 부분은, 왼쪽 정렬인지, 오른쪽 정렬인지의 여부에 따라서, for 루프를 돌면서,

왼쪽 정렬 일때에는, 왼쪽에서 오른쪽으로, 오른쪽 정렬일때에는 오른쪽에서 왼쪽으로 한글자 한글자 출력을 해 주게 됩니다.


숫자 한글자를 출력해 줄때에는 drawSmallFont() 라는 함수가 사용되었는데요.

    private void drawSmallFont(Graphics g, int x, int y, int num)
    {
        g2d.drawImage(x, y, imgSmallFont, num*SMALLFONT_WIDTH, 0,
                SMALLFONT_WIDTH, SMALLFONT_HEIGHT, Graphics2D.DRAW_COPY);
    }


이자로 받은 x, y 좌표에 해당 이미지상에서 해당 숫자의 좌표를 계산해서 화면에 그려주는 간단한 함수입니다.

폰트 이미지에서의 숫자 한글자 한글자는 모두 4*6 사이즈로 되어 있으므로,

출력하고 싶은 숫자에 글꼴의 폭(Width)을 곱한뒤, 글꼴의 폭만큼 출력을 해주면 되겠지요.


자, 어쨋든 지금까지 작성한 코드들을 실행시켜보면,

우리가 구현한 아래와 같은 화면들을 감상 하실 수 있습니다.
게임 시작 전 대기 화면.
(curViewState = GAME_WAIT).


게임 시작전의 그냥 단순한 대기 화면.

Player와 Computer의 자금 확인이 가능.


취소, *키 : 메뉴로 돌아 감.

선택, 확인, #키 : 게임 시작.
게임 진행시의 화면.
(curViewState = GAME_RUNNING)


게임 진행 중의 화면.

지금은 아무것도 구현이 안되있으므로,

아무 것도 없이 배경화면만 뜬다.


왼쪽 화살표 : COM의 패 확인.

오른쪽 화살표 : Player의 패 확인.

취소 키 : 메뉴로 돌아 감.

선택, 확인 키 : 현재 턴을 변경. (임시 디버그용)
턴이 변경될때, 화면 이동 체크.
획득 패 확인 화면.
(curViewState = GAME_ACQUIRE)


각각의 플레이어가 획득한 패를 보여준다.

지금은 게임이 구현이 안되있으므로,

초기화된 상태의 user 객체 정보가 출력된다.


취소, 선택, 확인, *, #키 : 게임으로 돌아감.

어떠세요?

이제 그럴듯한 맞고 게임 분위기가 제법 풍기는 화면들까지 출력을 해봤네요.

 

Posted by maysent
:

도움말을 제외한 나머지 서브 메뉴들은 모두, CyItemList라는 클래스를 포함하여 만듭니다.

CyItemList의 전체적인 구조는 CyMenu 클래스와 동일합니다.

다만, CyMenu 클래스의 경우, 메뉴 항목이 그림이었으나, CyItemList는 항목들이 문자열이라는 것이 다르죠.

* CyItemList.java

package common;

import javax.microedition.lcdui.Image;
import javax.microedition.lcdui.Font;
import javax.microedition.lcdui.Canvas;
import javax.microedition.lcdui.Graphics;

import com.skt.m.Graphics2D;

/**
 * 간단한 항목들을 표시할때 사용한다. 
 */

public class CyItemList
{
    /*
     * 게임에서 쓰일 상수.
     */
    private final int FONT_HEIGHT;  // 글자의 높이.

    private final byte FIRST;       // 제일 처음 항목의 인덱스.
    private final byte LAST;        // 제일 마지막 항목의 인덱스.

    private final byte TITLE_TOP=5; // 타이틀의 Y 좌표.

    // 스크롤 바의 타입.
    private final byte UPARROW = 0;
    private final byte DOWNARROW = 1;

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

    /*
     * 일반 변수.
     */
    private String title;           // ItemList의 제목.
    private String[] itemName;      // 각 항목의 이름.
    private byte[] itemValue;      // 각 항목의 설정된 값.

    private byte maxItem;           // 항목의 갯수.

    private byte curItemIdx;        // 현재 선택된 항목 인덱스.
    private byte dispCnt;           // 화면에 보여줄 최대 항목 갯수.
    private byte minDispItemIdx;    // 현재 화면에 보여지는 최소 메뉴 인덱스.
    private byte maxDispItemIdx;    // 현재 화면에 보여지는 최대 메뉴 인덱스.

    private short borderLeft;       // ItemList의 경계선 Left.
    private short borderTop;        // ItemList의 경게선 Top.
    private short borderWidth;      // ItemList의 경계선 Width.
    private short borderHeight;     // ItemList의 경계선 Hidth.

    private byte itemTop;           // 첫번째 Item의 Top.
    private byte itemBoxLeft;       // 선택된 Item Box의 Left.
    private byte itemBoxTop;        // 선택된 Item Box의 Top.
    private byte itemBoxWidth;      // 선택된 Item Box의 Width.
    private byte itemBoxHeight;     // 선택된 Item Box의 Height.

    private Image sprScroll;        // 스크롤 화살표의 스프라이트.

    private short upScrollLeft;     // 위쪽 스크롤 화살표의 Left.
    private short upScrollTop;      // 위쪽 스크롤 화살표의 Top.

    private short downScrollLeft;   // 아래쪽 스크롤 화살표의 Left.
    private short downScrollTop;    // 아래쪽 스크롤 화살표의 Top.

    private byte itemInterval;      // 각 항목간의 간격.
    private byte defaultInterval;   // 기본 Interval.
    private byte cntAddedItem;      // 추가된 항목의 수.

    /*
     * 각 객체의 색상 변수.
     */
    private int colorOfTitleText;   // 타이틀의 글자 색.
    private int colorOfItemText;    // 각 항목의 글자 색.
    private int colorOfItemBoxText; // 선택된 항목의 글자 색.

    private int colorOfTitleBg;     // 타이틀의 배경 색.
    private int colorOfBorderBg;    // Border의 배경 색.
    private int colorOfItemBg;      // 각 항목의 배경 색.
    private int colorOfItemBoxBg;   // 선택된 항목의 배경 색.

    private Canvas parent;          // ItemList를 그려줄 캔버스.

    int width;
    int height;

    /**
     * Constructor.
     * @param maxItem
     */
    public CyItemList(Canvas parent, int maxItem)
    {
        this.parent = parent;

        // 스크롤 바의 이미지를 읽어 옴.
        try {
            sprScroll = loadImage("/image/menu/SprScroll.lbm");
        } catch(Exception e) {}

        // 스크롤 바의 크기 저장.
        SCROLL_WIDTH = (byte)(sprScroll.getWidth()/2);
        SCROLL_HEIGHT = (byte)sprScroll.getHeight();

        // 폰트 높이를 구해온다.
        FONT_HEIGHT = (byte)((Font.getDefaultFont()).getHeight());

        /*
         * Default 색상 설정.
         */
        colorOfTitleBg = 0x000090;
        colorOfItemBg = 0x500000;
        colorOfItemBoxBg = 0x007800;

        colorOfTitleText = 0xffffff;
        colorOfItemText = 0xffffff;
        colorOfItemBoxText = 0xffff00;

        curItemIdx = 0;                  // 현재 선택된 항목의 인덱스 초기화.
        cntAddedItem = 0;                // 추가된 항목의 갯수 초기화.

        this.maxItem = (byte)maxItem;   // 항목의 갯수 초기화.

        // 항목이 없는 ItemList는,
        if(maxItem == 0)
        {
            FIRST = 0;
            LAST = 0;

            setBorderMaxSize(); // Border 크기를 최대로 설정한다.
        }
        else // 항목이 1개 이상이라면,
        {
            FIRST = 0;                       // 첫번째 항목의 인덱스.
            LAST = (byte)(maxItem - 1);     // 마지막 항목의 인덱스.

            itemInterval = 10;               // 항목 사이의 간격.
            defaultInterval = 5;             // 기본 간격 설정.

            itemName = new String[maxItem]; // 항목 이름을 저장할 배열 생성.
        }

        width = parent.getWidth();          // 캔버스 Width.    
        height = parent.getHeight()+16;     // 캔버스 Height.
    }

    /**
     * 이미지를 읽어온다.
     * @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;
    }

    /**
     * 화면에 ItemList를 그려줌.
     * @param g
     */
    public void draw(Graphics g)
    {
        drawObj(g);
    }

    /**
     * 아이템, 요소, 제목등을 화면에 표시
     * @param g 표시할 graphic 개체
     */
    public void drawObj(Graphics g)
    {
        drawTitle(g);
        drawBorder(g);

        // 메뉴 항목이 1개 이상 존재할때,
        if(maxItem != 0) // 메뉴 항목을 그려준다.
            drawItem(g);
    } // end public void drawObj(Graphics g, int baseX, int baseY).

    /**
     * Border를 그려 줌.
     */
    private void drawBorder(Graphics g)
    {
        // Border의 내부 배경색을 칠해줌.
        g.setColor(colorOfBorderBg);
        g.fillRect(borderLeft, borderTop, borderWidth, borderHeight);

        // Border의 외곽선을 그려줌.
        g.setColor(0xffffff);
        g.drawRect(borderLeft, borderTop, borderWidth, borderHeight);
    }

    /**
     * 타이틀을 그려줌.
     * @param g
     */
    private void drawTitle(Graphics g)
    {
        g.setColor(colorOfTitleBg);
        g.fillRect(0, TITLE_TOP, width, FONT_HEIGHT);
        g.setColor(colorOfTitleText);
        g.drawString(title, width/2, TITLE_TOP, Graphics.HCENTER|Graphics.TOP);
    }

    /**
     * 메뉴 항목을 그려 줌.
     * @param g
     */
    private void drawItem(Graphics g)
    {
        for(int i=minDispItemIdx; i<=maxDispItemIdx; i++)
        {
            int indexTop = (i-minDispItemIdx)*(FONT_HEIGHT+itemInterval);

            if(i == curItemIdx) // 현재 선택된 항목이라면,
            {
                // 선택 박스 출력 후,
                g.setColor(colorOfItemBoxBg);
                g.fillRect(itemBoxLeft,itemBoxTop+indexTop,
itemBoxWidth,itemBoxHeight); // 글씨 출력. g.setColor(colorOfItemBoxText); // 선택되었을 때의 글씨색. g.drawString(itemName[i], width/2, itemTop + indexTop,
Graphics.HCENTER|Graphics.TOP); } else
// 현재 선택된 항목이 아니라면, { // 글씨만 출력. g.setColor(colorOfItemText); // 선택이 안된 때의 글씨색. g.drawString(itemName[i], width/2, itemTop + indexTop,
Graphics.HCENTER|Graphics.TOP); } } /*
* 스크롤 바를 그려준다. */ // 화면에 보이는 최저 인덱스가, if(minDispItemIdx != FIRST) // 처음이 아니라면, { // 위쪽 화살표를 그려준다. drawScroll(g, UPARROW); } // 화면에 보이는 최대 인덱스가, if(maxDispItemIdx != LAST) // 마지막이 아니라면, { // 아래쪽 화살표를 그려준다. drawScroll(g, DOWNARROW); } } /** * 스크롤 바를 그려준다. * @param g * @param scrollType */ private void drawScroll(Graphics g, int scrollType) { Graphics2D g2d = Graphics2D.getGraphics2D(g); if(scrollType == UPARROW) // 위쪽 화살표라면, { g2d.drawImage(upScrollLeft, upScrollTop, sprScroll, 0, 0, SCROLL_WIDTH,
SCROLL_HEIGHT, Graphics2D.DRAW_COPY); } else
/* if(scrollType == DOWNARROW) */ { g2d.drawImage(downScrollLeft, downScrollTop, sprScroll, SCROLL_WIDTH,
0, SCROLL_WIDTH, SCROLL_HEIGHT, Graphics2D.DRAW_COPY); } } /**
* 타이틀 제목 설정. * @param titleStr */ public void setTitle(String titleStr) { title = titleStr; } /** * 항목 추가. * @param itemStr */ public void addItem(String itemStr) { // 항목 이름 대입. itemName[cntAddedItem] = itemStr; if(cntAddedItem == LAST) { setBorderSize(); setItemBoxSize(); System.out.println("마지막 항목 입력."); } cntAddedItem++; } /** * 스크롤 바의 위치 설정. */ private void setScrollPos() { // 위쪽 화살표의 위치 설정. upScrollLeft = (short)(borderLeft + borderWidth - SCROLL_WIDTH); upScrollTop = (short)(borderTop + SCROLL_HEIGHT); // 아랫쪽 화살표의 위치 설정. downScrollLeft = upScrollLeft; downScrollTop = (short)(borderTop+borderHeight
-defaultInterval-SCROLL_HEIGHT); } /**
* Border의 위치를 설정. * @param x * @param y */ private void setBorderPos(int x, int y) { borderLeft = (byte)x; borderTop = (byte)y; setScrollPos(); // 스크롤 바의 위치를 설정한다. } /** * Item List의 객체들을 가운데 정렬 한다. * @param bottomCanon 아래쪽의 어디까지 정렬 할 것인가. */ public void alignObjectCenter(int bottomCanon) { // Border 가운데 정렬. setBorderPos((width - borderWidth)/2, (TITLE_TOP+(TITLE_TOP+FONT_HEIGHT+bottomCanon)-borderHeight)/2); // 선택 사각형 가운데 정렬. setItemBoxPos((width-itemBoxWidth)/2+1,
borderTop+itemInterval+defaultInterval); } /**
* Border의 크기 설정. */ private void setBorderSize() { borderWidth = 106; borderHeight = (short)((FONT_HEIGHT+itemInterval)
* dispCnt + itemInterval + defaultInterval*2); } /**
* Border의 크기를 최대로 설정. */ private void setBorderMaxSize() { borderWidth = 106; borderHeight = 108; } /** * 첫번째 항목이 선택되었을때의 박스 위치 설정. * @param x * @param y */ private void setItemBoxPos(int x, int y) { // 첫번째 항목의 위치 설정. itemTop = (byte)y; // 첫번째 항목이 선택되었을때의 박스 위치 설정. itemBoxLeft = (byte)x; itemBoxTop = (byte)y; } /** * 첫번째 항목이 선택되었을때의 박스 크기 설정. */ private void setItemBoxSize() { // 선택 사각형의 폭과 높이 설정. itemBoxWidth = (byte)(borderWidth - 7); itemBoxHeight = (byte)FONT_HEIGHT; } /** * 현재 선택된 항목의 인덱스를 리턴. * @return */ public byte getCurItemIdx() { return curItemIdx; } /** * 화면에 보여줄 항목 갯수를 설정. * @param dispCnt 한 화면에 보여질 항목의 갯수. */ public void setDispCnt(int dispCnt) { this.dispCnt = (byte)dispCnt; // 한 화면에 보여줄 항목 갯수. // 화면에 보이는 최소 인덱스. minDispItemIdx = curItemIdx; // 화면에 보이는 최대 인덱스. maxDispItemIdx = (byte)(curItemIdx + dispCnt - 1); System.out.println(" ( " + minDispItemIdx + ", " + maxDispItemIdx + ")"); } /** * 각 항목간의 간격을 설정한다. * @param interval */ public void setItemInterval(int interval) { itemInterval = (byte)interval; } /** * 타이틀의 배경색 설정. * @param colorCode */ public void setColorOfTitleBg(int colorCode) { colorOfTitleBg = colorCode; } /** * Border의 배경색 설정. * @param colorCode */ public void setColorOfBorderBg(int colorCode) { colorOfBorderBg = colorCode; } /** * 선택된 항목의 배경색 설정. * @param colorCode */ public void setColorOfItemBoxBg(int colorCode) { colorOfItemBoxBg = colorCode; } /** * 이전 항목으로 포커스 이동. */ public void pre() { /* 현재 포커스가 가장 처음이라면, */ if(curItemIdx == FIRST) return; /* 현재 보이는 최소 인덱스가 처음이 아니라면, */ if(curItemIdx == minDispItemIdx) { minDispItemIdx--; maxDispItemIdx--; } curItemIdx--; // 현재 선택된 인덱스 +1 } /** * 다음 항목으로 포커스 이동. */ public void next() { /* 현재 포커스가 가장 마지막이라면, */ if(curItemIdx == LAST) return;
/* 현재 보이는 최대 인덱스가 마지막이 아니라면, */
if(curItemIdx == maxDispItemIdx) { minDispItemIdx++; maxDispItemIdx++; } curItemIdx++; // 현재 선택된 인덱스 +1. } }


소스가 조금 기네요.

그러나 대부분 인터페이스를 꾸미는데 필요한 코드 들일뿐, 실제 중요한 코드는 몇줄 되지 않습니다.

또 말씀 드렸다시피, CyMenu 클래스의 구조와 거의 흡사 합니다.

이해를 돕기 위해, 잠깐 CyItemList를 이용해서 만든 메뉴의 예를 한번 보도록 하죠.


구조가 거의 메인 메뉴와 비슷합니다. 그렇죠?

일단 차이점이라면, 먼저 이 화면이 뭘 하는 Item List인지 표시해 주는 타이틀이 있고,


스크롤바가 가운데에 있지 않고, 오른쪽에 있네요.

그리고 가장 큰 차이점은, 항목이 그림이 아니라 String 이라는 것...

그 외에는 모두 동일 합니다.




먼저, 생성자를 보면, 스크롤 바로 사용될 이미지를 불러오고, 스크롤 바의 크기를 구해서 상수를 초기화 시킵니다.

그리고 그다음에 폰트의 높이를 구하는 부분이 있는데요.


// 폰트 높이를 구해온다.
FONT_HEIGHT = (byte)((Font.getDefaultFont()).getHeight());

Font 클래스의 static 매소드인 getDefaultFont()를 이용해서 기본 글꼴의 정보를 얻어 온후,

getHeight() 함수를 이용해서 기본 글꼴의 높이를 얻어 왔습니다.


그리고 다음 부분은, 각각의 객체의 색깔을 지정해 주는 부분인데요.

/*
* Default 색상 설정.
*/
colorOfTitleBg = 0x000090;
colorOfItemBg = 0x500000;
colorOfItemBoxBg = 0x007800;
colorOfTitleText = 0xffffff;
colorOfItemText = 0xffffff;
colorOfItemBoxText = 0xffff00;

여태까지 우리는 g.setColor(255, 255, 255)와 같이 g객체를 통해서 색깔을 지정해 주었지만,

위에서는 직접 #ffffff와 같이 컬러 코드를 지정해 주고 있습니다.

#ffffff 와 같은 코드는, 앞에서부터 두자리씩 각각의 색상 코드를 나타냅니다.

즉, #RrGgBb와 같은 형식이죠.

앞에 두자리인 FF는 Red에 해당하는 16진수이고, 두번째 두자리는 Green에 해당하는 16진수, 세번째는 Blue...


FF는 10진수로 변환하면 255와 같으므로, 위의 #ffffff 는, 즉 RGB 코드로 (255, 255, 255)와 같은 의미이겠지요.

위와 같이 직접 컬로 코드로 정의해 놓으면, g.setColor(colorOfTitleBg)와 같이,

setColor()의 해당 컬러 코드를 인자로 넘겨주면 색상이 변경이 됩니다.


그외의 나머지 함수들은 전부다 외부 혹은 내부에서 클래스의 변수 값을 변경하기 위한 함수들입니다.

setXXXXXPos() 와 같은 함수는 XXXX라는 객체의 위치를 변경하는 함수를 의미하고,

setXXXXXSize() 와 같은 함수는 XXXX라는 객체의 위치를 변경하는 함수.... 뭐 이런 식이죠.

get의 경우에는 값을 얻어올때 사용하는 함수가 되겠죠.


그리고 마찬가지로, CyItemList 클래스에도 pre()와 next()가 있습니다.

pre()/next()의 코드 내용 자체는 CyMenu의 pre()/next()와 동일 합니다.

전혀 다를게 없죠.


이제, 이걸로 CyItemList 클래스도 살펴 보았습니다.

이제 CyItemList 클래스를 이용해서 ItemList를 출력해 주면 되겠네요.

그런데 메뉴 항목중에서 게임 시작과 도움말을 뺀 나머지 서브 메뉴들은 모두 CyItemList 클래스를 사용합니다.

결국... 나머지 서브메뉴들의 구조가 모두 동일하겠죠. 같은 클래스를 사용했으니까요.

실제로 저도 나머지 서브 메뉴들은, 가장 먼저 겔러리를 만든 후,

나머지는 모두 복사해서 클래스 이름만 변경해서 작성했습니다.


함수 이름이나 클래스 구조가 완전히 동일하므로, 여기서는, 겔러리 소스 만을 분석해 보도록 하겠습니다.


* Gallery.java
package twogo1p;

import javax.microedition.lcdui.Canvas;
import javax.microedition.lcdui.Graphics;
import common.CyItemList;

/**
 *      겔러리.
 */
class Gallery extends Canvas {

    private Midlet midlet;
    private CyItemList itemList;
    private short width, height;
    private Canvas callBackDisplayCanvas;

    /**
     * Constructor.
     * @param midlet
     */
    Gallery(Midlet midlet)
    {
        this.midlet = midlet;
        width = (short) getWidth();
        height = (short) (getHeight()+16);

        itemList = new CyItemList(this, 11);

        itemList.addItem("그림 1");
        itemList.addItem("그림 2");
        itemList.addItem("그림 3");
        itemList.addItem("그림 4");
        itemList.addItem("그림 5");
        itemList.addItem("그림 6");
        itemList.addItem("그림 7");
        itemList.addItem("그림 8");
        itemList.addItem("그림 9");
        itemList.addItem("그림 10");
        itemList.addItem("돌아가기");
		
        itemList.setDispCnt(6);
        itemList.setTitle("겔러리");
        itemList.setItemInterval(0);

        itemList.setColorOfTitleBg(0x0000A0);
        itemList.setColorOfBorderBg(0x990000);
        itemList.setColorOfItemBoxBg(0x997700);
		
		   // ItemList를 가운데 정렬 한다.
        itemList.alignObjectCenter(height-16*2);
    }

    /**
     * 이전 Canvas를 저장한다.
     * @param canvas
     */
    public void savePreCanvas(Canvas canvas)
    {
        this.callBackDisplayCanvas = canvas;
    }


    /**
     * 화면에 겔러리 화면을 그려준다.
     */
    protected void paint(Graphics g)
    {
        g.setColor(0,0,0);
        g.fillRect(0,0,width,height);

        itemList.draw(g);
    }

    /**
     * 전체화면 refresh.
     */
    private void repaints()
    {
        repaint(0, 0, width, height);
    }

    /**
     * 키 입력 처리.
     */
    public void keyPressed(int keyCode)
    {
        switch(keyCode)
        {
        case KEY_UP:
            itemList.pre();
            repaints();
            break;

        case KEY_DOWN:
            itemList.next();
            repaints();
            break;

        case KEY_CLR:
            returnToPreCanvas();
            break;

        case KEY_FIRE:
        case KEY_COMR:

            if(itemList.getCurItemIdx() == 10)
            {
                returnToPreCanvas();
            }
            break;

        }
    }


    /**
     * 이전 Canvas 화면을 보여준다.
     */
    private void returnToPreCanvas()
    {
        midlet.setCurrentDisplay(callBackDisplayCanvas);
        midlet = null;
        callBackDisplayCanvas = null;
        itemList = null;
        System.gc();
    }

}

먼저 생성자를 보면, ItemList의 여러 속성들을 초기화 시켜주고 있습니다.


itemList = new CyItemList(this, 11);
itemList.setDispCnt(6);
itemList.setTitle("겔러리");
itemList.setItemInterval(0);


CyItemList 객체를 생성할때 준 두번째의 인자가 11이므로, 총 11개의 항목을 생성할 수 있습니다.

그래서, 그림 1번 부터 10번까지와, 돌아가기 항목을 addItem() 함수로 생성해 주었습니다.


그리고 그다음 setDispCnt() 함수로, 한 화면에 총 6개의 항목씩 출력 되도록 DispCnt를 초기화 해주고,

Title 제목은 "겔러리" 라고 초기화 해 주었으며, 각 항목 사이의 간격은 0으로 초기화 해주었습니다.

그리고 다음으로 alignObjectCenter() 함수를 이용해서, ItemList 객체들을 화면에 가운데 정렬하여 보여줍니다.

항목을 입력 받은 뒤에 가운데 정렬을 하는 것은 항목의 갯수에 따라 ItemList의 사이즈가 다르기 때문입니다.


그리고 나머지 함수들은 예전에 이미 구현했던 내용과 동일합니다.

키 입력 처리 부분 역시 마찬가지로 메인 메뉴에서 구현했던 내용과 완전 동일 합니다.

위쪽 화살표가 입력되면, pre() 함수로 이전 항목을 선택하고,

아래쪽 화살표가 입력되면, next() 함수로 당므 항목을 선택합니다.


그런데, 사실 겔러리는 이게 다는 아닙니다.

겔러리는 원래 게임에서 번 돈으로 사진을 사서 감상할수 있는 공간인데,

아직 우리가 게임을 구현하지 않았으므로, 게임이 완벽하게 구현 된 뒤에, 겔러리를 완성 시키도록 하겠습니다.

환경 설정 부분도, 사운드와 진동을 설정해 주어야 하는데, 아직 우리가 사운드 클래스를 구현하지 않았으므로,

사운드 클래스를 제작한 후에, 다시 완성하도록 하겠

Posted by maysent
: