GameCanvas与Sprite-----转

GameCanvas与Sprite

 

 这一讲我们来学习MIDP2.0的新增功能GameAPI。
 GameAPI?从名称上面看是游戏用的API么?
 是啊。手机上面的程序大都是游戏吧。MIDP2.0新添了许多方便游戏开发的类。这些类都被包含在了javax.microedition.lcdui.game package里面。

 

 

1. 使用GameCanvas类

 下面我们先来看看GameCanvas类的使用方法。
 还有叫GameCanvas名称的类呢。感觉确实是游戏用的Canvas啊。
 恩,没错。是方便游戏开发的Canvas类。它继承了目前一直在使用的
javax.microedition.lcdui.Canvas类,因此Canvas备置的方法可以直接使用。

 

 

 GameCanvas与普通的Canvas有什么不同么?
 GameCanvas最大的特征,就是支持取得offscreen缓冲和按键的状态。
 取得offscreen缓冲和按键的状态?呃,不是很明白。
 offscreen缓冲,是指在画面以外的地方描绘,然后将描绘结果发送到实际画面的系统。有了这个,就可以防止显示动画时的画面飘飞现象了。
 那么,可以取得按键状态,又是什么意思呢?以前不也是通过按键进行程序操作的么。
 在以前的应用中,按键被按下时,JVM调出keyPressed方法,这样很容易明白按键的状态。这种方法被称为event驱动,但此方法在按键被按下后到方法的执行存在时间滞留现象,很难直接体现游戏中的动作。
 那么实际使用GameCanvas该如何进行程序的开发呢?
 一般来说与普通的Canvas比较一下,就明白GameCanvas的特征和使用方法了。
咱们使用GameCanvas和Canvas两个类,来制作同样内容的程序,比较一下代码吧。
 好的。
 下面,先来执行一下下面的GameCanvasTest.java和MyCanvas.java看看吧。这个是使用普通的Canvas类制作的显示动画的简单用例。

 

GameCanvasTest.java
    
1 import javax.microedition.lcdui.*; 
2 import javax.microedition.midlet.MIDlet; 
3   
4 public class GameCanvasTest extends MIDlet implements CommandListener { 
5    private Display display; 
6    private MyCanvas canvas; 
7   
8    public GameCanvasTest() { 
9       display = Display.getDisplay(this); 
10       canvas = new MyCanvas(); 
11       canvas.addCommand(new Command("Exit", Command.EXIT, 0)); 
12       canvas.setCommandListener(this); 
13    } 
14   
15    public void startApp() { 
16       display.setCurrent(canvas); 
17       canvas.start(); 
18    } 
19   
20    public void pauseApp() {} 
21   
22    public void destroyApp(boolean unconditional) { 
23       if (canvas != null) { 
24          canvas.stop(); 
25       } 
26    } 
27   
28    public void commandAction(Command c, Displayable s) { 
29       if (c.getCommandType() == Command.EXIT) { 
30           destroyApp(true); 
31          notifyDestroyed(); 
32       } 
33    } 
34 } 
    

 

MyCanvas.java
    
1 import javax.microedition.lcdui.*; 
2   
3 public class MyCanvas extends Canvas implements Runnable { 
4   
5    private final int BALL_SIZE = 20; // ball的大小 
6    private int ballX; // ball的x坐标 
7    private int ballY; // ball的y坐标 
8    private int dx; // ball在x轴的位移 
9    private int dy; // ball在y轴的位移 
10    private boolean running; // 根据thread显示有无loop处理的flag 
11   
12    /** 
13    * MyCanvas的constructor 
14    */ 
15    public MyCanvas() { 
16       ballX = getWidth() / 2; 
17       ballY = getHeight() / 2; 
18       dx = 10; 
19       dy = 0; 
20    } 
21   
22    /** 
23    * thread的开始 
24    */ 
25    public void start() { 
26       running = true; 
27       Thread t = new Thread(this); 
28       t.start(); 
29    } 
30   
31    /** 
32    * 由thread执行的方法 
33    */ 
34    public void run() { 
35       while(running) { 
36          // 每经过一定时间的处理 
37          tick(); 
38   
39          // 再描绘命令 
40          repaint(); 
41   
42          // 100毫秒待机 
43          try { 
44             Thread.sleep(100);  
45          } 
46          catch (InterruptedException e) { 
47             stop(); 
48          } 
49       } 
50    } 
51   
52    /** 
53    * thread的停止 
54    */ 
55    public void stop() {  
56       running = false; 
57    } 
58   
59    /** 
60    * 每经过一定时间的处理 
61    */ 
62    private void tick() { 
63       ballX += dx; 
64       ballY += dy; 
65   
66       if(ballX > getWidth()) { 
67          ballX = 0; 
68       } else if (ballX < 0) { 
69          ballX = getWidth(); 
70       } 
71   
72       if(ballY > getHeight()) { 
73          ballY = 0; 
74       } else if (ballY < 0) { 
75          ballY = getHeight(); 
76       } 
77    } 
78   
79    /** 
80    * 根据被按下的按键变更移动方向 
81    */ 
82    protected void keyPressed(int key) { 
83       int gameaction = getGameAction(key); 
84       switch(gameaction){ 
85          case Canvas.RIGHT: 
86             dx = 10; 
87             dy = 0; 
88             break; 
89          case Canvas.LEFT: 
90             dx = -10; 
91             dy = 0; 
92             break;  
93          case Canvas.UP: 
94             dx = 0; 
95             dy = -10; 
96             break; 
97          case Canvas.DOWN: 
98             dx = 0; 
99             dy = 10; 
100             break; 
101          default: 
102       } 
103    } 
104   
105    /** 
106    * 描绘处理 
107    */ 
108    public void paint(Graphics g) { 
109       // 用白色涂满画面 
110       g.setColor(0xffffff); 
111       g.fillRect(0, 0, getWidth(), getHeight()); 
112   
113       // 描绘红色的圆形 
114       g.setColor(0xff0000); 
115       g.fillArc(ballX, ballY, BALL_SIZE, BALL_SIZE, 0, 360);  
116    } 
117 } 
    

 

 完成程序了,开始编译…好了,可以运行了。
画面上一个红色的小球开始动了。
 按上下左右键可以改变小球移动的方向哦。

 

 

 上面的程序没有涉及到新的知识,代码的内容理解起来不难吧。
 是的。使用thread就可以显示动画了啊。
 恩,一开始thread就执行run方法了。主要的处理就是run方法中的内容。
 Run方法的内容如下所示。

 

Run方法(第34~50行)
   public void run() {
      while(running) {
         // 每经过一定时间的处理
         tick();

         // 再描绘命令
         repaint();

         // 100毫秒待机
         try {
            Thread.sleep(100);
         }
         catch (InterruptedException e) {
            stop();
         }
      }
   }

 

 在while loop中利用tick方法变更ball的位置,调出repaint()方法。之后有100毫秒的待机,就可以大概显示出ball位置变化的动画了。
 没错,这个方法是使用Canvas类的动画的基本。
 按下按键后执行keyPressed()方法,可以改变ball的移动方向。
 恩,是这样的。
 这样没有问题的话,那还有使用GameCanvas的必要么?
 GameCanvas里面有Canvas里没有的大的方法。接下来就使用GameCanvas来实现同样内容看看。
将下面的MyGameCanvas.java添加到程序里,把刚才的GameCanvasTest.java第6行和第10行出现的MyCanvas替换为MyGameCanvas。
这样,就替换成使用GameCanvas类的程序了。
 明白,我试一下。

 

MyGameCanvas.java
    
1 import javax.microedition.lcdui.*; 
2 import javax.microedition.lcdui.game.*; 
3   
4 public class MyGameCanvas extends GameCanvas implements Runnable { 
5   
6    private final int BALL_SIZE = 20; // ball的大小 
7    private int ballX; // ball的x坐标 
8    private int ballY; // ball的y坐标 
9    private int dx; // ball的x轴位移 
10    private int dy; // ball的y轴位移 
11    private boolean running; //根据thread显示有无loop处理的 
12   
13    /** 
14    * MyGameCanvas的constructor 
15    */ 
16    public MyGameCanvas() { 
17       super(true); 
18          ballX = getWidth() / 2; 
19          ballY = getHeight() / 2; 
20          dx = 10; 
21          dy = 0; 
22       } 
23   
24    /** 
25    * thread的开始 
26    */ 
27    public void start() { 
28       running = true; 
29       Thread t = new Thread(this); 
30       t.start(); 
31    } 
32   
33    /** 
34    * 由thread执行的方法 
35    */ 
36    public void run() { 
37       Graphics g = getGraphics(); 
38   
39       while(running) { 
40          // 每经过一定时间的处理 
41          tick(); 
42   
43          // 取得按键的状态 
44          int keyStates = getKeyStates(); 
45   
46          // 根据按键状态变更移动方向 
47          if ((keyStates & LEFT_PRESSED) != 0) { 
48             dx = -10; 
49             dy = 0; 
50          } else if ((keyStates & RIGHT_PRESSED) != 0) { 
51             dx = 10; 
52             dy = 0; 
53          } else if ((keyStates & UP_PRESSED) != 0) { 
54             dx = 0; 
55             dy = -10; 
56          } else if ((keyStates & DOWN_PRESSED) != 0) { 
57             dx = 0; 
58              dy = 10; 
59          }  
60   
61       // 用白色涂满画面 
62       g.setColor(0xffffff); 
63       g.fillRect(0, 0, getWidth(), getHeight()); 
64   
65       // 描绘红色的圆形 
66       g.setColor(0xff0000); 
67       g.fillArc(ballX, ballY, BALL_SIZE, BALL_SIZE, 0, 360); 
68   
69       // 更新描绘内容 
70       flushGraphics(); 
71   
72       // 100毫秒待机 
73       try { 
74          Thread.sleep(100);  
75       } 
76       catch (InterruptedException e) { 
77             stop(); 
78       } 
79    } 
80 } 
81   
82    /** 
83    * thread的停止 
84    */ 
85    public void stop() {  
86       running = false; 
87    } 
88   
89    /** 
90    * 每经过一定时间的处理 
91    */ 
92    private void tick() { 
93       ballX += dx; 
94       ballY += dy; 
95   
96       if(ballX > getWidth()) { 
97          ballX = 0; 
98       } else if (ballX < 0) { 
99          ballX = getWidth(); 
100       } 
101   
102       if(ballY > getHeight()) { 
103          ballY = 0; 
104       } else if (ballY < 0) { 
105          ballY = getHeight(); 
106       } 
107    } 
108 } 
    

 

 

 运行后发现与之前的结果一样啊。
 因为制作的是同样的程序,所以结果一样啊。很没意思吧。
为了学习MyGameCanvas.java和MyCanvas.java的差异,来比较一下代码吧。
 好的。在使用thread显示动画这一点上面两者是一样的啊。
但是使用GameCanvas的方法里,缺少了keyPressed()和paint()方法。
 是的。取而代之的是run方法中的whileloop内,取得按键状态和更新描绘两种方法。
Canvas类,决定了执行Java程序的JVM(Java virtual machine)按下按键时的通知和再描绘的timing,而GameCanvas不同的是,开发者可以根据自己的timing,更新描绘的命令。
Canvas类与基本的MyCanvas,以及GameCanvas类与基本的MyGameCanvas相比较,不同点如下图所示。

 

 

 看了上面的图觉得MyGameCanvas比较简单啊。
 Canvas类,按键被按下时的方法keypressed与再描绘的方法paint通过JVM的判断执行,因此与游戏状态的紧密连接比较难。而GameCanvas类,开发者可以随时查看按键的状态,执行再描绘,因此是方便对游戏状况进行管理的方法。
 程序的编码都弄懂了。
 另外,为了在while loop中执行全部的处理,可以通过调整待机时间严密的定义每个step的时间。
例如下面的代码可以每隔100毫秒执行一次含有处理和再描绘的一个step。

 

在100毫秒内执行一个step的代码
   while(running) {
      long startTime = System.currentTimeMillis();
      /*
      •每个step的处理
      •对应按键状态的处理
      •画面的描绘处理
      */

      long endTime = System.currentTimeMillis();
      int duration = (int)(endTime - startTime);
      if(duration < 100) {
         try {
            Thread.sleep(100 - duration);
         }
         catch (InterruptedException e) {
            stop();
         }
      }
   }

 

 果然啊。While loop的开始与执行处理之后,分别在startTime、endTime变量里取得时刻,从而从差分得知处理需要的时间。
 是的,那个处理需要的时间从100毫秒中分出来进行待机的话,就可以刚好在100毫秒内执行1个step了。
 对于游戏的开发来说速度的调整很重要啊。使用GameCanvas,就可以制作出考虑取得按键状态和再描绘时间的程序了。

 

 

2. 使用Sprite类

 接下来,来讲一讲有助于游戏制作的Sprite类的使用方法。
使用Sprite类,可以简单的管理角色动画使用的frame图像了。
 frame图像?
 比如准备显示角色动作的复数图像,将其在一定时间内替换,角色就可以表现出栩栩如生的动作了。像这样动画使用的图像被称为frame图像。
 把角色右脚在前和左脚在前的图像交替显示,看起来就像是在走路了。这样,使用两个frame图像就可以显示动画了。
 说的没错啊。下面的图就是为本讲程序专门制作的火箭的动画图像。全部4张,按顺序显示,就可以表现边旋转边升空的火箭动画了。

 

 

 也就是说,提前准备4张图像,按顺序替换就可以了。
 没错没错。但是,如果使用Sprite类,如下图,只需准备一张图像就可以了,将动画使用的图像按顺序排列好作成一张图片。然后Sprite类就自动的根据frame数对图像进行分割了。

 

 

 下面,我们就使用Sprite类,制作基于上面的rocket.png图像的动画程序吧。将下面的GameCanvasTest2.java与MyGameCanvas.java编译后执行试试吧。
 好的。

 

 

GameCanvasTest2.java

    
1 import javax.microedition.lcdui.*; 
2 import javax.microedition.midlet.MIDlet; 
3   
4 public class GameCanvasTest2 extends MIDlet implements CommandListener { 
5    private Display display; 
6    private MyGameCanvas gameCanvas; 
7   
8    public GameCanvasTest2() { 
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          gameCanvas.start(); 
19       } catch (Exception 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 } 
    

 

MyGameCanvas.java

    
1 import javax.microedition.lcdui.*; 
2 import javax.microedition.lcdui.game.*; 
3 import java.io.IOException; 
4   
5 public class MyGameCanvas extends GameCanvas implements Runnable { 
6   
7    private Sprite rocket; // 火箭的Sprite 
8    private int imageIndex; // 动画使用的图像的index 
9    private int rocketY; // 火箭的y坐标 
10    private boolean running; //根据thread显示有无loop处理的flag 
11   
12    /** 
13    * MyGameCanvas的constructor 
14    */ 
15    public MyGameCanvas() throws IOException { 
16       super(true); 
17       Image image = Image.createImage("/rocket.png"); 
18       rocket = new Sprite(image, 32, 32); 
19       rocketY = getHeight(); 
20    } 
21   
22    /** 
23    * thread的开始 
24    */ 
25    public void start() { 
26       running = true; 
27       imageIndex = 0; 
28       Thread t = new Thread(this); 
29       t.start(); 
30    } 
31   
32    /** 
33    * 由thread执行的方法 
34    */ 
35    public void run() { 
36       Graphics g = getGraphics(); 
37   
38       while(running) { 
39          tick(); 
40          draw(g); 
41   
42       try { 
43          Thread.sleep(200);  
44       } 
45       catch (InterruptedException e) { 
46          stop(); 
47       } 
48    } 
49 } 
50   
51 /** 
52 * 每经过一定时间的处理 
53 */ 
54 private void tick() { 
55       imageIndex = (imageIndex + 1) % 4; 
56       rocket.setFrame(imageIndex); 
57       rocketY -= 10; 
58       if(rocketY < 0) { 
59          rocketY = getHeight(); 
60       } 
61    rocket.setPosition(getWidth()/2, rocketY); 
62 } 
63   
64 /** 
65 * 描绘的更新 
66 */ 
67 private void draw(Graphics g) { 
68    g.setColor(0xffffff); 
69    g.fillRect(0, 0, getWidth(), getHeight()); 
70    rocket.paint(g); 
71    flushGraphics(); 
72 } 
73   
74 /** 
75 * thread的停止 
76 */ 
77 public void stop() { 
78    running = false; 
79 } 
80 } 
    

 

 这次是使用GameCanvas和thread的例子了。
 恩,编译完成后赶快运行一下看看吧。

 

 

 准备的一张图像,被分割后,就变成了动画显示了。
 没错。第18、19行,在Sprite类的constructor中,指定Image对象和1个frame的宽度和高度后,Image的图像就被自动分割了。

 

Sprite对象的生成(18、19行)

Image image = Image.createImage("/rocket.png");
rocket = new Sprite(image, 32, 32);

 

 怎么指定显示的frame啊?
 利用Sprite类的setFrame(int index)方法可以指定显示哪一个frame。本讲有四种的frame,因此可以在index里面指定0到3的值。这个在loop处理调出的tick方法的第56行进行。
 在执行实际描绘的draw方法中没有指定描绘火箭的坐标吗?
 恩。Sprite类利用setPosition方法指定描绘的坐标。在第61行进行。因此,实际描绘时,只需把Graphics对象传递到paint方法的参量里就可以了。

 

Sprite的坐标指定(第61行)

rocket.setPosition(getWidth()/2, rocketY);

 

Sprite的描绘(第70行)

rocket.paint(g);

 

 原来是这样啊,我终于理解了frame号码的指定和描绘方法了。
 呵呵,动画的表示方法我们顺利的讲完了。下一讲,再继续讲一下Sprite与LayerManager类的方便功能吧。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值