免费注册 查看新帖 |

Chinaunix

  平台 论坛 博客 文库
最近访问板块 发新帖
查看: 3678 | 回复: 8

[连载]我的J2ME游戏开发之路 [复制链接]

论坛徽章:
0
发表于 2006-05-11 18:18 |显示全部楼层
第一章. 计划简介

我不想再复制一大段关于什么是J2ME的东东~~

因为我自己对编程有一点点的概念,并且以前就已经学习过基本的JAVA语法,因此我不打算在从最基本的语法开始学习,为了能够更快地进入状态,我为自己设定了一个项目目标:制作一个FC上的吞食天地2之诸葛孔明传的手机版本。

首先为了能够最大程度地调试代码,我准备了一部索爱K700手机,为什么选择它呢?因为它支持CLDC 1.1和 MIDP 2.0,接着呢它的屏不是很小,最后也是最关键的它很便宜。同时我还准备了用于在PC上开发时调试的JDK/索爱SDK。

然后我就开始准备我的游戏思路了~

这个游戏是一个RPG,它里面的出场人物大概有500个左右,并且有大量的人物对话和特殊事件,并且各种物品品名繁多,这也是RPG的通常特点。为了能够最有效地控制这些数据内容,我并不打算将这些数据全部放在代码中,这样的好处就是以后维护的时候非常方便,并不需要为了修改某个人物的形象而翻阅大量的代码。不过这样也有难处,因为实际上这样作的话,整个核心代码就是一个完整的RPG游戏引擎了~~ 不过这本来就是我的学习目的,那么为什么还会怕这点难度呢?GOGOGO~~

整个游戏我把它分为了几个部分: 1. 主角人物,2.场景管理,3.NPC管理,4.事件管理,5.物品管理以及在用户界面并不一定能表现出来的6.异常管理和7.系统调试等七个部分。

明天我就开始我的第一部分主角人物了~~

附件一把,里面是我的这个连载的Demo。

DoE2.tar.gz

40.17 KB, 下载次数: 101

Demo

论坛徽章:
0
发表于 2006-05-11 18:26 |显示全部楼层

第二章. 为游戏创建主角

昨天介绍了一下我的J2ME游戏计划,那么,今天呢,就接着来看看游戏中主角的设计吧~

在我的游戏中,我打算让主角能够根据按键方向不同而作出不同的反应,比如按左键就转向到左边,按右键就转向到右边,并且我希望我的主角能够在我连续按左键的时候表现出一个在走动的动作,而不会就象块木头一样一动不动。

因此,我首先为我的主角准备了一个连续动作的PNG文件,这个图片中包括了8个静态单元,分别是向左/右/上/下各2个单元。接下来,我的目标就是实现这个动作的变化了。在 MIDP 2.0 (JSR 11 中为了对付这种情况,专门提供了一个class来进行处理,它就是鼎鼎有名的Sprite。具体Sprite中的Method请参考详细的 JSR 118文档。在这里我就不再累述,赶紧地把我的代码贴出来先:


  1. import javax.microedition.lcdui.*;
  2. import javax.microedition.lcdui.game.*;

  3. public class GameObject {
  4.     public int living_times = 0;                                            // 总计运行祯数限制
  5.     public boolean is_alive = false;
  6.     public Sprite sprite;
  7.     public int frame_speed = 0;                                            // 0 = 每祯更新一个图片,否则为每x祯更新一个图片
  8.     public int frame_count = 0;                                            // 每祯运行次数统计
  9.     private int living_count = 0;                                            // 总计运行祯数统计
  10.     private int screen_width, screen_height;                            // 屏幕宽度和高度
  11.     public static final int frames_per_action = 2;                        // 每个动作的帧数
  12.     private int current_direct = -1;                                            // 当前方向

  13.     public GameObject(Image img, int frame_width, int frame_height, int screen_width, int screen_height) {
  14.         sprite = new Sprite(img, frame_width, frame_height);
  15.         this.screen_width = screen_width;
  16.         this.screen_height = screen_height;
  17.         reset();
  18.     }

  19.     public void paint(Graphics g) {
  20.         if (!is_alive) return;
  21.         sprite.paint(g);
  22.     }

  23.     public void reset() {
  24.         living_count = 0;
  25.         frame_count = 0;
  26.         is_alive = true;
  27.         sprite.setFrame(0);
  28.     }

  29.     public void update() {
  30.         if (!is_alive) return;
  31.         if (++frame_count > frame_speed) {
  32.             frame_count = 0;
  33.             sprite.nextFrame();
  34.             if (living_times != 0 && ++living_count > living_times) is_alive = false;
  35.         }
  36.     }

  37.     public void move(int relative_x, int relative_y) {
  38.         sprite.move(relative_x, relative_y);
  39.     }

  40.     public void moveto(int absolute_x, int absolute_y) {
  41.         sprite.setPosition(absolute_x, absolute_y);
  42.     }

  43.     // 根据英雄ID和英雄当前方向更新英雄动作
  44.     public void updateByDirect(int brave_id, int direct, int move_x, int move_y) {
  45.         if (direct != current_direct) {
  46.             reset();
  47.             moveto(move_x, move_y);
  48.             int[] frame_sequence = new int[frames_per_action];
  49.             for (int i = 0; i < frames_per_action; i++) frame_sequence[i] = brave_id * frames_per_action * 4 + direct * frames_per_action + i;    // 设置当前动作的图片序列
  50.             sprite.setFrameSequence(frame_sequence);
  51.             frame_sequence = null;
  52.             current_direct = direct;
  53.             update();
  54.         }
  55.         else {
  56.             moveto(move_x, move_y);
  57.             update();
  58.         }
  59.     }
  60. }
复制代码


考虑到以后我还需要设计场景,并且游戏场景肯定是非常巨大的,因此我并不打算让我的主角实际移动,因为这样的话,我需要花费更多的内存来同时处理主角的移动和场景的移动。我的想法是在每次按键的时候主角仅针对按键的不同而作出不同的动作(也就是切换主角Sprite对应的frame),而移动的实际上是场景部分,这样我的主角处理就简单多了,不容易造成主角/场景同时移动时可能产生的矛盾了。一个比较通俗的比喻:就象车和车外的人,如果车和车外的人一起移动,那么势必车内的人有可能会看不到车外的人某个其他动作,而如果仅仅是车动,那么在车内的人看来,车外的人每个动作都一览无遗。

接下来解释一下上边的代码:

1. sprite = new Sprite(img, frame_width, frame_height);
这里我定义了一个Sprite,它每祯的宽度为 frame_width,高度为 frame_height,并且所有祯都包括在 img 这个 Image 对象中。 img 应该为使用 Image:createImage 方法创建的一个 Image 对象。根据Sprite的机制,如果我的这个 img 指定的图片文件宽度为 frame_width * 2,高度为 frame_height * 3,那么 sprite 中的祯总数就应该是6。

2. int[] frame_sequence = new int[frames_per_action];
for (int i = 0; i < frames_per_action; i++) frame_sequence[ i ] = brave_id * frames_per_action * 4 + direct * frames_per_action + i;
sprite.setFrameSequence(frame_sequence);
这里用到了Sprite的一个方法 setFrameSequence(int[] sequence),它用来定义 Sprite中可供使用的祯序列,从它的类型 int[] 可以看出它实际上是一个数组,这个数组的元素就是可供使用的每祯在Sprite实例中的祯Id。比如我用 1 的代码创建了一个包括8祯的 Sprite,而根据实际情况我只需要暂时使用第 2/4/6/8 祯,那么我就需要使用 setFrameSequence 来指定我暂时需要使用的祯序列,sequence 就应该是 { 2, 4, 6, 8 }。
然后再来看看我这里的代码:
frame_sequence[ i ] = brave_id * frames_per_action * 4 + direct * frames_per_action + i;
为什么是这样的呢? brave_id 是我的主角形象ID,因为我的主角并不总是一个人物,它可能在修改游戏人物队列顺序以后发生改变,因此我在这里定义了一个 brave_id 以适应以后的应用。direct 是当前主角的方向。
这样这段代码就容易理解了,frame_sequence[ i ]实际上就是每个方向上的动作在Sprite中的祯ID。

最后,附件是我的主角动作图片文件。

主角动作图片

主角动作图片

论坛徽章:
0
发表于 2006-05-12 09:37 |显示全部楼层
顶着先,下班回去以后继续写。。。

论坛徽章:
0
发表于 2006-05-12 13:53 |显示全部楼层
牛的

论坛徽章:
0
发表于 2006-05-12 14:10 |显示全部楼层
第一页的时候留个名, 混个脸熟, 前些时候还有人问我J2EE有前途还是J2ME有前途, 我说我现在老了, 不适合再钻研另外一个技术, 不如去读个MBA.

论坛徽章:
0
发表于 2006-05-27 11:15 |显示全部楼层

第三章. 游戏地图的绘制

在前一章里面,我介绍了我的主角设计思想:只让主角对对应的按键做出相应的动作,但实际上并不发生位移。那么肯定就有朋友会问了:一个RPG里面主角都不能发生位移,那能算是个RPG么?今天咱们就来解决看上去主角没有位移的问题。
在前面我也提到了一个相对移动的概念,所以我在这里不再重复了。今天咱们就来仔细看看游戏地图(背景)的实现,通过移动绘制地图,我们就可以让主角相对移动起来。

首先,我们先将该死的移动绘制地图抛开,考虑一下我们的地图大小吧。在一个RPG里面,主要的组成部分就是庞大的地图,巨大的文字量。也就是说我们的地图可能会以一个大怪物的身份登场,这个大怪物很有可能会将移动设备那可怜的内存一口吃掉,甚至让你的移动设备罢工。同时由于移动设备的屏幕大小是完全固定的,用户能看到的地图范围是固定的,因此我就想是不是只绘制用户所能看到的范围大小,而不是完全绘制地图呢?很明显的,这个思路能够大量地节约内存,并且能够达到尽量高效地输出图象桢的目的。
下边就是我用来绘制可视范围的方法:

  1. public void setAllCells(int offset_block_columns, int offset_block_rows) {
  2.         int new_block_column, new_block_row;
  3.         for (int i = 0; i < blocks_rows; i++) {
  4.                 for (int j = 0; j < blocks_columns; j++) {
  5.                         new_block_column = j + offset_block_columns;
  6.                         new_block_row = i + offset_block_rows;
  7.                         if (
  8.                                 new_block_column >= 0 && new_block_column < current_map_data[0].length
  9.                                 && new_block_row >= 0 && new_block_row < current_map_data.length - 1
  10.                         ) tiled_layer.setCell(j, i, parseModel(current_map_data[new_block_row][new_block_column]));
  11.                         else tiled_layer.setCell(j, i, 0);
  12.                 }
  13.         }
  14. }
  15. private int parseModel(String block_content) {
  16.         int p = block_content.indexOf("|");
  17.         if (p != -1) return Integer.parseInt(block_content.substring(0, p).trim());
  18.         else return Integer.parseInt(block_content.trim());
  19. }
复制代码

传递的两个参数分别是当前主角相对启始点的X位移和Y位移,每次按一次向左方向键,这个相对X位移就减1,每次按一次向上方向键,相对Y位移就加1。然后在绘制地图的时候,我首先根据这两个参数找到可视范围的左上角在实际地图数据中的位置(new_block_column 和 new_block_row),然后因为固定移动设备的屏幕大小是固定的,因此就可以根据这些绘制出可视范围内的所有地图元素了。

接着,让我们来想想如果我们的地图不仅仅是一张,比如分为了城市/野外/室内几种,那么我们不可能为每一张地图都写一个绘制方法吧?我们要考虑到方法复用,因此我就生成了一个专用的地图读取绘制类。这个地图读取绘制类的作用就是首先从地图配置文件里面读取数据,然后将这个数据存放在缓冲中供绘制方法使用。

最后看看我的完整的地图绘制类吧:

  1. // 地图绘制工具
  2. /* *
  3. *  地图格式
  4. *
  5. *  模型ID|事件描述
  6. *  地图最后一行为初始地图绝对坐标,如果不传入需要前往的地图绝对坐标,则使用该初始绝对坐标
  7. * */
  8. /*
  9. *  事件格式
  10. *        事件类型|事件是否自动运行|事件发生地图ID|事件需求类型|事件需求|允许输入|事件完成以后当前任务ID
  11. *        101|0|切换的目标地图ID|在目标地图的绝对块X坐标|在目标地图的绝对块Y坐标|0|0        切换地图
  12. *        102|1|NPC ID|0|0|0|0        自动对话。事件需求类型为任务,需求当前任务ID为0,不允许输入。输出的是ID为 NPC ID 的NPC对话
  13. *  事件类型
  14. *        事件ID<100则不能移动,即不监听上下左右按键事件
  15. *        2        对话
  16. *        101        切换地图
  17. */
  18. import javax.microedition.lcdui.*;
  19. import javax.microedition.lcdui.game.*;
  20. import java.lang.Math;

  21. public class BackgroundLayer {

  22.         private int screen_width, screen_height;        // 屏幕宽、高度
  23.         public static final int cell_width = 16;                // 地图单元格宽度
  24.         public static final int cell_height = 16;                // 地图单元格高度
  25.         private TiledLayer tiled_layer;
  26.         private String[][] current_map_data;                // 当前地图的数据
  27.         private int current_map_id;                                        // 当前地图ID
  28.         public static int blocks_columns, blocks_rows;        // 屏幕最大块列数和行数
  29.         private int start_block_x, start_block_y;                // 起始绝对坐标

  30.         // 构造器
  31.         public BackgroundLayer(int screen_width, int screen_height) {
  32.                 this.screen_width = screen_width;
  33.                 this.screen_height = screen_height;
  34.                 blocks_columns = (int)Math.ceil(screen_width / cell_width);        // 单元格列数
  35.                 blocks_rows = (int)Math.ceil(screen_height / cell_height);        // 单元格行数
  36.         }

  37.         // 装载指定地图
  38.         public String[][] loadMapData(int map_id) {
  39.                 if (current_map_id != map_id) {
  40.                         FileTool file_tool = new FileTool();
  41.                         current_map_data = file_tool.readFromTxtFile("/res/maps/" + map_id + ".txt");
  42.                         file_tool = null;
  43.                         current_map_id = map_id;
  44.                         this.start_block_x = Integer.parseInt(current_map_data[current_map_data.length - 1][0]);
  45.                         this.start_block_y = Integer.parseInt(current_map_data[current_map_data.length - 1][1]);
  46.                         tiled_layer = null;
  47.                         System.gc();
  48.                         try {
  49.                                 tiled_layer = new TiledLayer(blocks_columns, blocks_rows, Image.createImage("/res/images/background/" + current_map_data[current_map_data.length - 1][2] + ".png"), cell_width, cell_height);
  50.                         }
  51.                         catch (Exception e) { }
  52.                 }
  53.                 return current_map_data;
  54.         }

  55.         // 装载指定地图
  56.         public String[][] loadMapData(int map_id, int start_block_x, int start_block_y) {
  57.                 if (current_map_id != map_id) {
  58.                         FileTool file_tool = new FileTool();
  59.                         current_map_data = file_tool.readFromTxtFile("/res/maps/" + map_id + ".txt");
  60.                         file_tool = null;
  61.                         current_map_id = map_id;
  62.                         this.start_block_x = start_block_x;
  63.                         this.start_block_y = start_block_y;
  64.                         tiled_layer = null;
  65.                         System.gc();
  66.                         try {
  67.                                 tiled_layer = new TiledLayer(blocks_columns, blocks_rows, Image.createImage("/res/images/background/" + current_map_data[current_map_data.length - 1][2] + ".png"), cell_width, cell_height);
  68.                         }
  69.                         catch (Exception e) { }
  70.                 }
  71.                 return current_map_data;
  72.         }

  73.         public void paint(Graphics g) {
  74.                 tiled_layer.paint(g);
  75.         }

  76.         // 根据地图范围设置显示区域的模型内容
  77.         public void setAllCells(int offset_block_columns, int offset_block_rows) {
  78.                 int new_block_column, new_block_row;
  79.                 for (int i = 0; i < blocks_rows; i++) {
  80.                         for (int j = 0; j < blocks_columns; j++) {
  81.                                 new_block_column = j + offset_block_columns;
  82.                                 new_block_row = i + offset_block_rows;
  83.                                 if (
  84.                                         new_block_column >= 0 && new_block_column < current_map_data[0].length
  85.                                         && new_block_row >= 0 && new_block_row < current_map_data.length - 1
  86.                                 ) tiled_layer.setCell(j, i, parseModel(current_map_data[new_block_row][new_block_column]));
  87.                                 else tiled_layer.setCell(j, i, 0);
  88.                         }
  89.                 }
  90.         }

  91.         // 根据地图范围设置显示区域的模型内容
  92.         // default_block_id 缺省的模型ID,超出地图配置文件范围的内容使用该块绘制
  93.         public void setAllCells(int offset_block_columns, int offset_block_rows, int default_block_id) {
  94.                 int new_block_column, new_block_row;
  95.                 for (int i = 0; i < blocks_rows; i++) {
  96.                         for (int j = 0; j < blocks_columns; j++) {
  97.                                 new_block_column = j + offset_block_columns;
  98.                                 new_block_row = i + offset_block_rows;
  99.                                 if (
  100.                                         new_block_column >= 0 && new_block_column < current_map_data[0].length
  101.                                         && new_block_row >= 0 && new_block_row < current_map_data.length - 1
  102.                                 ) tiled_layer.setCell(j, i, parseModel(current_map_data[new_block_row][new_block_column]));
  103.                                 else tiled_layer.setCell(j, i, default_block_id);
  104.                         }
  105.                 }
  106.         }

  107.         // 解析地图模型内容
  108.         private int parseModel(String block_content) {
  109.                 int p = block_content.indexOf("|");
  110.                 if (p != -1) return Integer.parseInt(block_content.substring(0, p).trim());
  111.                 else return Integer.parseInt(block_content.trim());
  112.         }

  113.         // 解析英雄所在块数据内容
  114.         // 第一元素为事件类型,小于MainGameCanvas.STATUS_IN_MOVING的事件类型不能移动
  115.         // 其他参考 EventManager中的说明
  116.         public int[] parseBlock(int offset_x, int offset_y) {
  117.                 int[] block_info = new int[7];
  118.                 int brave_position_x = blocks_columns / 2 + offset_x;
  119.                 int brave_position_y = blocks_rows / 2 + offset_y;
  120.                 if (
  121.                         brave_position_x >= 0 && brave_position_x < current_map_data[0].length
  122.                         && brave_position_y >= 0 && brave_position_y < current_map_data.length - 1
  123.                         )
  124.                 {
  125.                         String tmp = current_map_data[brave_position_y][brave_position_x];
  126.                         int p = tmp.indexOf("|");
  127.                         int i = 0;
  128.                         if (p != -1) {
  129.                                 while ((p = tmp.indexOf("|")) != -1) {
  130.                                         if (i > 0) block_info[i - 1] = Integer.parseInt(tmp.substring(0, p).trim());        // 跳过地图模型描述字段
  131.                                         tmp = tmp.substring(p + 1);
  132.                                         i++;
  133.                                 }
  134.                                 block_info[i - 1] = Integer.parseInt(tmp.trim());
  135.                         }
  136.                         else block_info[0] = MainGameCanvas.STATUS_IN_MOVING;
  137.                 }
  138.                 else block_info[0] = MainGameCanvas.STATUS_IN_GAMING;
  139.                 return block_info;
  140.         }

  141.         public int getStartOffset(boolean is_x) {
  142.                 if (is_x) return start_block_x - blocks_columns / 2;
  143.                 else return start_block_y - blocks_rows / 2;
  144.         }
  145. }
复制代码


其中用到了一个我的自定义类 FileTool,这个文件我在下一次再发上来吧~~

论坛徽章:
0
发表于 2007-07-31 17:30 |显示全部楼层
怎么没有了
继续阿

论坛徽章:
0
发表于 2007-08-01 09:42 |显示全部楼层
强贴留名.....

论坛徽章:
0
发表于 2007-08-17 08:29 |显示全部楼层
顶一下
还有下文吗?
您需要登录后才可以回帖 登录 | 注册

本版积分规则 发表回复

  

北京盛拓优讯信息技术有限公司. 版权所有 京ICP备16024965号-6 北京市公安局海淀分局网监中心备案编号:11010802020122 niuxiaotong@pcpop.com 17352615567
未成年举报专区
中国互联网协会会员  联系我们:huangweiwei@itpub.net
感谢所有关心和支持过ChinaUnix的朋友们 转载本站内容请注明原作者名及出处

清除 Cookies - ChinaUnix - Archiver - WAP - TOP