|
|
作者: 222.248.37.* 2005-6-21 13:48 回复此发言 | |
2
|
2005年毕业论文----J2ME手机游戏开发高计(原创) | |
|
3. Java技术使网络带宽的应用更为有效,因为应用程序可以下载到器件上,并在本地运行,仅仅是在连接到服务器时才会占用网络带宽。
基于以上分析,Java手机将是未来手机的发展方向,是业界的热点。
1.6 本游戏的未来
看过周润发主演的《赌神》都知道里的豪斯是很好玩的,也很有发展前途。一,这个游戏对手机的要求不高;二,这个游戏简单;三,当这个游戏做成网络游戏时,可以是各个玩家斗智的平台;要实现这点技术要求并不难。在本设计的基础上再加上某些技术就可以了:a) 网络的连接 b) 能读能存网络数据库 c) 牌不再是用随机产生这么简,它应该根据玩家的积分来产生,这样会更好。
1.7 手机未来的发展方向
手机游戏的盈利主要是由于它们的涉及面很广。手机已经与现代生活方式紧紧地结合在一起。他们是最普遍携带的个人用品中仅次于钥匙和钱包的东西。传统的台式机游戏将目标锁定在低级趣味的人和青少年身上,而手机游戏则每个人都可以访问的到——随时,随地。尽管每个手机游戏都不贵,但是巨大的使用量(如:每人每星期一个新游戏)将使得这个市场商机无限。只要有好的游戏,就会有更广的使用量。
1.8 游戏业务及J2ME概述
虽然 Java 已经被用到许多企业级软体上,可是其实骨子里面还是非常适合用在嵌入式系统之中。Java平台演进到Java2后,Java平台分别针对不同领域的需求被分成四个版本,亦即J2EE、J2SE、J2ME以及JavaCard。其中J2ME定位在消费性电子产品的应用上。这个版本针对资源有限的电子消费产品的需求精简核心类库,并提供了模块化的架构让不同类型产品能够随时增加支持的能力。这个版本的应用层面相当广泛,会是未来Java平台发展的重点项目。J2ME 在设计其规格的时候,遵循着“对各种不同的装置而造出一个单一的开发系统是没有意义的事”这个基本原则。于是 J2ME 先将所有的嵌入式装置大体上区分为两种:一种是运算功能有限、电力供应也有限的嵌入式装置(比方说PDA 、手机);另外一种则是运算能力相对较佳、在电力供应上相对比较充足的嵌入式装置 (比方说冷气机、电冰箱、电视机上盒 (set-top box))。因为这两种型态的嵌入式装置,所以Java 引入了一个叫做Configuration 的概念,把上述运算功能有限、电力有限的嵌入式装置定义在Connected Limited Device Configuration(CLDC)规格之中;而另外一种装置则规范为 Connected Device Configuration(CDC)规格。也就是说, J2ME 先把所有的嵌入式装置利用Configuration 的概念区隔成两种抽象的型态。J2ME平台被认为是最杰出的手机游戏平台,它为开发者、设备制造商、网络通信公司和消费者广泛接受。它有一些非常重要的特征对所有组织都有益。因为J2ME应用在不同设备上都是便携式的,他们常常可在网络上下载和执行。如果没有正确的防范,它则为用户和无线通信公司冒着无数个安全的风险。幸运的是,Java被设计成一种安全的语言。所有字节码应用在执行之前都要校验;JVM在执行过程中监督应用的安全性和存储违反问题。MIDP v2 运行时间包括一个完全特征化的、基于域的安全管理员,它在应用的数字签名者鉴别的基础上赋予应用API级许可。
纵观IT产业的历史,就像军事的发展时常推动计算机的演化一样,计算机游戏已经成为技术创新背后的动力之一。计算机游戏者渴望更加强大的硬件计算能力;渴望不受不同的软件的限制——无论是将图形强制在人工智能(AI)上 还是网络安全性。游戏开发者和玩家常常是前沿计算机技术的最早的采用者。由于他们的创新天性,游戏不再是由大型采购公司控制的技术产品——游戏领域总是有充足的空间给那些在这方面有天分的单个创新者。
手机游戏的盈利主要是由于它们的涉及面很广。手机已经与现代生活方式紧紧地结合在一起。他们是最普遍携带的个人用品中仅次于钥匙和钱包的东西。传统的台式机游戏将目标锁定在低级趣味的人和青少年身上,而手机游戏则每个人都可以访问的到——随时,随地。尽管每个手机游戏都不贵,但是巨大的使用量(如:每人每星期一个新游戏)将使得这个市场商机无限。但是,对于开发者来说,将控制台游戏迁移到手机游戏工程很大。因为他们所面向的对象、生活方式和分布式模型都有着极大的区别。
二 开发环境及相关技术的介绍
2.1 Java语言的特点
1. 平台无关性
Java引进虚拟机原理,并运行于虚拟机,实现不同平台之间的Java接口。使
用Java编写的程序能在世界范围内共享。Java的数据类型与机器无关。
2. 安全性
Java的编程类似C++,但舍弃了C++的指针对存储器地址的直接操作,程序运
行时,内存由操作系统分配,这样可以避免病毒通过指针入侵系统。它提供
了安全管理器,防止程序的非法访问。
3. 面向对象
Java吸收了C++面向对象的概念,将数据封装于类中,实现了程序的简洁性和
便于维护性,使程序代码可以只需一次编译就可反复利用。
4. 分布式
Java建立在TCP/IP网络平台上,提供了用HTTP和FTP协议传送和接收信息
的库函数,使用其相关技术可以十分方便的构建分布式应用系统。
5. 健壮性
Java致力与检查程序在编译和运行时的错误,并自动回收内存,减少了内存出
错的可能性。Java取消了C语言的结构、指针、#define语句、多重继承、goto
语句、操作符、重载等不易被掌握的特性,提供垃圾收集器自动回收不用的内存空间。
2.3 J2ME 是什么?
据J2ME应用程序调用的API,J2ME应用程序分为标准应用程序和不易移植的应用程序两种。
据J2ME应用程序调用的API,J2ME应用程序分为标准应用程序和不易移植的应用程序两种。
术语:
CDC:连接设备配置
CLDC:连接限制设备配置
JRE:Java运行环境
MIDP:Mobile Information Device Profile规范
OTA:Over The Air
非OTA:数据线、蓝牙(Bluetooth)、红外线(IrDA)、多媒体信息服务(MMS)、Email附件
2.3 J2ME智能客户端
J2ME平台被认为是最杰出的手机游戏平台,它为开发者、设备制造商、网络通信公司和消费者广泛接受。它有一些非常重要的特征对所有组织都有益
2.4 J2ME 总体架构
J2ME 使用配置和简表定制 Java 运行时环境 (JRE)。作为一个完整的 JRE,J2ME 由配置和简表组成,配置决定了使用的 JVM,而简表通过添加特定于域的类来定义应用程序。
配置将基本运行时环境定义为一组核心类和一个运行在特定类型设备上的特定 JVM。我们将在 J2ME 配置一章中详细讨论配置。
简表定义应用程序;特别地,它向 J2ME 配置中添加特定于域的类,定义设备的某种作用。我们将在 J2ME 简表一章中深入介绍简表。
下面的图表描述了不同的虚拟机、配置和简表之间的关系。它同时把 J2SE API 和它的 Java 虚拟机进行了比较。虽然 J2SE 虚拟机通常被称为一种
JVM,但是 J2ME 虚拟机、KVM 和 CVM 都是 JVM 的子集。KVM 和 CVM 均可被看作是一种 Java 虚拟机 -- 它们是J2SE JVM 的压缩版,并特定于 J2ME。
移动通信的时代即将来临,通信相关待业变得前景可期,而除了移动通信的主要通信工具——手机,其功能越来越强大之外,有更多的厂商相继投入移动通信设备的生产与开发,其平台和操作系统的复杂度比目前混乱的PDA有过之而不及,除了Symbian 针对手机推出操作系统 Symbian OS(过去称做EPOC,)操作系统之外,微软也针对手机推出操作系统 Smartphone 2002 (Stinger),但是日前市面上的手机仍以使用厂商自行开发的操作系统者居多。
平台的复杂纷乱对一般用户当然没什么影响,但是对于程序开发人员来说,这么多不同的程序发展平台,光看完头就昏了。如果每个平台都有自己的程序写法以及程序库,那么光是看上面这些平台至少就要学习五种以上程序的写法。当然,只专精一种平台当然是很好的事情。可是程序员不禁要说:“如果我们写出来的软件可以在不经过修改源代码的情况下就能够在这些平台上执行,那不是更完美吗?“对程序开发人员来说,这样的投资报酬率当然是最大的。
要在那么多平台上开发程序,对程序员来说的确是很大的挑战,如果要把所有的时间和精力放在软件的可用性上,那么相对而言很多时候我们根本没有那么多时间撰写各种平台的程序。要解决这个问题,一般来说程序员会选用一个可以跨平台的Framework来达成至少source code level的跨平台。利用Java的“Write once,run anywhere”特性,我们可以真正达到程序只要写一次,拿到任何平台上都可以执行。
利用Java撰写手机上的程序当然有其缺点,最广为人知的可能就是执行效率的问题,Java在执行速度这个议题上一直让人诟病。不过笔者认为,随着技术的发达,将会有更快更省电的嵌入系统专用CPU出现,Java One中也传出将针对J2ME开发专用芯片,让Java在 手机上的程序可以跑得更快,因此效率上的总是其实是可以忽略的。更何况,当Sun在设计J2ME的时候,也用了很多方式企图加快Java 在手机上的执行速度。
因此,目前的移动通信设备大厂几乎采用J2ME平台。搭建J2ME 环境:
2.5 简单环境的搭建:
1、 下载JDK和J2ME无线工具包(www.sun.com.cn)
安装SUN JDK:j2sdk-1_4_0_03-windows=i386
安装SUN J2ME SDK:j2me_wireless_toolkit-1_0_3-win
2 关于Wireless Tool Kit
WTK(Wireless Tool Kit)是Sun公司针对J2ME推出的用于手机和Palm等
移动设备的开发包,是除手机厂商的专用开发包外唯一的手机模拟器开发包。
它通用性高,开发出的应用程序可保证能运行在大部分设备上,而不像专用
厂商具有一定的不兼容性。虽然它没有强大的功能和完善的调试手段,但它
提供运行模拟器的最基本组件,是其他IDE需集成采用的必备元素。
3, 用于手机游戏开发的环境不是只建立一个 java sdk 就可以了,它还需要了一个一个库类,叫 java toolkit。首先安装 java sdk 再在原来的目录下安装java toolkit这个新的环境就可以满足我们的需要了。然后我们编写一个最简单的程序用来验证这个环境是否可以用来编写我们的手机程序。
编写以下代码:
保存成 Hello.java 文件。
import javax.microedition.midlet.*;
import javax.microedition.lcdui.*;
public class Hello extends MIDlet
{ private Display display;
public Hello()
{ display = Display.getDisplay(this); }
public void startApp() {
TextBox t = new TextBox("Hello MIDlet", "Hello MIDP!", 256, 0);
display.setCurrent(t);
}
public void pauseApp() {
}
public void destroyApp(boolean unconditional) {
}
}
启动Ktoolbar 新建一个工程,取名 Hello 。把以上文件拷到 Hello 文件的目录编译。能通过编译并能运行,说明这个开发环境就能满足我们的需要了。
2.6 MIDP应用程序:
MIDP应用程序是指遵循由应用程序主体(Jar文件)和应用程序描述文件(Jad文件)组成。按部署方式可分OTA和非OTA两种。
MIDlet:是Jave一个类,扩展了javax.microediton.midlet.MIDlet抽象类。MIDlet生命周期分3种状态:运行状态,暂停状态,销毁状态等。
2.6 Java Appication Manager
手机中负责调配程序运行资源的管理后台是Java Application Manager。它所使用的传输媒体可以是红外线、网络、以及其他可用来传输的媒体。Java Application Manager 会从网络上下载代表该Application Suite 的JAR 档,接着在手机上安装此MIDlet Suite,
然后在手机开始执行该应用程序。整个详细的运作流程如图2-1所示。
2.7 预先审核:
审核动作一般在PC上执行,速度可以接受。为了是对安全的考虑,任何来自本地或者远程机器,都必须经过Byte Code审核器的验证,以防止程序在传输途中遭到恶意更改。如图
2.8 J2ME、J2SE 与 J2EE 之间的比较:
下面的图表描述了支持 J2ME 应用程序的设备,同时说明了 J2ME 适合 Java 平台之处:
三 J2ME 手机游戏需求分析
3.1 本程序需解决的有关技术问题
1. 游戏程序是一项精度要求很高的程序系统,因为其代码利用率很高。一个实时运行的最终作品,每秒都会运行成千上万行程序,绘图事件、键盘事件都会以极高的频率在后台等待响应,若有丝毫的差别都将很容易导致程序在运行不久后可能出现严重错误,甚至死循环。因此,其逻辑设计应当相当严谨,需将所有可能发生的事件及意外情况考虑在设计中。
2. 游戏中为了美观,适用性强,可能需要采用外部文件引入的图片贴图,有关贴图,在MIDP2.0中提供了用于增强游戏功能的game包,使得解决静态或动态、画面背景、屏幕刷新的双缓冲等都有较好的解决方案。
3. 一开始就把牌全部分配好,这样就可以简单而有效地避免了牌的重复,也可以大大地简化了程序代码的编写。
4. 牌的转化可以用一个函数来实现,这样就可以使代码更模块化,利用也就可以更高。
5.牌慢慢移动的实现。牌的移动是用线程来实现的,用Thread.sleep()
这个函数来作时间延迟,让坐标变量缓慢地变化。然后通过刷新画图,就会使人觉得牌是在缓慢地移动。
6.是否是要牌还是放弃(认输),它通过向右键(要牌)和向左键(放弃)来操作的。当点击向右键时,发生点击事伯,变量发生相应的变化,然后触发画图事件。
7.当玩家都跟到5张牌时,就要判断牌的大小了,它通过多次循环,来确定其大小。
8. Java是基于虚拟机的半解释型编译系统,其执行效率较C++等完全编译后的程序会低很多,程序如果不进行精简和优化,将可能导致运行的不流畅。除开发过程中对结构上的控制、变量的使用、算法的优化等优化外,还可以使用混淆器(Obfuscator)进行程序打包后的优化。
以上相关技术细节和整体流程将分别在以下小节阐述。
3.2 程序流程图
MIDlet suite是MIDP应用程序的最小单位,JAM负责将手机内的MIDlet suite以图形
v 化的方式呈现,让用户能够选取欲执行的MIDlet suite,一旦选取了某个MIDlet suite,操作系统就会激活KVM执行里面的MIDlet。MIDlet及相关的支持类组成了MIDP应用程序的实际内容。每个MIDlet都必须继承javax.microedition.midlet.MIDlet这个抽象类。在MIDP规格中定义了MIDlet的生命周期,以及可以存在的三种状态,包括Paused、Active以及Destroyed,每一个MIDlet在任何时刻只可能处于其中的一个状态这
v 三种状态的转换关系如图所示:
本程序采用面向对象的设计模式,对游戏中的所有物体赋予对象的概念和属性。运行程序后允许用户选择执行选项菜单,在开始游戏后将先从外部文件载入牌的图文件,对背景的所有物体进行绘图。在主程序运行的线程中,画面刷新将以一定的频率采用双缓冲技术对屏幕重绘,实时反映整个游戏的进行状态。用户控制的牌运行在主线程中,随屏幕刷新的频率而移动。牌将在游戏开始时逐渐新增线程,每增加一个牌对象就新增加一条线程,一旦线程数满到最大值(本程序暂设置最大值为几千多个),就不允许线程的再增加。这个线程问题就是本设计的一个 bug。
本程序主要的流程如图3—2所示:
流程简略解释:
程序一开始执行是先从主类 test.java 来开始。这个类从 MIDlet 类继承过
来,在程序的开始函数 startApp() 中我用
Navigator.current = Navigator.MAIN_SCREEN; 这个等式把程序的执行转到
MainScreen.java 文件(程序的画面)来。而这个类继承 List ,这个 List 类可以更方便地控制程序的选择。在这个文件中列出一个菜单供用户去选择,如下:
开始
设定
版权声明
离开
当点击开始时,程序转到 GameScreen 这个类去。GameScreen 这个类继承 Canvas,Canvas 专门是用来控制游戏的。里面包括屏幕的绘图,一些变量的初始化。
当点击设定时,程序转到SetScreen 这个类。这个类用于登录帮助网站的,当然这个是需要网络支持的。
当点击版权声明时,程序转到 CopyScreen 这个类,这个类继承 Alert,它用于描述版权的一些信息。
当点击离开时,程序退出 test 类。
3.3 绘图与MIDP2.0新增的GameCanvas包
3.3.1 提供低级绘制的Canvas类
为了能有程序开发人员控制接口的外观和行为,需要使用大量的初级用户接口类,尤其在游戏程序中,几乎完全依赖的就是Canvas抽象类进行绘图。从程序开发的观点看,Canvas类可与高级Screen类交互,程序可在需要时在Canvas中掺入高级类的组件。Canvas提供了键盘事件、指点杆事件(如果设备支持),并定义了允许将键盘按键映射为游戏控制键的函数。键盘事件由键代码指定,但这样控制游戏会导致缺乏通用性,并不是每个设备的键盘布局都适合游戏的操作。应当将键代码转换为游戏键的代码,以便硬件开发商能定义他们自己的游戏键布局。本程序中,操纵用户坦克运行的按键都定义为游戏控制键,这样便能适应所有的机器。
3.3.2 Graphics类
Graphics类提供了简单的2D绘图功能。它具有24位深度色彩的绘制能力,以三原色分别各占一个字节表示其颜色。程序只能在paint()函数中使用Graphics绘制,GameCanvas可调用getGraphics()函数直接绘制在缓冲区上,可以在任何时间请求传输到前台。其对象会被传给Canvas的paint()函数,
以便最终显示。
3.3.3 PNG格式
PNG(Portable Network Graphics)格式是MIDlet唯一支持的图象格式,PNG具体格式由PNG Specification,Version 1.0定义的。PNG格式提供透明背景的图象,这对绘制游戏画面和被操纵主角极有帮助。坦克之间或与障碍物碰撞时就不会因为背景有特定的颜色,显示出的效果像贴上的图片而缺乏真实感,物体之间轻微重叠时最上层图片也不会覆盖超过其有效象素外的部分。
PNG格式图片中包含许多定义其图片特性的冗余部分(Chunks)。这些代码包含在每一个单独的png格式图象中,然而如果将多个png图象合并在一张幅面稍大一些的整图中,多个chunks就可以得到精简,图片的大小可以得到控制。使用Image
类中的createImage函数可从整图中分割出所需要的元素。在Game包中的TiledLayer和Sprite类都整合了这样的
功能。本程序中的地图元素都集成在一张tile.png图片中,实现了方便的管理和程序体积的精简。
3.3.4 有关绘图的一些技术
在没有MIDP2.0前,进行游戏绘图一般需要手动编程使用双缓冲。需要在paint()方法内所想要画的图形画在一张预先准备好的背景,等所有绘图操作都完成后再将背景的数据拷贝到实际的屏幕上。Image类提供了一个建立背景的静态方法createImage(int width, int height),再利用getGraphics()方法取得属于这个背景的Graphics对象,所进行的绘图操作都会作用在背景上,等到全部的绘图操作完成后,再调用drawImage()方法将背景的数据复制到实际显示的屏幕上。
这样的技术在绘制动画时特别有用。绘制动画时经常需要不断地更新画面,而更新画面的操作就是先将屏幕以fillRect()的方式清除,再将下一张图片画在屏幕上,然而反复的清除及重绘会造成屏幕的闪烁现象(flicker),因此使用双重缓冲的好处就是在背景进行这个清除及重绘的操作,再将完成的绘图拷贝到屏幕上,由于用户看不到清除的操作,因此就不会出现闪烁的现象了。不过在某些MIDP的实现上已经加上了双重缓冲的支持,因此在处理前应先利用Canvas类的isDoubleBuffer()方法来判断。
3.7 内存使用的最佳化
通常在MIDP应用程序的手机执行环境中,所牵涉的内存有下列三种:
*应用程序存储内存
*RecordStore存储内存
*执行时期内存(Java Heap)
其中前两种是持久性的内存,关闭电源后还能保持数据的正确性,通常这两种内存所能存储的容量是合并计算的,这个上限对每种手机都不一样,大部分在一两百KB内。在这样的情况下需要在不影响原有功能的情况下适当的缩减JAR文件的大小,除了可以克服内存空间的限制外,也能大幅度缩短下载的时间(费用也降低了),势必会有更多的人愿意下载所开发的程序。其方法有:
第一,就是尽量缩短命名的长度。在应用程序内,对于所建立的类、接口、方法及变量名而言,都需要赋予一个识别的名称,所命名的名称每多一个字符就会在类文件内多产生一个字节,对于一个较复杂的应用程序而言就会增加为数不小的数据量。所有这些可以借助混淆器来帮助实现。
第二是减少复杂的程序结构,为一些共同的行为建立一个抽象类(Abstract Class) 来表示继承的子类的共通性。
第三是减少图形数据的大小。将PNG格式的小分辨率图象合并在一张大的高分辨率图象中,由于减少了chunks,将比合并前的总大小减少许多。
3.6 混淆器(Obfuscator)的使用
Java 语言并没有完全编译成二进制可执行文件,编译出的.class文件是一种介于源程序和二进制之间的一中基于半解释的字节码,需要虚拟机来执行。它包括了所有的信息。然而这样会导致.class很容易被反编译为源代码,从而不能保护作者的知识成果。目前流行的如decode,JAD等反编译工具可以以很快的速度生成源文件。如果不加以施行有效的措施,将造成严重的后果。
由此引入混淆器的概念。混淆器将代码中的所有变量、函数、类的名称变为简短的英文字母代号,如果缺乏相应的函数名指示和程序注释,即使被反编译,也将难以阅读。
混淆器的作用不仅仅是保护代码,它也有精简编译后程序大小的作用。由于以上介绍的减少变量、函数的命名长度的关系,编译后也会从.class文件中减少这些冗余的信息。混淆后,体积大约能减少25%,这对当前费用较贵的无线网络传输是有一定意义的。
为了能与各种IDE集成,就像Java2 SDK一样,混淆器采用命令行参数的形式,以便可被其调用。目前流行的Obfuscator有RetroGuard等。
3.7 模拟器的相关调试
IDE整合的Wireless Tool Kit提供了许多在运行时监视运行状态的工具。 包括内存状况的检测(手机上的内存空间十分有限,必须时刻关注机载内存是否大于程序所能使用到的最大可能的内存空间),网络状况的检测,运行函数的跟踪等。如图3-4,是内存跟踪测试随时间变化的调试器。其中,允许强制垃圾回收(Garbage Collection)。由于Java语言中,不像许多其他的如C++语言,不需要指定回收函数中特定不使用的资源,资源回收机制将自动清空无效变量占用的空间。在程序运行中也可以调用System类的gc()函数手动收回废弃的内存。
四 程序分析和编写手机代码
4.1 游戏进入前的选择
每个MIDlet程序都必须有一个主类,该类必须继承自MIDlet。它控制着整个程序的运行,并且可以通过相应函数从程序描述文件中获取相关的信息。该类中拥有可以管理程序的创建、开始、暂停(手机中很可能有正在运行程序却突然来电的情况,这时应进入暂停状态。)、结束的函数。
本设计程序进入时,首先载入画面的不是游戏运行状态,而是提供选项,当选择 开始 时正式运行。因此,在 MainScreen的构造函数中分配了List类,即选项画面的内存空间。在startApp()函数中,随即调用了Displable的setCurrent()函数将当前屏幕设置为Navigator.MAIN_SCREEN。在 版权声明 界面前,建造了一个Alert类。Alert对象用于显示提示、警告,还有版权信息等告之用户信息的临时闪现的屏幕,它可作为setCurrent的参数,提前显示在最终需要显示的屏幕前。当程序转到 GameScreen 时,画面将永久显示游戏本身,直到用户点击相应按钮手动结束。但是当显示的画面元素超过一屏大小时,将自动转换为永久状态。
GameScreen 继承了接口commandListener,这样,就可以使用高级界面的Command按钮。继承了commandListener的类必须拥有commandAction(),以决定对按键采取什么样的行为。即按钮事件触发后需执行的函数。在设置好commandlistener后,需要调用setCommandListener()以将按钮事件激活。键盘事件中,可用getCommandType()返回的Command类型来确定选择的是什么按钮。MainScreen继承了List类,用于显示列表选项,使用其append()函数可将选项加入到列表中。getSelectIndex()可检测到选择的项目的序号,序号从0开始递增。其中,当选择第一项时将载入正式游戏画面GameScreen类,第二项将显示转到设定画面,第三项用来显示用户信息,版权声明等。第四项是离开游戏选项。
4.2 主游戏逻辑及其涉及到的若干类
4.2.1 程序的菜单选项
这个写在 MainScreen.java 文件中,这个类继承 List 。同时也继承了接口commandListener,这样,就可以使用高级界面的Command按钮。继承了commandListener的类必须拥有commandAction(),以决定对按键采取什么样的行为。即按钮事件触发后需执行的函数。在设置好commandlistener后,需要调用setCommandListener()以将按钮事件激活。用append 方法来加入菜单:
append("开始",null);
append("设定",null);
append("版权声明",null);
append("离开",null);
setCommandListener(this);
再用 getString(int) 函数来获得菜单的选项。
MainScreen.java 文件:
import javax.microedition.lcdui.*;
public class MainScreen extends List implements CommandListener
{
private static Displayable instance;
synchronized public static Displayable getInstance()
{
if(instance == null)
instance = new MainScreen();
return instance;
}
private MainScreen()
{
super("进入Game",IMPLICIT);
/// 菜单选择
append("开始",null);
append("设定",null);
append("版权声明",null);
append("离开",null);
// 事件侦听
setCommandListener(this);
}
public void commandAction(Command c,Displayable s)
{
// 事件侦听
String cmd= getString(getSelectedIndex());
// 把得到的值传给程序控制器,用程序控制器来决定指引程序的执行。
Navigator.flow(cmd);
}
}
4.2.2 游戏的设定
这个写在 SetScreen.java 文件中,而这个类继承 Form 类,同时也继承了接口commandListener,这样,就可以使用高级界面的TextField按钮。继承了commandListener的类必须拥有commandAction(),以决定对 TextField 值获取采等行为。在设置好commandlistener后,需要调用setCommandListener()函数来把值传入程序中。
SetScreen.java 文件:
import javax.microedition.lcdui.*;
public class SetScreen extends Form
//继承 CommandListener 接口
implements CommandListener
{
private static Displayable instance;
synchronized public static Displayable getInstance()
{
if(instance == null)
instance = new SetScreen();
return instance;
}
// 定义 TextField (文本输入框)
TextField url;
Gauge volume;
private SetScreen()
{
super("设定");
url = new TextField("请输入服务器位置","socket://127.0.0.1:99",40,TextField.URL);
append(url);
volume = new Gauge("音量",true,10,3);
// 软键菜单
addCommand(new Command("辅助说明",Command.HELP,1));
addCommand(new Command("返回",Command.BACK,1));
// 事件侦听
setCommandListener(this);
}
public void commandAction(Command c,Displayable s)
{
Navigator.flow(c.getLabel());
}
}
4.2.3 游戏中的帮助和程序控制器
首先调用 HelpScreen 类,然后赋一个值:辅助说明给 Navigator 类,当Navigator接收到这个值时,就显示帮助字符串。
HelpScreen.java
import javax.microedition.lcdui.*;
public class HelpScreen extends Form implements CommandListener
{
public HelpScreen(String c)
{
super("辅助说明");
append©;
addCommand(new Command("返回",Command.BACK,1));
setCommandListener(this);
}
public void commandAction(Command c,Displayable s)
{
Navigator.flow(c.getLabel());
}
}
这个写在Navigator.java 里,它接收 SetScreen.,MainScreen等类传过来的值。然后根据它触发的按钮事件,来调用相对应的事件。
//Navigator.java
import javax.microedition.lcdui.*;
import javax.microedition.midlet.*;
import java.util.*;
public class Navigator
{
final public static int MAIN_SCREEN = 1;
final public static int GAME_SCREEN = 2;
final public static int SET_SCREEN = 3;
final public static int GAME_HELP_SCREEN = 4;
final public static int SET_HELP_SCREEN = 5;
public static MIDlet midlet;
public static Display display;
public static int current;
// 自定义一个类
// 重载函数
public static void show(Object obj)
{
switch(current)
{
case MAIN_SCREEN:
display.setCurrent(MainScreen.getInstance());
break;
case GAME_SCREEN:
display.setCurrent(GameScreen.getInstance());
if( Card[i]%13 == 6) { Num[i] = "8"; } if( Card[i]%13 == 7) { Num[i] = "9"; } if( Card[i]%13 == 8) { Num[i] = "10"; } if( Card[i]%13 == 9) { Num[i] = "J"; } if( Card[i]%13 == 10) { Num[i] = "Q"; } if( Card[i]%13 == 11) { Num[i] = "K"; } if( Card[i]%13 == 12) { Num[i] = "A"; } } ////// 牌的种类 for(int i = 0; i < 10 ; i++) { if(Card[i]/13 == 0) { ///// 黑桃 Kind[i] = 0; } if(Card[i]/13 == 3) { ///// 黑桃 Kind[i] = 3; } if(Card[i]/13 == 2) { ///// 红心 Kind[i] = 2; } if(Card[i]/13 == 1) { ///// 梅花 Kind[i] = 1; } } 把得到的版绘画在手机屏幕上: 它是用 paint() 函数来实现的,慢慢移动的镜头用线程的完成。这会使这个线程产生多个线程,这是程序要改进的地方。 // bCard1 用于控制程序的运行,当牌已经移动到目标地,就使bCard1= false if ( bCard1) { //对家 if(x <=12) { bCard1 = false; bCard2 = true; b2 = true; bb1 = true; } doThreadStart(); g.setColor(255,255,255); g.fillRect(x - 2,70,25,40); g.setColor(0,0,0); g.drawRect(x - 2,70,25,40); // 自家 if( Kind[1] == 3) { g.setColor(255,255,255); g.fillRect(x - 2,120,25,40); g.setColor(0,0,0); g.drawString(Num[1],x,120,g.TOP|g.LEFT); g.drawImage(king1,x - 2,133,g.TOP|g.LEFT); g.setColor(0,0,0); g.drawRect(x - 2,120,25,40); } if(Kind[1] == 2) { g.setColor(255,255,255); g.fillRect(x - 2,120,25,40); g.setColor(255,0,0); g.drawString(Num[1],x,120,g.TOP|g.LEFT); g.drawImage(king2,x - 2,133,g.TOP|g.LEFT); g.setColor(0,0,0); g.drawRect(x - 2,120,25,40); } if(Kind[1] == 1) { g.setColor(255,255,255); g.fillRect(x - 2,120,25,40); g.setColor(0,0,0); g.drawString(Num[1],x,120,g.TOP|g.LEFT); g.drawImage(king3,x - 2,133,g.TOP|g.LEFT); g.setColor(0,0,0); g.drawRect(x - 2,120,25,40); } if(Kind[1] == 0) { g.setColor(255,255,255); g.fillRect(x - 2,120,25,40); g.setColor(255,0,0); g.drawString(Num[1],x,120,g.TOP|g.LEFT); g.drawImage(king4,x - 2,133,g.TOP|g.LEFT); g.setColor(0,0,0); g.drawRect(x - 2,120,25,40); } repaint(); } // 画第一张牌: // 当移动到目标后,用以下来绘出这张牌。 if ( bb1) { g.setColor(255,255,255); g.fillRect(10,70,25,40); // 自家 if( Kind[1] == 3) { g.setColor(255,255,255); g.fillRect(10,120,25,40); g.setColor(0,0,0); g.drawString(Num[1],12,120,g.TOP|g.LEFT); g.drawImage(king1,10,133,g.TOP|g.LEFT); g.setColor(0,0,0); g.drawRect(10,120,25,40); } if(Kind[1] == 2) { g.setColor(255,255,255); g.fillRect(10,120,25,40); g.setColor(255,0,0); g.drawString(Num[1],12,120,g.TOP|g.LEFT); g.drawImage(king2,10,133,g.TOP|g.LEFT); g.setColor(0,0,0); g.drawRect(10,120,25,40); } if(Kind[1] == 1) { g.setColor(255,255,255); g.fillRect(10,120,25,40); g.setColor(0,0,0); g.drawString(Num[1],12,120,g.TOP|g.LEFT); g.drawImage(king3,10,133,g.TOP|g.LEFT); g.setColor(0,0,0); g.drawRect(10,120,25,40); } if(Kind[1] == 0) { g.setColor(255,255,255); g.fillRect(10,120,25,40); g.setColor(255,0,0); g.drawString(Num[1],12,120,g.TOP|g.LEFT); g.drawImage(king4,10,133,g.TOP|g.LEFT); g.setColor(0,0,0); g.drawRect(10,120,25,40); } } // 以上调用了 drawImage 这个函数,这个函数必须先把图片加入程序中:
|
||||||||||||||