文章索引

J2ME RPG游戏边学边做(十一)--游戏中的loading设计

    为什么很多游戏要加入Loading滚动条呢?加入Loading状态并不是为了使软件显得更专业美观,而是为了保证程序的运行内存不溢出。通常计算机/手机的存储系统分为:cup 的缓存,磁盘(或者手机中的存储用的的FLASH RAM或者其他类型的可以持久保存的存储系统),运行内存。我们知道通常NOKIA S40的heap size为200KB大小,而通常我们加入程序和3张128*128的图片之后内存就趋于崩溃了,再加入声音和地图,程序的运算内存就显得太不够了。一般来讲,很多游戏仅仅在运行的时候把所有的资源一次性读入heap memory这样,我们在模拟器看到程序运行的状况就非常接近崩溃的边缘,如果不小心加入了新的图片,可能就没有足够的运算内存了。

    我们如何解决heap size不够的事情呢?手机是不能够改变其heap size的,我们只有想办法控制heap memory的使用。最直观的做法就是:存储内存与运算内存的优化使用,当运算内存需要资源时从存储内存中调用,需要新的资源时,就把不需要的释放掉。下面我就结合一段代码解释我们是如何制作Loading状态的。

    众所周知,Java是内置多线程的,我们可以使用两个线程来解决loading的问题,一个读资源的线程,一个绘制资源的线程。程序代码:


import javax.microedition.midlet.*;
import javax.microedition.lcdui.*;
/**
* Loading演示
* @author gaogao
* */
class MainCanvas
extends Canvas
implements Runnable {
//程序状态
static final int LOADING = 0;
static final int GAMEING = 1;
//程序状态控制器
int state = LOADING;
//主线程
Thread thread = null;
//是否loading完毕,
boolean isLoaded = false;
//内部类,新开读取资源的 线程
class Loading
implements Runnable {
//内线程
Thread innerThread = null;
public Loading() {
innerThread = new Thread(this);
innerThread.start();
}
int counter = 100;
public void run() {
//模拟读取资源
//把下面的东西改成读取资源的代码即可
while (counter > 0) {
counter--;
try {
Thread.sleep(20);
}
catch (Exception ex) {}
}
//loading结束
isLoaded = true;
}
}
Loading loading = null;
public MainCanvas() {
loading = new Loading();
thread = new Thread(this);
thread.start();
}
int loadingCounter = 0;
//绘制..
public void paint(Graphics g) {
g.setColor(0);
g.fillRect(0, 0, getWidth(), getHeight());
switch (state) {
case LOADING: {
g.setColor(0XFFFFFF);
g.drawString("LOADING" + ">>>>>".substring(0, loadingCounter),
getWidth() >> 1, getHeight() >> 1,
Graphics.HCENTER | Graphics.TOP);
loadingCounter = ++loadingCounter % 5;
}
break;
case GAMEING: {
g.setColor(0XFFFFFF);
g.drawString("GAME", getWidth() >> 1, getHeight() >> 1,
Graphics.HCENTER | Graphics.TOP);
}
break;
}
}
public void run() {
while (true) {
try {
Thread.sleep(100);
}
catch (Exception ex) {
}
if (isLoaded) {
loading = null;
state = GAMEING;
}
repaint(0, 0, getWidth(), getHeight());
serviceRepaints();
}
}
}
public class Main
extends MIDlet {
MainCanvas mc;
public void startApp() {
if (mc == null) {
mc = new MainCanvas();
Display disp = Display.getDisplay(this);
disp.setCurrent(mc);
}
}
public void destroyApp(boolean bool) {}
public void pauseApp() {}
}

J2ME RPG游戏边学边做(十二)--J2ME 记录管理存储基础

移动信息设备框架(Mobile Information Device Profile)? 移动 Java 应用程序的平台 ?

为 MIDP 应用程序提供一种跨多个调用持久存储数据的机制。这种持久存储机制可以被视为一种简单的面向记录的数据库模型,被称为记录管理系统(record management system(RMS))。在此,Soma Ghosh 说明了您的 J2ME 应用程序怎样能够使用 RMS 来管理和解释数据。通过一个样本电话数据库,您还将了解到关于这个概念的说明。

J2ME 记录管理系统

J2ME 记录管理系统(RMS)提供了一种机制,通过这种机制,MIDlet 能够持久存储数据,并在以后检索数据。在面向记录的方法中,J2ME RMS 由多个记录存储构成。

可以将每个记录存储想像成一个记录集合,它将跨多个 MIDlet 调用持久存在。设备平台负责在平台正常使用的整个过程(包括重新启动、换电池等)中,尽全力维护 MIDlet 的记录存储的完整性。

记录存储在与平台相关的位置(比如非易失性设备存储器)创建,这些位置不直接公开给 MIDlet。RMS 类调用特定于平台的本机代码,这种本机代码使用标准 OS 数据管理器函数来执行实际的数据库操作。

记录存储实现确保所有单个的记录存储操作都是原子的、同步的以及序列化的,因此多个访问将不会出现数据毁坏。记录存储被盖上时间戳来指示它上次被修改的时间。记录存储还维护版本(version),它是一个整数,修改记录存储内容的操作每发生一次,这个数加一。版本和时间戳对于同步目的很有用。

当 MIDlet 使用多个线程访问一个记录存储时,协调该访问是 MIDlet 的责任;如果它不能这样做,可能出现无法意料的结果。同样,如果一个平台使用试图同时访问记录存储的多个线程执行记录存储的同步,那么对 MIDlet 及其同步引擎之间的记录存储实施排外访问是平台的责任。

记录存储中的每个记录是一个字节数组,并且有唯一的整数标识符。

管理设备数据库

javax.microedition.rms.RecordStore 类代表 RMS 记录存储。它提供了几个方法来管理以及插入、更新和删除记录存储中的记录。

管理记录存储

要打开一个记录存储,调用 javax.microedition.rms.RecordStore 的 openRecordStore() 方法。

public static RecordStore openRecordStore(String recordStoreName,
boolean createIfNecessary) 打开具有指定名称 recordStoreName 的记录存储。

如果没有具有这个名称的记录存储,那么调用这个方法来创建一个。

如果记录存储已经打开,这个方法将返回对同一个记录存储对象的引用。

清单 1. 打开一个 RecordStore
RecordStore rs = RecordStore.openRecordStore("MyAppointments",true);
一旦所有操作完成,对 closeRecordStore() 的调用将关闭指定名称的记录存储。当一个记录存储被关闭时,不能进行进一步的操作。

清单 2. 关闭一个 RecordStore
Rs.closeRecordStore();
通过调用 deleteRecordStore() 方法可以删除指定名称的记录存储。

清单 3. 删除一个 RecordStore
RecordStore.deleteRecordStore("MyAppointments");
插入记录
MIDlet 调用 javax.microedition.rms.RecordStore 类的 addRecord() 方法来将一条新记录插入到记录存储中。这是阻塞的原子操作,并返回新记录的 recordId。在这个方法返回之前,记录被写到持久存储中。
public int addRecord(byte[] data, int offset, int numBytes) 插入一条由字节数组 data 代表的记录,这个数组以 offset 作为它的起始索引,numBytes 作为它的长度。

清单 4. 插入一条记录
String appt = "new record";
byte bytes[] = appt.getBytes();
rs.addRecord(bytes,0,bytes.length);

更新记录

更新一条特殊记录包括获取这个记录的句柄以及设置新信息。
public int getRecord(int recordId, byte[] buffer, int offset) 返回存储在由 buffer 代表的字节数组中给定记录的数据。public byte[] getRecord(int recorded) 返回由 recordId 代表的数据的副本。public void setRecord(int recordId, byte[] newData, int offset, int numBytes) 在 recordId 所代表记录的位置设置新信息,新信息是以 offset 作为它的起始索引,并以 numBytes 作为它的长度的字节流(newData)。

清单 5. 更新一条记录
String newappt = "update record";
Byte data = newappt.getBytes();
Rs.setRecord(1, data, 0, data.length());
删除记录
MIDlet 调用 deleteRecord() 方法来从记录存储中删除记录。
public void deleteRecord(int recordId) 删除由 recordId 代表的记录。这个记录的 recordId
接下来不能重用。

清单 6. 删除一条记录
Rs.deleteRecord(1);
数据解释
J2ME API 提供某种接口来解释存储在记录存储中的数据。这个过程包括比较记录来确定它们的相对排序。
它还包括根据给定条件的内容过滤。
比较记录
MIDlet 实现 RecordComparator 接口,并定义 compare (byte[] rec1, byte[] rec2) 方法来比较两个候选记录。这个方法的返回值必须指示这两条记录的顺序。

清单 7. 比较记录并确定相对排序
Int compare (byte[] b1, byte[] b2)
{
String s1 = new String(b1);
String s2 = new String(b2);
If (s1.compareTo(s2) > 0)
Return RecordComparator.FOLLOWS;
Else if (s1.compareTo(s2) == 0)
Return RecordComparator.EQUIVALENT;
Else
Return RecordComparator.PRECEDES;
}
[code]

枚举记录
RecordEnumeration 接口负责枚举记录存储中的记录。它逻辑上维护记录存储中一连串的记录的 recordId。枚举器将以记录比较器确定的顺序迭代所有记录(或者如果提供了一个可选的记录过滤器,那么只是一个子集)。如果既没有指定过滤器又没有指定比较器,枚举将以未定义的顺序遍历记录存储中的所有记录。

清单 8. 枚举记录
[code]
RecordEnumeration re = rs.enumerateRecords(null, null, false);
If (re.hasNextElement())
Byte nextRec[] = re.nextRecord();
过滤记录
MIDlet 实现 RecordFilter 接口,定义检查记录是否满足应用程序定义的标准的过滤器。这个应用程序实现 RecordFilter 的 match() 方法来选择 RecordEnumeration 返回的记录。

清单 9. 过滤记录
Public boolean matches(byte[] candidate)
{
String s1 = new String(candidate);
If (s1.equals("XX"))
Returns true;
Else
Returns false;
}

开发电话约会簿

在这部分,我们将通过构建一个电话约会簿来说明 J2ME RMS 的功能。这个应用程序将允许用户设置某个日期和时间的约会,取消约会或查看已经设置好的约会列表。

构成这个应用程序的各种屏幕以及屏幕元素的用户界面元素的完整列表在与 J2ME Wireless Toolkit 一起提供的 MID 框架 API 文档中可以得到;要获取关于这些元素的更多详细信息,请查阅我早些时候给 developerWorks 写的一篇文章(请参阅下面的参考资料部分以获取这两个链接)。

记录存储可以以字节流形式存储记录。在我们的应用程序中,用户输入的日期和时间被连接成一个字符串,转换成字节,然后被存储。

清单 10. 将一个新的约会添加到数据库中

Public boolean matches(byte[] candidate)

String appt = apptName + " " + apptTime;

byte bytes[] = appt.getBytes();

rs.addRecord(bytes,0,bytes.length);

同样,这个应用程序以字节流形式检索记录,然后将它转换成一个字符串。这个字符串以 ####AAAA 格式,其中 # 表示代表时间信息的数字,AAAA 表示代表约会描述的字符。这个应用程序解析这个字符串来获得日期和时间信息,并以用户所希望的格式显示它们,比如 description - mm/dd/yyyy hh:mm AM_PM

清单 11. 从记录存储检索一条记录

byte b[] = rs.getRecord(j);

String str = new String(b,0,b.length);

清单 12. 解析从记录存储获得的数据,然后以用户所希望的格式显示

 

if (Character.isDigit(str.charAt(i)))

at += str.charAt(i);

else

name += str.charAt(i);

time = Long.parseLong(at);

java.util.Date date = new java.util.Date(time);

java.util.Calendar rightNow = java.util.Calendar.getInstance();

rightNow.setTime(date);

String year = String.valueOf

(rightNow.get(java.util.Calendar.YEAR));

String month = String.valueOf

(rightNow.get(java.util.Calendar.MONTH) + 1);

String day = String.valueOf

(rightNow.get(java.util.Calendar.DATE));

String displayName = name

+ "-" + year + " " + day;

用户被允许从记录存储中选择某种约会以及将它们从记录存储中删除。因为为了维持记录中原始的顺序所删除的 recordId 不能重用,所以这个记录通过特有的字符串模式标记为无效。清单 13. 将一条记录标记为已删除

String deactive = "@";

byte b[] = deactive.getBytes();

rs.setRecord(m+1, b,0,b.length);

 

当这个应用程序显示一个约会列表时,它检测那些无效记录的字符串模式,然后跳过它们。

清单 14. 跳过无效记录

if (!(str.startsWith("@")))

{

// Record is valid

}

else

{

// Record is invalid.

}

 

这个应用程序的一个重要的方面是用户界面。各种屏幕有下面这些:

欢迎表单:欢迎表单显示一个已经设置好的约会列表,如果没有设置约会,则通知用户。它提供继续或退出这个应用程序的各种选项。

菜单表单:菜单表单给用户提供查看约会、设置新约会或取消约会等选项。

显示表单:显示表单显示已经设置好的约会列表。

设置表单:设置表单提供一个日期选择域和一个输入文本域,以提供新约会的详细信息。当用户选择保存时,这条信息被存储到数据库中。

删除表单:删除表单列出一组约会,并提供选择一个或多个的选项。如果用户选择删除,所选择的这组约会在记录存储中被标记为无效。

应用程序实现使自己能够响应各种事件的 CommandListener ItemStateListener 接口。

ItemStateListener 使应用程序能够接收指示下面这些东西的内部状态的改变的事件:

DateField,一个显示日期和时间的可编辑组件

TextField,一个可编辑文本组件

ChoiceGroup,一组可选择的元素

清单 15. 从屏幕获取值

// The date value is set to a variable when the

// DateField item is changed

if (item == apptDate)

{

date = apptDate.getDate();

apptTime = String.valueOf(date.getTime());

}

// The name of appointment is set to a variable

//when the name input field is changed

if (item == apptField)

{

apptName = apptField.getString();

}

// If the ChoiceGroup item state on Delete form is

//changed, it sets an array of appointments selected for deletion

if (item == cg)

{

cg.getSelectedFlags(deleted);

}

清单 16 包含这个样本应用程序的完整清单。请参阅参考资料部分中我以前的一篇关于 J2ME 的文章,以获取关于下载将使您能够在桌面上运行这个程序的设备仿真器的指导。
总结
在本文中,我们讲述了 MID 应用程序持久存储和检索数据的能力;这种机制根据简单的面向记录的数据库建模。J2ME API javax.microedition.rms 包提供了一个开发者的方法和接口宝库,从而可以利用 MID 应用程序的这种独特功能。现在,您应该能够将数据存储集成到您自己的微型 Java 应用程序中了。