用GameAPI函式製作二維動作遊戲(轉)

post0發表於2007-08-12
用GameAPI函式製作二維動作遊戲(轉)[@more@]

  MIDP 2.0裡面包括一個用來簡化編寫二維遊戲的API函式。這個API函式是非常簡湊的,只包括javax.microedition.lcdui.game包裡的五個類。這五個類主要提供了兩個重要的功能:

  ◆ 新的GameCanvas類使得在一個遊戲迴圈體內畫一個screen和響應鍵盤輸入成為可能,而不需要呼叫系統的paint和input執行緒。

  ◆ 功能強大而複雜的圖層(layer)API函式可以輕鬆高效地建立複雜的場景。

  muTank Example

  利用GameCanvas類建立一個遊戲迴圈(game loop)

  GameCanvas類是附加了功能的Canvas類,它提供了立即重畫和檢查裝置按鍵狀態的方法。這些新的方法把一個遊戲的所有函式(功能)封裝在一個迴圈體內,並由一個單執行緒進行控制。為什麼這樣做就非常吸引人阿?先讓我們考慮一下你是如何執行一個使用了Canvas類的典型遊戲的:

  public void MicroTankCanvas

  extends Canvas

  implements Runnable {

  public void run() {

  while (true) {

  // Update the game state.

  repaint();

  // Delay one time step.

  }

  }

  public void paint(Graphics g) {

  // Painting code goes here.

  }

  protected void keyPressed(int keyCode) {

  // Respond to key presses here.

  }

  }

  這不是一個美麗的畫面 。執行在應用程式執行緒中的run()方法,每一個時間段都會重新整理遊戲。典型的任務是重新整理小球或飛行物的位置,繪製人物或飛行器動畫。每一次透過迴圈體,repaint()方法被用來重新整理螢幕。系統把按鍵事件傳送給KeyPressed(),它能適當地重新整理遊戲狀態。

  問題是,每樣東西都在不同的執行緒裡,遊戲程式碼在以上三種不同方法裡傳遞很容易混淆。當run()方法裡的主動畫迴圈體呼叫repaint()方法時,將沒有辦法確切知道系統什麼時候呼叫paint()方法。當系統呼叫KeyPressed()時,也沒有辦法知道程式的另一部分正在進行什麼。如果你KeyPressed()中的程式碼將要重新整理遊戲的狀態,而同一時刻paint()方法將表現螢幕,這時螢幕將會持續非常奇怪的狀態。如果表現螢幕所用時間超過一個單時間段,動畫會看起來顛簸不定或是很奇怪。

  GameCanvas類允許你避開常用繪畫(painting)和按鍵訊息(key-event)機制,所以所有的遊戲邏輯都可以被包括在一個單迴圈中。首先,GameCanvas類允許你用getGraphics()方法直接訪問Graphics物件。對於所返回的Graphics物件的任何表現(rendering)都可以透過螢幕外緩衝區(offscreen buffer)來實現。你可以用flushGraphics()複製緩衝區到螢幕上,直到螢幕被重新整理才會返回。這種方式給你提供比呼叫repaint()方法更完善的控制。Repaint()方法會立即返回值,以至於你的應用程式不能確定系統什麼時候會呼叫paint()來重新整理螢幕。

  GameCanvas類也包含一個用來獲得裝置按鍵當前狀態的方法,即所謂得polling技術。你可以透過呼叫GameCanvas類的getKeyStates()方法,馬上確定哪一個按鍵被按下,從而取代了等待系統呼叫KeyPressed()方法。

  下面是一個使用GameCanvas類的典型的遊戲迴圈體:

  public void MicroTankCanvas

  extends GameCanvas

  implements Runnable {

  public void run() {

  Graphics g = getGraphics();

  while (true) {

  // Update the game state.

  int keyState = getKeyStates();

  // Respond to key presses here.

  // Painting code goes here.

  flushGraphics();

  // Delay one time step.

  }

  }

  }

  接下來的例子描述了一個基本的遊戲迴圈體。它向你展現了一個旋轉的“X”,你可以用方向鍵在螢幕上移動它。這裡的Run()方法特別的瘦小,這要多虧了GameCanvas。

  import javax.microedition.lcdui.*;

  import javax.microedition.lcdui.game.*;

  public class SimpleGameCanvas

  extends GameCanvas

  implements Runnable {

  private boolean mTrucking;

  private long mFrameDelay;

  private int mX, mY;

  private int mState;

  public SimpleGameCanvas() {

  super(true);

  mX = getWidth() / 2;

  mY = getHeight() / 2;

  mState = 0;

  mFrameDelay = 20;

  }

  public void start() {

  mTrucking = true;

  Thread t = new Thread(this);

  t.start();

  }

  public void stop() { mTrucking = false; }

  public void run() {

  Graphics g = getGraphics();

  while (mTrucking == true) {

  tick();

  input();

  render(g);

  try { Thread.sleep(mFrameDelay); }

  catch (InterruptedException ie) {}

  }

  }

  private void tick() {

  mState = (mState + 1) % 20;

  }

  private void input() {

  int keyStates = getKeyStates();

  if ((keyStates & LEFT_PRESSED) != 0)

  mX = Math.max(0, mX - 1);

  if ((keyStates & RIGHT_PRESSED) != 0)

  mX = Math.min(getWidth(), mX + 1);

  if ((keyStates & UP_PRESSED) != 0)

  mY = Math.max(0, mY - 1);

  if ((keyStates & DOWN_PRESSED) != 0)

  mY = Math.min(getHeight(), mY + 1);

  }

  private void render(Graphics g) {

  g.setColor(0xffffff);

  g.fillRect(0, 0, getWidth(), getHeight());

  g.setColor(0x0000ff);

  g.drawLine(mX, mY, mX - 10 + mState, mY - 10);

  g.drawLine(mX, mY, mX + 10, mY - 10 + mState);

  g.drawLine(mX, mY, mX + 10 - mState, mY + 10);

  g.drawLine(mX, mY, mX - 10, mY + 10 - mState);

  flushGraphics();

  }

  }

  本文所舉示例的程式碼包括一個使用了這個canvas的MIDlet。你可以嘗試著執行SimpleGameMIDlet這個小程式,看看它是怎樣工作的。你將會看到一個像正在做健身操的海星的東西(或許它正在尋找自己失掉的腿)。

  SimpleGameMIDlet Screen Shot

  遊戲場景就像是洋蔥(有層次)

  典型的二維動作遊戲常包含一個背景和若干動畫人物。儘管你可以自己來描繪出這種場景,不過Game API函式使你能夠用圖層來建立場景。你可以做一個城市的背景圖層,另外再做一個含有一輛小汽車的圖層。將小汽車圖層放在背景上,你就創造出了一個完整的場景。把小汽車放在一個單獨的圖層中,可以很容易的熟練操控它,而不受背景和其他圖層的影響。

  Game API函式使用以下四個類為圖層提供靈活的支援

  Layer類是所有圖層類物件的抽象基類。它定義了一個圖層的基本屬性,包括位置,尺寸,和此圖層是否可見。Layer類的每個子類必須定義一個paint()方法,用來把這個圖層表現在一個圖象上,這個圖象將會被描畫到螢幕表面上。兩個確切的子類TiledLayer和Sprite應該能滿足你的二維遊戲的需要了。

  TiledLayer類用來建立背景影像。你可以用一個小的源影像貼的集合來高效的製作大的影像。

  Sprite類是一個動畫層。你提供源幀就可以對整個動畫進行完全的控制。Sprite類也提供映象,並可對源幀作90度旋轉。

  LayerManager類是一個非常有用的類,用來儲存你的場景中的所有圖層的動作軌跡。LayerManager類 paint()方法的一個簡單呼叫就足以控制所包含的所有圖層。

  使用TiledLayer類

  儘管包含一些不是顯而易見的微妙不同,TiledLayer類還是很容易理解。這個類的基本思想就是,用一個源影像提供一組影像貼片,這些貼片可以組合成一幅大的場景。例如,下面的影像是64*48畫素的。

  Source Image

  這個影像被分成了12塊16*16的影像貼片。TiledLayer類分配給每個影像貼片編號,左上角的圖片規定為1,以此類推。上面源影像的各個貼片如下編號:

  Tile Numbering

  用程式碼建立一個TiledLayer類是非常簡單的。你需要確定行數和列數,源影像以及這個源影像裡每個貼片的畫素大小。下面的程式碼片斷告訴你如何裝載影像和建立TiledLayer類。

  Image image = Image.createImage("/board.png");

  TiledLayer tiledLayer = new TiledLayer(10, 10, image, 16, 16);

  在例子中,新的TiledLayer類有10行,10列。這些來自image的影像貼片大小是16*16畫素。

  有趣的部分還是用這些影像貼片來建立一幕場景。利用setCell()方法可以把一個影像貼片分配到一個陣列元胞裡。你需要提供這個陣列元胞所在行列數以及影像貼片的編號。例如,你可以透過呼叫setCelll(2,1,5)方法把編號為5的影像貼片分配到第2行中的第3個陣列元胞裡。如果你覺得這些引數看起來不對,請注意,影像貼片編號是從1開始計數,而行和列的編號是從0開始的。引數預設情況下,新的TiledLayer類物件中的所有陣列元胞的影像貼片標號為0,這就意味著它們是空的。

  下面的程式碼片斷向你說明一種使用整數陣列來填充TiledLayer類物件。在實際影像中,TiledLayer類可以從資原始檔裡定義,這就使得定義背景時可以有更多的靈活性,並能提供新的背景和級別來增強遊戲的可玩性。

  private TiledLayer createBoard() {

  Image image = null;

  try { image = Image.createImage("/board.png"); }

  catch (IOException ioe) { return null; }

  TiledLayer tiledLayer = new TiledLayer(10, 10, image, 16, 16);

  int[] map = {

  1, 1, 1, 1, 11, 0, 0, 0, 0, 0,

  0, 0, 0, 0, 0, 0, 0, 0, 0, 0,

  0, 0, 0, 0, 0, 0, 0, 0, 0, 0,

  0, 0, 0, 0, 9, 0, 0, 0, 0, 0,

來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/8225414/viewspace-951667/,如需轉載,請註明出處,否則將追究法律責任。

相關文章