科技行者

行者学院 转型私董会 科技行者专题报道 网红大战科技行者

知识库

知识库 安全导航

至顶网软件频道基础软件MIDP 2.0 开发J2ME游戏起步之二

MIDP 2.0 开发J2ME游戏起步之二

  • 扫一扫
    分享文章到微信

  • 扫一扫
    关注官方公众号
    至顶头条

本节中,我将介绍Graphics对象类在游戏中被应用的主要方面。

作者:马岩 来源:天极网 2007年11月22日

关键字: MIDP2.0 J2ME 游戏

  • 评论
  • 分享微博
  • 分享邮件
用GameCanvas类处理Graphics对象

  本节中,我将介绍Graphics对象类在游戏中被应用的主要方面。在Tumbleweed游戏中,我要画一个穿越草原的牛仔。在设计中,我将玩家的得分情况显示在屏幕底端,而游戏时间显示在顶端(为使样例不至太过复杂,我仅仅在玩家使用玩规定的时间后才自动终止游戏)。因为牛仔独自在走,我的想法是让他的背景向右或者向左滚动(否则在这个狭小的屏幕上他好象根本没走多远),而时间和分数的显示不动。
为了实现这个设计,我用JumpCanvas类来画屏幕顶端和底端稳定不动的显示条,而动态有趣的图形是用LayerManager类来实现的。

  JumpCanva类创建时,你必须先分析要使用的显示屏幕。有一些显示屏幕信息来源于Graphics对象,一些来自于display对象,还有一些直接来源于GameCanvas类的方法。这些信息用来计算对象应当显示的位置,包括计算显示区域的尺寸,LayerManager子类(JumpManager)将在这些区域重复显示对象。如果你坚持维护Java“一处写就,处处运行”的特性,基于动态屏幕尺寸而不是基于常量尺寸设置屏幕显示区域显然更方便合理。

  当然,如果从一种目标显示设备转向另一种游戏需要巨大的代码更改量,显然应该根据不同设备显示维护多个游戏版本,运行时根据显示设备信息调用游戏的不同版本,而不是将所有代码置于一个版本中。

  在样例游戏中,如果实际显示的设备与游戏中定义的显示设备差别太大,将有一个异常抛出以便Jump类捕获并且显示警告给玩家。为了显得更专业,你应当确保给玩家的警告信息明确标明他针对当前的设备应该下载的版本号。

  也许有点多余,但我后面仍然会告诉大家我认为顶端和底端区域合适的尺寸,paint(Graphics g)方法将描绘出顶端的白色和底端的绿色,g.drawString()方法用来记录使用的时间和分数。(不用问我为什么游戏的草原中都是颜色相同的绿草和风滚草;唯一的解释是我对Java的了解当然远甚于西部荒原。)

  LayerManager类

  一个MIDP游戏中富有乐趣的图形对象常常是用javax.microedition.lcdui.game.Layer类的子类来展示。背景层是javax.microedition.lcdui.game.TiledLayer的实例化,游戏的主人公(和他的敌人)是javax.microedition.lcdui.game.Sprite的实例,这两个类都是Layer的子类。LayerManager类用来组织所有的图形对象层,追加你的图层对象到管理器LayerManager的顺序,决定了他们运行时被描绘的次序(后进先出,最先追加的最后显示)。规则是上面的图层将覆盖下面的图层,不过你可以通过创建包含透明区域的图象文件(上层)来部分地显示下层图层中你想要的部分。

  LayerManager类实际中最有用的也许是你可以创建一个远大于显示屏幕的图形对象,然后选择它将在屏幕中被显示的部分显示出来。你可以想象事先做出一幅巨大的、精心描绘的图画,然后用一张纸去覆盖它,而纸上有一个方孔在图画上自由移动。巨幅的整张图画代表你保存在LayberManager中的显示信息,而方孔是任一给定时刻在屏幕上显示图画的窗口。游戏代码设计中,一个比实际屏幕大得多的虚拟显示屏幕对于通常运行在小屏幕显示设备上的游戏是极其重要的,它将为你节省大量的时间和工作。

  举个例子,比如你的游戏中包含主人公在一个复杂的地牢中摸索前进的场景,最麻烦的是你必须处理两个独立坐标系统。GameCanva的图形对象有一个坐标系,但是根据LayerManager坐标系的要求,图形中的不同图层又需要被置于LayerManager中。所以,一定要牢记LayerManager.paint(Graphics g, int x, int y)方法根据GameCanvas的坐标系在屏幕上描绘图层,而LayerManager.setViewWindow(int x, int y, int width, int height)方法根据LayerManager坐标系的要求设置LayerManager的可视矩形的显示属性。

  在样例中,我设计了一个简单的背景(仅仅是一系列重复显示的草丛),但我想让牛仔从右向左行走的时候总是显示在屏幕中央,所以我需要不断改变LayerManager的图形可显示区域。这项工作是通过在LayerManager的子类JumpManager类中方法paint(Graphics g)中调用setViewWindow(int x, int y, int width, int height)方法完成的。更准确地说,逻辑是这样的:GameThread中的主循环调用JumpCanvas.checkKeys()来检查按键状态,并且通知JumpManager类牛仔此时应该向左、向右还是应该跳跃了。JumpCanva类通过调用setLeft(boolean left)或者jump()方法将信息传至JumpManager。如果信息表明牛仔此时应该向左走(向右与之类似),那么当GameThead调用JumpCanvas告诉JumpManager继续的时候(循环的下一步),JumpManager就告诉牛仔对象向左移动一个象素,同时通过向左移动视窗一个象素来保持牛仔在屏幕中央。你可以通过增加字段myCurrentLeftX的值(这个作为X坐标值传至setViewWindow(int x,int y,int width,int height)),接着调用myCowboy.advance(gameTicks, myLeft)来实现这两个动作

  当然,我可以不移动牛仔,也不把他追加到LayerManager,而只是独立地描绘他,这样也可以确保牛仔在屏幕中央。但显然通过将所有的移动对象置于相对静止的一系列图层中然后将视窗集中在牛仔对象上,借此保持所有对象的运动轨迹的方法要更容易。在通知牛仔前进的同时,风滚草对象Sprites和青草对象TiledLayer也也同时移动,然后检测牛仔是否被风滚草撞倒(在以下章节里我将说明实现这个功能的更多细节)。在移动了所有游戏对象后,JumpManager类调用wrap()方法来检查是否前端窗口到达了背景窗口的边缘,如果是,移动所有的有些对象以便视窗能继续在任一方向无限显示,接着JumpCanvas类重画每一个对象,再次执行游戏主循环。

  Wrap()方法需要多说几句。很不幸,如果你想让你的简单背景不确定地重复显示,LayerManager类并不提供现成的隐藏方法. 当传至方法setViewWindow(int x, int y, int width, int height)的坐标参数值大于Integer. MAX_VALUE 时,LayerManager的图形区域将被隐藏,但这似乎对我们的想法没有任何帮助。因此,你必须写自己的函数来避免牛仔Sprite对象离开背景区域。

  样例中,背景草丛总是在间隔数值Grass.TILE_WIDTH*Grass.CYCLE后被重画,所以任何时候视窗的X坐标值(myCurrentLeftX)都是背景图形宽度的整数倍。这样每次重画时,我都可以将视窗移回到显示中心,并且同时在相同的方向上移动Sprites对象,这样显然能平滑地阻止牛仔到达背景边界。

  Listing 4 JumpManager.java.

package net.frog_parrot.jump;

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

/**
* This handles the graphics objects.
*
* @author Carol Hamer
*/
public class JumpManager extends javax.microedition.lcdui.game.LayerManager {

//---------------------------------------------------------
// Dimension fields
// (constant after initialization)

/**
* The X coordinate of the place on the game canvas where
* the LayerManager window should appear, in terms of the
* coordinates of the game canvas.
*/
static int CANVAS_X;
/**
* The Y coordinate of the place on the game canvas where
* the LayerManager window should appear, in terms of the
* coordinates of the game canvas.
*/
static int CANVAS_Y;

/**
* The width of the display window.
*/
static int DISP_WIDTH;

/**
* The height of this object's graphical region. This is
* the same as the height of the visible part because
* in this game the layer manager's visible part scrolls
* only left and right but not up and down.
*/
static int DISP_HEIGHT;

//---------------------------------------------------------
// Game object fields

/**
* The player's object.
*/
private Cowboy myCowboy;

/**
* The tumbleweeds that enter from the left.
*/
private Tumbleweed[] myLeftTumbleweeds;

/**
* The tumbleweeds that enter from the right.
*/
private Tumbleweed[] myRightTumbleweeds;

/**
* The object representing the grass in the background.
*/
private Grass myGrass;

/**
* Whether the player is currently going left.
*/
private boolean myLeft;

/**
* The leftmost X coordinate that should be visible on the
* screen in terms of this objects internal coordinates.
*/
private int myCurrentLeftX;

//-----------------------------------------------------
// Gets/sets

/**
* This tells the player to turn left or right.
* @param left whether the turn is toward the left.
*/
void setLeft(boolean left) {
myLeft = left;
}

//-----------------------------------------------------
// Initialization and game state changes

/**
* Constructor sets the data and constructs the graphical objects.
* @param x The X coordinate of the place on the game canvas where
* the LayerManager window should appear, in terms of the
* coordinates of the game canvas.
* @param y The Y coordinate of the place on the game canvas where
* the LayerManager window should appear, in terms of the
* coordinates of the game canvas.
* @param width The width of the region that is to be
* occupied by the LayoutManager.
* @param height The height of the region that is to be
* occupied by the LayoutManager.
*/
public JumpManager(int x, int y, int width, int height)
throws Exception {
 CANVAS_X = x;
 CANVAS_Y = y;
 DISP_WIDTH = width;
 DISP_HEIGHT = height;
 myCurrentLeftX = Grass.CYCLE*Grass.TILE_WIDTH;
 setViewWindow(0, 0, DISP_WIDTH, DISP_HEIGHT);
 // Create the player:
 if(myCowboy == null) {
  myCowboy = new Cowboy(myCurrentLeftX + DISP_WIDTH/2, DISP_HEIGHT - Cowboy.HEIGHT - 2);
  append(myCowboy);
 }
 // Create the tumbleweeds to jump over:
 if(myLeftTumbleweeds == null) {
  myLeftTumbleweeds = new Tumbleweed[2];
  for(int i = 0; i < myLeftTumbleweeds.length; i++) {
   myLeftTumbleweeds[i] = new Tumbleweed(true);
   append(myLeftTumbleweeds[i]);
  }
 }
 if(myRightTumbleweeds == null) {
  myRightTumbleweeds = new Tumbleweed[2];
  for(int i = 0; i < myRightTumbleweeds.length; i++) {
   myRightTumbleweeds[i] = new Tumbleweed(false);
   append(myRightTumbleweeds[i]);
  }

  // Create the background object:
  if(myGrass == null) {
   myGrass = new Grass();
   append(myGrass);
  }
 }
 /**
  * Sets all variables back to their initial positions.
 */
 void reset() {
  if(myGrass != null) {
   myGrass.reset();
  }
  if(myCowboy != null) {
   myCowboy.reset();
  }
  if(myLeftTumbleweeds != null) {
   for(int i = 0; i < myLeftTumbleweeds.length; i++) {
   myLeftTumbleweeds[i].reset();
  }
 }
 if(myRightTumbleweeds != null) {
  for(int i = 0; i < myRightTumbleweeds.length; i++) {
   myRightTumbleweeds[i].reset();
  }
 }
 myLeft = false;
 myCurrentLeftX = Grass.CYCLE*Grass.TILE_WIDTH;
}
//-------------------------------------------------------
// Graphics methods

/**
* Paint the game graphic on the screen.
*/
public void paint(Graphics g) {
 etViewWindow(myCurrentLeftX, 0, DISP_WIDTH, DISP_HEIGHT);
 paint(g, CANVAS_X, CANVAS_Y);
}

/**
* If the cowboy gets to the end of the graphical region,
* move all of the pieces so that the screen appears to wrap.
*/
private void wrap() {
 if(myCurrentLeftX % (Grass.TILE_WIDTH*Grass.CYCLE) == 0) {
  if(myLeft) {
   myCowboy.move(Grass.TILE_WIDTH*Grass.CYCLE, 0);
   myCurrentLeftX += (Grass.TILE_WIDTH*Grass.CYCLE);
   for(int i = 0; i < myLeftTumbleweeds.length; i++) {
    myLeftTumbleweeds[i].move(Grass.TILE_WIDTH*Grass.CYCLE, 0);
   }
   for(int i = 0; i < myRightTumbleweeds.length; i++) {
    myRightTumbleweeds[i].move(Grass.TILE_WIDTH*Grass.CYCLE, 0);

   } else {
    myCowboy.move(-(Grass.TILE_WIDTH*Grass.CYCLE), 0);
    myCurrentLeftX -= (Grass.TILE_WIDTH*Grass.CYCLE);
    for(int i = 0; i < myLeftTumbleweeds.length; i++) {
     myLeftTumbleweeds[i].move(-Grass.TILE_WIDTH*Grass.CYCLE, 0);
    }
    for(int i = 0; i < myRightTumbleweeds.length; i++) {
     myRightTumbleweeds[i].move(-Grass.TILE_WIDTH*Grass.CYCLE, 0);
    }
   }
  }
 }

 //-------------------------------------------------------
 // Game movements

 /**
 * Tell all of the moving components to advance.
 * @param gameTicks The remaining number of times that
 * the main loop of the game will be executed
 * before the game ends.
 * @return The change in the score after the pieces
 * have advanced.
 */
 int advance(int gameTicks) {
  int retVal = 0;
  // First you move the view window
  // (so you are showing a slightly different view of
  // the manager's graphical area).
  if(myLeft) {
   myCurrentLeftX--;
  } else {
   myCurrentLeftX++;
  }
  // Now you tell the game objects to move accordingly.
  myGrass.advance(gameTicks);
  myCowboy.advance(gameTicks, myLeft);
  for(int i = 0; i < myLeftTumbleweeds.length; i++) {
   retVal += myLeftTumbleweeds[i].advance(myCowboy, gameTicks, myLeft, myCurrentLeftX, myCurrentLeftX + DISP_WIDTH);
   retVal -= myCowboy.checkCollision(myLeftTumbleweeds[i]);
  }
  for(int i = 0; i < myLeftTumbleweeds.length; i++) {
   retVal += myRightTumbleweeds[i].advance(myCowboy, gameTicks, myLeft, myCurrentLeftX, myCurrentLeftX + DISP_WIDTH);
   retVal -= myCowboy.checkCollision(myRightTumbleweeds[i]);
  }
  // Now you check if you have reached an edge of the viewable
  // area, and if so, you move the view area and all of the
  // game objects so that the game appears to wrap.
  wrap();
  return(retVal);
 }

 /**
 * Tell the cowboy to jump.
 */
 void jump() {
  myCowboy.jump();
 }
}
    • 评论
    • 分享微博
    • 分享邮件
    邮件订阅

    如果您非常迫切的想了解IT领域最新产品与技术信息,那么订阅至顶网技术邮件将是您的最佳途径之一。

    重磅专题
    往期文章
    最新文章