首页 arrow 开发技术 arrow 程序设计 arrow J2ME RPG游戏边学边做
J2ME RPG游戏边学边做
Author Author: 一滴蔚蓝色 | Date Date: 2007-08-16 | View Count View: 7126 | Section & Category 开发技术 - 程序设计 | Digg Digg: 16
跳转
J2ME RPG游戏边学边做
页面 2
页面 3
页面 4
页面 5
页面 6

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 应用程序中了。
{mospagebreak}
<strong>J2ME RPG游戏边学边做(十三)--J2ME 记录管理存储增强篇</strong>
 Record Management System是MIDP的子系统,提供了数据的持久性存储功能,本文并非上次讲述Record Management System的基础知识,而是从编程的角度提出高效使用Record Management System的建议。如果您对RMS还不够了解请参考本专题其他的文章。 
 
    在RecordStore中存储的数据是以字节的形势存在的,MIDP规范中并没有规定什么数据可以存储在RMS中,只要他可以转换成字节数组。那么读取和写入这些字节数据的时候我们应该注意些什么问题呢?
 
 
 
   由于非挥发性内存的存取速度都比较慢,因此我们应该尽量的少对RMS进行写操作,当然这也和设备有关系,有些设备的写操作是非常好费资源的。在读取数据的时候我们应该尽量复用对象,避免大量的创建对象然后丢弃对象,这样会给Heap和GC造成不小的负担。
    看下面读取数据的两个不同的代码片断
 
 
//片断1
 
RecordStore rs = ....; // an open record store
 
try {
 
int lastID = rs.getNextRecordID();
 
byte[] data;
 
 
 
for( int i = 0; i < lastid; ++i ){
 
try {
 
data = rs.getrecord( i );
 
.... // do something with the data
 
}
 
catch( invalidrecordidexception e ){
 
continue;
 
}
 
}
 
}
 
catch( exception e ){
 
// error
 
}
 
 
 
//片断2
 
RecordStore rs = ....; // an open record store
 
try {
 
RecordEnumeration enum = rs.enumerateRecords( 
 
null, null, false ); 
 
while( enum.hasNextElement() ){
 
byte[] data = enum.nextRecord();
 
.... // do something with the data
 
}
 
}
 
catch( Exception e ){
 
// error
 
}
 
 
 
上面的代码存在的问题是系统每次读取记录都要创建新的字节数组对象,这显然不够高效。
 
其实我们可以对字节数组进行复用,并可以适当的调整它的大小。
 
RecordStore rs = ....; // an open record store
 
try {
 
RecordEnumeration enum = rs.enumerateRecords( 
 
null, null, false );
 
byte[] data = new byte[100];
 
int len = 0;
 
 
 
while( enum.hasNextElement() ){
 
int id = enum.nextRecordId();
 
len = rs.getRecordSize( id );
 
if( len > data.length ){
 
// add a growth factor
 
data = new byte[ len + 40 ]; 
 
}
 
rs.getRecord( id, data, 0 );
 
// do something with the data
 
}
 
}
 
catch( Exception e ){
 
// error
 
}
 
 
 
 
 
    在我们读取数据的时候,通常还会用到javaIO,比如ByteArrayInputStreamDataInputStream类,那么在使用他们的时候,我们也应该尽量复用对象。比如当我们从RMS中读取纪录的时候,假设纪录包括一个布尔型和两个整型数据。
 
 
 
RecordStoreEnumeration enum = ...; // get a record enumeration
 
byte[] data = new byte[9]; // record size
 
ByteArrayInputStream bin = new ByteArrayInputStream( data );
 
DataInputStream din = new DataInputStream( bin );
 
while( enum.hasNextElement() ){
 
int id = enum.nextRecordId();
 
getRecord( id, data, 0 );
 
din.reset(); // move stream back to start
 
 
 
boolean first = din.readBoolean();
 
int second = din.readInt();
 
int third = din.readInt();
 
 
 
// do something here
 
}
 
下面提供一个封装好的Record类,你可以在使用RMS的时候使用它
 
import java.io.*;
 
import javax.microedition.rms.*;
 
public class Record implements DataInput {
 
private RecordStore _rs;
 
private byte[] _data;
 
private int _length;
 
private int _id;
 
private DataInputStream _din;
 
public Record( RecordStore rs ){
 
this( rs, 100 );
 
}
 
public Record( 
 
RecordStore rs, int initialRecordSize ){
 
_rs = rs;
 
_data = new byte[ initialRecordSize ];
 
_din = new DataInputStream( 
 
new ByteArrayInputStream( _data ) );
 
_length = -1;
 
}
 
public byte[] getByteArray() { return _data; }
 
public int getLength() { return _length; }
 
public byte[] moveTo( int id ) 
 
throws RecordStoreNotOpenException,
 
InvalidRecordIDException,
 
RecordStoreException,
 
IOException
 
{
 
_length = _rs.getRecordSize( id );
 
if( _length > _data.length ){
 
_data = new byte[ _length + 40 ];
 
_din = new DataInputStream( 
 
new ByteArrayInputStream( _data ) );
 
}
 
_rs.getRecord( id, _data, 0 );
 
_id = id;
 
_din.reset();
 
return _data;
 
}
 
public void readFully(byte b[]) 
 
throws IOException {
 
_din.readFully( b );
 
}
 
public void readFully(byte b[], int off, int len) 
 
throws IOException {
 
_din.readFully( b, off, len );
 
}
 
return _din.skipBytes( n );
 
}
 
public boolean readBoolean() throws IOException {
 
return _din.readBoolean();
 
}
 
public byte readByte() throws IOException {
 
return _din.readByte();
 
}
 
public int readUnsignedByte() 
 
throws IOException {
 
return _din.readUnsignedByte();
 
}
 
public short readShort() throws IOException {
 
return _din.readShort();
 
}
 
public int readUnsignedShort() 
 
throws IOException {
 
return _din.readUnsignedShort();
 
}
 
public char readChar() throws IOException {
 
return _din.readChar();
 
}
 
public int readInt() throws IOException {
 
return _din.readInt();
 
}
 
public long readLong() throws IOException {
 
return _din.readLong();
 
}
 
public String readUTF() throws IOException {
 
return _din.readUTF();
 
}
 
}
 
使用起来非常简单
 
try {
 
rs = RecordStore.openRecordStore( "mydata", true );
 
 
 
// Write two records to the record store
 
ByteArrayOutputStream bout = 
 
new ByteArrayOutputStream();
 
DataOutputStream dout = 
 
new DataOutputStream( bout );
 
byte[] data;
 
dout.writeUTF( "this is a test" );
 
dout.writeInt( 1 );
 
dout.flush();
 
data = bout.toByteArray();
 
rs.addRecord( data, 0, data.length );
 
bout.reset();
 
dout.writeUTF( "this is another test" );
 
dout.writeInt( 99 );
 
dout.flush();
 
data = bout.toByteArray();
 
rs.addRecord( data, 0, data.length );
 
 
 
// Now read through the record store
 
Record record = new Record( rs );
 
int lastID = rs.getNextRecordID();
 
RecordEnumeration enum = rs.enumerateRecords( 
 
null, null,
 
while( enum.hasNextElement() ){
 
int id = enum.nextRecordId();
 
record.moveTo( id );
 
System.out.println( record.readUTF() + " " + 
 
record.readInt() );
 
}
 
rs.closeRecordStore();
 
}
 
catch( Exception e ){
 
// handle error
 
}
<strong>J2ME RPG游戏边学边做(十四)--特辑:地图的设计与绘制</strong>
 
 
 
在开发很多类型的游戏中,地图系统都需要良好的设计,直观的说,我们需要的地图系统仅仅是一个2D数组,然后用最快的方式将数组影射到屏幕上。
 
 
 
游戏中的地图通常不是由程序员用键盘输入到程序里然后再在程序中修改然后再修改的狂乱过程,而是一般先由程序员做一个地图编辑器,在这个地图编辑器中用鼠标点点点,再保存的过程,或者是从网络上下载的一些成熟编辑器比如:mappy这样的工具生成地图,再用脚本语言为mappy写一个应该保存成什么样格式的程序。通常地图分为45度角,侧视角和俯视角等等,45度角的也有很多种,这种视角相对俯视角和侧视叫较复杂,我们主要讨论俯视角,其实侧视叫和俯视角主要的区别是图片的表现风格不一样,比如雷电这样的空战就是俯视角,mario这样的游戏就是侧视角,可以用相同的地图编辑器做出来。综上,你要知道游戏地图不是程序员用程序写出来的,你喜欢写也可以,修改起来较麻烦,也不能像资源一样动态管理而是一次性读入到内存里,比较不爽。
 
在这个文章里面,我们假设我们的2D数组是通过,资源读取出来的,内容如下:
 
public static byte[][] B_MAZE_2D_ARRAY = {
 
{
 
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}
 
, {
 
0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}
 
, {
 
0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}
 
, {
 
0, 1, 1, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}
 
, {
 
0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0}
 
, {
 
0, 1, 1, 1, 1, 1, 0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0}
 
, {
 
0, 1, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0}
 
, {
 
0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 1, 1, 1, 1, 1, 1, 0}
 
, {
 
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0}
 
, {
 
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0}
 
, {
 
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}
 
};
 
这个地图中一共0,1两种byte数,0代表一种图块,1代表一种图块,也可以是动画图块,你绘制的时候区别一下就可以了。因为我们程序里面想把这整个图形绘制出来的话有N多种方案,我给出两个比较合理的方案,当然第二种是比较优化的.我们假设你的主角一直在屏幕中央,当你主角移动的时候,地图相应的变化,就是说主角为参照物,地图动.我们知道地图的大小要超过屏幕的,我们需要设定一个坐标系统,我的方法是,以左上角为0,0也就是和我们常用的 Canvas的坐标系统是相同的,我们的图块大小为:ELEMENT_WIDTH, ELEMENT_HEIGHT,所以我们整个地图的面积(绝对面积)是ELEMENT_WIDTH * 横坐标的块数 * ELEMENT_HEIGHT*纵坐标的块数。因此,我们把这么大的题图画在屏幕上时,需要把需要画的坐标面积(也就是屏幕面积)从这个地图中拿出来,其他地方被切除,这就比较的高效了。
 
方法一:循环整个2维数组,不需要的地方不绘制只绘需要的部分:
 
int kI = 0;// 表示
 
int kJ = 0;
 
//nEleStartedX, nEleStartedY表示从2D Array哪个位置开始绘制地图
 
for (int i = nEleStartedY; i < B_MAZE_2D_ARRAY.length; i++) {
 
kJ = 0;
 
//是否需要绘制
 
boolean isDrawed = false;
 
for (int j = nEleStartedX; j < B_MAZE_2D_ARRAY[i].length; j++) {
 
//绘制需要的面积,N_MAZE_ELEMENT_WIDTH,N_MAZE_ELEMENT_HEIGHT表示图块宽高
 
int bX = nMapStartedX + j * N_MAZE_ELEMENT_WIDTH;
 
int bY = nMapStartedY + i * N_MAZE_ELEMENT_HEIGHT;
 
//SCREEN_WIDTH,SCREEN_HEIGHT屏幕大小
 
if (bX <= SCREEN_WIDTH
 
&&
 
bY <= SCREEN_HEIGHT
 
&&
 
bX >= -N_MAZE_ELEMENT_WIDTH
 
&&
 
bY >= -N_MAZE_ELEMENT_HEIGHT
 
) {
 
g.drawImage(mapImages[B_MAZE_2D_ARRAY[i][j]], bX,
 
bY,
 
Graphics.TOP | Graphics.LEFT);//绘制图块
 
isDrawed = true;
 
kJ++;
 
// N_MAX_MAZE_ITEM_X , N_MAX_MAZE_ITEM_Y屏幕面积内图块的最大值
 
if (kJ > N_MAX_MAZE_ITEM_X + 2) {
 
break;
 
}
 
}
 
}
 
if (isDrawed) {
 
kI++;
 
}
 
if (kI > N_MAX_MAZE_ITEM_Y + 2) {
 
break;
 
}
 
}
 
方法二:事先找到需要绘制的横坐标纵坐标的图块编号(2DArray的数组下标),循环屏幕面积大小的数组:
 
 
 
// 需要绘制的2DArray左上角位置,nMapStartedX,nMapStartedY在地图绝对面积上的坐标
 
int nArrayI = ( -N_MAZE_ELEMENT_HEIGHT - nMapStartedY) /
 
N_MAZE_ELEMENT_HEIGHT;
 
int nArrayJ = ( -N_MAZE_ELEMENT_WIDTH - nMapStartedX) /
 
N_MAZE_ELEMENT_WIDTH;
 
for (int i = nArrayI;
 
i < SCREEN_HEIGHT / N_MAZE_ELEMENT_HEIGHT + 2; i++) {
 
for (int j = nArrayJ;
 
j < SCREEN_WIDTH / N_MAZE_ELEMENT_WIDTH + 2; j++) {
 
if (i < 0 || j < 0 || i > B_MAZE_2D_ARRAY.length ||
 
j > B_MAZE_2D_ARRAY[0].length) {
 
continue;
 
}
 
else {
 
int bX = nMapStartedX + j * N_MAZE_ELEMENT_WIDTH;
 
int bY = nMapStartedY + i * N_MAZE_ELEMENT_HEIGHT;
 
g.drawImage(mapImages[B_MAZE_2D_ARRAY[i][j]], bX,
 
bY,
 
Graphics.TOP | Graphics.LEFT);
 
}
 
}
 
}
 
根据我的测试,方法一的地图面积越大fps掉的越为厉害,而方法二基本上不会掉fps,强烈推荐方法二.
 
地图系统做好了之后,你就可以使用地图做更多的表现力了,只要改变nMapStartedX,nMapStartedY,就可以绘制出地图上的相应部分,代码的复用效率非常的高。RPG, SLG, PUZZLE等游戏类型都可以使用.
 
欢迎跟我探讨更多的游戏制作技术,我还将写一个关于动画的相关东东,不过最近没什么时间
转自:matrix.org.cn

更多阅读:



最近更新 ( 2008-04-12 )
 

尚无评论发表

我要发表评论

登录菜单

最新文章

订阅本站

RSS 0.91 RSS 1.0 RSS 2.0 ATOM 0.3 OPML