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()方法可以接受的参数有:
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
教程首先介绍了 j2me 开发体系,然后深入各个MIDP2.0 API,最后是搭建平台的知识。 第一章 “J2ME 技术概述”让你在学习J2ME 以前知道什么是J2ME。本章介绍了J2ME 平 台的体系结构和MIDlet 生命周期的概念。为以后的内容打下良好的基础。 第二章“CLDC 简介”介绍了MIDP 的基础Java Community Process(JCP)公布的CLDC1.0 规范(即JSR30)。有了这些知识你就可以顺利的从j2se 的基础API 过渡到MIDP 的基础API 上了。 第三章“MIDP 高级UI 的使用”介绍了MIDP 的可移植UI API,我们称之为高级UI。这 样您的应用就可以栩栩如生了。 第四章“MIDP 低级UI 的使用” 介绍了MIDP 的不可移植UI API,我们称之为低级UI。 利用他你可以更加自由的绘画你的UI。你将了解到关于事件处理的很多知识。 第五章“MIDP 的持久化解决方案— RMS” 为我们讲解了数据持久化机制——记录管理 系统(Record Management System RMS)。这一特别的小型数据库使得MIDP 的数据保存变得很特 别。 第六章“GAME API” 介绍了 MIDP 2.0 相对于1.0 来说,最大的变化——新添加的用于支 持游戏的API,它们被放在javax.microedition.lcdui.game 包中。游戏API 包提供了一系列针对无 线设备的游戏开发类。你可以开发你的游戏了。COOL! 第七章“开发无线网络应用程序” 让我们学习如何开发令人激动的联网应用。无线网络在 当今的技术下与有线网络相比它的带宽更小、延迟更大、连接的稳定性更差。这要求我们在开 发无线联网应用程序时,和以往有很大不同。 第八章“MIDP 2.0 安全体系结构” 将主要介绍MIDP 的安全体系模型,并结合一个具体的 实例来讲述MIDP2.0 安全模型的主要概念。 第九章“MIDP 2.0 Push 技术”介绍了如何通过异步方式将信息传送给设备并自动启动 J2ME 中文教程 by www.j2medev.com MIDlet 程序的机制。 第十章“MIDlet 的开发流程与部署”介绍了如何真正完成你的程序并打包发往设备运行。 第十一章“搭建开发平台—WTK”主要讲述J2ME 新手最常使用的开发工具Wireless Toolkit (WTK)。从WTK 的安装、到MIDlet 项目的创建、以及最后的打包发布,一步步带领读者进 入MIDlet 的开发世界! 第十二章“搭建开发平台—Eclipse”讲述了如何利用EclipseME 作为Eclipse 一个插件,帮 助开发者开发J2ME 应用程序。 第十三章“搭建开发平台—JBuilder”介绍了如何利用久负盛名的JBuilder 作为开发工具来 开发J2ME 应用程序。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值