Sprite 的碰撞判断
![]() | 上一讲咱们制作了火箭在飞来飞去的陨石中前讲的程序,这一讲来制作稍微有意思的游戏吧。 |
![]() | 上讲的程序,火箭也没有和陨石相撞什么的。 |
![]() | 这个,是啊。 那首先来介绍一下火箭与陨石相撞的判断方法吧。 |
![](http://www.nec-mfriend.com/cn/lecture/images/t6_1.jpg)
1.Sprite 类的碰撞判断方法
![]() | 不过以前的MIDP2.0并没有Sprite类,还记得怎么进行碰撞判断的么? |
![]() | 呃,应该是通过计算角色的坐标值与大小,判断两个物体是否发生碰撞吧。 |
![]() | 呵呵,那先复习一下Java初级讲座第11讲的内容吧,通过计算车的大小判断是否碰撞。 ( http://www.nec-mfriend.com/cn/lecture/lecture_ca11_1.php ) |
![]() | 使用它可以使计算变的简单么? |
![]() | 呵呵,使用它就不用计算了。只需把要进行碰撞判断的其他的Sprite对象传递到方法的第一个参量,从返回值判断有无碰撞了。发生碰撞的返回值为true ,相反则为False 。 |
![]() | 好简单啊。那第二个参量pixelLevel是什么意思呢? |
![]() | 第二个参量,通过像素单位判断碰撞的发生。返回值为fales时,只是单纯的根据Sprite外形的四角形进行判断,返回值是true时,就要利用像素单位进行判断了。 |
![]() | 像素单位是什么意思呢? |
![]() | 来参看下面的图吧。(a)清楚显示没有相撞,( b )显示图像外面的四边形重叠在一起。 CollidesWith方法第二个自变量中指定false,(b)似乎并没有发生碰撞,而指定true时如c所示,实际上只是指角色图像发生碰撞的情况。指定的是哪一种情况,最好以判断的精确度和处理速度的优先度来判断。 |
![](http://www.nec-mfriend.com/cn/lecture/images/t8_1.jpg)
![]() | 下面咱们使用Sprite类的碰撞判断功能,将火箭与陨石碰撞时的背景设置为红色吧。 |
![]() | 使用前一讲的代码是吧? |
![]() | 嗯,继承MIDlet的GameCanvasTest.java和继承Sprite的MySprite.java已经讲过了。只需要修改MyGameCanvas就行了。 |
![]() | 首先,添加boolean型变量到member变量,显示火箭与陨石是否相撞。将下面一行添加到MyGameCanvas类的定义中。 |
private boolean colliding = false; // 显示火箭与陨石是否相撞的flag |
![]() | 接着,在每经过一定时间进行的处理tick方法中添加判断碰撞的处理。 |
/** |
![]() | 最后,描绘时通过colliding值改变背景色,就完成了。将draw做如下变更。 |
/** * 描绘的更新 */ private void draw(Graphics g) { if(colliding) { g.setColor(0xff0000); // 设定描绘色为红色 } else { g.setColor(0xffffff); // 设定描绘色为白色 } g.fillRect(0, 0, getWidth(), getHeight()); layerManager.paint(g, 0, 0); flushGraphics(); } |
![]() | 好的,做好了,执行一下试试吧。 |
![](http://www.nec-mfriend.com/cn/lecture/images/t8_2.jpg)
![]() | 使用 CollidesWith 方法 ,碰撞判断就变的很简单了吧。 |
![]() | 是的。 |
2.整理游戏的题材
![]() | 博士,我终于学会碰撞判断了,好想试试制作简单的游戏啊。 |
![]() | 呵呵。下面我们先来整理一下游戏的题材吧。 做为练习程序,试着遵守下面的简单规则。 |
■ 游戏的 rule
- 操作火箭,避开陨石
- 撞到陨石则游戏结束
- 每前进 1step 增加 1 分
- 画面中只显示一个星星,与陨石混在一起
- 抓到星星增加100 分
- 每增加 100 分,陨石个数增加1(最多为 10 个)
![]() | 继续来看画面的迁移吧。这次用三个画面简单的说明一下,分别是开始画面、结束画面和游戏进行画面。请参见下图。 |
![](http://www.nec-mfriend.com/cn/lecture/images/t8_3.jpg)
![]() | 好有游戏的感觉啊。 |
![]() | 下面就修改 一下source code试试吧。首先,定义MIDlet为RocketGame.java 。与之前学习的GameCanvasTest.java基本上相同,请参加下面的代码。 |
RocketGame.java
1 | import javax.microedition.lcdui.*; | ||
2 | import javax.microedition.midlet.MIDlet; | ||
3 | |||
4 | public class RocketGame extends MIDlet implements CommandListener { | ||
5 | private Display display; | ||
6 | private MyGameCanvas gameCanvas; | ||
7 | |||
8 | public RocketGame() { | ||
9 | display = Display.getDisplay(this); | ||
10 | } | ||
11 | |||
12 | public void startApp() { | ||
13 | try { | ||
14 | gameCanvas = new MyGameCanvas(); | ||
15 | gameCanvas.addCommand(new Command("Exit", Command.EXIT, 0)); | ||
16 | gameCanvas.setCommandListener(this); | ||
17 | display.setCurrent(gameCanvas); | ||
18 | } catch (Exception e) { | ||
19 | System.out.println(e); | ||
20 | } | ||
21 | } | ||
22 | |||
23 | public void pauseApp() {} | ||
24 | |||
25 | public void destroyApp(boolean unconditional) { | ||
26 | if (gameCanvas != null) { | ||
27 | gameCanvas.stop(); | ||
28 | } | ||
29 | } | ||
30 | |||
31 | public void commandAction(Command c, Displayable s) { | ||
32 | if (c.getCommandType() == Command.EXIT) { | ||
33 | destroyApp(true); | ||
34 | notifyDestroyed(); | ||
35 | } | ||
36 | } | ||
37 | } | ||
![]() | MySprite.java没有要修改的部分,就使用上一讲的就可以了。 接下来,在 MyGameCanvas.java 里面添加一些新的游戏功能。 首先准备显示游戏状态的常量,如下所示定义容纳其值的gameStatus变量。 根据gameStatus的值,切换对应的画面内容。 |
private final int GAME_OPENING = 0; // 显示游戏状态的常量 : Opening private final int GAME_PLAYING = 1; // 显示游戏状态的常量 : 游戏进行中 private final int GAME_OVER = 2; // 显示游戏状态的常量 : 游戏结束 private int gameStatus = GAME_OPENING; // 游戏的状态 |
![]() | 另外,还有新添加一些MyGameCanvas类的member 变量,如下所示。 |
private final int MAX_STONE_NUM = 10; // 画面上一次显示的陨石数量的最大值 private MySprite star; // 星星的 Sprite private int score; // 得分 private int width; // Canvas的宽度 private int height; // Canvas的高度 Random rand; // 生成 Random Number的Random对象 private boolean keyReleased = true; // 显示key button没被按下的状态的flag |
![]() | 然后,利用MyGameCanvas的constructor ,生成本讲新添加的“星星”的Sprite 对象。 与火箭的生成类似。需要提前准备星星的图像 star.png 。 |
// 作成星星用的MySprite对象 |
![]() | 游戏开始时调出的start方法,进行陨石、火箭和星星等的初始设定。省略与上一讲相同的处理,如下所示。 |
/** // 星星的设定 // 设定火箭的初始位置与移动方向 |
![]() | 火箭在捕获星星时需要行使星星的初始设定,因此专门准备 resetStar方法,行使此方法就可以了。如下所示。 |
/** star.setRefPixelPosition(x, y); |
![]() | 看了初始设定的详细内容,下面来看处理的要点。 执行 thread 处理的 run 方法的内容没有改变,但调出的 tick 、paint、keyEvent方法,根据游戏状态( gameStatus的值)的不同,处理也不尽相同。 分别使用类似下面的if~else if~else结构,进行处理的切换。 |
if(gameStatus == GAME_PLAYING) { /* 游戏进行中的处理 */ } else if (gameStatus == GAME_OVER) { /* 显示游戏结束画面时的处理*/ } else { /* 显示游戏开始画面时的处理*/ } |
![]() | 定义每经过一定时间执行的处理的 tick 方法如下所示。 游戏进行时,根据得分的不同,陨石的个数也发生变化。 |
/** // 获得星星的检查 |
![]() | 对应按键状态游戏的变化如下所示。游戏进行中的操作与之前的例子相同,不过游戏开始和结束时,只有 SELECT 按键有反应。 需要注意的是,为了防止 SELECT 按键被按时间过长,结束画面又跳回开始画面导致游戏重新开始,使用了 keyReleased 这个flag 来确认按键被一次按下有效的情况。 |
/** * 根据按键状态切换处理 */ private void keyEvent( int keyStates) { if(gameStatus == GAME_PLAYING) { if ((keyStates & LEFT_PRESSED) != 0) { // 左按键被按下时 rocket.setDirection(-MOVE_STEP_SIZE, 0); rocket.setTransform(Sprite.TRANS_ROT270); } else if ((keyStates & RIGHT_PRESSED) != 0) { // 右按键被按下时 rocket.setDirection(MOVE_STEP_SIZE, 0); rocket.setTransform(Sprite.TRANS_ROT90); } else if ((keyStates & UP_PRESSED) != 0) { // 上按键被按下时 rocket.setDirection(0, -MOVE_STEP_SIZE); rocket.setTransform(Sprite.TRANS_NONE); } else if ((keyStates & DOWN_PRESSED) != 0) { // 下按键被按下时 rocket.setDirection(0, MOVE_STEP_SIZE); rocket.setTransform(Sprite.TRANS_ROT180); } } else if (gameStatus == GAME_OVER) { // SELECT 按键被按下时 if ((keyStates & FIRE_PRESSED) != 0 && keyReleased) { gameStatus = GAME_OPENING; rocket.setTransform(Sprite.TRANS_NONE); } } else { // SELECT 按键被按下时 if ((keyStates & FIRE_PRESSED) != 0 && keyReleased) { start(); } } // 没有任何按键被按下时 keyReleased 的值为 true keyReleased = keyStates == 0; } |
![]() | 最后,咱们来看执行描绘更新的draw方法。当然,对应的游戏状态不同,显示的内容也不一样,因此需要单独确认内容。 不过,显示文字时,要使用Graphics类的drawString方法。 |
/** * 描绘的更新 */ private void draw(Graphics g) { // 点击画面 g.setColor(0xffffff); g.fillRect(0, 0, width, height); Font font = Font.getFont(Font.FACE_MONOSPACE, Font.STYLE_BOLD, Font.SIZE_LARGE); g.setFont(font);
if (gameStatus == GAME_PLAYING) { layerManager.paint(g, 0, 0); g.setColor(0x000000); g.drawString("" + score, 0, height, g.LEFT|g.BOTTOM); } else if (gameStatus == GAME_OVER) { layerManager.paint(g, 0, 0); g.setColor(0xff0000); // 描绘 message g.drawString("-- GAME OVER --", width/2, 60, g.BOTTOM|g.HCENTER); g.drawString("SCORE " + score, width/2, 120, g.BOTTOM|g.HCENTER); g.drawString("< Press SELECT Button to Start>", width/2, 180, g.BOTTOM|g.HCENTER); } else { g.setColor(0x000000); // 描绘 message g.drawString("[[ Rocket Game ]]", width/2, 50, g.BOTTOM|g.HCENTER); g.drawString("- Control Your Rocket", 80, 90, g.BASELINE|g.LEFT); g.drawString("- Get Star", 80, 125, g.BASELINE|g.LEFT); g.drawString("- Avoid meteorites!", 80, 160, g.BASELINE|g.LEFT); g.drawString("< Press SELECT Button>", width/2, 210, g.BOTTOM|g.HCENTER); // 描绘火箭 rocket.setRefPixelPosition(60,85); rocket.paint(g); // 描绘星星 star.setRefPixelPosition(60,120); star.paint(g); // 描绘陨石 stones[0].setRefPixelPosition(60,155); stones[0].paint(g); } flushGraphics(); } |
![]() | 呵,介绍的内容增多了啊,修改了的地方可以理解么? |
![]() | 恩,现在还不能全部理解,先修改代码试试吧,执行一下看看。 |
![](http://www.nec-mfriend.com/cn/lecture/images/t8_4.jpg)
Opening 画面
每一个角色都在转动
![](http://www.nec-mfriend.com/cn/lecture/images/t8_5.jpg)
游戏刚开始时状态
最初只有一个陨石
![](http://www.nec-mfriend.com/cn/lecture/images/t8_6.jpg)
游戏进行中状态
每增加 100 分陨石个数增加 1
![](http://www.nec-mfriend.com/cn/lecture/images/t8_7.jpg)
游戏结束
火箭碰到陨石则游戏结束
![](http://www.nec-mfriend.com/cn/lecture/images/t8_8.jpg)
![]() | 怎么样?有点游戏的感觉了吧。 |
![]() | 恩。挺有意思的,可以和朋友一起比赛了。 |
![]() | 别光顾着玩游戏,还要好好的复习代码的内容才行啊。只有理解了为什么这样做,才可以随心所欲的修改游戏呢。 |
![]() | 我知道啦。 如果能加上声音和保存游戏的分数就好了。 |
![]() | 恩,声音的话,用咱们第二讲讲到的知识就可以了。 关于最高分的保存,要参见中级讲座第六讲。 ( http://www.nec-mfriend.com/cn/lecture/lecture6_1.php ) 可见要完成一个完整的游戏,还是很难的啊。 |