游戲策劃咨訊
          做一個(gè)游戲并不難,難的是做一個(gè)好游戲;完美在于積累!

          本文通過(guò)使用J2ME開(kāi)發(fā)華容道游戲,介紹了J2ME游戲開(kāi)發(fā)的基本模式....

          一、序言

            昨天在網(wǎng)上閑逛,發(fā)現(xiàn)一篇講解用delphi實(shí)現(xiàn)華容道游戲的文章,頗受啟發(fā).于是,產(chǎn)生了將華容道游戲移植到手機(jī)中去的沖動(dòng).現(xiàn)在手機(jī)游戲琳瑯滿目,不一而足,華容道的實(shí)現(xiàn)版本也很多.正巧不久前筆者對(duì)J2ME下了一番功夫,正想借這個(gè)機(jī)會(huì)小試牛刀。選用J2ME的原因還有一個(gè)就是目前Java開(kāi)發(fā)大行其到,無(wú)線增殖業(yè)務(wù)迅猛發(fā)展,J2ME的應(yīng)用日漸活躍起來(lái),也希望我的這篇文章能夠?yàn)镴2ME知識(shí)的普及和開(kāi)發(fā)團(tuán)隊(duì)的壯大推波助瀾。由于長(zhǎng)期受ISO規(guī)范的影響,這次小試牛刀我也打算遵照軟件工程的要求,并采取瀑布式的開(kāi)發(fā)模式來(lái)規(guī)劃項(xiàng)目,也希望借此機(jī)會(huì)向各位沒(méi)有機(jī)會(huì)參與正式項(xiàng)目開(kāi)發(fā)的讀者介紹一下軟件開(kāi)發(fā)的流程。

            這里我們先定義項(xiàng)目組的人員體制(其實(shí)只有我一個(gè)人):技術(shù)調(diào)研、需求分析、概要設(shè)計(jì)、詳細(xì)設(shè)計(jì)、編碼、測(cè)試均有筆者一人擔(dān)任;美工這里我找了個(gè)捷徑,盜用網(wǎng)上現(xiàn)成的圖片,然后用ACDSee把它由BMP轉(zhuǎn)換成PNG格式(我出于講座的目的,未做商業(yè)應(yīng)用,應(yīng)該不算侵權(quán)吧);至于發(fā)布工作,由于缺少OTA服務(wù)器,此項(xiàng)工作不做(但是我會(huì)介紹這步如何做)。

            接下來(lái),我們規(guī)劃一下項(xiàng)目實(shí)現(xiàn)的時(shí)間表,以我個(gè)人經(jīng)驗(yàn),設(shè)想如下:技術(shù)調(diào)研用2天(這部分解決項(xiàng)目的可行性和重大技術(shù)問(wèn)題,時(shí)間會(huì)長(zhǎng)一些),需求分析用半天(畢竟有現(xiàn)成的東東可以參照,只要理清思路就行了,況且還有很多以前用過(guò)的設(shè)計(jì)模式和寫(xiě)好的代碼),概要設(shè)計(jì)再用半天(有了需求,概要只不夠是照方抓藥),詳細(xì)設(shè)計(jì)要用2天(這一步要把所有的問(wèn)題想清楚,還要盡可能的準(zhǔn)確描述出來(lái)),編碼用2天(其實(shí)1天就夠了,技術(shù)已經(jīng)不是問(wèn)題,多計(jì)劃出一天來(lái)應(yīng)付突發(fā)事件),測(cè)試用2天(測(cè)試應(yīng)該至少占全部項(xiàng)目的四分之一,不過(guò)這個(gè)項(xiàng)目只是一個(gè)Demo,也太簡(jiǎn)單了),發(fā)布也要用上半天(盡管我們不去實(shí)際發(fā)布它,但是還要花點(diǎn)時(shí)間搞清楚應(yīng)該如何做),最后就是項(xiàng)目總結(jié)和開(kāi)慶功會(huì)(時(shí)間待定)。

            二.利其器

            “公欲善其事,必先利其器”,做項(xiàng)目之前第一步是前期調(diào)研.我們要做的華容道這個(gè)東東隨處可見(jiàn),我們要調(diào)研的是兩個(gè)方面:

            1、游戲的內(nèi)容:游戲本身很簡(jiǎn)單,就是有幾個(gè)格子,曹操占據(jù)其中一個(gè)較大的格子,然后被幾個(gè)格子包圍,這些格子形狀不一定相同,但是擋住了曹操移動(dòng)的方向.游戲者需要挪動(dòng)這些格子最終把曹操移動(dòng)到一個(gè)指定的位置才算是過(guò)關(guān).更具體的分析我們放在后面需求分析和概要設(shè)計(jì)中討論。

            2、技術(shù)儲(chǔ)備:談到技術(shù),這里簡(jiǎn)單介紹一下J2ME.Java有三個(gè)版本,分別是J2ME(微型版).J2SE(標(biāo)準(zhǔn)版).J2EE(企業(yè)版).J2ME是一個(gè)標(biāo)準(zhǔn),采用3層結(jié)構(gòu)設(shè)計(jì).最低層是配置層(Configuration)也就是設(shè)備層,其上是簡(jiǎn)表層(Profile),再上是應(yīng)用層(Application).MIDP就是移動(dòng)信息設(shè)備簡(jiǎn)表,目前主流手機(jī)支持MIDP1.0,最新的是MIDP2.0,它比前一個(gè)版本增加了對(duì)游戲的支持,在javax.microedition.lcdui.game包中提供了一些類(lèi)來(lái)處理游戲中的技術(shù),比如我們后面會(huì)用到的Sprite類(lèi),它是用來(lái)翻轉(zhuǎn)圖片的.權(quán)衡再三,筆者決定使用MIDP2.0來(lái)做開(kāi)發(fā).首先需要安裝一個(gè)J2ME的模擬器,我們就用Sun公司的WTK2.0,我覺(jué)得Sun的東西最權(quán)威.當(dāng)然你也可以使用Nokia.Siemens或是Motolora等其他模擬器,但是他們的JDK不盡相同,寫(xiě)出來(lái)的程序移植是比較麻煩的.Sun公司的WTK2.0可以到<A href="http://here/下">http://here/下</A>載,當(dāng)然要想成功下載的前提是你要先注冊(cè)成為Sun的會(huì)員(其實(shí)這樣對(duì)你是有好處的).當(dāng)下來(lái)之后就是按照提示一步一步的安裝.安裝好了之后,我們用一個(gè)"Hello World"程序開(kāi)始你的J2ME之旅.我們啟動(dòng)WTK2.0工具集中的KToolBar,然后點(diǎn)擊New Project按鈕,在彈出的輸入框中輸入Project Name為HelloWorld,MIDlet Class Name為Hello,然后點(diǎn)擊Create Project,開(kāi)始生成項(xiàng)目,工具會(huì)彈出MIDP配置簡(jiǎn)表,這里接受生成的默認(rèn)值(以后還可以修改)點(diǎn)擊OK,工具提示我們把寫(xiě)好的Java源程序放到[WTK_HOME]\apps\HelloWorld\src目錄之下.我們編輯如下代碼,并保存在上述目錄之下,文件名為Hello.java。

          import javax.microedition.midlet.*;
          import javax.microedition.lcdui.*;
          public class Hello extends MIDlet
          {
          private Display display;
          public Hello(){
          display =Display.getDisplay(this);
          }
          public void startApp(){
          TextBox t = new TextBox("Hello","Hello",256,0);
          display.setCurrent(t);
          }
          public void pauseApp(){
          }
          public void destroyApp(boolean unconditional){
          }
          }


            保存好了之后,點(diǎn)擊Build按鈕,工具會(huì)為你編譯程序,如無(wú)意外再點(diǎn)擊Run按鈕,會(huì)彈出一個(gè)手機(jī)界面,剩下的就不用我教了吧(用鼠標(biāo)對(duì)手機(jī)按鍵一頓狂點(diǎn))。呵呵,你的第一個(gè)J2ME程序已經(jīng)OK了.什么?你還一點(diǎn)都沒(méi)懂呢(真是厲害,不懂都能寫(xiě)出J2ME程序來(lái),果然是高手).我這里主要是介紹WTK2.0工具的使用,程序并不是目的,不懂的話后面還會(huì)有詳細(xì)的解說(shuō),這里只是帶你上路.什么?你不懂Java!那也沒(méi)有關(guān)系,后面我再講得細(xì)一點(diǎn)。

            跳過(guò)J2ME,我們先來(lái)講點(diǎn)游戲的理論.具體到華容道這個(gè)游戲,主要有三個(gè)方面,貼圖.游戲操作.邏輯判斷.這里講講貼圖,其他兩方面放在概要設(shè)計(jì)和詳細(xì)設(shè)計(jì)里講.所謂的貼圖,其實(shí)就是畫(huà)圖,就是在要顯示圖形的位置上輸出一副圖片,(要是牽扯到動(dòng)畫(huà)就要麻煩一些,可以使用TimerTask.Thread或Rannable之類(lèi)的技術(shù)),這副圖片可以是事先準(zhǔn)備好的也可以是臨時(shí)處理的.在J2ME中有一個(gè)Image類(lèi),專門(mén)用于管理圖片,它有createImage()方法,可以直接讀取圖片文件(J2ME只支持PNG格式的圖片),也可以截取已有的圖片的一部分(這樣我們可以把很多圖片放在一起,然后一張一張的截下來(lái),好處是節(jié)省存儲(chǔ)空間和文件讀取時(shí)間,對(duì)于手機(jī)這兩者都是性能的瓶頸).J2ME還有一個(gè)Graphics類(lèi),專門(mén)用于繪圖,它有drawImage()方法,可以把一副圖片在指定的位置上顯示出來(lái),它還有drawRect()方法和setColor()方法,這兩個(gè)方法在后面我們進(jìn)行游戲操作時(shí)就會(huì)用到,這里先交代一下.有了圖片和繪圖的方法,還需要知道把圖畫(huà)到誰(shuí)身上,J2ME提供了一個(gè)Canvas類(lèi),字面意思就是畫(huà)布,它有一個(gè)paint()方法用于刷新頁(yè)面,還有一個(gè)repaint()方法用于調(diào)用paint()方法.聽(tīng)著有些糊涂是吧,不要緊,我來(lái)結(jié)合具體程序講解一下.為了今后編程的方便,我們創(chuàng)建兩個(gè)類(lèi)Images和Draw,Images用于保存一些常量值和圖片,Draw主要是用于畫(huà)圖,這兩個(gè)類(lèi)的源代碼如下。

            Images類(lèi)的源代碼如下:

          package huarongroad;

          import javax.microedition.lcdui.*;
          import javax.microedition.lcdui.game.*;

          public class Images {//保存常量
          //繪圖位置常量
          public static final int UNIT = 32;//方塊的單位長(zhǎng)度
          public static final int LEFT = 10;//畫(huà)圖的左邊界頂點(diǎn)
          public static final int TOP = 9;//畫(huà)圖的上邊界頂點(diǎn)
          //地圖位置常量
          public static final int WIDTH = 4;//地圖的寬度
          public static final int HEIGHT = 5;//地圖的高度
          //地圖標(biāo)記常量
          public static final byte CAOCAO = (byte) 'a'; <A href="file://曹">file://曹</A>操的地圖標(biāo)記
          public static final byte MACHAO = (byte) 'b';//馬超的地圖標(biāo)記
          public static final byte HUANGZHONG = (byte) 'c';//黃忠的地圖標(biāo)記
          public static final byte GUANYU = (byte) 'd';//關(guān)羽的地圖標(biāo)記
          public static final byte ZHANGFEI = (byte) 'e';//張飛的地圖標(biāo)記
          public static final byte ZHAOYUN = (byte) 'f';//趙云的地圖標(biāo)記
          public static final byte ZU = (byte) 'g';//卒的地圖標(biāo)記
          public static final byte BLANK = (byte) 'h';//空白的地圖標(biāo)記
          public static final byte CURSOR = (byte) 'i';//光標(biāo)的地圖標(biāo)記
          //地圖組合標(biāo)記常量
          public static final byte DLEFT = (byte) '1'; <A href="file://組">file://組</A>合圖形左邊標(biāo)記
          public static final byte DUP = (byte) '2'; <A href="file://組">file://組</A>合圖形上邊標(biāo)記
          public static final byte DLEFTUP = (byte) '3'; <A href="file://組">file://組</A>合圖形左上標(biāo)記
          //圖片常量
          public static Image image_base;//基本圖片
          public static Image image_Zhaoyun;//趙云的圖片
          public static Image image_Caocao;//曹操的圖片
          public static Image image_Huangzhong;//黃忠的圖片
          public static Image image_Machao;//馬超的圖片
          public static Image image_Guanyu;//關(guān)羽的圖片
          public static Image image_Zhangfei;//張飛的圖片
          public static Image image_Zu;//卒的圖片
          public static Image image_Blank;//空白的圖片
          public static Image image_Frame;//游戲框架的圖片

          public Images() {//構(gòu)造函數(shù)
          }

          public static boolean init() {//初始化游戲中用到的圖片
          try {
          image_base = Image.createImage("/huarongroad/BITBACK.png");
          image_Frame = Image.createImage(image_base, 126, 0, 145, 177,
          Sprite.TRANS_NONE);
          //Sprite類(lèi)是用來(lái)翻轉(zhuǎn)圖片的,是MIDP2.0新新增加的支持游戲的特性
          image_Zhaoyun = Image.createImage(image_base, 0, 0, UNIT, 2 * UNIT,
          Sprite.TRANS_NONE);
          image_Caocao = Image.createImage(image_base, UNIT, 0, 2 * UNIT,
          2 * UNIT, Sprite.TRANS_NONE);
          image_Huangzhong = Image.createImage(image_base, 3 * UNIT, 0, UNIT,
          2 * UNIT,
          Sprite.TRANS_NONE);
          image_Machao = Image.createImage(image_base, 0, 2 * UNIT, UNIT,
          2 * UNIT,
          Sprite.TRANS_NONE);
          image_Guanyu = Image.createImage(image_base, UNIT, 2 * UNIT,
          2 * UNIT, UNIT,
          Sprite.TRANS_NONE);
          image_Zhangfei = Image.createImage(image_base, 3 * UNIT, 2 * UNIT,
          UNIT, 2 * UNIT,
          Sprite.TRANS_NONE);
          image_Zu = Image.createImage(image_base, 0, 4 * UNIT, UNIT, UNIT,
          Sprite.TRANS_NONE);
          image_Blank = Image.createImage(image_base, 1 * UNIT, 4 * UNIT,UNIT,
          UNIT,
          Sprite.TRANS_NONE);

          return true;
          }catch (Exception ex) {
          return false;
          }
          }
          }

            Draw類(lèi)的源代碼如下:

          package huarongroad;

          import javax.microedition.lcdui.*;

          public class Draw {
          //繪制游戲中的圖片
          public Draw(Canvas canvas) {//構(gòu)造函數(shù)
          }

          public static boolean paint(Graphics g, byte img, int x, int y) {
          //在地圖的x,y點(diǎn)繪制img指定的圖片
          try {
          paint(g, img, x, y, Images.UNIT);//把地圖x,y點(diǎn)轉(zhuǎn)化成畫(huà)布的絕對(duì)坐標(biāo),繪圖
          return true;
          }
          catch (Exception ex) {
          return false;
          }
          }

          public static boolean paint(Graphics g, byte img, int x, int y, int unit) {
          try {
          switch (img) {
          case Images.CAOCAO://畫(huà)曹操
          //變成絕對(duì)坐標(biāo),并做調(diào)整
          g.drawImage(Images.image_Caocao, Images.LEFT + x * unit,
          Images.TOP + y * unit,
          Graphics.TOP | Graphics.LEFT);
          break;
          case Images.GUANYU://畫(huà)關(guān)羽
          g.drawImage(Images.image_Guanyu, Images.LEFT + x * unit,
          Images.TOP + y * unit,
          Graphics.TOP | Graphics.LEFT);
          break;
          case Images.HUANGZHONG://畫(huà)黃忠
          g.drawImage(Images.image_Huangzhong, Images.LEFT + x * unit,
          Images.TOP + y * unit,
          Graphics.TOP | Graphics.LEFT);
          break;
          case Images.MACHAO://畫(huà)馬超
          g.drawImage(Images.image_Machao, Images.LEFT + x * unit,
          Images.TOP + y * unit,
          Graphics.TOP | Graphics.LEFT);
          break;
          case Images.ZHANGFEI://畫(huà)張飛
          g.drawImage(Images.image_Zhangfei, Images.LEFT + x * unit,
          Images.TOP + y * unit,
          Graphics.TOP | Graphics.LEFT);
          break;
          case Images.ZHAOYUN://畫(huà)趙云
          g.drawImage(Images.image_Zhaoyun, Images.LEFT + x * unit,
          Images.TOP + y * unit,
          Graphics.TOP | Graphics.LEFT);
          break;
          case Images.ZU://畫(huà)卒
          g.drawImage(Images.image_Zu, Images.LEFT + x * unit,
          Images.TOP + y * unit,
          Graphics.TOP | Graphics.LEFT);
          break;
          case Images.BLANK://畫(huà)空白
          g.drawImage(Images.image_Blank, Images.LEFT + x * unit,
          Images.TOP + y * unit,
          Graphics.TOP | Graphics.LEFT);
          break;
          case Images.CURSOR://畫(huà)光標(biāo)
          g.drawRect(Images.LEFT + x * unit,
          Images.TOP + y * unit,Images.UNIT,Images.UNIT);
          break;
          }
          return true;
          }catch (Exception ex) {
          return false;
          }
          }
          }

            其中Images類(lèi)存的是繪圖位置常量(也就是在畫(huà)圖時(shí)每個(gè)格子的長(zhǎng)度和相對(duì)坐標(biāo)原點(diǎn)位置要進(jìn)行的調(diào)整)、地圖位置常量(地圖的長(zhǎng)、寬),地圖標(biāo)記常量(人物對(duì)應(yīng)的記號(hào)),地圖組合標(biāo)記常量(后面會(huì)細(xì)說(shuō)),圖片常量(存放人物的圖片);Draw類(lèi)主要負(fù)責(zé)在制定的位置畫(huà)出人物圖片。下面我來(lái)說(shuō)說(shuō)Images類(lèi)中的地圖標(biāo)記常量和地圖組合標(biāo)記常量。為了能夠靈活的安排各個(gè)關(guān)面的布局,我們決定把游戲布局的信息存儲(chǔ)在外部文件中,然后程序啟動(dòng)后把它讀進(jìn)來(lái)。這樣我們制定了一套存儲(chǔ)圖片的代碼,這就是地圖標(biāo)記常量,如上面Images類(lèi)中定義的Caocao(曹操)用a字符來(lái)表示,當(dāng)程序讀到a字符時(shí)就能將它轉(zhuǎn)化成曹操對(duì)應(yīng)的圖片,并在讀到a字符的位置上進(jìn)行顯示。但是從實(shí)際觀察中我們發(fā)現(xiàn)所有的圖片并不是統(tǒng)一大小的,有的占4個(gè)格子,有的占2個(gè)格子,還有的占1個(gè)格子,而且即便同是占兩個(gè)格子的圖片還有橫、豎之分。有鑒于此,我們引入了地圖組合標(biāo)記常量,就是說(shuō)在遇到占有多個(gè)格子的時(shí)候,值1(也就是Images.LEFT)表示它的左邊是一個(gè)真正的地圖標(biāo)記,值2(也就是Images.UP)表示它的上邊是一個(gè)真正的地圖標(biāo)記,值1(也就是Images.LEFTUP)表示它的左上邊是一個(gè)真正的地圖標(biāo)記。地圖組合標(biāo)記常量其實(shí)就是用來(lái)占位置的,與實(shí)際顯示無(wú)關(guān),當(dāng)后面我們將到移動(dòng)時(shí)還會(huì)再來(lái)分析組合標(biāo)記的使用。

            Draw類(lèi)主要是用來(lái)在畫(huà)布上畫(huà)出圖形,它有兩個(gè)paint方法,這是很常見(jiàn)的函數(shù)重載。但是程序中實(shí)際上只用到了4個(gè)參數(shù)的paint方法,它直接獲得要畫(huà)圖片的相對(duì)坐標(biāo)位置信息,然后調(diào)用5個(gè)參數(shù)的paint方法。5個(gè)參數(shù)的paint方法將相對(duì)坐標(biāo)位置信息轉(zhuǎn)換成絕對(duì)位置,并實(shí)際調(diào)用Graphics.drawImage()方法,將Images中的圖片畫(huà)了出來(lái)。這種實(shí)現(xiàn)方法的好處是靈活和便于擴(kuò)展,但你需要畫(huà)圖的位置并不能夠?qū)?yīng)到格子中的相對(duì)坐標(biāo)位置時(shí),你就可以直接調(diào)用5個(gè)參數(shù)的paint方法,而不必再去修改這各類(lèi);但你添加新的圖片時(shí),只要在Images中增加對(duì)應(yīng)的常量,然后向Draw中5個(gè)參數(shù)的paint方法添加一條處理就可以了。
          寫(xiě)到這里,兩天的時(shí)間剛好用完。

            三、需求分析

            這部分叫做需求分析,聽(tīng)起來(lái)挺嚇人的,其實(shí)就是搞清楚我們要做什么,做成什么樣,那些不做。下面我引領(lǐng)著大家共同來(lái)完成這一步驟。首先,我們要做一個(gè)華容道的游戲,華容道的故事這里不再贅述了,但其中的人物在這里限定一下,如上面Images類(lèi)里的定義,我們這個(gè)版本只提供曹操(Caocao)、關(guān)羽(Guanyu)、張飛(Zhangfei)、趙云(Zhaoyun)、黃忠(Huangzhong)、馬超(Machao)和卒(Zu)。我們這里也限定一下游戲的操作方法:首先要通過(guò)方向鍵選擇一個(gè)要移動(dòng)的區(qū)域(就是一張圖片),被選擇的區(qū)域用黑色方框框住;選好后按Fire鍵(就是確定鍵)將這塊區(qū)域選中,被選中的區(qū)域用綠色方框框住;然后選擇要移動(dòng)到的區(qū)域,此時(shí)用紅色方框框住被選擇的區(qū)域;選好要移動(dòng)到的區(qū)域之后按Fire鍵將要移動(dòng)的區(qū)域(圖片)移到要移動(dòng)到的區(qū)域,并去掉綠色和紅色的方框。這里需要強(qiáng)調(diào)的概念有選擇的區(qū)域、選中的區(qū)域、要移動(dòng)的區(qū)域和要移動(dòng)到的區(qū)域,這四個(gè)概念請(qǐng)讀者注意區(qū)分,當(dāng)然也應(yīng)當(dāng)把這一部分記入數(shù)據(jù)字典之中。為了使文章的重點(diǎn)突出(介紹如何制作一個(gè)J2ME的收集游戲),我們這里限定一些與本主題無(wú)關(guān)的內(nèi)容暫不去實(shí)現(xiàn):過(guò)關(guān)之后的動(dòng)畫(huà)(實(shí)現(xiàn)時(shí)要用到TimerTask或Thread類(lèi),后續(xù)的系列文章中我會(huì)詳細(xì)介紹動(dòng)畫(huà)方面的知識(shí))、關(guān)面之間的切換(其實(shí)很簡(jiǎn)單,當(dāng)完成任務(wù)之后重新再做一邊)、暫停和保存等操作(這部分的內(nèi)容介紹的資料很多,我也寫(xiě)不出什么新的東東來(lái),難免抄襲,故此免掉)。

            需求分析基本完成,離下午還有一段時(shí)間,馬上動(dòng)手用ACDSee把從網(wǎng)上找來(lái)的BMP文件,調(diào)整其大小為271*177(我的這個(gè)圖片是兩個(gè)部分合在一起,所以比手機(jī)實(shí)際屏幕大了),另存為PNG格式。半天時(shí)間剛剛好,不但搞清楚了要做的東東,還把要用的圖片準(zhǔn)備好了。

            四、概要設(shè)計(jì)

            概要設(shè)計(jì)是從需求分析過(guò)渡到詳細(xì)設(shè)計(jì)的橋梁和紐帶,這一部分中我們確定項(xiàng)目的實(shí)現(xiàn)方法和模塊的劃分。我們決定將整個(gè)項(xiàng)目分成五個(gè)部分,分別是前面介紹的Images、Draw,還有Map和Displayable1和MIDlet1。Images和Draw類(lèi)功能簡(jiǎn)單、結(jié)構(gòu)固定,因此很多項(xiàng)目我們都使用這兩各類(lèi),這里直接拿來(lái)改改就能用了,前面已經(jīng)介紹過(guò)這里不再贅述。Map類(lèi)是用來(lái)從外部文件讀入地圖,然后保存在一個(gè)數(shù)組之中,這部分的內(nèi)容是我們?cè)诒倦A段討論的重點(diǎn)。Displayable1是一個(gè)繼承了Canvas類(lèi)的畫(huà)布,它用來(lái)處理程序的主要控制邏輯和一部分控制邏輯所需的輔助函數(shù),主要函數(shù)應(yīng)該包括用來(lái)繪圖的paint()函數(shù)、用來(lái)控制操作的keyPressed()函數(shù)、用來(lái)控制選擇區(qū)域的setRange()函數(shù)、用來(lái)控制選擇要移動(dòng)到區(qū)域的setMoveRange()函數(shù)、用來(lái)移動(dòng)選中區(qū)域的Move()函數(shù)和判斷是否完成任務(wù)的win()函數(shù),更具體的分析,我們放到詳細(xì)設(shè)計(jì)中去細(xì)化。MIDlet1實(shí)際上就是一個(gè)控制整個(gè)J2ME應(yīng)用的控制程序,其實(shí)也沒(méi)有什么可特別的,它和我們前面介紹的"Hello World"程序大同小異,這里就不展開(kāi)來(lái)說(shuō)了,后面會(huì)貼出它的全部代碼。

            Map類(lèi)主要應(yīng)該有一個(gè)Grid[][]的二維數(shù)組,用來(lái)存放華容道的地圖,還應(yīng)該有一個(gè)read_map()函數(shù)用來(lái)從外部文件讀取地圖內(nèi)容填充Grid數(shù)據(jù)結(jié)構(gòu),再就是要有一個(gè)draw_map()函數(shù)用來(lái)把Grid數(shù)據(jù)結(jié)構(gòu)中的地圖內(nèi)容轉(zhuǎn)換成圖片顯示出來(lái)(當(dāng)然要調(diào)用Draw類(lèi)的paint方法)。說(shuō)到讀取外部文件,筆者知道有兩種方法:一種是傳統(tǒng)的定義一個(gè)InputStream對(duì)象,然后用getClass().getResourceAsStream()方法取得輸入流,然后再?gòu)妮斎肓髦腥〉猛獠课募膬?nèi)容,例如

          InputStream is = getClass().getResourceAsStream("/filename");
          if (is != null) {
          byte a = (byte) is.read();
          }

            這里請(qǐng)注意文件名中的根路徑是相對(duì)于便以后的class文件放置的位置,而不是源文件(java)。第二種方法是使用onnector.openInputStream方法,然后打開(kāi)的協(xié)議是Resource,但是這種方法筆者反復(fù)嘗試都沒(méi)能調(diào)通,報(bào)告的錯(cuò)誤是缺少Resource協(xié)議,估計(jì)第二種方法用到J2ME的某些擴(kuò)展類(lèi)包,此處不再深究。由于以前已經(jīng)做過(guò)一些類(lèi)似華容道這樣的地圖,這里直接給出Map類(lèi)的代碼,后面就不再詳細(xì)解釋Map類(lèi)了,以便于我們可以集中精力處理Displayable1中的邏輯。Map類(lèi)的代碼如下:

          package huarongroad;

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

          public class Map {
          //處理游戲的地圖,負(fù)責(zé)從外部文件加載地圖數(shù)據(jù),存放地圖數(shù)據(jù),并按照地圖數(shù)據(jù)繪制地圖

          public byte Grid[][];//存放地圖數(shù)據(jù)

          public Map() {//構(gòu)造函數(shù),負(fù)責(zé)初始化地圖數(shù)據(jù)的存儲(chǔ)結(jié)構(gòu)
          this.Grid = new byte[Images.HEIGHT][Images.WIDTH];
          //用二維數(shù)組存放地圖數(shù)據(jù),注意第一維是豎直坐標(biāo),第二維是水平坐標(biāo)
          }

          public int[] read_map(int i) {
          <A href="file://從">file://從</A>外部文件加載地圖數(shù)據(jù),并存放在存儲(chǔ)結(jié)構(gòu)中,返回值是光標(biāo)點(diǎn)的位置
          //參數(shù)是加載地圖文件的等級(jí)
          int[] a = new int[2];//光標(biāo)點(diǎn)的位置,0是水平位置,1是豎直位置
          try {
          InputStream is = getClass().getResourceAsStream(
          "/huarongroad/level".concat(String.valueOf(i)));
          if (is != null) {
          for (int k = 0; k < Images.HEIGHT; k++) {
          for (int j = 0; j < Images.WIDTH; j++) {
          this.Grid[k][j] = (byte) is.read();
          if ( this.Grid[k][j] == Images.CURSOR ) {
          //判斷出光標(biāo)所在位置
          a[0] = j;//光標(biāo)水平位置
          a[1] = k;//光標(biāo)豎直位置
          this.Grid[k][j] = Images.BLANK;//將光標(biāo)位置設(shè)成空白背景
          }
          }
          is.read();//讀取回車(chē)(13),忽略掉
          is.read();//讀取換行(10),忽略掉
          }
          is.close();
          }else {
          //讀取文件失敗
          a[0] = -1;
          a[1] = -1;
          }
          }catch (Exception ex) {
          //打開(kāi)文件失敗
          a[0] = -1;
          a[1] = -1;
          }
          return a;
          }

          public boolean draw_map(Graphics g) {
          //調(diào)用Draw類(lèi)的靜態(tài)方法,繪制地圖
          try {
          for (int i = 0; i < Images.HEIGHT; i++) {
          for (int j = 0; j < Images.WIDTH; j++) {
          Draw.paint(g, this.Grid[i][j], j, i);//繪制地圖
          }
          }
          return true;
          }catch (Exception ex) {
          return false;
          }
          }
          }

            對(duì)于像華容道這樣的小型地圖可以直接用手工來(lái)繪制地圖的內(nèi)容,比如:

          fa1c
          2232
          bd1e
          2gg2
          gihg

            但是,如果遇到像坦克大戰(zhàn)或超級(jí)瑪莉那樣的地圖,就必須另外開(kāi)發(fā)一個(gè)地圖編輯器了(我會(huì)在后續(xù)的文章中介紹用vb來(lái)開(kāi)發(fā)一個(gè)地圖編輯器)。

            五、詳細(xì)設(shè)計(jì)

            詳細(xì)設(shè)計(jì)是程序開(kāi)發(fā)過(guò)程中至關(guān)重要的一個(gè)環(huán)節(jié),好在我們?cè)谇懊娴母鱾€(gè)階段中已經(jīng)搭建好了項(xiàng)目所需的一些工具,現(xiàn)在這個(gè)階段中我們只需集中精力設(shè)計(jì)好Displayable1中的邏輯。(兩天的時(shí)間當(dāng)然不只干這點(diǎn)活,還要把其他幾個(gè)類(lèi)的設(shè)計(jì)修改一下)

            Displayable1這個(gè)類(lèi)負(fù)責(zé)處理程序的控制邏輯。首先,它需要有表示當(dāng)前關(guān)面的變量level、表示當(dāng)前光標(biāo)位置的變量loc、表示要移動(dòng)區(qū)域的變量SelectArea、表示要移動(dòng)到的區(qū)域的變量MoveArea、表示是否已有區(qū)域被選中而準(zhǔn)備移動(dòng)的變量Selected和Map類(lèi)的實(shí)例MyMap。然后,我們根據(jù)用戶按不同的鍵來(lái)處理不同的消息,我們要實(shí)現(xiàn)keyPressed()函數(shù),在函數(shù)中我們處理按鍵的上下左右和選中(Fire),這里的處理需要我展開(kāi)來(lái)講一講,后面我很快會(huì)把這一部分詳細(xì)展開(kāi)。

            接下來(lái),是實(shí)現(xiàn)paint()函數(shù),我們打算在這一部分中反復(fù)的重畫(huà)背景、地圖和選擇區(qū)域,這個(gè)函數(shù)必須處理好區(qū)域被選中之后的畫(huà)筆顏色的切換,具體講就是在沒(méi)有選中任何區(qū)域時(shí)要用黑色畫(huà)筆,當(dāng)選重要移動(dòng)的區(qū)域時(shí)使用綠色畫(huà)筆,當(dāng)選擇要移動(dòng)到的區(qū)域時(shí)改用紅色畫(huà)筆(當(dāng)然附加一張流程圖是必不可少的)。

            再下面要實(shí)現(xiàn)的setRange()函數(shù)和setMoveRange()函數(shù),這兩個(gè)函數(shù)用來(lái)設(shè)置要移動(dòng)的區(qū)域和要移動(dòng)到的區(qū)域,我的思路就是利用前面在Images類(lèi)中介紹過(guò)的地圖組合標(biāo)記常量,當(dāng)移動(dòng)到地圖組合標(biāo)記常量時(shí),根據(jù)該點(diǎn)地圖中的值做逆向變換找到相應(yīng)的地圖標(biāo)記常量,然后設(shè)置相應(yīng)的loc、SelectArea和MoveArea,其中setMoveRange()函數(shù)還用到了一個(gè)輔助函數(shù)isInRange(),isInRange()函數(shù)是用來(lái)判斷給定的點(diǎn)是否在已選中的要移動(dòng)的區(qū)域之內(nèi),如果isInRange()的返回值是假并且該點(diǎn)處的值不是空白就表明要移動(dòng)到的區(qū)域侵犯了其他以被占用的區(qū)域。有了setRange()和setMoveRange()函數(shù),Move()函數(shù)就水到渠成了,Move()函數(shù)將要移動(dòng)的區(qū)域移動(dòng)到要移動(dòng)到的區(qū)域,在移動(dòng)過(guò)程中分為三步進(jìn)行:

            第一.復(fù)制要移動(dòng)的區(qū)域;

            第二.將復(fù)制出的要移動(dòng)區(qū)域復(fù)制到要移動(dòng)到的區(qū)域(這兩步分開(kāi)進(jìn)行的目的是防止在復(fù)制過(guò)程中覆蓋掉要移動(dòng)的區(qū)域);

            第三.用isInRange2()判斷給定的點(diǎn)是否在要移動(dòng)到的區(qū)域內(nèi),將不在要移動(dòng)到的區(qū)域內(nèi)的點(diǎn)設(shè)置成空白。

            下面我們?cè)敿?xì)的分析一下keyPressed()函數(shù)的實(shí)現(xiàn)方法:首先,keyPressed()函數(shù)要處理按鍵的上下左右和選中(Fire),在處理時(shí)需要用Canvas類(lèi)的getGameAction函數(shù)來(lái)將按鍵的鍵值轉(zhuǎn)換成游戲的方向,這樣可以提高游戲的兼容性(因?yàn)椴煌腏2ME實(shí)現(xiàn),其方向鍵的鍵值不一定是相同的)。

            接下來(lái),分別處理四個(gè)方向和選中.當(dāng)按下向上時(shí),先判斷是否已經(jīng)選定了要移動(dòng)的區(qū)域(即this.selected是否為真),如果沒(méi)有選中要移動(dòng)區(qū)域則讓光標(biāo)向上移動(dòng)一格,然后調(diào)用setRange()函數(shù)設(shè)置選擇要移動(dòng)的區(qū)域,再調(diào)用repaint()函數(shù)刷新屏幕,否則如果已經(jīng)選中了要移動(dòng)的區(qū)域,就讓光標(biāo)向上移動(dòng)一格,然后調(diào)用setMoveRange()函數(shù)判斷是否能夠向上移動(dòng)已選中的區(qū)域,如果能移動(dòng)就調(diào)用repaint()函數(shù)刷新屏幕,如果不能移動(dòng)就讓光標(biāo)向下退回到原來(lái)的位置。

            當(dāng)按下向下時(shí),先判斷是否已經(jīng)選定了要移動(dòng)的區(qū)域,如果沒(méi)有選中要移動(dòng)的區(qū)域則判斷當(dāng)前所處的區(qū)域是否為兩個(gè)格高,如果是兩個(gè)格高則向下移動(dòng)兩格,如果是一個(gè)格高則向下移動(dòng)一格,接著再調(diào)用setRange()函數(shù)設(shè)置選擇要移動(dòng)的區(qū)域,而后調(diào)用repaint()函數(shù)刷新屏幕,否則如果已經(jīng)選中了要移動(dòng)的區(qū)域,就讓光標(biāo)向下移動(dòng)一格,然后調(diào)用setMoveRange()函數(shù)判斷是否能夠向下移動(dòng)已選中的區(qū)域,如果能移動(dòng)就調(diào)用repaint()函數(shù)刷新屏幕,如果不能移動(dòng)就讓光標(biāo)向上退回到原來(lái)的位置.按下向左時(shí)情況完全類(lèi)似向上的情況,按下向右時(shí)情況完全類(lèi)似向下的情況,因此這里不再贅述,詳細(xì)情況請(qǐng)參見(jiàn)程序的源代碼。

            當(dāng)按下選中鍵時(shí),先判斷是否已經(jīng)選中了要移動(dòng)的區(qū)域,如果已經(jīng)選中了要移動(dòng)的區(qū)域就調(diào)用Move()函數(shù)完成由要移動(dòng)的區(qū)域到要移動(dòng)到的區(qū)域的移動(dòng)過(guò)程,接著調(diào)用repaint()函數(shù)刷新屏幕,然后將已選擇標(biāo)記置成false,繼續(xù)調(diào)用win()函數(shù)判斷是否完成了任務(wù),否則如果還沒(méi)有選定要移動(dòng)的區(qū)域則再判斷當(dāng)前選中區(qū)域是否為空白,如果不是空白就將選中標(biāo)記置成true,然后刷新屏幕.這里介紹一個(gè)技巧,在開(kāi)發(fā)程序遇到復(fù)雜的邏輯的時(shí)候,可以構(gòu)造一格打印函數(shù)來(lái)將所關(guān)心的數(shù)據(jù)結(jié)構(gòu)打印出來(lái)以利調(diào)試,這里我們就構(gòu)造一個(gè)PrintGrid()函數(shù),這個(gè)函數(shù)純粹是為了調(diào)試之用,效果這得不錯(cuò).至此我們完成了編碼前的全部工作。

            六.編碼

            整個(gè)項(xiàng)目共有五個(gè)類(lèi),有四個(gè)類(lèi)的代碼前面已經(jīng)介紹過(guò)了,而且是在其他項(xiàng)目中使用過(guò)的相對(duì)成熟的代碼.現(xiàn)在只需全力去實(shí)現(xiàn)Displayable1類(lèi).Displayable1類(lèi)的代碼如下:

          package huarongroad;

          import javax.microedition.lcdui.*;

          public class Displayable1 extends Canvas implements CommandListener {

          private int[] loc = new int[2]; <A href="file://光">file://光</A>標(biāo)的當(dāng)前位置,0是水平位置,1是豎直位置
          private int[] SelectArea = new int[4];//被選定的區(qū)域,即要移動(dòng)的區(qū)域
          private int[] MoveArea = new int[4];//要移動(dòng)到的區(qū)域
          private Map MyMap = new Map();//地圖類(lèi)
          private boolean selected;//是否已經(jīng)選中要移動(dòng)區(qū)域的標(biāo)志
          private int level;//但前的關(guān)面
          public Displayable1() {//構(gòu)造函數(shù)
          try {
          jbInit();//JBuilder定義的初始化函數(shù)
          }catch (Exception e) {
          e.printStackTrace();
          }
          }
          private void Init_game(){
          //初始化游戲,讀取地圖,設(shè)置選擇區(qū)域,清空要移動(dòng)到的區(qū)域
          this.loc = MyMap.read_map(this.level);//讀取地圖文件,并返回光標(biāo)的初始位置
          //0為水平位置,1為豎直位置
          this.SelectArea[0] = this.loc[0];//初始化選中的區(qū)域
          this.SelectArea[1] = this.loc[1];
          this.SelectArea[2] = 1;
          this.SelectArea[3] = 1;
          this.MoveArea[0] = -1;//初始化要移動(dòng)到的區(qū)域
          this.MoveArea[1] = -1;
          this.MoveArea[2] = 0;
          this.MoveArea[3] = 0;
          }
          private void jbInit() throws Exception {//JBuilder定義的初始化函數(shù)
          <A href="file://初">file://初</A>始化實(shí)例變量
          this.selected = false;//設(shè)置沒(méi)有被選中的要移動(dòng)區(qū)域
          this.level = 1;
          Images.init();//初始化圖片常量
          Init_game();//初始化游戲,讀取地圖,設(shè)置選擇區(qū)域,清空要移動(dòng)到的區(qū)域
          setCommandListener(this);//添加命令監(jiān)聽(tīng),這是Displayable的實(shí)例方法
          addCommand(new Command("Exit", Command.EXIT, 1));//添加“退出”按鈕
          }

          public void commandAction(Command command, Displayable displayable) {
          //命令處理函數(shù)
          if (command.getCommandType() == Command.EXIT) {//處理“退出”
          MIDlet1.quitApp();
          }
          }

          protected void paint(Graphics g) {
          //畫(huà)圖函數(shù),用于繪制用戶畫(huà)面,即顯示圖片,勾畫(huà)選中區(qū)域和要移動(dòng)到的區(qū)域
          try {
          g.drawImage(Images.image_Frame, 0, 0,
          Graphics.TOP | Graphics.LEFT);//畫(huà)背景
          MyMap.draw_map(g);//按照地圖內(nèi)容畫(huà)圖
          if ( this.selected )
          g.setColor(0,255,0);//如果被選中,改用綠色畫(huà)出被選中的區(qū)域
          g.drawRect(this.SelectArea[0] * Images.UNIT + Images.LEFT,
          this.SelectArea[1] * Images.UNIT + Images.TOP,
          this.SelectArea[2] * Images.UNIT,
          this.SelectArea[3] * Images.UNIT);//畫(huà)出選擇區(qū)域,
          <A href="file://如">file://如</A>果被選中,就用綠色
          <A href="file://否">file://否</A>則,使用黑色
          g.setColor(255,255,255);//恢復(fù)畫(huà)筆顏色
          if (this.selected) {//已經(jīng)選中了要移動(dòng)的區(qū)域
          g.setColor(255, 0, 255);//改用紅色
          g.drawRect(this.MoveArea[0] * Images.UNIT + Images.LEFT,
          this.MoveArea[1] * Images.UNIT + Images.TOP,
          this.MoveArea[2] * Images.UNIT,
          this.MoveArea[3] * Images.UNIT);//畫(huà)出要移動(dòng)到的區(qū)域
          g.setColor(255, 255, 255);//恢復(fù)畫(huà)筆顏色
          }
          }catch (Exception ex) {
          }
          System.out.println(Runtime.getRuntime().freeMemory());
          System.out.println(Runtime.getRuntime().totalMemory());
          }

          private void setRange() {
          //設(shè)置移動(dòng)后能夠選中的區(qū)域
          //調(diào)整當(dāng)前光標(biāo)位置到地圖的主位置,即記錄人物信息的位置
          if (this.MyMap.Grid[this.loc[1]][this.loc[0]] == Images.DLEFT) {
          this.loc[0] -= 1;//向左調(diào)
          }else if (this.MyMap.Grid[this.loc[1]][this.loc[0]] == Images.DUP) {
          this.loc[1] -= 1;//向上調(diào)
          }else if (this.MyMap.Grid[this.loc[1]][this.loc[0]] == Images.DLEFTUP) {
          this.loc[0] -= 1;//向左調(diào)
          this.loc[1] -= 1;//向上調(diào)
          }
          this.SelectArea[0] = this.loc[0];//設(shè)置光標(biāo)的水平位置
          this.SelectArea[1] = this.loc[1];//設(shè)置光標(biāo)的豎直位置
          //設(shè)置光標(biāo)的寬度
          if (this.loc[0] + 1 < Images.WIDTH) {
          this.SelectArea[2] = this.MyMap.Grid[this.loc[1]][this.loc[0] + 1] != (byte) '1' ?
          1 : 2;
          }else {
          this.SelectArea[2] = 1;
          }
          //設(shè)置光標(biāo)的高度
          if (this.loc[1] + 1 < Images.HEIGHT) {
          this.SelectArea[3] = this.MyMap.Grid[this.loc[1] + 1][this.loc[0]] != (byte) '2' ?
          1 : 2;
          }else {
          this.SelectArea[3] = 1;
          }
          }

          private boolean setMoveRange() {
          //設(shè)置要移動(dòng)到的區(qū)域,能夠移動(dòng)返回true,否則返回false
          for (int i = 0; i < this.SelectArea[2]; i++) {
          for (int j = 0; j < this.SelectArea[3]; j++) {
          if (this.loc[1] + j >= Images.HEIGHT ||
          this.loc[0] + i >= Images.WIDTH ||
          (!isInRange(this.loc[0] + i, this.loc[1] + j) &&
          this.MyMap.Grid[this.loc[1] + j][this.loc[0] + i] !=
          Images.BLANK)) {
          return false;
          }
          }
          }
          this.MoveArea[0] = this.loc[0];
          this.MoveArea[1] = this.loc[1];
          this.MoveArea[2] = this.SelectArea[2];
          this.MoveArea[3] = this.SelectArea[3];
          return true;
          }

          private boolean isInRange(int x, int y) {
          //判斷給定的(x,y)點(diǎn)是否在選定區(qū)域之內(nèi),x是水平坐標(biāo),y是豎直坐標(biāo)
          if (x >= this.SelectArea[0] &&
          x < this.SelectArea[0] + this.SelectArea[2] &&
          y >= this.SelectArea[1] &&
          y < this.SelectArea[1] + this.SelectArea[3]) {
          return true;
          }else {
          return false;
          }
          }

          private boolean isInRange2(int x, int y) {
          //判斷給定的(x,y)點(diǎn)是否在要移動(dòng)到的區(qū)域之內(nèi),x是水平坐標(biāo),y是豎直坐標(biāo)
          if (x >= this.MoveArea[0] &&
          x < this.MoveArea[0] + this.MoveArea[2] &&
          y >= this.MoveArea[1] &&
          y < this.MoveArea[1] + this.MoveArea[3]) {
          return true;
          }else {
          return false;
          }
          }

          protected void keyPressed(int keyCode) {
          //處理按下鍵盤(pán)的事件,這是Canvas的實(shí)例方法
          switch (getGameAction(keyCode)) {//將按鍵的值轉(zhuǎn)化成方向常量
          case Canvas.UP://向上
          if (!this.selected) {//還沒(méi)有選定要移動(dòng)的區(qū)域
          if (this.loc[1] - 1 >= 0) {//向上還有移動(dòng)空間
          this.loc[1]--;//向上移動(dòng)一下
          setRange();//設(shè)置光標(biāo)移動(dòng)的區(qū)域,該函數(shù)能將光標(biāo)移動(dòng)到地圖主位置
          repaint();//重新繪圖
          }
          }else {//已經(jīng)選定了要移動(dòng)的區(qū)域
          if (this.loc[1] - 1 >= 0) {//向上還有移動(dòng)空間
          this.loc[1]--;//向上移動(dòng)一下
          if (setMoveRange()) {//能夠移動(dòng),該函數(shù)能夠設(shè)置要移動(dòng)到的區(qū)域
          repaint();//重新繪圖
          }else {//不能移動(dòng)
          this.loc[1]++;//退回來(lái)
          }
          }
          }
          break;
          case Canvas.DOWN://向下
          if (!this.selected) {//還沒(méi)有選定要移動(dòng)的區(qū)域
          if (this.loc[1] + 1 < Images.HEIGHT) {//向下還有移動(dòng)空間
          if (this.MyMap.Grid[this.loc[1] + 1][this.loc[0]] ==
          Images.DUP){//該圖片有兩個(gè)格高
          this.loc[1]++;//向下移動(dòng)一下
          if (this.loc[1] + 1 < Images.HEIGHT) {//向下還有
          <A href="file://移">file://移</A>動(dòng)空間
          this.loc[1]++;//向下移動(dòng)一下
          setRange();//設(shè)置光標(biāo)移動(dòng)的區(qū)域,
          <A href="file://該">file://該</A>函數(shù)能將光標(biāo)移動(dòng)到地圖主位置
          repaint();//重新繪圖
          }else {//向下沒(méi)有移動(dòng)空間
          this.loc[1]--;//退回來(lái)
          }
          }else {//該圖片只有一個(gè)格高
          this.loc[1]++;//向下移動(dòng)一下
          setRange();//設(shè)置光標(biāo)移動(dòng)的區(qū)域,
          <A href="file://該">file://該</A>函數(shù)能將光標(biāo)移動(dòng)到地圖主位置
          repaint();//重新繪圖
          }
          }else {
          }
          }else {//已經(jīng)選定了要移動(dòng)的區(qū)域
          if (this.loc[1] + 1 < Images.HEIGHT) {//向下還有移動(dòng)空間
          this.loc[1]++;//向下移動(dòng)一下
          if (setMoveRange()) {//能夠移動(dòng),該函數(shù)能夠設(shè)置要移動(dòng)到的區(qū)域
          repaint();//重新繪圖
          }else {//不能移動(dòng)
          this.loc[1]--;//退回來(lái)
          }
          }
          }
          break;
          case Canvas.LEFT://向左
          if (!this.selected) {//還沒(méi)有選定要移動(dòng)的區(qū)域
          if (this.loc[0] - 1 >= 0) {//向左還有移動(dòng)空間
          this.loc[0]--;//向左移動(dòng)一下
          setRange();//設(shè)置光標(biāo)移動(dòng)的區(qū)域,該函數(shù)能將光標(biāo)移動(dòng)到地圖主位置
          repaint();//重新繪圖
          }
          }else {//已經(jīng)選定了要移動(dòng)的區(qū)域
          if (this.loc[0] - 1 >= 0) {//向左還有移動(dòng)空間
          this.loc[0]--;//向左移動(dòng)一下
          if (setMoveRange()) {//能夠移動(dòng),該函數(shù)能夠設(shè)置要移動(dòng)到的區(qū)域
          repaint();//重新繪圖
          }else {//不能移動(dòng)
          this.loc[0]++;//退回來(lái)
          }
          }
          }
          break;
          case Canvas.RIGHT://向右
          if (!this.selected) {//還沒(méi)有選定要移動(dòng)的區(qū)域
          if (this.loc[0] + 1 < Images.WIDTH) {//向右還有移動(dòng)空間
          if (this.MyMap.Grid[this.loc[1]][this.loc[0] + 1] ==
          Images.DLEFT) {//該圖片有兩個(gè)格寬
          this.loc[0]++;//向右移動(dòng)一下
          if (this.loc[0] + 1 < Images.WIDTH) {//向右還有
          <A href="file://移">file://移</A>動(dòng)空間
          this.loc[0]++;//向右移動(dòng)一下
          setRange();//設(shè)置光標(biāo)移動(dòng)的區(qū)域,
          <A href="file://該">file://該</A>函數(shù)能將光標(biāo)移動(dòng)到地圖主位置
          repaint();//重新繪圖
          }else {//向右沒(méi)有移動(dòng)空間
          this.loc[0]--;//退回來(lái)
          }
          }else {//該圖片只有一個(gè)格寬
          this.loc[0]++;//向右移動(dòng)一下
          setRange();//設(shè)置光標(biāo)移動(dòng)的區(qū)域,
          <A href="file://該">file://該</A>函數(shù)能將光標(biāo)移動(dòng)到地圖主位置
          repaint();//重新繪圖
          }
          }else {
          }
          }else {//已經(jīng)選定了要移動(dòng)的區(qū)域
          if (this.loc[0] + 1 < Images.WIDTH) {//向右還有移動(dòng)空間
          this.loc[0]++;//向右移動(dòng)一下
          if (setMoveRange()) {//能夠移動(dòng),該函數(shù)能夠設(shè)置要移動(dòng)到的區(qū)域
          repaint();//重新繪圖
          }else {//不能移動(dòng)
          this.loc[0]--;//退回來(lái)
          }
          }
          }
          break;
          case Canvas.FIRE:
          if (this.selected) {//已經(jīng)選定了要移動(dòng)的區(qū)域
          Move();//將要移動(dòng)的區(qū)域移動(dòng)到剛選中的區(qū)域
          repaint();//重新繪圖
          this.selected = false;//清除已選定要移動(dòng)區(qū)域的標(biāo)志
          if ( win()) {
          System.out.println("win");
          }
          }else {//還沒(méi)有選定要移動(dòng)的區(qū)域
          if (this.MyMap.Grid[this.loc[1]][this.loc[0]] ==
          Images.BLANK) {//要移到的位置是一個(gè)空白
          }else {//要移到的位置不是空白
          this.selected = true;//設(shè)置已選定要移動(dòng)區(qū)域的標(biāo)志
          }
          repaint();//重新繪圖
          }
          break;
          }
          }

          private boolean win(){
          <A href="file://判">file://判</A>斷是否已經(jīng)救出了曹操
          if ( this.MyMap.Grid[Images.HEIGHT - 2 ][Images.WIDTH - 3 ] == Images.CAOCAO )
          return true;
          else
          return false;
          }

          private void PrintGrid(String a) {
          <A href="file://打">file://打</A>印當(dāng)前地圖的內(nèi)容,用于調(diào)試
          System.out.println(a);
          for (int i = 0; i < Images.HEIGHT; i++) {
          for (int j = 0; j < Images.WIDTH; j++) {
          System.out.print( (char)this.MyMap.Grid[i][j]);
          }
          System.out.println("");
          }
          }

          private void Move() {
          <A href="file://將">file://將</A>要移動(dòng)的區(qū)域移動(dòng)到剛選中的區(qū)域
          if (this.MoveArea[0] == -1 || this.MoveArea[1] == -1 ||
          this.SelectArea[0] == -1 || this.SelectArea[1] == -1) {//沒(méi)有選中區(qū)域
          }else {//已經(jīng)選中了要移動(dòng)的區(qū)域和要移動(dòng)到的區(qū)域
          byte[][] temp = new byte[this.SelectArea[3]][this.SelectArea[2]];
          <A href="file://復(fù)">file://復(fù)</A>制要移動(dòng)的區(qū)域,因?yàn)檫@塊區(qū)域可能會(huì)被覆蓋掉
          for (int i = 0; i < this.SelectArea[2]; i++) {
          for (int j = 0; j < this.SelectArea[3]; j++) {
          temp[j][i] =
          this.MyMap.Grid[this.SelectArea[1] +j]
          [this.SelectArea[0] + i];
          }
          }
          <A href="file://PrintGrid">file://PrintGrid</A>("1"); // 調(diào)試信息
          <A href="file://將">file://將</A>要移動(dòng)的區(qū)域移動(dòng)到剛選中的區(qū)域(即要移動(dòng)到的區(qū)域)
          for (int i = 0; i < this.SelectArea[2]; i++) {
          for (int j = 0; j < this.SelectArea[3]; j++) {
          this.MyMap.Grid[this.MoveArea[1] + j]
          [this.MoveArea[0] + i] = temp[j][i];
          }
          }
          <A href="file://PrintGrid">file://PrintGrid</A>("2");// 調(diào)試信息
          <A href="file://將">file://將</A>要移動(dòng)的區(qū)域中無(wú)用內(nèi)容置成空白
          for (int i = 0; i < this.SelectArea[3]; i++) {
          for (int j = 0; j < this.SelectArea[2]; j++) {
          if (!isInRange2(this.SelectArea[0] + j,
          this.SelectArea[1] + i)) {//該點(diǎn)是不在要移動(dòng)到
          <A href="file://的">file://的</A>區(qū)域之內(nèi),需置空
          this.MyMap.Grid[this.SelectArea[1] + i]
          [this.SelectArea[0] + j] = Images.BLANK;
          }else {
          }
          }
          }
          <A href="file://PrintGrid">file://PrintGrid</A>("3");// 調(diào)試信息
          this.SelectArea[0] = this.MoveArea[0];//重置選中位置的水平坐標(biāo)
          this.SelectArea[1] = this.MoveArea[1];//重置選中位置的豎直坐標(biāo)
          this.MoveArea[0] = -1;//清空要移動(dòng)到的位置
          this.MoveArea[1] = -1;//清空要移動(dòng)到的位置
          this.MoveArea[2] = 0;//清空要移動(dòng)到的位置
          this.MoveArea[3] = 0;//清空要移動(dòng)到的位置
          }
          }
          }

            代碼的相關(guān)分析,在詳細(xì)設(shè)計(jì)階段已經(jīng)講過(guò),代碼中有比較相近的注釋,請(qǐng)讀者自行研讀分析.將全部的代碼寫(xiě)好,用wtk2.0自帶的Ktoolbar工具建立一個(gè)工程,接下來(lái)把去不源文件放到正確位置下,然后點(diǎn)擊build,再點(diǎn)run,就完成了程序的編寫(xiě).當(dāng)然如果有錯(cuò)誤還要修改和調(diào)試.

            七、測(cè)試

            作為一個(gè)真正的產(chǎn)品要經(jīng)過(guò)單體測(cè)試、結(jié)合測(cè)試和系統(tǒng)測(cè)試。由于項(xiàng)目本身簡(jiǎn)單,而且大部分代碼已經(jīng)是相對(duì)成熟的,我們跳過(guò)單體測(cè)試;又由于筆者的實(shí)際環(huán)境所限,無(wú)法搞到Java手機(jī),無(wú)法架設(shè)OTA服務(wù)器,因此我們也只能放棄系統(tǒng)測(cè)試。那么就讓我們開(kāi)始結(jié)合測(cè)試吧。測(cè)試之前要先出一個(gè)測(cè)試式樣書(shū),也就是測(cè)試的計(jì)劃。我們將它簡(jiǎn)化一下,只測(cè)試如下幾種情況:第一、對(duì)各種形狀的區(qū)域的選擇和移動(dòng);第二、臨近邊界區(qū)域的選擇和移動(dòng);第三、同一區(qū)域的反復(fù)選擇和反復(fù)移動(dòng);第四、非法選擇和非法移動(dòng)。有了測(cè)試的目標(biāo),接下來(lái)的工作就是用wtk2.0自帶的Run MIDP Application工具進(jìn)行測(cè)試。打開(kāi)這個(gè)工具,加載huarongRoad的jad文件,程序就會(huì)自動(dòng)運(yùn)行,選擇launch上MIDlet1這個(gè)程序,華容道游戲就會(huì)躍然屏幕之上,接下來(lái)的工作就是左三點(diǎn).右三點(diǎn),拇指扭扭,來(lái)做測(cè)試。測(cè)試過(guò)程中發(fā)現(xiàn)任何的問(wèn)題,立刻發(fā)一個(gè)bug票給自己,然后就又是痛苦的調(diào)試和修正bug,如此如此。

            八.發(fā)布

            談到發(fā)布,其實(shí)是個(gè)關(guān)鍵,再好的產(chǎn)品不能很好的發(fā)布出去也只是個(gè)產(chǎn)品而已,變不成商品也就得不到回報(bào).由于筆者的條件所限,這里只能是紙上談兵,不過(guò)還是希望能夠使讀者對(duì)這一過(guò)程有所了解(網(wǎng)上的資料也很多)。

            J2ME的程序發(fā)布一般都是通過(guò)OTA(Over The Air),你只需要一臺(tái)有公網(wǎng)IP的主機(jī)和一個(gè)普通的web Server就可以了(盡管要求很低,但筆者還是沒(méi)有),這里我們以apache為例介紹一下OTA服務(wù)的配置,首先是安裝好了apache服務(wù)器,然后在conf目錄下找到mime.types文件,在該文件中加入如下兩行

          application/java-archive jar
          text/vnd.sun.j2me.app-descriptor jad

            然后重起apache服務(wù)器就可以了。接下來(lái)的工作就是修改jad文件中MIDlet-Jar-URL:后面的參數(shù),將它改為URL的絕對(duì)路徑,即<A href="http://***/">http://***/</A>huarongroad.jar(其中***是你的域名或IP地址)。在下面就是用java手機(jī)下載jad文件,它會(huì)自動(dòng)部署相應(yīng)的jar文件并加載它。剩下的工作就和在模擬器上操作是一樣的了。

            九、項(xiàng)目總結(jié)

            至此,我們已經(jīng)完成了一個(gè)J2ME游戲的全部開(kāi)發(fā)過(guò)程,程序中涉及到了調(diào)研、分析、設(shè)計(jì)、編碼、測(cè)試和發(fā)布等方面的問(wèn)題,其實(shí)在實(shí)際的工作中還有很多更為具體的問(wèn)題,畢竟技術(shù)只在軟件開(kāi)發(fā)過(guò)程中占據(jù)很有限的一部分,這里限于篇幅的限制無(wú)法一一具體展開(kāi)。今后,筆者計(jì)劃再寫(xiě)一篇使用J2ME開(kāi)發(fā)手機(jī)屏保的文章,借此機(jī)會(huì)向讀者展示J2ME動(dòng)畫(huà)技術(shù);然后再寫(xiě)一篇J2ME網(wǎng)絡(luò)應(yīng)用的文章,做一個(gè)類(lèi)似開(kāi)心辭典那樣的知識(shí)問(wèn)答游戲,以便向讀者展示J2ME的網(wǎng)絡(luò)技術(shù);待這兩方面的技術(shù)交待清楚之后,我將引領(lǐng)讀者制作一個(gè)稍大一些的游戲。

          posted on 2005-02-17 01:02 藍(lán)色雪焰 閱讀(453) 評(píng)論(0)  編輯  收藏 所屬分類(lèi): 編程技術(shù)
           
          主站蜘蛛池模板: 安宁市| 永年县| 盐池县| 扶沟县| 绿春县| 新田县| 金门县| 寿阳县| 岐山县| 锡林浩特市| 延长县| 山阴县| 宜宾县| 灯塔市| 华蓥市| 马尔康县| 信阳市| 靖边县| 怀远县| 舒城县| 陈巴尔虎旗| 屏边| 女性| 饶阳县| 松阳县| 兴山县| 苍梧县| 吉首市| 封丘县| 巧家县| 南澳县| 甘孜| 封开县| 湘乡市| 武乡县| 昭平县| 和静县| 安化县| 英超| 青阳县| 绵竹市|