J2ME遊戲開發技巧--一個MotoT720下的彩色遊戲

不久前我曾用J2ME開發了一個MotoT720下的彩色遊戲—寶石方塊(GridOne)。開發過程中積累了一些經驗,現在寫出來與大家分享。

  使用雙緩衝避免屏幕閃爍

  雙緩衝技術是編寫J2ME遊戲程序的關鍵技術之一。實際上,雙緩衝技術是計算機動畫的一項傳統技術。造成屏幕閃爍的主要原因在於,畫面在顯示的同時,程序又在改變它,於是畫面閃爍。解決辦法就是在內存中開闢一片區域作爲後臺畫面,程序對它更新,修改,完成後再顯示它。這樣被顯示的圖像永遠是已經完全畫好的圖像,程序修改的將不是正在被顯示的圖像。當然還有其它方法可以解決屏幕閃爍問題,但使用雙緩衝技術是一種值得推薦的解決方案。具體方法可參見如下代碼:


public class BlocksCanvas extends Canvas implements Runnable
{
 Graphics bg;
 Image buf;
 public BlocksCanvas()
 {
  ......
  height = getHeight();
  width = getWidth();
  //按屏幕大小建立緩衝對象
  buf = Image.createImage(width, height);
  //將緩衝對象的Graphics附給bg
  bg = buf.getGraphics();
  ......
 }   
 public void run()
 {......
  for(i=0;i<ROWS;i++)
  {
   for(j=0;j<COLS;j++)
   {//畫方塊
    drawBlock(x,y);
   }
  }
  repaint();
 }
 private void drawBlock(int block_x, int block_y)
 {  
  //取得方塊的座標
  int x = getLeft(block_x);
  int y = getTop(block_y);
  //取得方塊的顏色
  int c= board[block_x][block_y];  
  bg.drawImage(imgs[c], x, y, Graphics.TOP | Graphics.LEFT);
 }

 public void paint(Graphics g)
 {
  g.drawImage(buf, 0, 0, Graphics.TOP | Graphics.LEFT);
 }
}
 


  由上面代碼可見,雙緩衝思想體現在程序上就是要依次完成以下幾步工作:

  1. 定義一個Graphics對象bg和一個Image對象buf,按屏幕大小建立一個緩衝對象附給buf,然後取得buf的Graphics對象附給bg。在這裏,Graphics對象可以理解爲緩衝的屏幕,Image對象則可當成緩衝屏幕上的圖片。

  2. 在bg(緩衝屏幕)上用drawImage()和drawString等語句畫圖,相當於在緩衝屏幕上畫圖。

  3. 調用repaint()語句,它的功能是告知系統調用paint()來完成真實屏幕的顯示。這裏需要注意的是,paint()是一個系統調用語句,不能手工調用,只能通過paint()語句來調用。

  4. 在paint(Graphics g)函數裏,將buf(緩衝屏幕上的圖片)畫到真實屏幕上。

  以上的步驟雖然看似繁瑣,但是效果還是很不錯的。如果想要在屏幕上顯示什麼東西,只管畫在bg上,然後調用repaint()將其顯示出來就可以了。

  編寫自己的斷點函數

 

  圖1 斷點測試

  在開發J2ME程序過程中,最困擾人的問題就是程序容易莫名其妙地死機。當使用JBuilder或者CodeWarrior設置斷點功能來查找程序錯誤時,死機的概率就更大了。即使不死機,也會擔心程序受到了意外的干擾,所以一般不推薦使用開發工具自帶的斷點功能。但有時候又需要一個功能來顯示當前各變量的值,以便查錯時做出正確的判斷。於是我想了一個辦法,就是編寫自己的斷點函數。具體代碼如下:


public class BlocksCanvas extends Canvas implements Runnable
{
 private boolean stopFlag=false;//調試標誌
 ......
 public void run()
 {
  //斷點位置1
  testFun(“x:”+x+“y:”+y);
  ......
  //斷點位置2
  testFun(“”);
  ......
 }
 private void testFun(String str)
 { 
  stopFlag=true;
  //畫一個白色長方形
  bg.setGrayScale(255);
  bg.fillRect(0,0, fontW, fontH);
  //在白色長方形上顯示str的內容
  bg.setGrayScale(0);
  bg.drawString(str, 0,0, Graphics.TOP | Graphics.LEFT);
  repaint();
        
  while(stopFlag){}
 }

 public void keyPressed(int keyCode)
 {  
  stopFlag=false;
 }
}
 


  首先定義一個boolean類型的stopFlag變量來記錄調試標誌。一開始它的值爲false,進入testFun()函數後,值設爲true。顯示完str的內容後,因爲stopFlag的值爲true,所以while語句進入了死循環,程序停了下來。這時可以仔細地看清楚變量的值。然後當按下任意鍵時,keyPressed()函數捕捉到這一事件,將stopFlag設爲false,死循環自動解開。使用此方法非常方便,只要在需要斷點的地方放置testFun()語句即可,一個程序可以放置多個testFun()語句,在循環語句中也可以放置testFun()語句。程序運行到testFun()語句會自動停下顯示變量值,按任意鍵程序又會自動運行,程序也不會受到意外的干擾。圖1是調試時的截圖。

  還有一點需要說明,此方法的testFun()語句必須放在run()函數中或者run()函數運行時調用的函數中,否則就會因爲while()佔用了所有CPU時間而導致keyPressed()函數無法捕捉按鍵事件,最後導致死機。

  此方法只要稍加修改,就可以用做遊戲的暫停功能,而且比sleep()方法好,畢竟理論上sleep()方法不能無限期暫停下去。下面給出相應的代碼:


public class BlocksCanvas extends Canvas implements Runnable
{
 private boolean stopFlag=false;//暫停標誌
 ......
 public void run()
 {......
  testFun();
  ......
 }
 
 private void testFun()
 { 
  while(stopFlag){}
 }

 public void keyPressed(int keyCode)
 {      
  int action = getGameAction(keyCode);
  if(action== FIRE)stopFlag=!stopFlag;
 }
}
 


  該程序段的功能爲,當使用者按下FIRE鍵時,遊戲暫停;再次按下FIRE鍵,遊戲繼續運行。

  編寫自己的工具類

  因爲手機內存和功能的限制,J2ME只提供了部分的J2SE工具類供使用者調用。所以有時我們不得不編寫自己的工具類來實現一些特殊的功能。下面給出的kSet類就類似於J2SE中Set工具類的功能。它用來記錄遊戲中被刪去的方塊集合,同時保證集合中沒有相同元素。


/**
*<p>Description: Set類在J2ME上的實現</p>
*<p>Date:2003.2.28</p>
*<p>Author:TomJava</p>
*<p>email:[email protected]</p>
*/

public class kSet
{//用單鏈表實現
 private kSetNode head;

 public kSet()
 {
  head=null;
 }
 //將kSet清空
 public void clear()
 {
  head=null;
 }
 //向kSet中添加元素                   
 public boolean add(int x,int y)
 {
  kSetNode node=new kSetNode(x,y);
  return add(node);
 }
 //向kSet中添加元素
 public boolean add(kSetNode node)
 {
  if(!contains(node))
  { 
   node.next=head;
   head=node;
   return true;
  }else
  {
   return false;
  }
 }
 //判斷kSet是否爲空
 public boolean isEmpty()
 {
  if(head==null)
   return true;
  else
   return false;
 }
 //摘下鏈表頭元素並返回此元素
 public kSetNode getFirst()
 { 
  kSetNode p=head;
  head=p.next;
  return p;
 }
 //遍歷kSet,如果有相同元素返回true,否則返回false   
 public boolean contains(kSetNode node)
 {
  kSetNode p = head;
  while (p != null) {
   if(p.equals(node))return true;
   p=p.next;  
  }
  return false;
 }
}
//kSet中的元素
public class kSetNode
{
 public int x,y;
 public kSetNode next;
 
 public kSetNode(int x,int y)
 {
  this.x=x;
  this.y=y;
  next=null;
 }
     
 public boolean equals(kSetNode node)
 {
  if(node.x==x&&node.y==y)
   return true;
  else
   return false;
 }
 
 public int getX()
 {
  return x;
 }
      
 public int getY()
 {
  return y;
 }
}
 


  kSetNode類負責記錄被刪除方塊的座標,它重載equals()方法用來判斷兩個方塊是否是同一個方塊。kSet類是由kSetNode對象組成的沒有相同元素的集合,用單鏈表實現,並且提供了 getFirst()、add()、clear()、isEmpty()、contains()等方法供其它類調用。編寫和使用一些這樣的工具類,將大大加快編程的速度,也使程序變得更加清晰。

  矯正屏幕座標

  GridOne這個遊戲是專門爲MotoT720開發的,也就是說遊戲背景圖片大小和MotoT720型手機的大小是相等的。如果它在那些屏幕比MotoT720大的手機上運行,遊戲背景圖片會顯示在屏幕左上角而影響美觀,這時就要用到屏幕矯正技術,使得遊戲背景圖片居中顯示。矯正屏幕座標代碼如下:


public class BlocksCanvas extends Canvas implements Runnable
{
 private  final int addX;//座標矯正
 private  final int addY;
 private  final int SCREEN_X;//屏幕頂點
 private  final int SCREEN_Y;
 private  final int WAITBLOCK_X;//等待方塊頂點
 private  final int WAITBLOCK_Y;
 private  final int SCORES_X;//分數頂點
 private  final int SCORES_Y;
 private  final int STAR_X;//五角星的頂點
 private  final int STAR_Y;

 public BlocksCanvas()
 {
  //取得當前手機屏幕的高度和寬度
  height = getHeight();
  width = getWidth();

  //座標矯正量 
  addX = (width-120)/2;
  addY = (height-142)/2;

  //初始化屏幕參數
  SCREEN_X = addX + 48;//屏幕頂點
  SCREEN_Y = addY + 10;
  WAITBLOCK_X = addX + 19;//等待方塊頂點
  WAITBLOCK_Y = addY + 103;
  SCORES_X = addX + 36;//分數頂點
  SCORES_Y = addY + 34;
  STAR_X=addX+4;//五角星的頂點
  STAR_Y=addY+70;      
 }
}
 


  首先把所有有關屏幕的參數都定義成private final int型變量。這裏之所以加上final 修飾符,是因爲不希望變量附初值後,它們的值會發生變化;之所以不加static修飾符,是因爲要在其函數中初始化變量,而不是在定義時就初始化好了。先用getHeight()和getWidth()函數取得當前手機屏幕的高度和寬度,再計算出需要的偏移量addX和addY,然後加到各屏幕參數上,這樣遊戲內容就會居中顯示了。圖2與圖3是效果比較圖。

 

  圖2 矯正前

 

  圖3 矯正後

  還有一點需要注意,用getHeight()取得的並不是手機屏幕的真實高度,而是手機屏幕的高度減去Command標籤高度,因爲屏幕需要留出地方顯示Command標籤。

  合理使用內存

  本來使用Java編程是不需要關心內存使用的,因爲Java有它引以爲豪的垃圾處理機制。但到了J2ME裏,情況發生了變化,因爲手機的內存只有屈指可數的幾百K,再也不能像在J2SE裏那樣大手大腳了。否則就會發現,即使程序沒有任何語法和邏輯錯誤,也不能在模擬器中運行。下面給出合理使用內存的幾個建議:

  1. 儘可能使用本地變量代替類成員,減少對象的創建,最好能重新利用對象;

  2. 不要試圖在初始化的時候把所有Form或者Canvas對象都讀入內存中,而應該在需要的時候再創建,雖然這樣在顯示上會有一些延遲,但是總比程序不能運行或者內存溢出要好;

  3. 一旦對象不需要使用就及時將其置爲null,以便能夠被垃圾處理器回收,適當的時候調用System.gc()語句提示虛擬機調用垃圾處理器;

  4.必須記住Java的內存管理是有向邊機制,所以對於不使用的對象,千萬不要讓正在使用的對象指向它,以免內存得不到回收;

  5.儘量使圖片佔有的字節數小一點,可以使用Fireworks在保證圖片質量同時減小圖片的大小;

  即使做到上面幾點,也不能保證程序不會發生內存泄漏,因爲手機內存畢竟那麼少。所以我提出最後一個建議,就是先完成遊戲的主體部分,使它能夠正常運行並沒有內存泄漏,再慢慢擴展遊戲,給它加上封面和其它功能。一旦發現內存不足,再去掉部分功能。

  使遊戲更有魅力

  編寫遊戲當然希望它能吸引人,我覺得下面幾個地方值得大家注意:

  1. 注意控制遊戲的節奏

  原來我在削除方塊的時候,什麼都不做就直接刪除,然後開始一個新的循環,讓等待的方塊往下掉。在實際運行的時候感覺效果不是很好,因爲削得越快,上面的方塊也掉得越快,讓遊戲者有一種措手不及的感覺。後來我在刪除方塊的時候,用空循環停頓了幾秒鐘,這樣就給了遊戲者一個反應時間,感覺就好些了,而且如果連削的話,反應時間會更長,這樣使得遊戲者在玩遊戲時有一種輕鬆的感覺。再後來,我將空循環改成對屏幕上的方塊遍歷三次,讓被刪的方塊閃爍,使得遊戲者能夠看清楚被刪除的方塊,欣賞到自己的成果,這樣又增加了遊戲的吸引力。

  2. 利用圖片實現豐富多彩的表達效果

  在J2ME中如果不能控制文字的大小和字體,那麼這將使遊戲的效果大打折扣。不過,可以通過把各種特殊的文字做成圖片的方法來解決這個問題。有些地方我們也可以用圖片來取代文字,使得遊戲更加生動,比如在等級欄用五角星來表示遊戲的難度。使用圖片還有一個好處就是增加遊戲的通用性,使得遊戲在不同手機上的顯示基本相同。另外,如果出現字體顏色在模擬器中顯示正常,而在手機上顯示不正常的情況,也可以用這種方式解決。

  3. 讓遊戲能夠自動調整難度

  我使用如下函數,使得遊戲難度不斷加大。


private void giveLevel()
{
 if(level<=10)
 { 
  levelSleep=600-(level - 1) * 50;
  if(levelSleep<200)
   levelSleep=200;
  levelDelTask=200+(level-1)*100;
  if(levelDelTask>1000)
   levelDelTask=1000;
  //畫等級--五角星
  paintStar();
 }else
 {
  gameWin();
 }
}
 


  level表示遊戲等級,levelSleep表示方塊下落一個的等待時間,levelDelTask表示過一關需要刪除的方塊數,也可以理解爲過一關需要完成的任務。上面的計算公式可以保證遊戲會越來越難,增加了遊戲的吸引力。

 

發佈了8 篇原創文章 · 獲贊 0 · 訪問量 10萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章