您的位置:首页 > 其它

J2ME游戏开发教程:初级动作闯关类-《混血王子与梦幻国度》(二)

2008-06-11 00:06 381 查看
所有资源图片下载请到我的blog:www.yinowl.com

状态机制
这一篇的开头先说一下游戏流程的核心:状态机制。我们给游戏定下若干状态,例如菜单界面、帮助界面、游戏主画面等等,比较大的状态下还可以细分子状态,然后通过状态切换,实现游戏的流程。作为例子,我们先定义几个状态,并实现这些界面
在Canvas类中添加如下状态,看名字应该大致能了解是什么界面了

static final int GAME_BEFORE=-1;
static final int GAME_LOGO1=0;
static final int GAME_LOGO2=1;
static final int GAME_SPLASH=2;
static final int GAME_MAIN=3;
static int gamestate=-1;
int laststate=-1;

gamestate保存了当前状态,laststate保存了上一个状态,也就是从哪个状态切换到了当前状态,然我们在之前run函数的省略代码处添加switch结构,更具当前的状态做响应的逻辑处理

switch(gamestate){
case GAME_BEFORE:
changeGameState(GAME_LOGO1);
break;
case GAME_LOGO1:
if(timecount timecount++;
else
changeGameState(GAME_LOGO2);
break;
case GAME_LOGO2:
if(timecount timecount++;
else
changeGameState(GAME_SPLASH);
break;
case GAME_SPLASH:
if(timecount timecount++;
else
changeGameState(GAME_MAIN);
break;
}

这个结构应该非常清晰,我们需要添加一个帧计数器timecount,游戏一次循环称为一帧,timecount就加1,LOGO1和LOGO2状态都是显示某个图片一定帧数然后通过changeGameState跳转到另一个状态,这里需要强调这个方法,整个游戏的状态管理是通过这个方法实现的,离开某个状态要做什么,进入新的状态要做什么,等等。在看这个方法的内容之前,我们要先添加几个刚才用到的常量和变量
int TIME_LOGO1=500;
int TIME_LOGO2=1000;
int TIME_SPLASH=300;
static int timecount=0;
changeGameState方法的结构,猜也能猜到,两个switch结构,一个处理上一状态,一个处理新状态。我们依旧先添加这几个状态的代码,内容非常相似,离开状态的时候都把帧计数器置0并且释放了图片,进入新状态的时候创建了新状态需要使用的图片,然后在方法的最后把当前状态赋给laststate,再把新状态赋给gamestate,实现了状态切换。

public void changeGameState(int newgamestate){
switch(gamestate){
case GAME_LOGO1:
timecount=0;
firstImg=null;
break;
case GAME_LOGO2:
timecount=0;
boxImg=null;
logoImg=null;
break;
case GAME_SPLASH:
timecount=0;
splashImg=null;
splashbgImg=null;
splashcpImg=null;
break;
case GAME_MAIN:
tempIdx=0;//main主菜单选项的当前选定值
splashImg=null;
splashbgImg=null;
splashcpImg=null;
menuImg=null;
menubgImg=null;
initGame();//正式进入游戏的初始化
break;
}
switch(newgamestate){
case GAME_LOGO1:
firstImg=Util.myCreateImage("first");
break;
case GAME_LOGO2:
boxImg=Util.myCreateImage("box");
logoImg=Util.myCreateImage("logo");
break;
case GAME_SPLASH:
splashImg=Util.myCreateImage("splash");
splashbgImg=Util.myCreateImage("splashbg");
splashcpImg=Util.myCreateImage("splashcp");
break;
case GAME_MAIN:
splashImg=Util.myCreateImage("splash");
splashbgImg=Util.myCreateImage("splashbg");
splashcpImg=Util.myCreateImage("splashcp");
menuImg=Util.myCreateImage("menu");
menubgImg=Util.myCreateImage("menubg");
break;
}
laststate=gamestate;
gamestate=newgamestate;
}

这里我们需要添加几个图片对象的声明,和Image包
import javax.microedition.lcdui.Image;

int tempIdx=0;
Image firstImg,boxImg,logoImg;
Image splashImg,splashbgImg,splashcpImg,menuImg,menubgImg;

这里用到了Util类的myCreateImage方法,这是一个对图片创建的封装,之前提到过Uitl类是一个工具类,是对一些静态方法的封装,这个游戏中我们只有这一个方法,整个Uitl类的内容如下:
package com.eshouji.theball;
import java.io.IOException;
import javax.microedition.lcdui.Image;

public class Util {
public static Image myCreateImage(String filename){
Image image=null;
try{
image=Image.createImage("/"+filename+".png");
}catch(IOException e){System.out.println("create image error!");}
return image;
}
}
这里还有一个方法我们没有建立,就是initGame,我们先在Canvas类中创建一个空的方法,内容以后再说
private void initGame() {
}
到目前为止,我们的代码在IDE中应该是没有异常的,现在游戏的前几个状态的逻辑已经有了,状态切换也已经有了,要让游戏在前几个状态跑起来我们还需要完善paint方法,完成在各个状态下的屏幕绘制,在paint发放中添加:
g.setColor(0xFFFFFF);
g.fillRect(0,0,CANVASW,CANVASH);
switch(gamestate){
case GAME_LOGO1:
paintLogo1Screen(g);
break;
case GAME_LOGO2:
paintLogo2Screen(g);
break;
case GAME_SPLASH:
paintSplashScreen(g);
break;
case GAME_MAIN:
paintMainScreen(g);
break;
}
这种结构我们应该已经非常熟悉了,之前已经多次用到。我们在paint的最前面用白色填充屏幕,清除上一帧的画面,然后根据不同的状态调用不同的方法绘制,我们接着添加这些方法
private void paintLogo1Screen(Graphics g) {
g.drawImage(firstImg,CANVASW>>1,CANVASH>>1,Graphics.HCENTER|Graphics.VCENTER);
}
private void paintLogo2Screen(Graphics g) {
g.drawImage(logoImg,CANVASW>>1,CANVASH>>1,Graphics.HCENTER|Graphics.VCENTER);
g.drawImage(boxImg,0,0,Graphics.LEFT|Graphics.TOP);
g.setColor(0x626466);
g.drawRect((CANVASW>>2)-1,((CANVASW+logoImg.getHeight())>>1)+20,(CANVASW>>1)+1,2);
g.setColor(0x00AB9D);
g.drawRect(CANVASW>>2,((CANVASW+logoImg.getHeight())>>1)+21,timecount*((CANVASW>>1))*TIME_PER_FRAME/TIME_LOGO2,0);//进度条
}
private void paintSplashScreen(Graphics g) {
paintBGScreen(g);
}
private void paintMainScreen(Graphics g) {
paintBGScreen(g);
g.drawImage(menubgImg,CANVASW>>1,(CANVASH*3)>>2,Graphics.HCENTER|Graphics.VCENTER);
g.setClip((CANVASW-menuW)>>1,((CANVASH*3)>>2)-(menuH>>1),menuW,menuH);//确定菜单位置,设置只在该区域可以绘制图片
g.drawImage(menuImg,CANVASW>>1,((CANVASH*3)>>2)-(tempIdx<<4)+tempIdx-(menuH>>1),Graphics.HCENTER|Graphics.TOP);//根据tempIdx计算菜单图片当前选项的偏移量,然后绘制图片
g.setClip(0,0,CANVASW,CANVASH);
}
private void paintBGScreen(Graphics g) {
for(int i=0;i for(int j=0;j g.drawImage(splashbgImg,i,j,Graphics.LEFT|Graphics.TOP);
g.drawImage(splashImg,CANVASW>>1,CANVASH/3,Graphics.VCENTER|Graphics.HCENTER);
g.drawImage(splashcpImg,CANVASW>>1,CANVASH-10,Graphics.HCENTER|Graphics.BOTTOM);
}
这里大量用到了CANVASH等几个常量,我们一定要习惯于把这些固定不变的值定义成一个常量,千万不要把数值直接写在代码里,否则对于之后的修改非常的困难。在Canvas类中添加这几个常量
static int CANVASW=176;//这是我们屏幕的宽和高
static int CANVASH=204;//这应该是代码里最最最基本的一对常量了
static int menuW=58;//菜单元素的宽
static int menuH=15;//菜单元素的高
paintLogo2Screen和paintLogo1Screen的代码比较简单,就是画几张图和一个进度条,可以看出来,这个进度条是假的,根据timecount计数和这一状态的停留时间平均的画出了进度条(呵呵,我们那时候的游戏都是这个样子)。只有paintMainScreen稍微有一点点复杂,这里根据tempIdx这个选项计数画出菜单文字图上的一部分。

好了,让我们现在在IDE中运行一下我们的程序。你的程序现在应该能够运行到主菜单看到“开始游戏”这个选项。我写这个教程的时候,另建了一个工程,每写一点就考响应的代码过去,所以我能跑起来你也应该可以,当然一定要记得把这个游戏的资源包下载回来(也许有的地方只转载了文章,请到http://www.yinowl.com文章的开头下载资源,当然全文也在那里)。如果你的程序跑起来了,你一定会发现不能切换主菜单的选项,没错,因为我们还差最后一部分内容,各状态的按键处理,让我们现在就来加上,结构依旧是一样的switch-case结构,在Canvas类的keyPressed方法中加上:
switch(gamestate){
case GAME_MAIN:
if (keyAction == Canvas.UP)
tempIdx=(tempIdx-1+MAIN_COUNT)%MAIN_COUNT;
else if (keyAction == Canvas.DOWN)
tempIdx=(tempIdx+1)%MAIN_COUNT;
else if(keyAction==Canvas.FIRE || keyCode==keySoftRight || keyCode==-keySoftRight)
keyActionMain(tempIdx);
break;
}
现在只有一个状态的代码,因为前面几个状态都是自动跳转的,不需要接收用户按键,在GAME_MAIN这个状态,用户按上下键切换菜单项,按中间键和左右软键都可以激活选项,因为不同手机的左右软键和中间键键值不同,我们定义了这几个变量方便修改。激活选项后调用了keyActionMain方法,并且把选项值传入作不同处理,在Canvas类中添加变量常量和方法:
static final int MAIN_START=0;
static final int MAIN_RETURN=1;
static final int MAIN_HELP=2;
static final int MAIN_SET=3;
static final int MAIN_ABOUT=4;
static final int MAIN_EXIT=5;
static final int MAIN_COUNT=6;

int keySoftLeft=21; //左软键
int keySoftRight=22; //右软键
int keySoftAction=20; //中间确认键

private void keyActionMain(int tempIdx) {
switch(tempIdx){
case MAIN_START:
if(gamestate==GAME_PAUSE)
changeGameState(GAME_LEVEL);
else{
nowLevel=0;
changeGameState(GAME_START);
}
break;
case MAIN_HELP:
changeGameState(GAME_HELP);
break;
case MAIN_ABOUT:
changeGameState(GAME_ABOUT);
break;
case MAIN_EXIT:
changeGameState(GAME_EXIT);
break;
case MAIN_SET:
changeGameState(GAME_SET);
break;
case MAIN_RETURN:
if(gamestate==GAME_PAUSE)
changeGameState(GAME_GAMING);
else{
nowLevel=maxLevel;
changeGameState(GAME_LEVEL);
}
break;
}
}
哇,这里一下子又出现了好多游戏状态,是的,当用户在主菜单激活选项后,程序就更具不同的选项进入不同的状态了,比如进入帮助界面。
我们在之前添加状态常量的地方再把这几个状态加上,LEVEL是选关状态,既然有选关,那一定就有当前关变量和最大关变量了。其他的几个状态应该看字面就理解了。
static final int GAME_GAMING=4;
static final int GAME_HELP=5;
static final int GAME_ABOUT=6;
static final int GAME_SET=7;
static final int GAME_LEVEL=8;
static final int GAME_EXIT=9;
static final int GAME_PAUSE=10;
static final int GAME_START=11;

static int nowLevel=0;
int maxLevel=0;
现在再跑一下程序,哈哈,主菜单能切换选项了,但是可千万激活选项啊,我们还没有这几个状态下的代码,进去了可就出不来了,现在点进去就白屏了,想想为什么呢?
接下来我们一口气把HELP,ABOUT,SET,EXIT这几个状态的代码添加上,虽然有点无聊,不过我们会同时把游戏的声音添加上,SET状态就是设置声音开关的
在Canvas类paint方法的switch结构中添加下面这几个case并添加被调用的几个方法
case GAME_HELP:
paitnHelpScreen(g);
break;
case GAME_ABOUT:
paitnAboutScreen(g);
break;
case GAME_SET:
paintSetScreen(g);
break;
paintHelpScreen方法绘制帮助界面,同样要一起添加一些常量和变量,这里我们又用到了tempIdx变量来保存当前页id,我们可以在多个状态公用变量,但这是很不安全的,我们一定要保证切换状态的时候要把这个状态重置,也就是置0,否则很有可能会数组越界。这里我们需要导入Font类
import javax.microedition.lcdui.Font;

final String[] strGameHelp ={
"游戏简介:",
"有一天,球球公主在草原",
"上散步时被方块恶魔所",
"捕获,球球王子当当为了",
"救出公主,开始向方块国",
"进发。",
"当玩家面对强敌时,根据",
"不同的弱点,抓住时机向",
"他们体内充气,令其漂浮",
"到空中。",
"按键操作:",
"数字1:向左跳跃",
"数字3:向右跳跃",
"方向上/数字2:向上跳跃",
"方向左/数字4:向左移动",
"方向右/数字6:向右移动",
"中间确定键/数字5:攻击",
"游戏规则:",
"游戏中有隐藏的陷井,控",
"制人物跳跃时要掌握好",
"角度,不然会碰壁被弹回",
"失命后按不同的控制键",
"主人公会有不同的复活",
"效果,能否救出公主就看",
"你的表现啦."};
static final Font lowFont = Font.getFont (Font.FACE_SYSTEM, Font.STYLE_PLAIN, Font.SIZE_SMALL); //创建系统字体对象,设置字体大小等属性
int HELP_LINE=9;//每页显示9行

private void paitnHelpScreen(Graphics g) {
g.setColor(0x00008080);
g.fillRect(0,0,CANVASW,CANVASH);
g.setFont(lowFont);
g.setColor(0x00FFFF99);
for(int i=0;i if(tempIdx*HELP_LINE+i g.drawString(strGameHelp[tempIdx*HELP_LINE+i],5,4+(lowFont.getHeight())*i,Graphics.TOP|Graphics.LEFT);
}
if(tempIdx>0){
g.drawLine((CANVASW>>1)-2,3,CANVASW>>1,0);
g.drawLine((CANVASW>>1)+2,3,CANVASW>>1,0);
}
if(tempIdx g.drawLine((CANVASW>>1)-2,CANVASH-4,CANVASW>>1,CANVASH-1);
g.drawLine((CANVASW>>1)+2,CANVASH-4,CANVASW>>1,CANVASH-1);
}
g.setColor(0x00FFFFFF);
g.drawString("返回",0,CANVASH,Graphics.LEFT|Graphics.BOTTOM);
}

paitnAboutScreen方式绘制关于界面,里面有公司的简介,这个就不多说了,和HELP状态几乎一样
String[] strGameAbout={ "欢乐金网",
"信息技术有限公司",
"eShouJi InfoTech",
"客服电话",
"010-82630056",
"客服信箱",
"Service@eshouji.com",
"制作人员",
"策划:石宇",
"美工:张文卓、王亮",
"程序:yinowl",
"想拥有更多精彩内容",
"请依次执行以下步骤:",
"进入功能页面->",
"访问网络->转到网址,",
"输入wap.eshouji.com",
"即可轻松享有."};

private void paitnAboutScreen(Graphics g) {
g.setColor(0x00008080);
g.fillRect(0,0,CANVASW,CANVASH);
g.setFont(lowFont);
g.setColor(0x00FFFF99);
for(int i=0;i if(tempIdx*HELP_LINE+i g.drawString(strGameAbout[tempIdx*HELP_LINE+i],88,4+(lowFont.getHeight())*i,Graphics.TOP|Graphics.HCENTER);
}
if(tempIdx>0){
g.drawLine((CANVASW>>1)-2,3,CANVASW>>1,0);
g.drawLine((CANVASW>>1)+2,3,CANVASW>>1,0);
}
if(tempIdx g.drawLine((CANVASW>>1)-2,CANVASH-4,CANVASW>>1,CANVASH-1);
g.drawLine((CANVASW>>1)+2,CANVASH-4,CANVASW>>1,CANVASH-1);
}
g.setColor(0x00FFFFFF);
g.drawString("返回",0,CANVASH,Graphics.LEFT|Graphics.BOTTOM);
}

最后就是paintSetScreen来绘制游戏设置界面,界面的绘制依旧和前两个差不多,没什么复杂的,我们这里使用了两个变量,canSound用来在之后的代码中控制是否有音乐,canShake控制手机的震动

static String[] strGameSet={"音 效","振 动"};
boolean canSound=true;
boolean canShake=true;

private void paintSetScreen(Graphics g) {
g.setColor(0x00008080);
g.fillRect(0,0,CANVASW,CANVASH);
g.setFont(lowFont);
g.setColor(0x00FFFF99);
g.drawString("游戏设置",CANVASW>>1,CANVASH>>2,Graphics.HCENTER|Graphics.TOP);
for(int i=0;i if(tempIdx==i)
g.setColor(0x00FFFFFF);
else
g.setColor(0x00FFFF99);
g.drawString(strGameSet[i],CANVASW>>2,(CANVASH>>2)*(i+2),Graphics.LEFT|Graphics.TOP);
g.drawString("开",(CANVASW>>1),(CANVASH>>2)*(i+2),Graphics.LEFT|Graphics.TOP);
g.drawString("关",(CANVASW*3)>>2,(CANVASH>>2)*(i+2),Graphics.RIGHT|Graphics.TOP);
if(tempIdx==0)
g.setColor(0x00FFFFFF);
else
g.setColor(0x00FFFF99);
if (canSound){
g.drawRect(CANVASW>>1,CANVASH>>1,lowFont.stringWidth("开"),lowFont.getHeight());
}
else{
g.drawRect(((CANVASW*3)>>2)-lowFont.stringWidth("关"),CANVASH>>1,lowFont.stringWidth("关"),lowFont.getHeight());
}
if(tempIdx==1)
g.setColor(0x00FFFFFF);
else
g.setColor(0x00FFFF99);
if (canShake){
g.drawRect(CANVASW>>1,(CANVASH>>2)*3,lowFont.stringWidth("开"),lowFont.getHeight());
}
else{
g.drawRect(((CANVASW*3)>>2)-lowFont.stringWidth("关"),(CANVASH>>2)*3,lowFont.stringWidth("关"),lowFont.getHeight());
}
}
g.setColor(0x00FFFFFF);
g.drawString("确定",CANVASW,CANVASH,Graphics.RIGHT|Graphics.BOTTOM);
}

这样,这3个状态的绘制就完成了,赶紧运行一下看看,没问题,我们已经能够看到内容了,但是不能翻页也不会返回到主菜单,因为我们还没添加这几个状态的按键处理代码,还是在Canvas类的keyPressed方法中的switch接口添加下面几个case
case GAME_HELP:
if(keyAction==Canvas.DOWN){
if(tempIdx tempIdx++;
}
else if(keyAction==Canvas.UP){
if(tempIdx>0)
tempIdx--;
}
if(keyCode==keySoftLeft || keyCode==-keySoftLeft){//因为有的机型键值是负的但是绝对值一样,所以这里多了一个判断
changeGameState(laststate);
}
break;
case GAME_ABOUT:
if(keyAction==Canvas.DOWN){
if(tempIdx tempIdx++;
}
else if(keyAction==Canvas.UP){
if(tempIdx>0)
tempIdx--;
}
if(keyCode==keySoftLeft || keyCode==-keySoftLeft){
changeGameState(laststate);
}
break;
case GAME_SET:
keyActionSet();
break;
这段代码也应该很好理解,就是按键后通过改变tempIdx来翻页,我们来看看keyActionSet的内容,按上下修改tempIdx来改变是设置声音还是震动,按左右根据tempIdx来改变canSound和canShake,也应该很好理解。下面有一个play对象,这就是用来播放声音的Player类的对象。
private void keyActionSet() {
if(keyAction==Canvas.UP){
tempIdx=(tempIdx-1+strGameSet.length)%strGameSet.length;
}
else if(keyAction==Canvas.DOWN){
tempIdx=((tempIdx+1)%strGameSet.length);
}
else if(keyAction==Canvas.LEFT){
if(tempIdx==0)
canSound=!canSound;
else if(tempIdx==1){
canShake=!canShake;
}
}
else if(keyAction==Canvas.RIGHT){
if(tempIdx==0)
canSound=!canSound;
else if(tempIdx==1){
canShake=!canShake;
}
}
if(keyCode==keySoftRight || keyCode==-keySoftRight){
try{
if(canSound){
player.stop();
player.start();
}
else{
player.stop();
}
}catch(MediaException e){}
changeGameState(laststate);
}
}
我们需要导入异常类和Player类
import javax.microedition.media.MediaException;
import javax.microedition.media.Player;

接着我们就来补全声音部分的代码。在Canvas的构造方法TheBallCanvas中添加如下代码,这是一段标准的声音创建过程,J2ME的API文档里应该有实例
try{
InputStream in = getClass().getResourceAsStream("/musicbg.mid");
while(player==null)
player = Manager.createPlayer(in,"audio/midi");
player.realize();
toneControl = (ToneControl)player.getControl("ToneControl");
player.setLoopCount(-1);
}catch(IOException e){}
catch(MediaException e){}
要让这段代码生效,我们同样需要导入几个类库和声明对象
import java.io.IOException;
import java.io.InputStream;
import javax.microedition.media.control.ToneControl;
import javax.microedition.media.Manager;

Player player;
ToneControl toneControl ;
这样,我们就可以在后面的代码中实用player对象来播放声音了,我们在changeGameState方法switch(gamestate)结构的case GAME_SPLASH:中添加如下代码,然后运行一下试试,应该就有背景音乐了
if(canSound){
try{
player.stop();
player.start();
}catch(MediaException e){}
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: