J2ME简明教程( 第七章)

来源:互联网 发布:python idle64位下载 编辑:程序博客网 时间:2024/06/05 04:03
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()方法可以接受的参数有: