Sprite 的碰撞判断-----转

Sprite 的碰撞判断

 

 上一讲咱们制作了火箭在飞来飞去的陨石中前讲的程序,这一讲来制作稍微有意思的游戏吧。
 上讲的程序,火箭也没有和陨石相撞什么的。
 这个,是啊。 那首先来介绍一下火箭与陨石相撞的判断方法吧。

 

 

1.Sprite 类的碰撞判断方法

 不过以前的MIDP2.0并没有Sprite类,还记得怎么进行碰撞判断的么?
 呃,应该是通过计算角色的坐标值与大小,判断两个物体是否发生碰撞吧。
 

呵呵,那先复习一下Java初级讲座第11讲的内容吧,通过计算车的大小判断是否碰撞。 ( http://www.nec-mfriend.com/cn/lecture/lecture_ca11_1.php
不过,Sprite类里准备了专门的碰撞判断方法,即collidesWith(Sprite s, boolean pixelLevel) 。

 使用它可以使计算变的简单么?
 呵呵,使用它就不用计算了。只需把要进行碰撞判断的其他的Sprite对象传递到方法的第一个参量,从返回值判断有无碰撞了。发生碰撞的返回值为true ,相反则为False 。
 好简单啊。那第二个参量pixelLevel是什么意思呢?
 第二个参量,通过像素单位判断碰撞的发生。返回值为fales时,只是单纯的根据Sprite外形的四角形进行判断,返回值是true时,就要利用像素单位进行判断了。
 像素单位是什么意思呢?
 来参看下面的图吧。(a)清楚显示没有相撞,( b )显示图像外面的四边形重叠在一起。 CollidesWith方法第二个自变量中指定false,(b)似乎并没有发生碰撞,而指定true时如c所示,实际上只是指角色图像发生碰撞的情况。指定的是哪一种情况,最好以判断的精确度和处理速度的优先度来判断。

 

 

 下面咱们使用Sprite类的碰撞判断功能,将火箭与陨石碰撞时的背景设置为红色吧。
 

使用前一讲的代码是吧?

 嗯,继承MIDlet的GameCanvasTest.java和继承Sprite的MySprite.java已经讲过了。只需要修改MyGameCanvas就行了。

 

 首先,添加boolean型变量到member变量,显示火箭与陨石是否相撞。将下面一行添加到MyGameCanvas类的定义中。

 

private boolean colliding = false; // 显示火箭与陨石是否相撞的flag

 

 接着,在每经过一定时间进行的处理tick方法中添加判断碰撞的处理。

 

/**
* 每经过一定时间的处理
*/

private void tick() {
   // 更新火箭的frame图像的index
   imageIndex = (imageIndex + 1) % 4;
   rocket.setFrame(imageIndex);
   // 移动火箭
   rocket.move();

   colliding = false;
   // 更新所有的陨石位置与转动量
   for(int i = 0; i < STONE_NUM; i++) {
      stones[i].move();
      stones[i].rotate();
      if(rocket.collidesWith(stones[i], true)) {
         colliding = true;
      }
   }
}

 

 最后,描绘时通过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();
}

 

 好的,做好了,执行一下试试吧。

 

 

 使用 CollidesWith 方法 ,碰撞判断就变的很简单了吧。
 是的。

 

2.整理游戏的题材

 博士,我终于学会碰撞判断了,好想试试制作简单的游戏啊。
 

呵呵。下面我们先来整理一下游戏的题材吧。 做为练习程序,试着遵守下面的简单规则。

 

游戏的 rule

  • 操作火箭,避开陨石
  • 撞到陨石则游戏结束
  • 每前进 1step 增加 1 分
  • 画面中只显示一个星星,与陨石混在一起
  • 抓到星星增加100 分
  • 每增加 100 分,陨石个数增加1(最多为 10 个)

 

 

继续来看画面的迁移吧。这次用三个画面简单的说明一下,分别是开始画面、结束画面和游戏进行画面。请参见下图。

 

 

 好有游戏的感觉啊。
 下面就修改 一下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对象
Image starImage = Image.createImage("/star.png");
star = new MySprite(starImage, 24, 24, width, height);
layerManager.append(star); // 在LayerManager对象里登陆Sprite

 

 

游戏开始时调出的start方法,进行陨石、火箭和星星等的初始设定。省略与上一讲相同的处理,如下所示。

 

/**
* 游戏开始
*/

public void start() {
   // 重新设定得分
   score = 0;

   // 陨石的设定
   /* 略 */

   // 星星的设定
   resetStar();

   // 设定火箭的初始位置与移动方向
   /* 略 */


   // 游戏状态的更新
   gameStatus = GAME_PLAYING;
}

 

 

火箭在捕获星星时需要行使星星的初始设定,因此专门准备 resetStar方法,行使此方法就可以了。如下所示。

 

/**
* 再设定星星的位置与移动的方向
*/

private void resetStar() {
   // 任意设定星星的位置
   int x, y;
   do {
      x = Math.abs(rand.nextInt()) % width;
      y = Math.abs(rand.nextInt()) % height;
   } while(Math.abs(x - rocket.getRefPixelX()) < 15 &&
      Math.abs(y - rocket.getRefPixelY()) < 15);

      star.setRefPixelPosition(x, y);

      // 任意设定星星的移动方向和初始转动量
      intdx = rand.nextInt() % 6;
      intdy = rand.nextInt() % 6;
      star.setDirection(dx, dy);
      star.transformIndex = Math.abs(rand.nextInt()) % 4;
   }

 

 

看了初始设定的详细内容,下面来看处理的要点。 执行 thread 处理的 run 方法的内容没有改变,但调出的 tick 、paint、keyEvent方法,根据游戏状态( gameStatus的值)的不同,处理也不尽相同。 分别使用类似下面的if~else if~else结构,进行处理的切换。

 

if(gameStatus == GAME_PLAYING) {

   /* 游戏进行中的处理 */

} else if (gameStatus == GAME_OVER) {

   /* 显示游戏结束画面时的处理*/

} else {

   /* 显示游戏开始画面时的处理*/

}

 

 

定义每经过一定时间执行的处理的 tick 方法如下所示。 游戏进行时,根据得分的不同,陨石的个数也发生变化。

 

/**
* 每经过一定时间的处理
*/

private void tick() {
   if(gameStatus == GAME_PLAYING) {
      // 陨石状态的更新
      for(int i = 0; i < MAX_STONE_NUM; i++) {
         if(score / 100 < i) {
            continue;
         }

         stones[i].setVisible(true);

         // 移动和转动
         stones[i].move();
         stones[i].rotate();

         // 碰撞判断
         if(rocket.collidesWith(stones[i], true)) {
            gameStatus = GAME_OVER;
         }
      }

      // 更新火箭的frame图像的index
      imageIndex = (imageIndex + 1) % 4;
      rocket.setFrame(imageIndex);

      // 移动火箭
      rocket.move();

      // 星星的移动和转动
      star.move();
      star.rotate();

      // 获得星星的检查
      if(rocket.collidesWith(star, true)) {
         score += 100;
         resetStar();
      }
      score++;
      } else if(gameStatus == GAME_OVER) {
      // 陨石转动的更新
         for(int i = 0; i < MAX_STONE_NUM; i++) {
            stones[i].rotate();
         }
      } else {
         // Opening显示用的更新
         imageIndex = (imageIndex + 1) % 4;
         rocket.setFrame(imageIndex);
         stones[0].rotate();
         star.rotate();
      }
   }

 

 

对应按键状态游戏的变化如下所示。游戏进行中的操作与之前的例子相同,不过游戏开始和结束时,只有 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();

   }

 

 

呵,介绍的内容增多了啊,修改了的地方可以理解么?

 

恩,现在还不能全部理解,先修改代码试试吧,执行一下看看。

 


Opening 画面

每一个角色都在转动

 

游戏刚开始时状态

最初只有一个陨石

 

游戏进行中状态

每增加 100 分陨石个数增加 1

 

游戏结束

火箭碰到陨石则游戏结束

 

 

 怎么样?有点游戏的感觉了吧。
 恩。挺有意思的,可以和朋友一起比赛了。
 别光顾着玩游戏,还要好好的复习代码的内容才行啊。只有理解了为什么这样做,才可以随心所欲的修改游戏呢。
 我知道啦。 如果能加上声音和保存游戏的分数就好了。
 

恩,声音的话,用咱们第二讲讲到的知识就可以了。 关于最高分的保存,要参见中级讲座第六讲。 ( http://www.nec-mfriend.com/cn/lecture/lecture6_1.php ) 可见要完成一个完整的游戏,还是很难的啊。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值