📄 jumpcanvas.java
字号:
第三章
GameCanvas类
GameCanvas类主要是用来刷新屏幕显示自己设计的游戏背景图象。
GameCanvas类与Canvas类的不同
GameCanvas类能描绘设备分配给你的屏幕的所有区域。javax.microedition.lcdui.game.GameCanvas类不同于它的超类javax.microedition.lcdui.Canvas在于两个重要的方面:图形缓存和轮巡键值的能力。这两个变化给了游戏开发人员面对需要处理诸如击键事件、屏幕刷新事件时需要的更强大、精确的控制手段。
图形缓存的好处是它可以使图形对象在后端逐渐生成,然而在生成时可以瞬间显示,这样动画显示将会更加平滑。在Listing 3中的advance()方法中可以看到如何使用图形缓存。
(请回想方法advance()是在GameThread对象的主循环中被调用)。注意对于调整屏幕显示和重画屏幕,你所要做只是调用paint(getGraphics()),然后再调用flushGraphics().
为了使程序更有效率,如果你知道需要重画的只是屏幕的一部分,你可以使用flushGraphics()方法的另一个版本。作为一种经验,我尝试过用对repaint()方法的调用来替代对paint(getGraphics())和flushGraphics()的调用,再接着调用serviceRepaints()来实现屏幕重画,注意调用serviceRepaints()的前提是你的类必须是从Canvas类扩展而来,而不是GameCanvas。在本例简单的游戏调用来看,性能差别并不大,但如果你的游戏有大量复杂的图形,GameCanvas的使用毫无疑问将极大地增强性能。
轮巡按键状态的技巧对于游戏进程的管理是很重要的。你可以直接扩展Canvas类,同时如果你的游戏支持键盘操作,你必须实现keyPressed(int keyCode)接口。
接着玩家击键事件促使应用管理软件调用这个方法。但如果你的程序完全运行在自己的线程里,该方法可能根据游戏规则在任何点被调用。如果你忽略了样例中的同步处理代码块,这可能导致潜在的错误发生,例如一个线程正在修改与游戏当前状态值相关的数据而同时另一个线程正在利用这些数据进行计算的情况。样例程序很简单,你可以很容易跟踪到当你调用GameCanvas方法getKeyStates()时,是否获得了击键信息。
getKeystates()方法的一个额外利用是它可以告诉你是否多键被同时按下。一次仅有一个键值码被传入keyPressed(int keyCode)方法中,所以即使玩家同时按下多个键,对它来说也只是增加被调用的次数而不是一次传递多个键值。
在一个游戏中,每一次按键的精确时刻常常是非常重要的数据,所以Canvas类的keyPressed()方法其实缺失了很多有价值的信息。注意Listing 3中的checkKeys()方法,你可以看到getKeystates()方法的返回值中包含了所有按键信息。
你所要做的是将getKeyStates()方法的返回值和一个给定键值比如GameCanvas.LEFT_PRESSED进行“与”(&)运算,借此判断是否给定键值被按下。这是一个庞大的代码段,但是你可以找到它的主要逻辑脉络是,首先,主循环GameThread类告诉GameCanvas子类JumpCanvas去轮巡按键状态(细节请见Listing 3中的JumpCanvas.checkKeys()方法),接着一旦按键事件处理完毕,主循环GameThread类再调用JumpCanvas.advance()方法告诉LayerManager对图形做出相应的修改并最终在屏幕上画出来。
Listing 3是JumpCanvas.java的代码
Listing 3. JumpCanvas.java
package net.frog_parrot.jump;
import javax.microedition.lcdui.*;
import javax.microedition.lcdui.game.*;
/**
* This class is the display of the game.
*
* @author Carol Hamer
*/
public class JumpCanvas extends javax.microedition.lcdui.game.GameCanvas {
//---------------------------------------------------------
// Dimension fields
// (constant after initialization)
/**
* The height of the green region below the ground.
*/
static final int GROUND_HEIGHT = 32;
/**
* A screen dimension.
*/
static final int CORNER_X = 0;
/**
* A screen dimension.
*/
static final int CORNER_Y = 0;
/**
* A screen dimension.
*/
static int DISP_WIDTH;
/**
* A screen dimension.
*/
static int DISP_HEIGHT;
/**
* A font dimension.
*/
static int FONT_HEIGHT;
/**
* The default font.
*/
static Font FONT;
/**
* A font dimension.
*/
static int SCORE_WIDTH;
/**
* The width of the string that displays the time,
* saved for placement of time display.
*/
static int TIME_WIDTH;
/**
* Color constant
*/
public static final int BLACK = 0;
/**
* Color constant
*/
public static final int WHITE = 0xffffff;
//---------------------------------------------------------
// Game object fields
/**
* A handle to the display.
*/
private Display myDisplay;
/**
* A handle to the MIDlet object (to keep track of buttons).
*/
private Jump myJump;
/**
* The LayerManager that handles the game graphics.
*/
private JumpManager myManager;
/**
* Whether the game has ended.
*/
private boolean myGameOver;
/**
* The player's score.
*/
private int myScore = 0;
/**
* How many ticks you start with.
*/
private int myInitialGameTicks = 950;
/**
* This is saved to determine if the time string needs
* to be recomputed.
*/
private int myOldGameTicks = myInitialGameTicks;
/**
* The number of game ticks that have passed.
*/
private int myGameTicks = myOldGameTicks;
/**
* You save the time string to avoid recreating it
* unnecessarily.
*/
private static String myInitialString = "1:00";
/**
* You save the time string to avoid recreating it
* unnecessarily.
*/
private String myTimeString = myInitialString;
//-----------------------------------------------------
// Gets/sets
/**
* This is called when the game ends.
*/
void setGameOver() {
myGameOver = true;
myJump.pauseApp();
}
//-----------------------------------------------------
// Initialization and game state changes
/**
* Constructor sets the data, performs dimension calculations,
* and creates the graphical objects.
*/
public JumpCanvas(Jump midlet) throws Exception { //将主类Jump传入,这样的化,主类的参数就都可以在这个canvas中看到了
super(false);
myDisplay = Display.getDisplay(midlet);
myJump = midlet;
// Calculate the dimensions.
DISP_WIDTH = getWidth();
DISP_HEIGHT = getHeight();
Display disp = Display.getDisplay(myJump);
if(disp.numColors() < 256) {
throw(new Exception("game requires 256 shades"));
}
if((DISP_WIDTH < 150) || (DISP_HEIGHT < 170)) {
throw(new Exception("Screen too small"));
}
if((DISP_WIDTH > 250) || (DISP_HEIGHT > 250)) {
throw(new Exception("Screen too large"));
}
FONT = getGraphics().getFont();
FONT_HEIGHT = FONT.getHeight();
SCORE_WIDTH = FONT.stringWidth("Score: 000");
TIME_WIDTH = FONT.stringWidth("Time: " + myInitialString);
if(myManager == null) {
myManager = new JumpManager(CORNER_X, CORNER_Y + FONT_HEIGHT*2,
DISP_WIDTH, DISP_HEIGHT - FONT_HEIGHT*2 - GROUND_HEIGHT);
}
}
/**
* This is called as soon as the application begins.
*/
void start() {
myGameOver = false;
myDisplay.setCurrent(this);
repaint();
}
/**
* Sets all variables back to their initial positions.
*/
void reset() {
myManager.reset();
myScore = 0;
myGameOver = false;
myGameTicks = myInitialGameTicks;
myOldGameTicks = myInitialGameTicks;
repaint();
}
/**
* Clears the key states.
*/
void flushKeys() {
getKeyStates();
}
/**
* This version of the game does not deal with what happens
* when the game is hidden, so I hope it won't be hidden...
* see the version in the next chapter for how to implement
* hideNotify and showNotify.
*/
protected void hideNotify() {}
/**
* This version of the game does not deal with what happens
* when the game is hidden, so I hope it won't be hidden...
* see the version in the next chapter for how to implement
* hideNotify and showNotify.
*/
protected void showNotify() {}
//-------------------------------------------------------
// Graphics methods
/**
* Paint the game graphic on the screen.
*/
public void paint(Graphics g) {
// Clear the screen:
g.setColor(WHITE);
g.fillRect(CORNER_X, CORNER_Y, DISP_WIDTH, DISP_HEIGHT);
// Color the grass green:
g.setColor(0, 255, 0);
g.fillRect(CORNER_X, CORNER_Y + DISP_HEIGHT - GROUND_HEIGHT, DISP_WIDTH, DISP_HEIGHT);
// Paint the layer manager:
try {
myManager.paint(g);
} catch(Exception e) {
myJump.errorMsg(e);
}
// Draw the time and score:
g.setColor(BLACK);
g.setFont(FONT);
g.drawString("Score: " + myScore, (DISP_WIDTH - SCORE_WIDTH)/2, DISP_HEIGHT + 5 - GROUND_HEIGHT, g.TOP|g.LEFT);
g.drawString("Time: " + formatTime(), (DISP_WIDTH - TIME_WIDTH)/2, CORNER_Y + FONT_HEIGHT, g.TOP|g.LEFT);
// Write game over if the game is over:
if(myGameOver) {
myJump.setNewCommand();
// Clear the top region:
g.setColor(WHITE);
g.fillRect(CORNER_X, CORNER_Y, DISP_WIDTH, FONT_HEIGHT*2 + 1);
int goWidth = FONT.stringWidth("Game Over");
g.setColor(BLACK);
g.setFont(FONT);
g.drawString("Game Over", (DISP_WIDTH - goWidth)/2,
CORNER_Y + FONT_HEIGHT, g.TOP|g.LEFT);
}
}
/**
* A simple utility to make the number of ticks look like a time.
*/
public String formatTime() {
if((myGameTicks / 16) + 1 != myOldGameTicks) {
myTimeString = "";
myOldGameTicks = (myGameTicks / 16) + 1;
int smallPart = myOldGameTicks % 60;
int bigPart = myOldGameTicks / 60;
myTimeString += bigPart + ":";
if(smallPart / 10 < 1) {
myTimeString += "0";
}
myTimeString += smallPart;
}
return(myTimeString);
}
//-------------------------------------------------------
// Game movements
/**
* Tell the layer manager to advance the layers and then
* update the display.
*/
void advance() {
myGameTicks--;
myScore += myManager.advance(myGameTicks);
if(myGameTicks == 0) {
setGameOver();
}
// Paint the display.
try {
paint(getGraphics());
flushGraphics();
} catch(Exception e) {
myJump.errorMsg(e);
}
}
/**
* Respond to keystrokes.
*/
public void checkKeys() {
if(! myGameOver) {
int keyState = getKeyStates();
if((keyState & LEFT_PRESSED) != 0) {
myManager.setLeft(true);
}
if((keyState & RIGHT_PRESSED) != 0) {
myManager.setLeft(false);
}
if((keyState & UP_PRESSED) != 0) {
myManager.jump();
}
}
}
}
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -