实例说明:
1.实例规则
玩家控制一架飞机(该项目用一矩形代替);按手机方向键可以控制飞机上下左右移动,但飞机不能出屏幕,按中间键可向前方发射子弹,按新号键,飞机发射炮弹的火力会逐渐增强,当炮弹触屏后会自动消失。
2.实例效果:
该实例在模拟器上的运行效果如下:
飞机的初始火力
按*号键后的火力
3.类设计:
(1)com.main包下的MainMidet主类和MyCanvas画布类;
①.MainMidlet主类:
package com.main;
import javax.microedition.lcdui.Display;
import javax.microedition.midlet.MIDlet;
import javax.microedition.midlet.MIDletStateChangeException;
public class MainMidlet extends MIDlet {
public MainMidlet() {
//显示游戏画布类
Display.getDisplay(this).setCurrent(new MyCanvas());
}
public void exit() {
try {
destroyApp(true);
} catch (MIDletStateChangeException e) {
e.printStackTrace();
}
this.notifyDestroyed();// 通知结束应用程序
}
protected void destroyApp(boolean arg0) throws MIDletStateChangeException {
}
protected void pauseApp() {
// 由被呼叫或其他原因使程序暂停
}
protected void startApp() throws MIDletStateChangeException {
}
}
②.MyCanvas游戏画布类
package com.main;
import javax.microedition.lcdui.Canvas;
import javax.microedition.lcdui.Font;
import javax.microedition.lcdui.Graphics;
import javax.microedition.lcdui.Image;
import com.data.FontLib;
import com.data.ScreenData;
import com.stage.GameStage;
import com.stage.SuperStage;
public class MyCanvas extends Canvas {
private Image backscreen;//后台屏幕
private Graphics backgraphics;//后台画笔
public static final int FPS = 20;//游戏每秒刷新率
private SuperStage stage;//当前舞台对象的引用
private int key;//用户按键的键值
private int frame;
private long frametimestar = 0;
private int totalframe = 20;
public MyCanvas() {
this.setFullScreenMode(true);
key = -100;
backscreen = Image.createImage(ScreenData.SCREENWIDTH,ScreenData.SCREENHEIGHT);
backgraphics = backscreen.getGraphics();
stage = new GameStage(this);//默认为GameStage
//启动游戏线程
new GameThread().start();
}
protected void paint(Graphics g) {
g.drawImage(backscreen, 0, 0, 20);//将后台屏幕渲染到前台设备
}
//将游戏元素绘制在后台屏幕上
public void render(){
frame++;
backgraphics.setColor(-1);//设置颜色为白色
backgraphics.fillRect(0, 0, ScreenData.SCREENWIDTH, ScreenData.SCREENHEIGHT);
stage.draw(backgraphics);//渲染舞台
if(frametimestar == 0){
frametimestar = System.currentTimeMillis();
}
if(System.currentTimeMillis() - frametimestar >= 1000){
totalframe = frame;
frame = 0;
frametimestar = 0;
}
drawFPS();
}
private void drawFPS(){//将每秒刷新的频率(每秒刷新的帧数)绘到屏幕上以便观察和衡量游戏的刷新速度;
backgraphics.setColor(0xffff0000);//设置画笔的颜色
Font defaultfont = backgraphics.getFont();//得到设画笔的字体
backgraphics.setFont(FontLib.MEDIUM);//设置画笔字体
backgraphics.drawString("FPS:"+totalframe, 0, 0, 20);
backgraphics.setFont(defaultfont);
}
public void input(){//接收用户输入
stage.input(key);//调用游戏舞台接收用户输入的input方法
}
public void releaseKey(){
key = -100;//在做单键按下时,用户释放按键,键值无条件归为-100;
}
public void logic(){//处理游戏逻辑
stage.logic();//调用游戏舞台的逻辑
}
protected void keyPressed(int keyCode) {//用户的单键按下事件
key = keyCode;
}
protected void keyReleased(int keyCode) {//用户单键释放事件
key = -100;
}
private class GameThread extends Thread{//游戏线程
public void run(){
while(true){
long start = System.currentTimeMillis();
input();//用户输入
logic();//游戏逻辑
render();//渲染
repaint();//请求重绘
long end = System.currentTimeMillis();
if(end - start <= 1000/FPS){//每秒刷新的次数
try {
Thread.sleep(1000/FPS - (end - start));
} catch (InterruptedException e) {
e.printStackTrace();
}
}else{
try {
Thread.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
}
(2)com.data包下的字体接口FontLib和屏幕数据接口ScreenData;
①FontLib常用字体接口:(封装了常用字体);
package com.data;
import javax.microedition.lcdui.Font;
//封装常用字体
public interface FontLib {
Font SMALL = Font.getFont(Font.FACE_MONOSPACE, Font.STYLE_PLAIN, Font.SIZE_SMALL);
Font MEDIUM = Font.getFont(Font.FACE_MONOSPACE, Font.STYLE_PLAIN, Font.SIZE_MEDIUM);
Font LARGE = Font.getFont(Font.FACE_MONOSPACE, Font.STYLE_PLAIN, Font.SIZE_LARGE);
}
②ScreenData屏幕数据接口(封装了手机屏幕的宽高和键盘的按键对应的值);
package com.data;
import javax.microedition.lcdui.Canvas;
public interface ScreenData {
int SCREENWIDTH = 240;//屏幕宽
int SCREENHEIGHT = 320;//屏幕高
//左右上下及*号键
int LEFT = -3;
int RIGHT = -4;
int UP = -1;
int DOWN = -2;
int STAR = Canvas.KEY_STAR;
}
(3)com.statge游戏舞台类的设计:
设计思想:每一个游戏都应该有游戏菜单,游戏帮助,游戏设置,游戏大厅界面;每个界面好比是一个舞台,用户在游戏菜单,玩游戏,游戏帮助,游戏设置中的切换,可以看作是舞台之间的
相互切换;所有的游戏舞台可以抽象出一个父类(SuperStage);父类中有一个游戏画布Mycanvas类对象的引用,并且包含了以下抽象方法:初始化舞台(init());处理逻辑(logic);处理舞台按键事件(input());绘制舞台(draw());释放舞台资源(gc());
①SuperStage舞台抽象类的设计
package com.stage;
import javax.microedition.lcdui.Graphics;
import com.main.MyCanvas;
public abstract class SuperStage {
protected MyCanvas canvas;
public SuperStage(MyCanvas canvas) {// 每一个舞台都A
this.canvas = canvas;
}
public abstract void init();// 初始化舞台
public abstract void gc();// 释放舞台资源
public abstract void logic();// 处理逻辑
public abstract void draw(Graphics g);// 绘制舞台
public abstract void input(int key);// 处理舞台按键事件
}
②子类游戏舞台类(GameStage)的设计;
这里只写了GameStage类继承SuperStage;以后可以根据游戏的具体情况设置相应的舞台类;如(菜单舞台(MenuStage),设置舞台(SetStage)),GameStage类的代码具体如下:
package com.stage;
import javax.microedition.lcdui.Graphics;
import com.data.ScreenData;
import com.game.Bullet;
import com.game.Plane;
import com.main.MyCanvas;
public class GameStage extends SuperStage {
private Plane myplane;//游戏飞机类型的成员变量
public GameStage(MyCanvas canvas) {
super(canvas);
init();
}
public void init() {//初始化游戏飞机的位置
int w = 50;
int h = 30;
myplane = new Plane((ScreenData.SCREENWIDTH>>1) - (w >> 1), (ScreenData.SCREENHEIGHT-h-10), w, h);
}
public void gc() {//释放资源
}
public void input(int key) {//用户输入并根据相应的情况移动飞机的位置
if(key == ScreenData.LEFT){
myplane.move(-5, 0);
if(myplane.rect.x<=0){
myplane.rect.x=0;
return;
}
}else if(key == ScreenData.RIGHT){
myplane.move(5, 0);
if(myplane.rect.x>=ScreenData.SCREENWIDTH-myplane.rect.w){
myplane.rect.x=ScreenData.SCREENWIDTH-myplane.rect.w;
return;
}
}else if(key == ScreenData.UP){
myplane.move(0, -5);
if(myplane.rect.y<=0){
myplane.rect.y=0;
return;
}
}else if(key == ScreenData.DOWN){
myplane.move(0, 5);
if(myplane.rect.y>=ScreenData.SCREENHEIGHT-myplane.rect.h){
myplane.rect.y=ScreenData.SCREENHEIGHT-myplane.rect.h;
return;
}
}else if(key == ScreenData.STAR){//增加火力
myplane.power += 2;
canvas.releaseKey();
}
}
long start = 0;
public void logic() {//更新逻辑
if(start == 0){
start = System.currentTimeMillis();
}
//更新飞机子弹容器
if(System.currentTimeMillis() - start >= 100){//每隔100毫秒飞机自动开火
myplane.fire();
start = 0;
}
//更新子弹的逻辑
//从容器中取出子弹并移动
for (int i = 0; i < myplane.bullet_v.size(); i++) {
Bullet temp = (Bullet) myplane.bullet_v.elementAt(i);
temp.move(0, -6);
if(temp.outScreen()){
temp.visible = false;
myplane.bullet_v.removeElementAt(i);
i--;
}
}
}
public void draw(Graphics g) {
myplane.draw(g);
for (int i = 0; i < myplane.bullet_v.size(); i++) {
Bullet temp = (Bullet) myplane.bullet_v.elementAt(i);
temp.draw(g);
}
}
}
(4)com.gameitem包下的Ractangle类设计:
为什么要设计这个类呢;我们可以这样想,子弹和飞机都有自己的x,y坐标和宽高属性,我们可以将他们的x,y坐标和宽高属性封装成一个矩形;生成一个子弹对象货飞机对象的时候都生成一个矩形对象 存储对应子弹或者飞机的坐标和宽高属性。
Rectangle类的代码如下
package com.gameitem;
import com.data.ScreenData;
public class Rectangle {
public int x,y;
public int w,h;
public Rectangle(int x,int y,int w,int h) {
this.x = x;
this.y = y;
this.w = w;
this.h = h;
}
public void move(int dx,int dy){
this.x += dx;
this.y += dy;
}
public boolean outScreen(){//判断是否出屏
if(this.x + this.w <= 0 || this.x >= ScreenData.SCREENWIDTH || this.y + this.h <= 0 ||
this.y >= ScreenData.SCREENHEIGHT)
return true;
return false;
}
}
(5)com.game包下的飞机类(Plane)和子弹类(bullet)的设计;
①Plane类的设计:
Plane除了有包含自身坐标和宽高属性的矩形类成员变量外,还应该有存储子弹的容器类Vector对象;用于存储子弹,同时飞机应该有开火(fire()),移动(move())和绘图(draw())方法;
Plane类的代码具体如下:
package com.game;
import java.util.Vector;
import javax.microedition.lcdui.Graphics;
import com.gameitem.Rectangle;
public class Plane {
public Rectangle rect;
public int power ;
public Vector bullet_v;
public Plane(int x,int y,int w,int h) {
this.rect = new Rectangle(x, y, w, h);
this.power = 1;
bullet_v = new Vector();
}
public void move(int dx,int dy){
this.rect.move(dx, dy);
}
public void draw(Graphics g){
g.setColor(0xff0000ff);
g.fillRect(this.rect.x, this.rect.y, this.rect.w, this.rect.h);
}
public void fire() {//飞机开火
int w = 6;
int h = 12;
//开火的时候向飞机的子弹容器中添加子弹
for(int i = -power/2; i <= power/2 ; i++){
Bullet temp = new Bullet(this.rect.x+this.rect.w/2 - w/2 + i*10, this.rect.y-h+6, w, h);
this.bullet_v.addElement(temp);
}
}
}
②子弹类(Bullet)的设计
Bullet除了有包含自身坐标和宽高属性的矩形类成员变量外,同时应该有移动(move())和绘图(draw())方法;
Bullet类的代码具体如下:
package com.game;
import javax.microedition.lcdui.Graphics;
import com.gameitem.Rectangle;
public class Bullet {
public Rectangle rect;
public boolean visible;
public Bullet(int x,int y,int w,int h) {
rect = new Rectangle(x, y, w, h);
this.visible = true;
}
public void move(int dx,int dy){
this.rect.move(dx, dy);
}
public boolean outScreen(){
return this.rect.outScreen();
}
public void draw(Graphics g){
if(!visible)
return;
g.setColor(0xffff0000);
g.fillRect(this.rect.x, this.rect.y, this.rect.w, this.rect.h);
}
}
注意:如果子弹出屏幕后,还对其进行重绘,也不从游戏飞机的子弹容器中移除;随着飞机的开火(fire())功能被调用,容器中的子弹越来越多,屏幕上要绘制的子弹对象越来越多,这样对内存的消耗逐渐变大,最终耗尽内存,如果上面的程序不进行出屏检测和在出片后移除子弹,FPS会逐渐减小,整个游戏的绘图将会出现问题;让我们取出出屏检测和移除子弹,随着游戏的运行,将会出现如下图情况:
其次,我们也可以在飞机类和子弹类中加入input ();logic();gc()等方法,减少gamestage中这些方法的代码量。