J2ME簡明教程( 第七章)

MIDP2.0 Game API入門
一、 Game API結構體系
五個類構成:
GameCanvas繼承自Canvas,具有Canvas提供的所有的功能,在Canvas基礎上增加了便於遊戲設計的功能:
1、 鍵盤事件處理方面:
過去要等keyPressed()/keyRelease()/keyRepeated()被調用之後才能知道按鍵被按下的狀態。而在GameCanvas中提供了getKeyStates()方法,可以在同一個線程中自己檢測按鍵的狀態。某些設備,getKeyStates()可以檢測到很多按鈕同時間被按下的情形。
2、 圖形繪製方法:
提供flushGraphics()方法,相當於過去repaint()再調用serviceRepaints(),而且還帶有雙緩衝區的概念,但flushGraphics並不會產生重繪事件,而是直接將Off-Screen的內容顯示到屏幕上,所以在GameCanvas中,paint()的地位就不像過去那樣重要了。
3、 圖層管理:
利用LayerManager可以實現管理許多圖層的功能,可以方便的將前景與背景混合在同一個畫面之後再輸出到屏幕上。LayerManager中可以有多個Layer子類。
二、 使用GameCanvas
每產生一個GameCanvas子類,其內部就會產生一塊Off-Screen,大小與全屏幕模式的寬高相同。所以,除非必要,不要產生太多的GameCanvas,這樣會佔用太多的內存空間。
CameCanvas的paint()方法默認情況下就是繪出Off-Screen的內容:
public void paint(Graphics g)
{
g.drawImage(offscreen_buffer, 0, 0, 0);
}
所以,一般我們不需要在我們編寫的類中重寫paint()方法。
GameCanvas唯一的構造方法有一個參數,該boolean型參數的意義是:是否抑制鍵盤事件,true抑制,false不抑制。傳入true,系統抑制大多數鍵盤事件的產生,keyPressed()/keyRelease()/keyRepeated()將不會被調用。傳入false,則用戶按下按鈕,就會產生鍵盤事件。
可見,構造方法的第一件事就是super(true)或者super(false)。
GameCanvas中之所以可以選擇抑制鍵盤事件的發生,是因爲我們可以通過getKeyStates()取得按鍵被按下的狀態。
注意:抑制鍵盤事件,只在當前畫面有效。
GameCanvas中,圖形都被繪製到Off-Screen上,而不是直接被繪在屏幕上。程序中調用getGraphics取得的Graphics對象,是屬於Off-Screen的。
繪製好Off-Screen後,可以調用flushGraphics()將Off-Screen的內容繪製到屏幕上。flushGaphics()會等到Off-Screen真正被繪製到屏幕上纔會返回。相當於過去調用repaint()再調用serviceRepaints()的功能,並且帶有雙緩衝概念。但flushGraphics()並不會產生重繪事件,而是直接將Off-Screen的內容顯示到屏幕上,所以,調用flushGraphics()時,並不會調用paint()方法。
如果希望只是重繪Off-Screen的某些部分,可以調用flushGraphics()具有四個參數的重載方法,給定x、y、寬度、高度即可。
使用GameCanvas基本步驟:
1、 import javax.microedition.lcdui.game.* ;
import javax.microedition.lcdui.* ;
2、 extends GameCanvas;
3、 構造方法中調用super(true)或者super(false)選擇抑制或者不抑制鍵盤事件;
4、 獲得Off-Screen的Graphics實例g;
5、 利用Off-Screen的g繪圖至Off-Screen並調用flushGraphics將其顯示。
例:
import javax.microedition.lcdui.game.*;
import javax.microedition.lcdui.*;
//1.extends GameCanvas
public class BaseGameCanvas extends GameCanvas
{
private Graphics g;
public BaseGameCanvas()
{
//2.調用super()選擇是否抑制鍵盤事件
super(true);
//3.獲得Off-Screen的Graphics
g = getGraphics();
//繪圖
render(g);
}
public void render(Graphics g)
{
//4.利用獲取的Off-Screen在其表面繪製圖形
g.setColor(127, 127, 127);
g.fillRect(0, 0, getWidth(), getHeight());
g.setColor(255, 0, 0);
g.drawString("Hello the world!", 10, 50, 0);
//5.將Off-Screen輸出到屏幕
flushGraphics();
}
}
三、 取得鍵盤狀態
GameCanvas中改變了以往等待鍵盤事件發生再決定動作的做法,使用getKeyStates()方法主動查詢鍵盤狀態。
GameCanvas提供的鍵盤碼常量有9個:
常量
功能
UP_PRESSED
向上方向鍵
DOWN_PRESSED
向下
LEFT_PRESSED
向左
RIGHT_PRESSED
向右
FIRE_PRESSED
發射
GAME_A_PRESSED
遊戲A鍵,不是所有設備都支持
GAME_B_PRESSED
遊戲B鍵,不是所有設備都支持
GAME_C_PRESSED
遊戲C鍵,不是所有設備都支持
GAME_D_PRESSED
遊戲D鍵,不是所有設備都支持
其中,UP_PRESSED、DOWN_PRESSED、LEFT_PRESSED、RIGHT_PRESSED及FIRE_PRESSED對應手機鍵盤上的方向鍵及Select鍵或者有的手機對應2、8、4、6
及5鍵。
GAME_A_PRESSED、GAME_B_PRESSED、GAME_C_PRESSED、
GAME_D_PRESSED分別對應鍵盤的1、3、7、9鍵。
在GameCanvas中獲得手機鍵盤碼並進行驗證的方法如下例所示,驗證了鍵盤是否被按下上鍵和下鍵:
int keystate = getKeyStates();
if ((keystate & GameCanvas.UP_PRESSED) != 0)
{
y = y - 2;
}
else if ((keystate & GameCanvas.DOWN_PRESSED) != 0)
{
y = y + 2;
}
由於GameCanvas中鍵盤狀態的獲得並沒有提供鍵盤監聽的方法,僅僅調用getKeyStates()不能夠保證一定能夠監聽到鍵盤是否已經被按下,因此需要一個無限循環語句來監聽鍵盤事件。
while (true)
{
int keystate = getKeyStates();
… …
}
通常該循環會被寫在一個線程中。
例如:
public void run()
{
long startTime = 0;
long endTime = 0;
while (loop)
{
startTime = System.currentTimeMillis();
input();
render(g);
endTime = System.currentTimeMillis();
if ((endTime-startTime) < rate)
{
try
{
Thread.sleep(rate - (endTime-startTime));
}
catch (java.lang.InterruptedException e)
{
}
}
}
}
public void input()
{
int keystate = getKeyStates();
if ((keystate & GameCanvas.UP_PRESSED) != 0)
{
y = y - 2;
}
else if ((keystate & GameCanvas.DOWN_PRESSED) != 0)
{
y = y + 2;
}
}
四、 Sprite
Sprite就是畫面上能夠獨立移動的圖形,是爲了實現遊戲中角色動畫、移動和碰撞檢測而設計的。Game API中提供了Sprite類用來方便的建立Sprite。
Sprite類先根據讀入的圖像在其建立一個Raw Frame數組,另外一個Frame Sequence數組的內容都是畫面的索引值。Current Frame指的是目前屏幕上顯示的畫面。
Sprite開發基礎
分割圖片:爲了便於動畫圖片資源的管理和內存的合理使用,往往把完整動畫的圖像的每一幀都繪製在同一完整的圖片中,所以在遊戲開發的時候需要分割圖片。
Sprite分割圖片的規則:從左到到右,從上到下。
序號的分配:按照分割的順序分配的。
分割的要求:既可以按照正方形來分塊,也可以按照長方形分塊。
Sprite的創建和使用
3個構造方法:
public Sprite(Image image)
public Sprite(Image image, int frameWidth, int frameHeight)
public Sprite(Sprite s)
其中,參數image爲要分割的原始圖像;參數frameWidth,frameHeight分別制定了將以什麼樣的寬度和高度分割原始圖像。
使用步驟:
1) 創建一個用於讀取圖像資源的Image對象;
private Image spriteImage;

try
{
spriteImage = Image.createImage(“/picName.png”);
}
catch (Exception e)
{
}
2) 構造Sprite,可以指定以什麼樣的寬高分割圖像:
private Sprite sprite;

sprite = new Sprite(spriteImage, 32, 32);
3) 成功創建Sprite之後,可以設置圖片分割後動畫播放的順序:
可以使用setFrameSequence()方法設置動畫播放的序列,該播放序列存放在一個一維數組中。
例,指定播放序號爲0,1,2的圖片:
private int seq[] = {0, 1, 2}
sprite.setFrameSequence(seq);
注意:數組索引是從0開始的。
4) Sprite動畫播放數組設置好後,使用paint()方法可以把Sprite的一幀圖像顯示
在屏幕上了,例:
Graphics g;
g = this.getGraphics();
sprite.paint(g);
flushGraphics();
5) 使用sprite.nextFrame()方法可以顯示下一幀圖像,也可以使用setFrame(int index)指定需要播放的圖片。
6) 使用sprite.setPosition(int x, int y)可以改變精靈圖片在屏幕上顯示位置的座標。
五、 封裝Sprite
移動坦克使其在指定區域內移動。
例:
import javax.microedition.lcdui.game.*;
import javax.microedition.lcdui.*;
/**
*
* @author Administrator
*/
public class TankSpriteCanvas extends GameCanvas implements Runnable //3.
{
private Graphics g;
private int rate = 50;
private LayerManager lm;
private Sprite tank;
private int x = 10;
private int y = 10;
private boolean isStopped = false;
public TankSpriteCanvas()
{
super(true);
g = getGraphics();
lm = new LayerManager();
tank = createTank("/res/tank.png");
lm.append(tank);
//render(g);
new Thread(this).start();
}
public Sprite createTank(String pic)
{
try
{
Image tankImg = Image.createImage(pic);
tank = new Sprite(tankImg, 32, 32);
}
catch (Exception e)
{
e.printStackTrace();
}
return tank;
}
public void render(Graphics g)
{
g.setColor(0x00FFFFFF);
g.fillRect(0, 0, getWidth(), getHeight());
//1.
tank.setPosition(x, y);
lm.paint(g, 0, 0);
//2.
g.setColor(0, 0, 0);
g.drawRect(10, 10, 128, 128);
flushGraphics();
}
public void run()
{
long startTime = 0;
long endTime = 0;
while (!isStopped)
{
startTime = System.currentTimeMillis();
input();
render(g);
endTime = System.currentTimeMillis();
if ((endTime-startTime) < rate)
{
try
{
Thread.sleep(rate - (endTime - startTime));
}
catch (Exception e)
{
}
}
}
}
public void input()
{
int keystate = getKeyStates();
if ((keystate & UP_PRESSED) != 0)
{
y = y - 2;
if (y <= 10)
{
y = 10;
}
}
else if ((keystate & GameCanvas.DOWN_PRESSED) != 0)
{
y = y + 2;
if (y >= 138 - tank.getHeight())
{
y = 138 - tank.getHeight();
}
}
else if ((keystate & GameCanvas.LEFT_PRESSED) != 0)
{
x = x - 2;
if (x <= 10)
{
x = 10;
}
}
else if ((keystate & GameCanvas.RIGHT_PRESSED) != 0)
{
x = x + 2;
if (x >= 138 - tank.getWidth())
{
x = 138 - tank.getWidth();
}
}
}
}
可以看到,程序雖然可以運行,但是不夠結構化,許多代碼糾纏在一起,爲了讓程序看起來更加清晰,將程序重構如下:
//TankSprite
import javax.microedition.lcdui.game.*;
import javax.microedition.lcdui.*;
/**
*
* @author Administrator
*/
public class TankSprite extends Sprite
{
private int bx = 0;
private int by = 0;
private int speed = 2;
public TankSprite(Image img, int w, int h, int bx, int by)
{
super(img, w, h);
this.bx = bx;
this.by = by;
}
public void moveUp()
{
move(0, -speed);
if (getY() <= 0)
{
setPosition(getX(), 0);
}
}
public void moveDown()
{
move(0, speed);
if (getY() >= (by - getHeight()))
{
setPosition(getX(), by - getHeight());
}
}
public void moveLeft()
{
move(-speed, 0);
if (getX() <= 0)
{
setPosition(0, getY());
}
}
public void moveRight()
{
move(speed, 0);
if (getX() >= (bx - getWidth()))
{
setPosition((bx - getWidth()), getY());
}
System.out.println("********");
}
}
上述代碼將角色(坦克)封裝成一個獨立的類。
//
import javax.microedition.lcdui.game.*;
import javax.microedition.lcdui.*;
public class TankSpriteCanvas extends GameCanvas implements Runnable //3.
{
private Graphics g;
private int rate = 50;
private LayerManager lm;
private TankSprite tank;
private boolean isStopped = false;
public TankSpriteCanvas()
{
super(true);
g = getGraphics();
lm = new LayerManager();
tank = createTank("/res/tank.png");
lm.append(tank);
//render(g);
new Thread(this).start();
}
public TankSprite createTank(String pic)
{
try
{
Image tankImg = Image.createImage(pic);
tank = new TankSprite(tankImg, 32, 32, 128, 128);
}
catch (Exception e)
{
e.printStackTrace();
}
return tank;
}
public void render(Graphics g)
{
g.setColor(0x00FFFFFF);
g.fillRect(0, 0, getWidth(), getHeight());
//1.
//tank.setPosition(x, y);
lm.paint(g, 10, 10);
//2.
g.setColor(0, 0, 0);
g.drawRect(10, 10, 128, 128);
flushGraphics();
}
public void run()
{
long startTime = 0;
long endTime = 0;
while (!isStopped)
{
startTime = System.currentTimeMillis();
input();
render(g);
endTime = System.currentTimeMillis();
if ((endTime-startTime) < rate)
{
try
{
Thread.sleep(rate - (endTime - startTime));
}
catch (Exception e)
{
}
}
}
}
public void input()
{
int keystate = getKeyStates();
if ((keystate & UP_PRESSED) != 0)
{
tank.moveUp();
}
else if ((keystate & GameCanvas.DOWN_PRESSED) != 0)
{
tank.moveDown();
}
else if ((keystate & GameCanvas.LEFT_PRESSED) != 0)
{
tank.moveLeft();
}
else if ((keystate & GameCanvas.RIGHT_PRESSED) != 0)
{
tank.moveRight();
}
}
}
從修改結果看來,現在的程序有條理多了。
六、 Sprite的繪製
默認情況下,Layer的繪製起點(即Layer左上角的點)爲相對於LayerManager起點(0, 0)的位置,可以用getX()或者getY()取得當前Sprite的繪製起點位置。
Sprite之中引入了一個稱作Reference Pixel的概念,而且每個Sprite默認的Reference Pixel爲座標(0, 0)的位置。可以利用defineReferencePixel()方法來設置Reference Pixel的座標。
Reference Pixel除了可以用來做爲setRefPixelPosition()的參考位置之外,也可以當作setTransform()的參考位置。
可以利用getRefPixelX()/getRefPixelY()來得到參考點實際在LayerManager上的位置。
七、 Sprite的旋轉
我們可以藉助Sprite提供的名爲setTransform()的方法轉動Sprite。轉動的時候以Reference Pixel爲轉動中心。如果我們希望以整張圖片的中心轉動,通常會把Reference Pixel設定在Sprite的中心點(getWidth()/2, getHeight()/2)。
setTransform()方法可以接受的參數有:
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章