J2ME游戏设计框架

      因为移动设备运行速度问题,J2ME开发比较注重程序的复杂度。为了寻求时间复杂度与空间复杂度的一个平衡,通常将程序分为多个页面。多个页面就需要一个管理工具。本文展示了一种经典的J2ME中管理多个页面的调度器。

基础结构

为每一个页面编写一个类,在每一个类中实现以下方法

//无参的构造方法
//执行的run方法
public void run() {}
//绘图的paint方法,参数为屏幕的画笔
public void paint(Graphics g) {}
//按键时的响应方法,参数为按键消息码
public void keyPressed(int key) {}

调度器应该继承Canvas类,实现Runnable接口,完成以下功能

1、changeState方法

负责一个页面到另一个页面的切换。为了节约资源,应该根据旧的状态量释放当前页面,再根据请求的新状态创建另一个页面。

2、run方法

根据状态量判断当前哪个页面,并调用它(当前页面)的run方法。

3、paint方法

调用当前页面的paint(绘图)方法

4、keyPressed方法

调用当前页面的keyPressed(键盘相应)方法

这些是基本的功能,还可以根据需要添加更多的Canvas类中的方法,实现更多功能。

实现示例

这里演示了ModelLogo,ModelMenu和ModelGame三个页面的切换

public class ZYMIDlet extends MIDlet
{
	static ZYCanvas canvas = null;
	static ZYMIDlet midlet = null;
	/**初始动作 不要在构造函数中进行 可能出现问题
	 * 这主要是因为移动设备的资源性能都有限,
	 * 因此此种程序开发的一个宗旨是“在需要用资源的时候申请,用完立刻释放”
	 * */
	public ZYMIDlet()
	{
		midlet = this;
	}
	
	/**大多数初始化工作是放在startApp中完成
	 * midlet创建的时候 执行完构造函数,会调用此函数,midlet进入活跃状态
	 * midlet从暂停状态 恢复的时候 也会自动调用此函数*/
	public void startApp()
	{
		if(canvas == null)
		{
			canvas = new ZYCanvas();
		}
		Display display = Display.getDisplay(this);
		display.setCurrent(canvas);
	}
	public void pauseApp()
	{}	
	public void destroyApp(boolean unconditional)
	{}
}
class ZYCanvas extends Canvas implements Runnable
{
	public static final byte STATE_LOGO = 0;
	public static final byte STATE_MENU = 1;
	public static final byte STATE_GAME = 2;

	public static int state;

	public ModelLogo modelLogo;
	public ModelMenu modelMenu;
	public ModelGame modelGame;

	public ZYCanvas()
	{
		state = STATE_LOGO;

		//开启线程
		changeState(STATE_LOGO);
	}

	/**改变状态:这里可以做一些 上个状态的模块清除资源工作,和 新状态模块的初始化工作*/
	public static void changeState(byte new_state)
	{
		//可做的事情:清除上一个模块的资源
		state = new_state;
		//可作的事情:创建新状态模块,初始化
	}

	//每80毫秒执行一次
	public void run()
	{
		long time = System.currentTime();
		
		action();
		repaint();	//通知调用paint()函数
		serverPaint();	//强制paint执行

		long dur = System.currentTime() - time;
		long test = 80 - dur
		if(test > 0){
			try{Thread.sleep(test);}catch(Exception e){}
		}
	}
	//可以把要做的事情全部放到一个函数中
	public void action()
	{
		switch(state)
		{
			case STATE_LOGO: modelLogo.run();break;
			case STATE_MENU: modelMenu.run();break;
			case STATE_GAME: modelGame.run();break;		
		}	
	}


	//可以把要做的事情全部放到一个函数中
	public void paint(Graphics g)
	{
		switch(state)
		{
			case STATE_LOGO: modelLogo.paint(g);break;
			case STATE_MENU: modelMenu.paint(g);break;
			case STATE_GAME: modelGame.paint(g);break;		
		}	
	}
	
	public void keyPressed(int key)
	{
		switch(state)
		{
			case STATE_LOGO: modelLogo.keyPressed(key);break;
			case STATE_MENU: modelMenu.keyPressed(key);break;
			case STATE_GAME: modelGame.keyPressed(key);break;		
		}
	}
}

/**封装模块*/
class ModelLogo
{
	public ModelLogo(){}
	public void run()
	{
		//运行自己的逻辑
		//如果这个状态结束可以调用ZYCanvas.changeState()来改变状态
	}
	public void paint(Graphics g){}
	public void keyPressed(int key){}
}
class ModelMenu
{
	public ModelGame(){}
	public void run()
	{
		//运行自己的逻辑
		//如果这个状态结束可以调用ZYCanvas.changeState()来改变状态
	}
	public void paint(Graphics g){}
	public void keyPressed(int key){}
}
class ModelGame
{
	public ModelGame(){}
	public void run()
	{
		//运行自己的逻辑
		//如果这个状态结束可以调用ZYCanvas.changeState()来改变状态
	}
	public void paint(Graphics g){}
	public void keyPressed(int key){}
}

一些问题

通过调度器只需要调用changeState方法就可以在页面直接进行切换。但是实际应用时调度器有一些不足

我们想添加一个页面,需要声明一个状态量,再页面相应的类,然后在调度器每个方法中添加对于新的页面要执行的操作。

很明显的,不符合封装的设计思想。

怎样把调度器封装起来呢?

改进思路

很明显,我们要解决的最主要的问题是当调度器编写时,并不知道未来有一个什么样的页面类将要供调度器调度。

该如何使用未知的类?

我们可以应用java语言反射机制动态加载类来解决这类问题。通常,我们想要创建一个对象,我们采用以下方法

Class c = new Class();

java提供了动态加载类的机制,在运行时通过一个表示类名的字符串查找一个类并加载,还可以产生它的一个实例。使用方法如下

getClass().forName("package.className").newInstance();

只要创建一个调度器对象,再将页面类的类名添加进调度器,调度器使用动态加载类创建页面对象就可以了

改进

通过动态加载类创建的对象是Object类型的,显然这是没有我们想要的run,paint,keyPressed方法的,要通过强制类型转换把Object类型对象装换成含有这些方法的对象。

把它转换成什么类型对象合适,又如何保证用户编写的页面类肯定有这些方法呢?

通过java的接口来保证!

下面编写一个接口,规定用户编写的所有页面必须实现这个接口

package com.shiying.frame;

import javax.microedition.lcdui.Graphics;

//页面接口
public interface Model {
	
	public void run();
	
	public void paint(Graphics g);
	
	public void keyPressed(int key);
}

要使用动态加载类,应该保存每个页面类的类名字符串,同时还要保存每个页面所对应的状态量。最简单的使用集合来保存

private static Vector stateVector = new Vector();
private static Vector nameVector = new Vector();

为了调用当前类的方法,还要保存当前类的状态和对当前类对象的引用,这个页面应该是Model类型

private static Model model = null;
private static byte state;

下面编写一个方法,用来向调度器注册页面

public void addModel(byte stateId, String modelClassName) {
	stateVector.addElement(new Byte(stateId));
	nameVector.addElement(modelClassName);
}

当用户发出切换页面的请求后,应该释放当前页面,并启动新的页面

     /*
	 * 通过状态查找在表中的位置
	 */
	private static int searchState(byte state) {
		for (int n=0; nif (((Byte)Framework.stateVector.elementAt(n)).byteValue() == state) {
				return n;
			}
		}
		return -1;
	}

	/**改变状态:这里可以做一些 上个状态的模块清除资源工作,和 新状态模块的初始化工作*/
	public static void changeState(byte new_state)
	{
		//清除上一个模块的资源
		int index = searchState(state);
		if (index != -1)
			model = null;
		
		//状态转换
		state = new_state;
		
		//创建新状态模块,初始化
		index = searchState(new_state);
		if (index != -1)
		{
			String name = (String)Framework.nameVector.elementAt(index);
			Class c = null;
			try {
				c = Class.forName(name);
				model = (Model) c.newInstance();
			} catch (ClassNotFoundException e) {
				e.printStackTrace();
			} catch (InstantiationException e) {
				e.printStackTrace();
			} catch (IllegalAccessException e) {
				e.printStackTrace();
			}
		}
	}

因为每个时刻至多有一个页面对象,所以不需要判断状态,对页面函数调用就非常简单

     //每80毫秒执行一次
	public void run()
	{
		while (true) {
			long time = System.currentTimeMillis();

			action();
			repaint();	//通知调用paint()函数
			serviceRepaints();	//强制paint执行

			long dur = System.currentTimeMillis() - time;
			long test = 80 - dur;
			if(test > 0){
				try {
					Thread.sleep(test);
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
			}
		}
	}
	//可以把要做的事情全部放到一个函数中
	public void action()
	{
		if (model != null) {
			model.run();
		}
	}


	//可以把要做的事情全部放到一个函数中
	public void paint(Graphics g)
	{
		if (model != null) {
			model.paint(g);
		}
	}
	
	public void keyPressed(int key)
	{
		if (model != null) {
			model.keyPressed(key);
		}
	}

编写一个启动调度器的方法,需要指明开启的第一个页面

     public void go(byte start) {
		//开启线程 
		state = start;
		changeState(start);
		
		Thread th = new Thread(this);
		th.start();
	}
OK,改装结束,整理一下代码
 

完整代码

//    Main.java    //////////////////////////////////////
package com.shiying.frame;


import javax.microedition.lcdui.Display;
import javax.microedition.midlet.MIDlet;

import com.shiying.Application.Frame;

public class Main extends MIDlet{

	static Frame canvas = null;
	static Main midlet = null;

	public Main()
	{
		midlet = this;
	}
	
	public void startApp()
	{
		if(canvas == null)
		{
			canvas = new Frame();
		}
		Display display = Display.getDisplay(this);
		display.setCurrent(canvas);
	}
	public void pauseApp()
	{}
	public void destroyApp(boolean unconditional)
	{}
	
	public static void shutDown() {
		midlet.notifyDestroyed();
	}

}


//    Model.java    //////////////////////////////////////
package com.shiying.frame;

import javax.microedition.lcdui.Graphics;

//页面接口
public interface Model {
	
	public void run();
	
	public void paint(Graphics g);
	
	public void keyPressed(int key);
}


//    Framework.java    //////////////////////////////////////
package com.shiying.frame;

import java.util.Vector;

import javax.microedition.lcdui.Canvas;
import javax.microedition.lcdui.Graphics;

public class Framework extends Canvas implements Runnable {

	public static Framework canvas = null;
	
	private static Vector stateVector = new Vector();
	private static Vector nameVector = new Vector();
	
	private static Model model = null;
	private static byte state;
	
	public Framework()
	{
		Framework.canvas = this;
	}
	
	public void go(byte start) {
		//开启线程 
		state = start;
		changeState(start);
		
		Thread th = new Thread(this);
		th.start();
	}
	
	public void addModel(byte stateId, String modelClassName) {
		stateVector.addElement(new Byte(stateId));
		nameVector.addElement(modelClassName);
	}
	
	/*
	 * 通过状态查找在表中的位置
	 */
	private static int searchState(byte state) {
		for (int n=0; nif (((Byte)Framework.stateVector.elementAt(n)).byteValue() == state) {
				return n;
			}
		}
		return -1;
	}

	/**改变状态:这里可以做一些 上个状态的模块清除资源工作,和 新状态模块的初始化工作*/
	public static void changeState(byte new_state)
	{
		//清除上一个模块的资源
		int index = searchState(state);
		if (index != -1)
			model = null;
		
		//状态转换
		state = new_state;
		
		//创建新状态模块,初始化
		index = searchState(new_state);
		if (index != -1)
		{
			String name = (String)Framework.nameVector.elementAt(index);
			Class c = null;
			try {
				c = Class.forName(name);
				model = (Model) c.newInstance();
			} catch (ClassNotFoundException e) {
				e.printStackTrace();
			} catch (InstantiationException e) {
				e.printStackTrace();
			} catch (IllegalAccessException e) {
				e.printStackTrace();
			}
		}
	}

	//每80毫秒执行一次
	public void run()
	{
		while (true) {
			long time = System.currentTimeMillis();

			action();
			repaint();	//通知调用paint()函数
			serviceRepaints();	//强制paint执行

			long dur = System.currentTimeMillis() - time;
			long test = 80 - dur;
			if(test > 0){
				try {
					Thread.sleep(test);
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
			}
		}
	}
	//可以把要做的事情全部放到一个函数中
	public void action()
	{
		if (model != null) {
			model.run();
		}
	}


	//可以把要做的事情全部放到一个函数中
	public void paint(Graphics g)
	{
		if (model != null) {
			model.paint(g);
		}
	}
	
	public void keyPressed(int key)
	{
		if (model != null) {
			model.keyPressed(key);
		}
	}
}
 

如何使用

首先编写的所有页面需要实现Model接口,并且具有公有的无参构造方法

package com.shiying.Application;

import javax.microedition.lcdui.Graphics;

import com.shiying.frame.Model;

/*
 * 这是一个页面的示例,每一个页面必须实现com.shiying.frame.Model接口
 */
public class ModelDemo implements Model {
	
	/*
	 * 页面必须具有一个共有的无参构造方法
	 */
	public ModelDemo() {
		
	}

	/*
	 * 这个方法中添加页面对按键消息的响应过程
	 */
	public void keyPressed(int key) {
		// TODO Auto-generated method stub
		
	}

	/*
	 * 这个方法实现页面绘图过程
	 */
	public void paint(Graphics g) {
		// TODO Auto-generated method stub
		System.out.println("Demo paint");
		g.drawString("Frame Demo", Frame.canvas.getWidth() / 2, 100,
				Graphics.HCENTER|Graphics.TOP);
	}

	/*
	 * 这个方法实现页面要执行的操作
	 */
	public void run() {
		// TODO Auto-generated method stub
		System.out.println("Demo run");
	}

}
下面编写一个调度器类,这个类是Framework类的子类
这个类需要完成的工作有
1、声明每个页面的状态量
2、注册页面类
3、启动第一个页面
package com.shiying.Application;

import com.shiying.frame.Framework;



public class Frame extends Framework {
	
	/*
	 * 这里声明代表每个页面的状态量(页面ID) 
	 */
	public static final byte STATE_DEMO = 0;
	

	public Frame() {
		super();
		
		/*
		 * 这里添加页面,需要传入两个参数,页面ID和相应页面类所在位置
		 */
		this.addModel(Frame.STATE_DEMO, "com.shiying.Application.ModelDemo");
		
		
		/*
		 * 这里设置第一个活跃的页面
		 */
		this.go(Frame.STATE_DEMO);
	}
	
}
 当这个类被创建时,第一个页面会被自动调用。在第一个页面中可以根据需要调用changeState可以切换到下一个页面。
 
posted @ 2010-10-11 09:21  石莹  阅读(570)  评论(0编辑  收藏  举报