随笔-30  评论-123  文章-0  trackbacks-0
概述
如果你正在开发一款赛车游戏,那么其中很重要的一个方面就是检验赛车什么时候冲出了跑道或是装上了别的车。检验上述事件在使用精灵进行编程时是非常容易的事情。精灵提供了4个专门用于处理上述类似事件的方法:
boolean collidesWith(Image image, int x, int y, boolean pixelLevel)
boolean collidesWith(Sprite s, boolean pixelLevel)
boolean collidesWith(TiledLayer t, boolean pixelLevel)
void defineCollisionRectangle(int x, int y, int width, int height)

请注意你能检验的碰撞的类型有很大的灵活性。例如,你不但可以检验精灵之间的碰撞,还能检验精灵与图像、平铺的图层之间的碰撞。
注意:平铺图层(Tiled Layer)是一个由一系列格子组成的可视化元素。这些格子能建立起一个大的卷轴背景而不需要一张大的图画。

碰撞检验
学习碰撞检验的最佳方法就是实际应用它。在这一章,你将创建你的第二个MIDlet。这个MIDlet将向你展示如何检验精灵之间的碰撞。
我们先实际看一看这个MIDlet。我们的目标是:在屏幕上移动小绿人而不碰到其他任何精灵,也就是说不能碰到苹果、立方体、星星和中间的螺旋体。图18展示了MIDlet运行时的3幅屏幕截图,你可以看到小绿人在屏幕上移动但是却没碰到任何其他精灵。
o_18.jpg(图18)
图19显示了MIDlet如何相应碰撞。你会注意到,每次发生碰撞时,屏幕的背光会闪烁(屏幕边缘出现浅绿色)
o_19.jpg(图19)

创建固定的精灵
第一步,我们先来创建固定不动的精灵(立方体、苹果还有星星)。
在WTK中新建工程和精灵:
1、创建一个名为Collisions的工程;
2、把后面出现的所有代码复制到文本编辑器中;
3、把这些源文件保存到WTK安装目录下的\apps\Collisions\src目录下。
下面是代码:

/* --------------------------------------------------
 * AppleSprite.java
 *-------------------------------------------------
*/

import  javax.microedition.lcdui.game. * ;
import  javax.microedition.lcdui. * ;

public   class  AppleSprite  extends  Sprite  {
    
public  AppleSprite(Image image)  {
        
//  构造函数
         super (image);
        
//  设置显示在屏幕上的位置
        setRefPixelPosition( 146 35 );
    }

}

/* --------------------------------------------------
 * StarSprite.java
 *-------------------------------------------------
*/

import  javax.microedition.lcdui.game. * ;
import  javax.microedition.lcdui. * ;

public   class  StarSprite  extends  Sprite  {
    
public  StarSprite(Image image)  {
        
//  构造函数
         super (image);
        
//  设置显示在屏幕上的位置
        setRefPixelPosition( 5 65 );
    }

}

/* --------------------------------------------------
 * CubeSprite.java
 *-------------------------------------------------
*/

import  javax.microedition.lcdui.game. * ;
import  javax.microedition.lcdui. * ;

public   class  CubeSprite  extends  Sprite  {
    
public  CubeSprite(Image image)  {
        
//  构造函数
         super (image);
        
//  设置显示在屏幕上的位置
        setRefPixelPosition( 120 116 );
    }

}

注意精灵参考像素的位置决定了精灵在画布上的位置。例如,立方体精灵被放置在横坐标=120,纵坐标= 116处。

创建动画精灵
上一章创建的动画精灵会在这一章使用。
1
、把上一章的代码复制到文本编辑器,
2
、将文件另存为AnimatedSprite.java,保存到WTK安装目录下的\apps\Collisions\src目录
下面是代码:

/* --------------------------------------------------
 * AnimatedSprite.java
 *-------------------------------------------------
*/

import  javax.microedition.lcdui.game. * ;
import  javax.microedition.lcdui. * ;

public   class  AnimatedSprite  extends  Sprite  {
    
public  AnimatedSprite(Image image,  int  frameWidth,  int  frameHeight)  {
        
//  调用Sprite构造函数
         super (image, frameWidth, frameHeight);
    }

}

创建移动精灵:代码书写
我们将控制移动一个绿色小人图案的精灵。
1、将下面的代码拷贝到文本编辑器。
2、将此源文件保存到WTK安装目录下的\apps\Collisions\src目录

/* --------------------------------------------------
 * ManSprite.java
 *
 * 这个精灵可以通过调用moveLeft(), moveRight()
 * moveUp() and moveDown()方法在屏幕上移动
 *-------------------------------------------------
*/

import  javax.microedition.lcdui.game. * ;
import  javax.microedition.lcdui. * ;

public   class  ManSprite  extends  Sprite  {

    
private   int  x  =   0 , y  =   0 //  当前的x,y坐标
            previous_x, previous_y;  //  是一个x,y坐标

    
private   static   final   int  MAN_WIDTH  =   25 //  精灵宽度

    
private   static   final   int  MAN_HEIGHT  =   25 //  精灵高度

    
public  ManSprite(Image image)  {
        
//  调用Sprite的构造函数
         super (image);
    }


    
public   void  moveLeft()  {
        
//  如果绿色的小人精灵没有碰到左边缘
         if  (x  >   0 {
            saveXY();
            
//  如果离左边界小与3,则置0,
            
//  否则从当前位置减3
            x  =  (x  <   3   ?   0  : x  -   3 );
            setPosition(x, y);
        }

    }


    
public   void  moveRight( int  w)  {
        
//  如果绿色的小人精灵没有碰到右边缘
         if  ((x  +  MAN_WIDTH)  <  w)  {
            saveXY();
            
//  如果当前横坐标加上精灵宽度超出了右边界,
            
//  将当前位置设为最右边. 否则当前位置加3.
            x  =  ((x  +  MAN_WIDTH  >  w)  ?  (w  -  MAN_WIDTH) : x  +   3 );
            setPosition(x, y);
        }

    }


    
public   void  moveUp()  {
        
//  如果绿色的小人精灵没有碰到上边缘
         if  (y  >   0 {
            saveXY();
            
//  如果离上边界小于3,则置为0
            
//  否则从当前位置减.
            y  =  (y  <   3   ?   0  : y  -   3 );
            setPosition(x, y);
        }

    }


    
public   void  moveDown( int  h)  {
        
//  如果绿色的小人精灵没有碰到下边缘
         if  ((y  +  MAN_HEIGHT)  <  h)  {
            saveXY();
            
//  如果当前纵坐标加上精灵高度超出了下边界,
            
//  将当前位置设为最下边. 否则当前位置加3.
            y  =  ((y  +  MAN_WIDTH  >  h)  ?  (h  -  MAN_WIDTH) : y  +   3 );
            setPosition(x, y);
        }

    }


    
/* --------------------------------------------------
     * 保存x,y坐标用于碰撞检验
     *-------------------------------------------------
*/

    
private   void  saveXY()  {
        
//  将当前坐标缓存到上一坐标
        previous_x  =  x;
        previous_y 
=  y;
    }


    
/* --------------------------------------------------
     * 如果检验到碰撞发生,
     * 则返回上一坐标
     *-------------------------------------------------
*/

    
public   void  restoreXY()  {
        x 
=  previous_x;
        y 
=  previous_y;
        setPosition(x, y);
    }

}

让我们回过头快速回顾一下上一节代码里的几个关键点。
有4个方法控制精灵的运动。
传递给moveRight() 和 moveDown()方法的参数分别是画布的宽度和高度。这两个参数分别用来判断小人精灵是否到达了屏幕的右边界和下边界。
1. moveLeft()
2. moveRight(int w)
3. moveUp()
4. moveDown(int h)
无论何时,你移动精灵,你首先要将精灵的当前位置保存起来(saveXY()),以便于精灵发生碰撞时使用。一旦碰撞真的发生,你就需要一种方法将精灵恢复到原来位置(restoreXY())
/* --------------------------------------------------
     * 保存x,y坐标用于碰撞检验
     *-------------------------------------------------
*/

    
private   void  saveXY()  {
        
//  将当前坐标缓存到上一坐标

        previous_x  =  x;
        previous_y 
=
 y;
    }


/* --------------------------------------------------
     * 如果检验到碰撞发生,
     * 则返回上一坐标
     *-------------------------------------------------
*/

    
public   void  restoreXY()  {
        x 
=
 previous_x;
        y 
=
 previous_y;
        setPosition(x, y);
    }

上面两个方法可以处理任何运动,不管什么方向,都遵循相同的逻辑。 根据精灵的当前位置检验边缘碰撞。例如有一个向左移动的请求,确认当前坐标是否比0大,如果是,则对横坐标进行相应的变换。下面是左移方法的代码:
public     void   moveLeft()    {
         
//   如果绿色的小人精灵没有碰到左边缘 

            if   (x   >     0  )    {
            saveXY();
             
//
  如果离左边界小与3,则置0,
             
//   否则从当前位置减3 

             x   =   (x   <     3     ?     0   : x   -     3  );
            setPosition(x, y);
        }
 
    }
 
注意:用3来更新横坐标x值(或者纵坐标y值),这样得到的用户界面反应速度更快。如果每次只移动一个像素,那么在屏幕上移动将是一个漫长的过程。

将精灵添加到画布:代码书写
画布的作用就象是游戏的背景。所有的精灵连同画布一起显示到屏幕上。本节同时处理事件,包括当键(上,下,左,右)按下时屏幕的刷新。更着下面的步骤一步步来:
1、将下面的代码拷贝到文本编辑器。
2、将此源文件保存到WTK安装目录下的\apps\Collisions\src目录,并命名为CollisionCanvas.java。
一旦 所有代码书写完成,就可以返回此代码检验几个关键点。
/*--------------------------------------------------
 * CollisionCanvas.java
 *-------------------------------------------------
*/

import javax.microedition.lcdui.game.*;
import javax.microedition.lcdui.*;

public class CollisionCanvas extends GameCanvas implements Runnable {
    
private AnimatedSprite spSpiral; // 动画精灵

    
private static final int FRAME_WIDTH = 57// 一帧画面的宽度

    
private static final int FRAME_HEIGHT = 53// 一帧画面的高度

    
private int canvas_width, canvas_height; // 保存画布信息

    
private ManSprite spMan; // 小人 (可移动)

    
private AppleSprite spApple; // 苹果 (不可移动)

    
private CubeSprite spCube; // 立方体 (不可移动)

    
private StarSprite spStar; // 星星 (不可移动)

    
private LayerManager lmgr; // 图层管理器

    
private boolean running = false// 线程是否运行?

    
private Collisions midlet; // 主MIDlet

    
public CollisionCanvas(Collisions midlet) {
        
// 构造函数
        super(true);
        
this.midlet = midlet;
        
try {
            
// 非动画精灵
            spMan = new ManSprite(Image.createImage("/man.png"));
            spApple 
= new AppleSprite(Image.createImage("/apple.png"));
            spCube 
= new CubeSprite(Image.createImage("/cube.png"));
            spStar 
= new StarSprite(Image.createImage("/star.png"));
            
// 动画精灵
            spSpiral = new AnimatedSprite(Image.createImage("/spiral.png"),
                    FRAME_WIDTH, FRAME_HEIGHT);
            
// 将参考像素改到精灵的中心
            spSpiral.defineReferencePixel(FRAME_WIDTH / 2, FRAME_HEIGHT / 2);
            
// 将精灵放置在画布中心
            
// (精灵中心和画布中心重合)
            spSpiral.setRefPixelPosition(getWidth() / 2, getHeight() / 2);
            
// 创建并添加到图层管理器
            lmgr = new LayerManager();
            lmgr.append(spSpiral);
            lmgr.append(spMan);
            lmgr.append(spApple);
            lmgr.append(spCube);
            lmgr.append(spStar);
        }
 catch (Exception e) {
            System.out.println(
"Unable to read PNG image");
        }

        
// 保存画布的宽度和高度
        canvas_width = getWidth();
        canvas_height 
= getHeight();
    }
 
    
    
/*--------------------------------------------------
     * 启动线程
     *-------------------------------------------------
*/

    
public void start() {
        running 
= true;
        Thread t 
= new Thread(this);
        t.start();
    }


    
/*--------------------------------------------------
     * Main game loop
     *-------------------------------------------------
*/

    
public void run() {
        Graphics g 
= getGraphics();
        
while (running) {
            
// 探测有没有键被按下
            checkForKeys();
            
if (checkForCollision() == false{
                drawDisplay(g);
            }
 else {
                
// 背光灯闪烁,设备震动
                midlet.display.flashBacklight(500);
                midlet.display.vibrate(
500);
            }

            
try {
                Thread.sleep(
125);
            }
 catch (InterruptedException ie) {
                System.out.println(
"Thread exception");
            }

        }

    }


    
/*--------------------------------------------------
     * 检测有没有发生碰撞.
     *-------------------------------------------------
*/

    
private boolean checkForCollision() {
        
if (spMan.collidesWith(spSpiral, true)
                
|| spMan.collidesWith(spApple, true)
                
|| spMan.collidesWith(spCube, true)
                
|| spMan.collidesWith(spStar, true)) {
            
// 发生碰撞,恢复到上一个位置
            spMan.restoreXY();
            
return true;
        }
 else
            
return false;
    }


    
/*--------------------------------------------------
     * 有键按下,移动精灵
     *-------------------------------------------------
*/

    
private void checkForKeys() {
        
int keyState = getKeyStates();
        
if ((keyState & LEFT_PRESSED) != 0{
            spMan.moveLeft();
        }
 else if ((keyState & RIGHT_PRESSED) != 0{
            spMan.moveRight(canvas_width);
        }
 else if ((keyState & UP_PRESSED) != 0{
            spMan.moveUp();
        }
 else if ((keyState & DOWN_PRESSED) != 0{
            spMan.moveDown(canvas_height);
        }

    }


    
/*--------------------------------------------------
     * 刷新屏幕
     *-------------------------------------------------
*/

    
private void drawDisplay(Graphics g) {
        
// 创建背景
        g.setColor(0xffffff);
        g.fillRect(
00, getWidth(), getHeight());
        
// 动画精灵,按次序显示下一帧
        spSpiral.nextFrame();
        
// 绘制所有图层
        lmgr.paint(g, 00);
        
// 将屏幕缓冲刷新到屏幕
        flushGraphics();
    }


    
/*--------------------------------------------------
     * 停止线程
     *-------------------------------------------------
*/

    
public void stop() {
        running 
= false;
    }

}

将精灵添加到画布:代码回顾
尽管Canvas类中的许多代码都没变,但是你必须增加两个关键方法。首先你要增加一个事件处理方法来响应按键事件。
private   void  checkForKeys()  {
        
int  keyState  =  getKeyStates();
        
if  ((keyState  &  LEFT_PRESSED)  !=   0 {
            spMan.moveLeft();
        }
  else   if  ((keyState  &  RIGHT_PRESSED)  !=   0 {
            spMan.moveRight(canvas_width);
        }
  else   if  ((keyState  &  UP_PRESSED)  !=   0 {
            spMan.moveUp();
        }
  else   if  ((keyState  &  DOWN_PRESSED)  !=   0 {
            spMan.moveDown(canvas_height);
        }

    }

当用户选择了一个方向,按下了相应的键时,调用Man类中的相应方法改变横坐标x和纵坐标y的值,从而处理精灵移动。
第二个变化的地方是碰撞检验代码:
private   boolean  checkForCollision()  {
        
if  (spMan.collidesWith(spSpiral,  true
)
                
||  spMan.collidesWith(spApple,  true
)
                
||  spMan.collidesWith(spCube,  true
)
                
||  spMan.collidesWith(spStar,  true )) 
{
            
//  发生碰撞,恢复到上一个位置

            spMan.restoreXY();
            
return   true
;
        }
  else
            
return   false ;
    }

每一次有键按下,你必须通过调用Man类的collidesWith()方法与可能发生碰撞的精灵进行检验碰撞。一旦发生了碰撞,就将提前在ManSprite.java中的保存的x,y坐标值恢复回来。

主MIDlet代码
下面的代码主要负责启动和关闭MIDlet,这跟上一章例子中的主MIDlet差不多。
1、将下面的代码拷贝到文本编辑器。
2、将此源文件保存到WTK安装目录下的\apps\Collisions\src目录,并命名为Collisions.java
/*--------------------------------------------------
 * Collisions.java
 * 使用精灵的演示MIDlet
 * 包括碰撞检验.
 *-------------------------------------------------
*/

import javax.microedition.midlet.*;
import javax.microedition.lcdui.*;

public class Collisions extends MIDlet implements CommandListener {
    
protected Display display; // Reference to display

    
private CollisionCanvas canvas; // 游戏画布

    
private Command cmExit; // 退出按钮

    
public Collisions() {
        display 
= Display.getDisplay(this);
        
// 创建游戏画布和退出按钮
        if ((canvas = new CollisionCanvas(this)) != null{
            cmExit 
= new Command("Exit", Command.EXIT, 1);
            canvas.addCommand(cmExit);
            canvas.setCommandListener(
this);
        }

    }


    
public void startApp() {
        
if (canvas != null{
            display.setCurrent(canvas);
            canvas.start();
        }

    }


    
public void pauseApp() {
    }


    
public void destroyApp(boolean unconditional) {
        canvas.stop();
    }


    
public void commandAction(Command c, Displayable s) {
        
if (c == cmExit) {
            destroyApp(
true);
            notifyDestroyed();
        }

    }

}


编译、预校验、运行
在WTK中单击Build按钮编译、预校验并打包MIDlet。选择Run启动应用管理器,点击Launch运行MIDlet。
o_18.jpg(图20)

像素级碰撞检验:第一部分
让我们回到代码来确认一下两个精灵间是否发生了碰撞:

private   boolean  checkForCollision()  {
        
if  (spMan.collidesWith(spSpiral,  true
)
                
||  spMan.collidesWith(spApple,  true
)
                
||  spMan.collidesWith(spCube,  true
)
                
||  spMan.collidesWith(spStar,  true )) 
{
            
//  发生碰撞,恢复到上一个位置

            spMan.restoreXY();
            
return   true
;
        }
  else
            
return   false ;
    }

上一节中关于这个方法的讨论,我们并没有澄清布尔参数表示的含义。当传入true时,说明要求像素级碰撞。在像素级碰撞的情况下,两个精灵的不透明像素碰撞时碰撞才发生。下一节你将学到更多这方面的知识。

像素级碰撞:第二部分
请注意图21中,当碰撞发生时两个精灵间的接近情况。
o_19.jpg(图21)
让我们将修改以下代码,关掉像素级碰撞,看看会有什么情况发生。

private   boolean  checkForCollision()  {
        
if  (spMan.collidesWith(spSpiral,  false
)
                
||  spMan.collidesWith(spApple,  false
)
                
||  spMan.collidesWith(spCube,  false
)
                
||  spMan.collidesWith(spStar,  false )) 
{
            
 ... ...

    }

像素级碰撞:第三部分
图22中,请大家注意碰撞被检测到时的不同。我画出了精灵的外围边界来突出他的碰撞范围。
r_21.jpg(像素级碰撞关闭状态,当碰撞范围有交差时,碰撞就被检测到)
r_22.jpg(像素级碰撞打开状态,精灵间能更接近)
使用像素级碰撞时,精灵的透明部分可以交叠。提升了碰撞检验的精确度。
posted on 2006-05-03 12:16 学二的猫 阅读(3206) 评论(2)  编辑  收藏 所属分类: J2ME

评论:
# re: 用Sprite编写J2ME程序--第四章 碰撞检验 2006-05-11 01:00 | 浪子无泪
不错,很适合初学者看  回复  更多评论
  
# re: 用Sprite编写J2ME程序--第四章 碰撞检验 2007-08-05 11:07 | 学者
呵呵,支持一下!很容易理解.不过,可不可以,再讲一下如何更精确实现碰撞,就是重叠的部分多一些.  回复  更多评论
  

只有注册用户登录后才能发表评论。


网站导航: