求J2ME编写的求h小游戏戏

J2ME手机连连看游戏开发_百度文库
两大类热门资源免费畅读
续费一年阅读会员,立省24元!
评价文档:
J2ME手机连连看游戏开发|选​填​,​简​要​介​绍​文​档​的​主​要​内​容​,​方​便​文​档​被​更​多​人​浏​览​和​下​载​。
把文档贴到Blog、BBS或个人站等:
普通尺寸(450*500pix)
较大尺寸(630*500pix)
你可能喜欢加载中,请稍候...
加载中,请稍候...
京 东 价:
¥35.90 [7.4折]
[定价:¥49.00
温馨提示:
其它类似商品
正在加载中,请稍候...
正在加载中,请稍候...
正在加载中,请稍候...
JavaME游戏编程(原书第2版)
¥35.90 [7.4折]
查找同类商品
  《JavaME游戏编程》对Java
MIDP类给出大量指导,详细介绍了MIDP
2.0类在开发基于文本游戏的MIDlet时的应用,并进一步结合图形化元素与Game
API来开发动作游戏。《JavaME游戏编程》从命令行开始,向读者展示了如何在开发时使用Java无线工具包和NetBeans,对下载、安装和配置所需的所有工具给出了分步指南。读者随后能够使用多种MIDlet研究MIDP
2.0中包括的主要类。在后面章节中,《JavaME游戏编程》重点介绍了Game
API。《JavaME游戏编程》从头至尾都使用了亲身体验的方法,对每个示例程序都进行了仔细的讨论。《JavaME游戏编程》给出了所有MIDlet示例用到的资源和NetBeans项目。读者很快就能创建自己的移动游戏了!  《JavaME游戏编程》包括以下内容:  ?基本MIDP
2.0类的使用。  ?使用标准MIDP组件对MIDlet的开发。  ?使用MIDP
API开发游戏。  ?对移动游戏的基本体系结构的理解。  《JavaME游戏编程》的以下文件可从华章网站下载:  ?全部源代码。  ?图形文件。  ?容易上手的NetBeans项目文件。
  《JavaME游戏编程》主要针对已经具有一定Java编程基本知识的读者,从游戏开发的基础知识入手,介绍使用Java
ME技术为移动信息设备开发游戏,详细讨论利用Java
MIDP粪进行设备编程。全书分为5部分,主要内容包括移动设备基本原理、建立开发环境、基本MIDP
2.0类的使用、使用标准MIDP组件对MIDlet的开发、使用MIDP
API开发游戏等。此外,附录中还介绍如何实现滚动背景。  《JavaME游戏编程》内容丰富,有许多其他同类书籍中没有的、更易于读者理解的基础处理方法。《JavaME游戏编程》适合游戏开发人员参考使用。
  JohnP.Flynt,博士,曾在高等院校任教,并编写过多本具有大学水平的游戏开发教程。他曾涉足信息技术.社会科学和人文科学。他的著作包括:《IntheMindofaGame,PerlPower!》、《JavaProgrammingfortheAbsoluteBeginner》、《UnrealScriptGameProgrammingAllinOne》(与ChrisCaviness合著)、《SoftwareEngineeringforGameDevelopers》、《SimulationandEventModelingforGameDevelopers》(与BenVinson合著)、《Pre-CalculusforGameDevelopers》(与BorisMeltreger合著)、《BasicMathConceptsforGameDevelopers》(与BorisMeltreger合著)和《UnrealTournamentGameProgrammingforTeens》(与BrandonBooth合著)、他现居住于科罗拉多州Boulder附近。  MartinJ.Wells现任TasmanStudiosPty有限公司的首席程序员,该公司位于澳大利亚悉尼。在15年的职业生涯中,他开发了大量项目、他是多种计算机语言(包括Java)的专家,并且曾参与高性能网络互连和多线程系统开发、他在12岁时编写并销售了为Tandy和Commodore微型计算机开发的游戏。
译者序前言作者简介第一部分
移动设备基本原理第1章
JavaME发展历史1.1
Java的由来1.2
Java的成长史1.3
什么是Java1.4
多种版本1.5
无所不在的移动信息设备1.6
微型设备与软件1.7
JavaME概述2.1
全面的工具箱2.1.1
JavaME的体系结构2.1.2
配置与配置文件2.2
CLDC目标设备特性2.5
CLDC安全模式2.5.1
虚拟机安全2.5.2
应用程序安全2.6
应用程序管理2.7
错误处理2.7.3
新老版本2.8
JVM差异2.9
CLDC包和类库2.10
MIDP2.10.1
目标硬件环境2.10.2
目标软件环境2.11
MIDP包和类库2.12
MIDP2.0游戏包2.13
MID应用程序2.13.1
MID运行时环境2.13.2
MID套件打包2.13.3
Java应用程序描述符2.14
MIDP2.0和MIDP1.02.15
支持JavaME的设备3.1
MID概述3.2
诺基亚3.2.1
Series303.2.2
Series403.2.3
Series603.2.4
Series803.2.5
Series903.3
索爱Z5203.4
摩托罗拉3.4.1
摩托罗拉A8303.4.2
iDEN手机3.4.3
摩托罗拉E5503.5
小结第二部分
建立开发环境第4章
JDK、MIDP和MIDlet套件4.1
工具获得4.2
JDK的安装与设置4.2.1
获得JDK4.2.2
开始安装WindowsJDK4.2.3
继续JDK的安装与设置4.2.4
复制路径信息4.2.5
设置路径和CLASSPATH变量4.2.6
测试安装4.3
安装并设置MIDP4.3.1
复制MIDP至某一目录4.3.2
复制MIDP的路径4.3.3
设置PATH和CLASSPATH变量4.3.4
设置MIDP_HOME4.3.5
验证MIDP配置4.4
设置工作目录4.5
创建一个MIDlet4.5.1
编译应用程序4.5.2
使用类文件进行预验证4.5.3
运行MIDlet4.6
创建完整工具包4.6.1
再次创建一个Hello4.6.2
构建类4.6.3
创建清单和JAR4.6.4
创建JAD4.6.5
运行MIDlet套件4.6.6
修改JAD4.7
使用JavaWirelessToolkit2.55.1
开发设置5.2
JavaWirelessToolkit5.2.1
安装工具包5.2.2
基本的WTK2.55.3
创建新项目5.4
创建HelloToolkit源代码5.4.1
连编并运行HelloToolkit.java5.4.2
生成JAD、JAR和清单文件5.4.3
JWT选项5.5
使用NetBeans6.1
NetBeansIDE6.1.1
安装NetBeans6.1.2
IDE的完整性检查6.2
添加移动性6.2.1
下载移动性程序包6.2.2
CDC移动性程序包的安装6.2.3
基本移动性程序包的安装6.2.4
确认Mobile与CDC6.3
创建一个MIDlet项目6.4
添加代码6.5
JAD和JAR文件6.5.1
添加消息6.5.2
修改JAD文件6.6
小结第三部分
面向文本的活动第7章
JavaMEAPI基础7.1
MIDPAPI概述7.2
MIDlet类7.2.1
LifecycleTest类7.2.2
导入与构造7.2.3
开始与停止7.2.4
命令动作7.3
使用Timer和TimerTask对象7.3.1
TimerTest类7.3.2
导入与构造7.3.3
取消任务7.3.4
PrintTask内部类7.4
网络互连7.4.1
Connector类7.4.2
HttpConnection接口7.4.3
NetworkingHTTPTest类7.5
RMS的持久性8.1
持久性8.1.1
RecordStore类8.1.2
RecordStoreTest类8.1.3
添加记录8.1.5
取回并显示记录8.1.6
关闭与销毁8.1.7
删除记录8.1.8
更新记录8.2
记录枚举和记录存储8.2.1
RecEnumTest类8.2.2
向量与枚举8.2.3
RecordStore和RecordEnumeration8.3
使用RecordComparator对象8.3.1
ComparatorTest类8.3.2
使用enumerateRecords()方法8.3.3
特殊化RecordComparator接口8.4
使用RecordFilter对象8.4.1
FilterTest类8.4.2
FilterTest的构造8.4.3
特殊化RecordFilter接口8.5
RecordListener对象的使用8.5.1
RecordListenerTest类8.5.2
RecordListenerTest的构造8.5.3
指派记录8.5.4
RecordListener的动作8.5.5
特殊化RecordListener接口8.5.6
用户界面基础9.1
用户界面(LCDUI)9.2
类层次结构9.3
Display与Displayable9.3.1
DisplayTest类9.3.2
Command与CommandListener9.3.3
TextBox9.3.4
Alert与AlertType9.4
NameGameTest类9.4.1
构造与定义9.4.2
TextBox的生命周期9.4.3
Alert的处理9.5
单选列表9.6.1
构造与定义9.6.2
将Vector对象用于数据9.6.3
消息处理9.7
复选列表9.7.1
构造与定义9.7.2
消息处理9.8
小结第四部分
使用图形第10章
表单与项10.1
Item与Form类的一般特性10.2
Form类10.3
TextField10.4
处理数字10.4.1
构造与定义10.4.2
事件处理10.5
StringItem10.6
ItemPlayTest类10.6.1
定义与构造10.6.2
位OR运算符的使用10.6.3
字体定义、文字串和附加10.6.4
分隔符和隐式附加10.6.5
使用事件10.7
小结第11章
图像与选择11.1
ChoiceGroup11.2
ComedyChoiceGroup类11.2.1
类定义11.2.2
ChoiceGroup对象的定义11.2.3
消息处理11.2.4
格式化字体并显示结果11.2.5
Quotes类11.2.6
构造与定义11.2.7
ImageItem与Image11.3
ImageItemFind类11.3.1
构造与定义11.3.2
获得Image并定义ImageItem…11.3.3
作为内部类的Image11.4
小结第12章
Gauge类、Calendar类与Date类12.1
Calendar类与Date类12.2
DateField12.3
CalendarFortune类12.3.1
构造与定义12.3.2
Date类和DateField类的使用…12.3.3
事件处理12.3.4
从日历生成事件12.3.5
预测12.3.6
Gauge类12.5
SonnetMaker类12.5.1
构造与定义12.5.2
CompositionTask12.5.3
显示诗行12.5.4
显示完成12.5.5
Sonnets类12.5.6
停止与退出消息12.6
小结第五部分
游戏定位第13章
Canvas类、Graphics类和Thread类13.1
Canvas类13.2
CGExplorer类13.2.1
定义和构造13.2.2
特殊化Canvas类13.2.3
颜色13.2.4
矩形13.2.5
字符串13.2.6
呈现图像和绘制圆弧13.2.7
扩展画布的工作13.4
GameStart类13.4.1
定义和构造13.4.2
GSCanvas类13.6
GSCanvas类的定义和构造13.6.1
文件、图像和颜色13.6.2
Runnable接口和Thread类13.6.3
键值和事件13.6.4
不同的消息和键13.6.5
绘画和重绘13.6.6
边界、坐标和碰撞13.7
小结第14章
GameAPI14.1
GameAPI14.2
GameCanvas类14.2.1
Sprite类和帧序列14.2.2
SpriteStart类14.3
SpritePlay类14.3.1
定义和构造14.3.2
帧序列14.3.3
创建Sprite和Image对象14.4
TiledLayer类14.4.1
设置单元14.4.2
Sprite碰撞以及设置和变换图像14.4.3
TiledLayer碰撞14.5
按键事件14.5.1
显示Sprite化身的位置14.5.2
清除、冲洗和计时14.6
小结第15章
GameAPI和游戏实现15.1
DiamondDasher游戏15.2
DasherStart类15.3
DasherSprite类15.4
定义和构造15.4.1
创建钻石15.4.2
定位钻石15.4.3
DasherCanvas类15.6
构造和定义15.6.1
开始游戏15.6.2
运行游戏15.6.3
边界和随机跳跃15.6.4
更新15.6.5
显示最终的得分15.7
2.5  5.1
开发设置  在第4章中,图4.1给出了在Java
ME环境中开发MID应用程序所需的两组软件的概述,它们是Java开发包(Java
Development
Kit,JDK)和移动信息设备配置文件(Mobile
Information
Profile,MIDP)。  使用的是JDK
1.5.x和MIDP
2.0。第4章讨论了这些项目的获取和安装,并给出创建一个包含两个MIDlet的MIDlet包的简要指南。在该环境中,用户使用命令提示符和记事本,其目的在于展示出从无到有开发一个MIDlet所涉及的命令和开发动作。从这一实践中所获得的知识对于用户的开发工作是无价的。  然而,如果你是一名必须在给定环境内工作的开发者,那么可以着手编写自动完成工作的应用程序或脚本。例如,在第4章中,几乎不可避免地要使用DOS
shell脚本。它能够更容易地将JAD运行命令置于一个shell脚本中并继而执行该脚本,而无需重复地输入命令。  在这方面,几乎所有的移动信息设备(MID)主要制定者都以这样那样的方式提供了一些工具,让开发者能够更迅速地为其设备开发软件。近几年,Sun为MID提供了Java
Toolkit(JWT),也即本章的主题。顾名思义,JWT是一组工具。它不是一个完整部署好的集成开发环境(IDE)。最新引入并且完全无法比拟的是:NetBeans,将在第6章讨论。NetBeans
IDE中的包使用JwT。表5.1给出关于WTK(无线工具包)和:NetBeans
IDE的基本信息。
  手机游戏日益普及,也越来越受到人们的欢迎,而Java
ME技术是开发手机游戏的最佳方式。本书从游戏开发的基础知识人手,介绍了使用Java
ME技术为移动信息设备开发游戏,详细讨论了利用Java
MIDP类进行设备编程。  本书首先回顾了Java的发展历史和移动设备编程,介绍了MIDP的发展历史及相关技术,并且引导读者建立必要的软、硬件环境。然后,介绍了Java
ME游戏编程中涉及的各种类和包,最后借助一个实际的游戏开发项目,引领读者了解游戏开发的一般原理与方法,以及对开发的游戏进行测试。本书最后包含一个附录,介绍了开发游戏的滚动背景,从而创建更逼真的游戏效果。  本书主要针对已经具有初、中级Java编程背景的读者。本书可以帮助读者过渡到使用Java为设备编程。如果读者基本理解了如何使用Java编程,并试图寻找一种方法将自己的知识扩展到手机和其他移动设备领域,那么本书非常适合你。  参加本书翻译的人员有:陈宗斌、张景友、易小丽、陈婷、管学岗、王新彦、金惠敏、张海峰、徐哗、戴锋、张德福、张士华、张锁玲等。  由于时间紧迫,加之译者水平有限,错误在所难免,恳请广大读者批评指正。
正在加载中,请稍候...
正在加载中,请稍候...
正在加载中,请稍候...
正在加载中,请稍候...
正在加载中,请稍候...
正在加载中,请稍候...
正在加载中,请稍候...
七日畅销榜
新书热卖榜本文章描述了代码优化在为移动设备写运行起来速度快的游戏中扮演的角色。我会用例子说明如何、什么时候和为什么要优化你的代码,来榨干兼容的手机的每一滴性能。我们将要讨论为什么优化是必要的和为什么有时候最好不要优化。我将解释高级优化和低级优化的差别,然后我们会知道如何使用无线开发包(WTK)自带的Profile程序来发现到哪里去优化你的代码。这篇文章最后揭示了很多让你的MIDlet运行的技术。
为什么优化?计算机游戏可以分为两大类: 实时的和输入驱动的. 输入驱动的游戏显示游戏的当前运行状态,并在继续之前无限地等待用户的输入.扑克牌游戏属于这一类,同样,大多数的猜谜游戏、过关游戏和文字冒险游戏都属于这一类。实时游戏,有时候被称为技能或动作游戏,不等待用户,他们不停地运行直到游戏结束。技能和动作游戏经常以大量的屏幕上运东为特征(想想Galaga游戏和Robotron游戏)。刷新率必须至少有10fps(每秒的帧数)并且要有足够的动作来保持玩家的挑战性。它们需要玩家快速的反应和好的手眼配合,所以就*S&A(技能和动作)游戏必须对玩家的输入有很强的响应能力。在快速响应玩家案件的同时提供高帧数的图形动作,这是实时游戏的代码必须运行起来快的原因。在用J2ME开发的时候,挑战性就更大了。 2 Micro Edition(J2ME)是java的一个分解版本。 适用于有限功能的小型设备,比如手机和PDA。J2ME设备有:*有限的输入能力(没有键盘!)(译者注:这里键盘特指个人电脑的键盘)*小的显示尺寸*有限的内存容量和堆大小*慢速的CPU在J2ME平台上写出快的游戏-------写出在比桌面电脑里的慢得多的CPU上运行的代码更是挑战了开发者。
什么时候不优化如果你不是在写一个技能或者动作游戏,那么可能不需要优化。如果玩家已经为自己的下一步考虑了几秒钟抑或几分钟,她可能不会介意如果你的游戏响应花掉了几百微秒。这个规则的一个例外是,如果这个游戏在决定下一步如何运行的时候有大量的工作要处理,比如搜索一百万个可能的象棋片组合。这种情况下,你可能想要优化你的代码,从而在几秒钟内计算出电脑的下一步,而不是几分钟。就算你正在写这种类型的游戏,优化也可能是危险的。许多这样的技术伴随着一个代价--他们表示着好&的程序设计这个通常概念飞过来的时候,同时使你的代码更难读懂。有些是一个权衡,需要开发者大大增加程序的大小来得到性能上一点点的改进。J2ME开发者们对于保持他们的JAR尽可能的小这个挑战再熟悉不过了。这里是一些不优化的理由:*优化是一个增加bug的好手*有些技术会降低你的代码的移植性*你可能要花费大量的努力来得到微小的或者没有改进*优化是困难的最后一点需要一些阐述。优化是一个活动目标,在Java平台上更是这样,而且在J2ME上就更加突出,因为其运行环境是那样的多变。
你优化后的代码可能在一个模拟器上运行得更快,但却在实际设备上更慢,或者相反。为一部手机优化可能会降低其在另一部上的性能。不过还是有希望。有两条路径你可以做优化,高层的和底层的。第一条基本上会在所有的平台上增加执行性能,甚至会改进你代码的整个质量。第二条是可能会让你头疼的,但是那些底层技术是很容易创造的,而且更加容易消去如果你不想使用它们。最起码,他们看起来很有趣。
我们将用系统的timer在实际设备上剖析你的代码,这可以帮助你测量出那些技术在你所开发的硬件上到底有多有效。& 最后一点:& *优化是有趣的
一个反面例子:让我们来看一看这个包含两个类的简单的,首先,是Midlet...
&import&javax.microedition.midlet.*; &&
import&javax.microedition.lcdui.*; &&
public&class&OptimizeMe&extends&MIDlet&implements&CommandListener&{ &&
&&private&static&final&boolean&debug&=&false; &&
&&private&Display& &&
&&private&OCanvas&oC &&
&&private&Form& &&
&&private&StringItem&timeItem&=&new&StringItem(&"Time:&",&"Unknown"&); &&
&&private&StringItem&resultItem&= &&
&&&&&&&&&&&&&&&&&&&&&&&&&&&&new&StringItem(&"Result:&",&"No&results"&); &&
&&private&Command&cmdStart&=&new&Command(&"Start",&Command.SCREEN,&1&); &&
&&private&Command&cmdExit&=&new&Command(&"Exit",&Command.EXIT,&2&); &&
&&public&boolean&running&=&true; &&
&&public&OptimizeMe()&{ &&
&&&&display&=&Display.getDisplay(this); &&
&&&&form&=&new&Form(&"Optimize"&); &&
&&&&form.append(&timeItem&); &&
&&&&form.append(&resultItem&); &&
&&&&form.addCommand(&cmdStart&); &&
&&&&form.addCommand(&cmdExit&); &&
&&&&form.setCommandListener(&this&); &&
&&&&oCanvas&=&new&OCanvas(&this&); &&
&&public&void&startApp()&throws&MIDletStateChangeException&{ &&
&&&&running&=&true; &&
&&&&display.setCurrent(&form&); &&
&&public&void&pauseApp()&{ &&
&&&&running&=&false; &&
&&public&void&exitCanvas(int&status)&{ &&
&&&&debug(&"exitCanvas&-&status&=&"&+&status&); &&
&&&&switch&(status)&{ &&
&&&&&&case&OCanvas.USER_EXIT: &&
&&&&&&&&timeItem.setText(&"Aborted"&); &&
&&&&&&&&resultItem.setText(&"Unknown"&); &&
&&&&&&break; &&
&&&&&&case&OCanvas.EXIT_DONE: &&
&&&&&&&&timeItem.setText(&oCanvas.elapsed+"ms"&); &&
&&&&&&&&resultItem.setText(&String.valueOf(&oCanvas.result&)&); &&
&&&&&&break; &&
&&&&display.setCurrent(&form&); &&
&&public&void&destroyApp(boolean&unconditional) &&
&&&&&&&&&&&&&&&&&&&&&&&&&&throws&MIDletStateChangeException&{ &&
&&&&oCanvas&=&null; &&
&&&&display.setCurrent&(&null&); &&
&&&&display&=&null; &&
&&public&void&commandAction(Command&c,&Displayable&d)&{ &&
&&&&if&(&c&==&cmdExit&)&{ &&
&&&&&&oCanvas&=&null; &&
&&&&&&display.setCurrent&(&null&); &&
&&&&&&display&=&null; &&
&&&&&&notifyDestroyed(); &&
&&&&else&{ &&
&&&&&&running&=&true; &&
&&&&&&display.setCurrent(&oCanvas&); &&
&&&&&&oCanvas.start(); &&
&&public&static&final&void&debug(&String&s&)&{ &&
&&&&if&(debug)&System.out.println(&s&); &&
Second,&the&OCanvas&class&that&does&most&of&the&work&in&this&example... &&
import&javax.microedition.midlet.*; &&
import&javax.microedition.lcdui.*; &&
import&java.util.R &&
public&class&OCanvas&extends&Canvas&implements&Runnable&{ &&
&&public&static&final&int&USER_EXIT&=&1; &&
&&public&static&final&int&EXIT_DONE&=&2; &&
&&public&static&final&int&LOOP_COUNT&=&100; &&
&&public&static&final&int&DRAW_COUNT&=&16; &&
&&public&static&final&int&NUMBER_COUNT&=&64; &&
&&public&static&final&int&DIVISOR_COUNT&=&8; &&
&&public&static&final&int&WAIT_TIME&=&50; &&
&&public&static&final&int&COLOR_BG&=&0x00FFFFFF; &&
&&public&static&final&int&COLOR_FG&=&0x; &&
&&public&long&elapsed&=&0l; &&
&&public&int&exitS &&
&&public&int& &&
&&private&Thread&animationT &&
&&private&OptimizeMe& &&
&&private&boolean& &&
&&private&long& &&
&&private&long&frameS &&
&&private&long&frameT &&
&&private&int[]& &&
&&private&int&loopC &&
&&private&Random&random&=&new&Random(&System.currentTimeMillis()&); &&
&&public&OCanvas(&OptimizeMe&_o&)&{ &&
&&&&midlet&=&_o; &&
&&&&numbers&=&new&int[&NUMBER_COUNT&]; &&
&&&&for&(&int&i&=&0&;&i&&&numbers.length&;&i++&)&{ &&
&&&&&&numbers&=&i+1; &&
&&public&synchronized&void&start()&{ &&
&&&&started&=&frameStarted&=&System.currentTimeMillis(); &&
&&&&loopCounter&=&result&=&0; &&
&&&&finished&=&false; &&
&&&&exitStatus&=&EXIT_DONE; &&
&&&&animationThread&=&new&Thread(&this&); &&
&&&&animationThread.start(); &&
&&public&void&run()&{ &&
&&&&Thread&currentThread&=&Thread.currentThread(); &&
&&&&try&{ &&
&&&&&&while&(&animationThread&==&currentThread&&&&midlet.running &&
&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&!finished&)&{ &&
&&&&&&&&frameTime&=&System.currentTimeMillis()&-&frameS &&
&&&&&&&&frameStarted&=&System.currentTimeMillis(); &&
&&&&&&&&result&+=&work(&numbers&); &&
&&&&&&&&repaint(); &&
&&&&&&&&synchronized(this)&{ &&
&&&&&&&&&&wait(&WAIT_TIME&); &&
&&&&&&&&} &&
&&&&&&&&loopCounter++; &&
&&&&&&&&finished&=&(&loopCounter&&&LOOP_COUNT&); &&
&&&&&&} &&
&&&&catch&(&InterruptedException&ie&)&{ &&
&&&&&&OptimizeMe.debug(&"interrupted"&); &&
&&&&elapsed&=&System.currentTimeMillis()&-& &&
&&&&midlet.exitCanvas(&exitStatus&); &&
&&public&void&paint(Graphics&g)&{ &&
&&&&g.setColor(&COLOR_BG&); &&
&&&&g.fillRect(&0,&0,&getWidth(),&getHeight()&); &&
&&&&g.setColor(&COLOR_FG&); &&
&&&&g.setFont(&Font.getFont(&Font.FACE_PROPORTIONAL, &&
&&&&&&&&&Font.STYLE_BOLD&|&Font.STYLE_ITALIC,&Font.SIZE_SMALL&)&); &&
&&&&for&(&int&i&&=&0&;&i&&&DRAW_COUNT&;&i&++&)&{ &&
&&&&&&g.drawString(&frameTime&+&"&ms&per&frame", &&
&&&&&&&&&&&&&&&&&&&&getRandom(&getWidth()&), &&
&&&&&&&&&&&&&&&&&&&&getRandom(&getHeight()&), &&
&&&&&&&&&&&&&&&&&&&&Graphics.TOP&|&Graphics.HCENTER&); &&
&&private&int& &&
&&private&int&r; &&
&&public&synchronized&int&work(&int[]&n&)&{ &&
&&&&r&=&0; &&
&&&&for&(&int&j&=&0&;&j&&&DIVISOR_COUNT&;&j++&)&{ &&
&&&&&&for&(&int&i&=&0&;&i&&&n.length&;&i++&)&{ &&
&&&&&&&&divisor&=&getDivisor(j); &&
&&&&&&&&r&+=&workMore(&n,&i,&divisor&); &&
&&&&&&} &&
&&&&return&r; &&
&&private&int&a; &&
&&public&synchronized&int&getDivisor(&int&n&)&{ &&
&&&&if&(&n&==&0&)&return&1; &&
&&&&a&=&1; &&
&&&&for&(&int&i&=&0&;&i&&&n&;&i++&)&{ &&
&&&&&&a&*=&2; &&
&&&&return&a; &&
&&public&synchronized&int&workMore(&int[]&n,&int&_i,&int&_d&)&{ &&
&&&&return&n[_i]&*&n[_i]&/&_d&+&n[_i]; &&
&&public&void&keyReleased(int&keyCode)&{ &&
&&&&if&(&System.currentTimeMillis()&-&started&&&1000l&)&{ &&
&&&&&&exitStatus&=&USER_EXIT; &&
&&&&&&midlet.running&=&false; &&
&&private&int&getRandom(&int&bound&) &&
&&&&return&Math.abs(&random.nextInt()&%&bound&); &&
这个程序是一个模拟一个简单游戏循环的MIDlet:& *work&&&&&&&&& 执行& *draw&&&&&&&&& 绘制& *poll for user input&&&&&&& 等待用户输入& *repeat&&&&&&&&& 重复& 对于快速游戏,这个循环一定要尽可能的紧凑和快速。我们的循环持续一个有限的次数(LOOP_COUNT=100),并且用系统timer来计算整个作业花费了多少毫秒,我们就可以测量并改善它的性能。时间和执行的结果会显示在一个简单的窗口上。用Start命令来开启测试。按任意键会提前退出循环,退出按钮用来结束程序。& 在大多数游戏里面,主游戏循环中的作业会更新整个游戏状态-----移动所有的角色,检测并处理冲突,更新分数,等等。在这个例子里面,我们并没有做什么特别有用的事。程序仅仅是在一个数组之间做一些算数运算,然后& 把这些结果加起来。& run()函数计算了每次循环所花费的时间。每一帧,OCanvas.paint()方法都在屏幕上的16个随机的地方显示这个数。一般的,你可以用这个方法在你的游戏里面画出你的图像元素,我们的代码在该过程中作了一些有用的摹写。& 不管这些代码看起来有多么的无意义,它给了我们足够的机会去优化它的性能。
******************第二页哪里去优化 -- 90/10规则在苛求性能的游戏里面,有90%的时间是在执行其中%10的代码。我们的优化努力就应该针对这10%的代码。我们用一个Profier来定位这 10%. 要运行J2ME无线开发包中的profier工具,选择edit菜单下的preferences选项. 这将会显示preferences窗口.选择Monitoring这一栏,将"Enable Profiling"悬赏,然后点ok按钮。什么也没有出现。这是对的,在Profier窗口显示之前,我们需要在模拟器中运行我们的程序然后退出。现在就做.
我的模拟器(运行在Windows XP下,Inter P4 2.4GHz的CPU)报告我100次这个循环用了6,407毫秒,或者说6又1/2秒。这个程序报告说62或者63毫秒每帧。在硬件(一个 的i85s)上运行会慢得多。 一帧的时间大约是500毫秒,整个循环用了52460毫秒。
在本文这一课中,我们将试着改善这个数据。当你退出这个程序时,profiler窗口就会出现,然后你会看见一个文件夹浏览器中有一些东西,在左边的面板上会有一个熟悉的树形部件。方法间的联系会在这个结构列表中显示。每一个文件夹是一个方法,打开一个文件夹会显示它所调用过的方法。在该树中选择一个方法会显示那个方法的profiling信息并在右边的面板显示所有被它调用过的方法。注意在每一个元素旁边显示了一个百分数。这就是该方法在整个执行过程中所占的执行时间的百分比。我们必须翻遍这棵树,来寻找时间都到哪里去了,并对占用百分比最高的方法进行优化,如果可能的话。
对这个profiler,有几点需要说明。首先你的百分比多半会和我的不一样,但是他们的比例会比较相似--总是在最大的数之后。我的数据在被次运行的时候都会改变。为了保持情况一致,你可能希望关掉所有的后台程序,像Email客户端,并在你测试的时候保持你正在进行的任务最少。还有,不要在用profiler之前混淆(obfuscate)你的代码,不然你的方法会被神秘的标示为b或者a或者ff。最后profiler不会因为你运行模拟器的设备的差别而改变,它和硬件是完全独立的。打开最高百分比的那个文件夹,我们看到有66.8%的时间在执行一个被称为 "com..kvem.midp.lcdui.EmulEventHandler$EventLoop.run"的方法,这个对我们并没有什么帮助。用类似的方法,再往下寻找更深层次的方法,持续下去,你就会找到一个大的百分比停留在serviceRepaints()上,最后到了我们的 OCanvas.paint()方法.另外有30%的时间在OCanvas.run()方法里.这两个方法都在我们的主程序循环中,这并不奇怪.我们不会在我们的MIDlet类中花任何时间做优化,同样地我们不会对游戏的主循环外的任何代码做优化.在我们的例子程序中的百分比的划分在真实的游戏中并不是完全的没有特性. 你多半会在一个真实的视觉游戏中发现这个大的执行时间的比例是在paint()方法中. 相比于非图形化程序,图形化程序总是要花很长的时间. 不幸的是,我们的图形程序已经被写在了J2ME API这一层下,对于改善它们的性能,我们没有多少可以做的.我们可以做的是在用哪个和如何用它们之间做出聪明的决定.
高级vs低级优化
我们在该文章随后的地方会看到一些低级代码优化的技术.你会看见它们很容易被嵌入到现有代码中,并且在改善性能的同时相应的降低其可读性. 在我们使用那些技术之前,最好还是继续在我们的代码和算法的设计上下功夫.这是高级优化.Michael Abrash,"Quake"的一位开发者,一次写道,"the best optimizer is between your ears"(最好的游戏器就在你的两耳之间).这有不只一种方法而且如果如果实现花更多的时间来思考正确的做事的方式,你会得到极大的回报. 使用正确的算法所带来的性能提升,会比用低级优化技术在普通算法上作优化得到的提升大很多. 你用低级技术可能会得到几点百分比的提升,但是请首先从最上层
开始并且使用你的大脑(你可以在你的两耳之间找到它).现在让我们来看一看我们在paint()方法中作了什么.每次在屏幕上打印消息"n ms per frame"时,我们调用了
Graphics.drawString() 16次. 我们不知道drawString的任何内部作业,但是我们知道它用掉了大量时间,所以让我们试试其它的方式.让我们直接将这个字符串画到一个图片实例上, 然后再画16次这个图片.
public&void&paint(Graphics&g)&{ &&
&&&&g.setColor(&COLOR_BG&); &&
&&&&g.fillRect(&0,&0,&getWidth(),&getHeight()&); &&
&&&&Font&font&=&Font.getFont(&Font.FACE_PROPORTIONAL, &&
&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&Font.STYLE_BOLD&|&Font.STYLE_ITALIC, &&
&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&Font.SIZE_SMALL&); &&
&&&&String&msMessage&=&frameTime&+&"ms&per&frame"; &&
&&&&Image&stringImage&= &&
&&&&&&&&&Image.createImage(&font.stringWidth(&msMessage&), &&
&&&&&&&&&&&&&&&&&&&&&&&&&&&&font.getBaselinePosition()&); &&
&&&&Graphics&imageGraphics&=&stringImage.getGraphics(); &&
&&&&imageGraphics.setColor(&COLOR_BG&); &&
&&&&imageGraphics.fillRect(&0,&0,&stringImage.getWidth(), &&
&&&&&&&&&&&&&&&&&&&&&&&&&&&&stringImage.getHeight()&); &&
&&&&imageGraphics.setColor(&COLOR_FG&); &&
&&&&imageGraphics.setFont(&font&); &&
&&&&imageGraphics.drawString(&msMessage,&0,&0, &&
&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&Graphics.TOP&|&Graphics.LEFT&); &&
&&&&for&(&int&i&&=&0&;&i&&&DRAW_COUNT&;&i&++&)&{ &&
&&&&&&g.drawImage(&stringImage,&getRandom(&getWidth()&), &&
&&&&&&&&&&&&&&&&&&&getRandom(&getHeight()&), &&
&&&&&&&&&&&&&&&&&&&Graphics.VCENTER&|&Graphics.HCENTER&); &&
当我们运行这个版本的软件时,我们看到我们的paint()方法占用的时间百分比减少了一点点.往里看,我们看到drawString()方法只被调用了101次,而且现在是敌人啊我Image方法执的次数最多,被调用了1616次。虽然我们做了更多的工作,,但是程序运行得快了一点,因为我们所用的graphics调用要快一点。你或许发现了吧一个字符串画到一个图片上会影响显示,因为J2MEbing不支持图片的透明,所以大量的背景被重写了。这是一个weruhe优化可能导致你重新审核程序需求的例子。如果你真的需要与文字重合,你可能*要用更少的时间来处理。这个代码或许好了一点点,但是它仍然有很大的可改进空间。让我们来看一看我们的第一个低级优化技术。
第三页循环之外?循环多少次,在for()内部的代码就会执行多少次。要改善性能,那么,我们想要尽可能的把循环中的代码移动到循环外。我们可以在 profiler中看到paint()被调用了101次,并且在它之中的循环又循环了16次。在这两个循环中有哪些我们可以移出来呢?让我们从他们的定义说明开始,每当调用paint()时,我们声明了一个字体,一个字符串,一个图片对象和一个图形对象.我们将要把它们移出到该类的最前面.
public&static&final&Font&font&= &&
&&Font.getFont(&Font.FACE_PROPORTIONAL, &&
&&&&&&&&&&&&&&&&Font.STYLE_BOLD&|&Font.STYLE_ITALIC, &&
&&&&&&&&&&&&&&&&Font.SIZE_SMALL); &&
public&static&final&int&graphicAnchor&= &&
&&&&&&&&&&&&&&&&&&&Graphics.VCENTER&|&Graphics.HCENTER; &&
public&static&final&int&textAnchor&= &&
&&&&&&Graphics.TOP&|&Graphics.LEFT; &&
private&static&final&String&MESSAGE&=&"&ms&per&frame"; &&
private&String&msMessage&=&"000"&+&MESSAGE; &&
private&Image&stringI &&
private&Graphics&imageG &&
private&long&oldFrameT &&
你会发现,我把Font对象变成了一个公共的常量.这一点在你的程序中通常是有用的,你可以把你所要用到的字体声明都集中到一个地方.我发现anchor也一样,所以我也把文本和图像坐标放到了一起.对这些的预处理,保持了这些运算--虽然不怎么重要--在循环之外了.我把MESSAGE也变成了一个常量.那是因为Java喜欢到处创建字符串对象.字符串如果没有被控制,它们可能导致大量的内存消耗.不要把它们留给自动回收,否则你很可能会遇到,那最终会影响你的程序性能,特别是当垃圾回收器被调用得过于频繁时.字符串创造垃圾,而垃圾不好.用一个字符串常量减少了这类问题.稍后我们会看到如何运用一个StringBuffer来完全的阻止字符串滥用带来的内存流失.既然我们把那些变成了实例变量,我们需要在构造函数里面添加这些代码:
stringImage&=&Image.createImage(&font.stringWidth(&msMessage&), &&
&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&font.getBaselinePosition()&); &&
imageGraphics&=&stringImage.getGraphics(); &&
imageGraphics.setFont(&font&); &&
另一个很酷的对于图形对象的大写字符的事是,我们可以设置一次字体然后就可以忘掉它了,不用每次在循环中都设置一次. 每次我们还需要用fillRect()擦去图片对象. 热情的编码者可能会发现这里有一个机会从同一个图片创建两个图形对象,然后为fillRect()的调用预设其中一个的颜色为COLOR_BG,并为 drawString()的调用预设另一个的颜色为COLOR_FG.不幸地,对同一个图片的多次调用getGraphics()没有被定义,在不同的平台上不一样,于是你的技巧可能在Motorola上有效但在NOKIA上不行.如果不确定,就不做.还有另一种改进我们的paint()的方法.再次使用我们的大脑我们认识到,如果从上次调用以来frameTime的值改变了,那么我们只需要重画这个字符串.那是我们的新变量oldFrameTime到来的地方,下面是新的方法:
public&void&paint(Graphics&g)&{ &&
&&g.setColor(&COLOR_BG&); &&
&&g.fillRect(&0,&0,&getWidth(),&getHeight()&); &&
&&if&(&frameTime&!=&oldFrameTime&)&{ &&
&&&&msMessage&=&frameTime&+&MESSAGE; &&
&&&&imageGraphics.setColor(&COLOR_BG&); &&
&&&&imageGraphics.fillRect(&0,&0,&stringImage.getWidth(), &&
&&&&&&&&&&&&&&&&&&&&&&&&&&&&stringImage.getHeight()&); &&
&&&&imageGraphics.setColor(&COLOR_FG&); &&
&&&&imageGraphics.drawString(&msMessage,&0,&0,&textAnchor&); &&
&&for&(&int&i&&=&0&;&i&&&DRAW_COUNT&;&i&++&)&{ &&
&&&&g.drawImage(&stringImage,&getRandom(&getWidth()&), &&
&&&&&&&&&&&&&&&&&getRandom(&getHeight()&),&graphicAnchor&); &&
&&oldFrameTime&=&frameT &&
现在Profiler显示OCanvas的paint总共所花费的时间百分比已经降低为42.01%了.对比结果frameTime在 paint()中的调用,对drawString()和fillRect()的调用次数已经从101变为69了.那时一个不错的节约,没有多少可以做的了,现在是该认真的时候了.你优化得越多,它就变得越困难.现在我们要去挖掉最后几块循环中的代码.我们现在正在剃去非常小的百分比或者说百分比的碎片了,但是我们比较幸运,他们加起来还是比较可观的.让我们从一些简单的开始.让我们调用那些函数一次并且把结果暂存在循环之外,而不是每次都调用getHeight()和getWidth(). 下一步,我们将停止使用字符串并手动使用StringBuffer来做所有事.依靠在Graphics.setClip()的调用中限制绘画区域,我们将剃掉一些对drawImage()的调用.最后,我们将避免在循环中对java.util.Random.nextInt()的调用.这是些新的变量...
private&static&final&String&MESSAGE&=&"ms&per&frame:"; &&
&&private&int&iw,&ih,&dw,& &&
&&private&StringBuffer&stringB &&
&&private&int&messageL &&
&&private&int&stringL &&
&&private&char[]&stringC &&
&&private&static&final&int&RANDOMCOUNT&=&256; &&
&&private&int[]&randomNumbersX&=&new&int[RANDOMCOUNT]; &&
&&private&int[]&randomNumbersY&=&new&int[RANDOMCOUNT]; &&
&&private&int& &&
...这里是我们构造函数里的新代码:
iw&=&stringImage.getWidth(); &&
ih&=&stringImage.getHeight(); &&
dw&=&getWidth(); &&
dh&=&getHeight(); &&
for&(&int&i&=&0&;&i&&&RANDOMCOUNT&;&i++&)&{ &&
&&randomNumbersX&=&getRandom(&dw&); &&
&&randomNumbersY&=&getRandom(&dh&); &&
ri&=&0; &&
stringBuffer&=&new&StringBuffer(&MESSAGE+"000"&); &&
messageLength&=&MESSAGE.length(); &&
stringLength&=&stringBuffer.length(); &&
stringChars&=&new&char[stringLength]; &&
stringBuffer.getChars(&0,&stringLength,&stringChars,&0&); &&
你现在可以看到我们在预处理显示(Display)和图片(Image).我们也在暂存512次getRandom()的调用的结果,有了StringBuffer也不再需要msMessage这个字符串.当然,肉依然在paint()方法中:
public&void&paint(Graphics&g)&{ &&
&&&&g.setColor(&COLOR_BG&); &&
&&&&g.fillRect(&0,&0,&dw,&dh&); &&
&&&&if&(&frameTime&!=&oldFrameTime&)&{ &&
&&&&&&stringBuffer.delete(&messageLength,&stringLength&); &&
&&&&&&stringBuffer.append(&(int)frameTime&); &&
&&&&&&stringLength&=&stringBuffer.length(); &&
&&&&&&stringBuffer.getChars(&messageLength, &&
&&&&&&&&&&&&&&&&&&&&&&&&&&&&&stringLength, &&
&&&&&&&&&&&&&&&&&&&&&&&&&&&&&stringChars, &&
&&&&&&&&&&&&&&&&&&&&&&&&&&&&&messageLength&); &&
&&&&&&iw&=&font.charsWidth(&stringChars,&0,&stringLength&); &&
&&&&&&imageGraphics.setColor(&COLOR_BG&); &&
&&&&&&imageGraphics.fillRect(&0,&0,&iw,&ih&); &&
&&&&&&imageGraphics.setColor(&COLOR_FG&); &&
&&&&&&imageGraphics.drawChars(&stringChars,&0, &&
&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&stringLength,&0,&0,&textAnchor&); &&
&&&&for&(&int&i&&=&0&;&i&&&DRAW_COUNT&;&i&++&)&{ &&
&&&&&&g.setClip(&randomNumbersX[ri],&randomNumbersY[ri],&iw,&ih&); &&
&&&&&&g.drawImage(&stringImage,&randomNumbersX[ri], &&
&&&&&&&&&&&&&&&&&&&randomNumbersY[ri],&textAnchor&); &&
&&&&&&ri&=&(ri+1)&%&RANDOMCOUNT; &&
&&&&oldFrameTime&=&frameT &&
我们现在正在用一个StringBuffer来写我们的消息中的字符.相比于在开头插入一个字符,在StringBuffer的后面添加要容易得多,所以我把字符显示的顺序调换了,现在frameTime在消息的最后了,比如:"ms per frame:120".我们每次重写最后的几位frameTime字符,保持消息的一部分不变. 像这样明白的运用StringBuffer会节约paint()方法内系统从创建到销毁字符串的时间.它是额外的工作,但值得做.注意,我在把 frameTimer强制转换为一个整数.我发现用append(long) 导致了一个内存泄露.我不知道为什么,但这是一个为什么你需要用软件注意事情的例子.我们用font.charsWidth()来计算消息图片的宽度,以让我们可以画得最少.我们使用均衡字符,来使"ms per frame:1"的宽度比这绘制它图片小,我们用Graphics.setClip(),所以我们就不需要画更多的. 这同样意味着我们只需要填充一个足够遮掩我们需要的区域那么大的一个矩形.我们希望绘图省下的时间比调用font.charWidth()花去的时间多.在这里这可能不会带来多大区别,但它确实是一个绘制玩家的分数到屏幕上的不错的技术.那种情况下,在绘制0分和150,000,000分之间有着巨大的差别. 这多少是因为font.getBaselinePosition()的不正确的返回值,这个值好像和font.getHeight()的返回值一样,啊! (叹气)最后,我们刚刚搞定了我们的两个数组中预先计算的"随机"数,这节约了我们产生随机数的一些调用.注意用取模运算来实现一个循环数组的用法.注意我们用同一个TextAnchor绘制图片和字符串,所以现在setClip()工作正常.我们已经在一个灰色地带,怀着对这个版本的代码产生的数据。Profiler高速我们这个代码比没有这些改变的代码在paint()方法里多花了大概7%多一点的时间。对font.charsWidth()的调用可能是个原因,它占了4.6%。(这并不多,但它可以被减少。注意我们每次都会获取 MESSAGE字符串的宽度。我们可以简单的在循环体之间计算它,并简单的把它加到frameTime的宽度上。)同样,新的对setClip()的调用被标识为0.85%,而且看起来大大的增加了drawImage所占用的时间百分比(从27.58%到33.94%)。到这一点了,看起来所有额外的代码肯定会使执行慢下来,但是程序产生的书记和这个假设矛盾了。在模拟器上的数据上下波动,看起来好像没有长时间的测试,是不能下决定了,但是我的i85s报告说额外的代码比不加要快一点点,在没有对setClip()或者charsWidth()时数据是 37130毫秒,而两个都有的时候是36540。我做了我的耐心所能忍受的那么多次,结果都一致。这使执行环境差异这一点的影响突出起来。一旦你到了一个你不能确定会不会有进展的地方,你可能会*继续所有在硬件上的测试,这需要大量的对JAR文件的安装和卸载。看起来我们已经从我们的图形程序段压榨出了大量的性能。现在是对我们的work()方法进行同样的高级和低级优化的时候了。让我们来回顾一下那个方法:
public&synchronized&int&work(&int[]&n&)&{ &&
&&&&r&=&0; &&
&&&&for&(&int&j&=&0&;&j&&&DIVISOR_COUNT&;&j++&)&{ &&
&&&&&&for&(&int&i&=&0&;&i&&&n.length&;&i++&)&{ &&
&&&&&&&&divisor&=&getDivisor(j); &&
&&&&&&&&r&+=&workMore(&n,&i,&divisor&); &&
&&&&&&} &&
&&&&return&r; &&
每次在run()中的循环,我们都传递一个数组参数。在work()中的循环外计算了我们的除数,然后调用workMore()来做这个除法。这里所有事都错了,你可能也发现了。因为一开始,程序员已经把getDivisor()的调用放到了循环内。如果j的值在循环内部没有改变,那么除数是不变的,真的属于内循环外面。但是让我们多想一想,这个调用本身就是完全不必要的。下面的代码做了同样的事情...
public&synchronized&int&work(&int[]&n&)&{ &&
&&&&r&=&0; &&
&&&&divisor&=&1; &&
&&&&for&(&int&j&=&0&;&j&&&DIVISOR_COUNT&;&j++&)&{ &&
&&&&&&for&(&int&i&=&0&;&i&&&n.length&;&i++&)&{ &&
&&&&&&&&r&+=&workMore(&n,&i,&divisor&); &&
&&&&&&} &&
&&&&&&divisor&*=&2; &&
&&&&return&r; &&
...没有对getDivisor()的调用。现在我们的profiler告诉我们run()方法花了23.72%的时间,对应于我们做这些改进以前的38.78%。请总是在使用低级优化技术之前首先使用你的大脑来优化。接下来,让我们看一看它们中的一些技术。
第四页低级优化所有的程序员都对子程序和函数---为了避免在多个地方重复,把程序中共用的代码从应用程序中提出来--的概念很熟悉。不幸地,这个通常的 &好&变成习惯会影响性能,因为方法的调用会带来一定量的开销。最简单的减少对一个方法的调用所耗费的时间的方法是,仔细地挑选他们的声明修饰语。我们的程序员已经很小心了,已经把他的work()和workMore()方法同步了,以防万一其它的线程同时调用了它们。这很不错,但是如果我们对性能很看重,我们常常要做出一些牺牲,今天我们的牺牲是安全的。好了,我们知道不会有其它人会调用这些方法,那么我们可以不怎么担心地把他们同步起来。还有什么其它的可以做?让我们来看一看这个方法类型的列表:*synchronized 该方法是最慢的,因为需要获取一个对象锁*interface 该方法是次慢的*instance 这个方法居中*final 该方法比较快*static 该方法是最快的所以我们不应该让所有的都是synchronized,而且看起来我们甚至可以把work()和workMore标示为final static方法。这样做会减少1%模拟器中run()方法所花的时间。另一个影响方法调用性能的因素是,传递给该方法的参数的个数。我们调用了workMore()51712次,每次都传递一个整形数组和两个整形变量给这个参数并返回一个整形变量。在这个有点微不足道的例子中,可以很容易地把workMore()方法拆散到work()的方法体中来完全地避免这个调用。在真实世界中,这是个很难的决定,特别是当这意味着需要把代码复制到你程序周围的时候。在profiler上测试一下来看和没做这一步之前到底有多大的差别。如果你不能把所有的方法去掉,试着减少你传递的参数的个数。参数越多,开销就越大。
public&final&static&int&work(&int[]&n&)&{ &&
&&&divisor&=&1; &&
&&&r&=&0; &&
&&&for&(&int&j&=&0&;&j&&&DIVISOR_COUNT&;&j++&)&{ &&
&&&&&for&(&int&i&=&0&;&i&&&n.length&;&i++&)&{ &&
&&&&&&&r&+=&n&*&n&/&divisor&+&n; &&
&&&&&divisor&*=&2; &&
&&&return&r; &&
哇哦!去掉workMore()的调用把run中的时间开销砍到了9.96%。现在开始,一路上都会上升。让我们来看一看两个最基本的优化技术
---降低强度(strength reduction)和解开循环。降低强度就是将一个慢一点的操作用一个相对快一点的完成同样的工作的去替换。最普通的就是使用位移运算符,它和对2的乘除运算是相等的。比如说,x&&2和x/4是相等的(2的2次幂),x&&10和x*1024是相等的(2的10次幂)。一个令人惊讶的巧合,我们的除数总是2的幂方(是不是很幸运!),所以我们可以用位移来替换那些除法。解开循环减少了代码控制流的开销,但在循环中做更多的操作,少执行几次循环,或者完全把循环去掉。由于我们的DIVISOR&&COUNT只是8,解开我们的循环变得简单起来。
public&final&static&int&work(&int[]&n&)&{ &&
&&r&=&0; &&
&&for&(&int&i&=&0&;&i&&&n.length&;&i++&)&{&r&+=&&n&*&n&&+&n;&} &&
&&for&(&int&i&=&0&;&i&&&n.length&;&i++&)&{&r&+=&&(n&*&n&&&&1)&+&n;&} &&
&&for&(&int&i&=&0&;&i&&&n.length&;&i++&)&{&r&+=&&(n&*&n&&&&2)&+&n;&} &&
&&for&(&int&i&=&0&;&i&&&n.length&;&i++&)&{&r&+=&&(n&*&n&&&&3)&+&n;&} &&
&&for&(&int&i&=&0&;&i&&&n.length&;&i++&)&{&r&+=&&(n&*&n&&&&4)&+&n;&} &&
&&for&(&int&i&=&0&;&i&&&n.length&;&i++&)&{&r&+=&&(n&*&n&&&&5)&+&n;&} &&
&&for&(&int&i&=&0&;&i&&&n.length&;&i++&)&{&r&+=&&(n&*&n&&&&6)&+&n;&} &&
&&for&(&int&i&=&0&;&i&&&n.length&;&i++&)&{&r&+=&&(n&*&n&&&&7)&+&n;&} &&
&&return&r; &&
有两个重点,第一,你会发现解开我们的循环需要我们复制一些代码。这是你在J2ME中想要的最后一件事,程序员们总是在与JAR作战,但要记得 JARing(打包)过程包括了压缩,而压缩工作对重复的代码最有效,所以上面的代码可能不会像你所想的那样对你的jar文件大小产生大的影响。再者,这都是代价交换。你的代码可以很小,很快,易读,任意选择其中的两个。第二点是位移操作符合乘除运算的优先级不一样,所以你常常需要在表达式周围放置括号,而乘除运算符则不需要。解开循环和使用位移操作符提升了1%多一点,不算坏。现在让我们把注意力集中到数组访问上。数组在C中是快速的数据结构,但因为那个原因他们也很危险---如果你的代码访问了超过数组尾部的地址,那么你就重写了你不应该访问的内存区域,而且结果通常都是可怕的。相比之下,java是一个很安全的语言---像那样执行到数组尾部以外会简单地抛出一个 ArrayIndexOutOfBoundsException(一个数组地址越界异常)。每次访问数组的时候系统都检查数组的下标是否有效,这使得数组的访问比C中要慢。再者,对于java内部对于数组的处理我们没有什么可以做的,但是我们可以在它周围作一些聪明的决定。在上面的代码中,举例来说,我们访问了n24次。我们可以通过把n的值存储于一个变量来省略掉很多那样的数组访问。稍微高级一点的想法同样揭示了我们可以用聪明的多的方式重新安排他们,像这样...
private&static&int& &&
&&private&static&int&r; &&
&&private&static&int& &&
&&public&final&static&int&work(&int[]&n&)&{ &&
&&&&r&=&0; &&
&&&&for&(&int&i&=&0&;&i&&&n.length&;&i++&)&&{ &&
&&&&&&ni&=&n; &&
&&&&&&r&+=&&ni&*&ni&+& &&
&&&&&&r&+=&&(ni&*&ni&&&&1)&+& &&
&&&&&&r&+=&&(ni&*&ni&&&&2)&+& &&
&&&&&&r&+=&&(ni&*&ni&&&&3)&+& &&
&&&&&&r&+=&&(ni&*&ni&&&&4)&+& &&
&&&&&&r&+=&&(ni&*&ni&&&&5)&+& &&
&&&&&&r&+=&&(ni&*&ni&&&&6)&+& &&
&&&&&&r&+=&&(ni&*&ni&&&&7)&+& &&
&&&&return&r; &&
...把run()中耗费的时间减少到了6.18%,一点也不坏。在我们继续之前,让我多说一点关于数组的事。一个稍微高级的优化(也就是&thought&)可能揭示了数组可能不是这里必要的正确的数据结构。想想一个链表或者一些其他的结构,如果他们将提升性能。第二点,如果你将要使用一个数组而且也需要复制它的内容到另一个数组,请总是使用System.arraycopy()。完成同样的工作,它会比自己写的函数要快一点。最后,数组的性能比java.util.Vector对象的性能要好一点。如果你需要一种Vectors提供的功能,想想自己写代码并测试一下,确保你的代码要快一点。好了,我们真的把优化的事情做完了。我们刚刚介绍了暂存数组变量和该变量的平房的另一对变量。你可能在想为什么那些变量在方法体的声明之外就被声明了。他们在循环外是因为每次定义一个整形数都有一点开销,而且我们需要保持他们在循环外也有效,对么?错!什么都不假设,我们确实为整形变量的声明节约了时间,但是如果那些变量在方法内部的定义为局部的,代码实际上可能会更慢。这是因为局部变量表现得更好,因为JVM解释一个在方法外声明的变量会花更长的时间。所以让我们把他们变为局部变量。最后,我们可以微微改变一下for()循环。计算机处理和零比较比处理和其他的非零数比较要快。那意味着我们可以改变我们的循环的顺序并像这样重写方法,我们就可以和零比较:
public&final&static&int&work(&int[]&n&)&{ &&
&&&&int&r&=&0; &&
&&&&int& &&
&&&&int& &&
&&&&int&i; &&
&&&&for&(&i&=&n.length&;&--i&&=&0&;&)&{ &&
&&&&&&ni&=&n; &&
&&&&&&nis&=&ni&*& &&
&&&&&&r&+=&&nis&&+& &&
&&&&&&r&+=&&(nis&&&&1)&+& &&
&&&&&&r&+=&&(nis&&&&2)&+& &&
&&&&&&r&+=&&(nis&&&&3)&+& &&
&&&&&&r&+=&&(nis&&&&4)&+& &&
&&&&&&r&+=&&(nis&&&&5)&+& &&
&&&&&&r&+=&&(nis&&&&6)&+& &&
&&&&&&r&+=&&(nis&&&&7)&+& &&
&&&&return&r; &&
就是它了!这个代码可能会快一点,但是 profiler的结果不是那么的明显,清楚地是这个方法变得难懂了。或许这里还有更多可改进的空间,但是让我们再看一下paint()方法,看看我们所学的里面有没有什么在这儿可以介绍的。记住我们所学的关于局部变量的么?如果你*要用一个实例变量,而且你在一个方法中引用了那个变量多次,它可能值得你创建一个局部变量来让 JVM只处理那个引用一次。你将引入一个声明和一个赋值,这会让程序变慢,但根据经验,如果一个变量被引用了超过两次,我们将会使用这个技术。我们同样可以在我们的paint()方法中运用降低强度,用一个循环移位计数器来代替取模运算符。这只有在我们的随机数暂存数组的长度是2的倍数(令人惊讶地)的时候是可能的。最后,我们可以把我们的比较运算改成总是和零比较。这里是新的改进过的paint()方法:
public&void&paint(Graphics&g)&{ &&
&&&&StringBuffer&sb&=&stringB &&
&&&&Graphics&ig&=&imageG &&
&&&&char[]&sc&=&stringC &&
&&&&int& &&
&&&&int&ml&=&messageL &&
&&&&int&ril&=& &&
&&&&int&iw&=&0; &&
&&&&g.setColor(&COLOR_BG&); &&
&&&&g.fillRect(&0,&0,&dw,&dh&); &&
&&&&if&(&frameTime&-&oldFrameTime&!=&0&&)&{ &&
&&&&&&sb.delete(&ml,&stringLength&); &&
&&&&&&sb.append(&(int)frameTime&); &&
&&&&&&sl&=&stringLength&=&sb.length(); &&
&&&&&&sb.getChars(&ml,&sl,&sc,&ml&); &&
&&&&&&iw&=&font.charsWidth(&sc,&0,&sl&); &&
&&&&&&ig.setColor(&COLOR_BG&); &&
&&&&&&ig.fillRect(&0,&0,&iw,&ih&); &&
&&&&&&ig.setColor(&COLOR_FG&); &&
&&&&&&ig.drawChars(&sc,&0,&sl,&0,&0,&textAnchor&); &&
&&&&for&(&int&i&&=&DRAW_COUNT&;&--i&&=0&&;&)&{ &&
&&&&&&g.setClip(&randomNumbersX[ril],&randomNumbersY[ril],&iw,&ih&); &&
&&&&&&g.drawImage(&stringImage,&randomNumbersX[ril], &&
&&&&&&&&&&&&&&&&&&&randomNumbersY[ril],&textAnchor&); &&
&&&&&&ril++; &&
&&&&&&ril&&=&255; &&
&&&&ri&=& &&
&&&&oldFrameTime&=&frameT &&
此外,怀着对profiler结果的敬意,我们已经在路的尽头了。这些改变并不影响图形函数被调用的次数,所以最好情况下这个差别也会很小。但是当我们把所有对我们的work()方法的改变组合起来并且装在新的JAR到设备上时,这个差别是很大的。我的motorola i85s现在在14030毫秒内完成了测试----快了两倍多!这个代码还有最后一点需要改变。我把它放到最后是因为它没有书写得特别好,并且我的经验是它的表现在不同的实现间不一样。看着OCanvas中的start()和run()方法,你可以看到我已经用了一个单独的动画线程。这是Java中处理动画的传统方法。在游戏中用这个技术的一个问题是,每当重复循环时,我们*等待系统事件,比如说按键或者一个命令被传输了。毕竟我们在一个同步块中调用wait()方法等待。这是艰辛的优化代码。毕竟我们的困难工作优化了其他所有事情,但我们在最激烈的时候实际上什么正确的事情也没能做。更坏的是,为WAIT_TIME得到一个好的数据并不简单。如果我们 wait()太长,游戏就变慢了。如果我们没有wait()足够的时间,按键可能被错过然后游戏停止了对用户输入的响应。J2ME提供了一个这个问题的解决方案,用Display.callSerially()方法。API声明callSerially (Runnable r)"导致了在repaint周期完成不久之后,为了和事件流同步,Runnable对象r让其run()被推迟调用
&[原文是:"causes the Runnable object r to have its run() method called later, serialized with the event stream, soon after completion of the repaint cycle"]。通过使用callSerially(),我们可以完全的取消对wait()的调用。系统会保证我们的work()和paint()方法和用户输入程序同步地被调用,那样游戏就会保持可响应性。这里是一些新的方法...
public&void&start()&{ &&
&&&started&=&frameStarted&=&System.currentTimeMillis(); &&
&&&loopCounter&=&result&=&0; &&
&&&finished&=&false; &&
&&&exitStatus&=&EXIT_DONE; &&
&&&run(); &&
&public&void&run()&{ &&
&&&frameTime&=&System.currentTimeMillis()&-&frameS &&
&&&frameStarted&=&System.currentTimeMillis(); &&
&&&if&(&midlet.running&&&&!finished&)&{ &&
&&&&&result&+=&work(&numbers&); &&
&&&&&repaint(); &&
&&&&&display.callSerially(this); &&
&&&&&loopCounter++; &&
&&&&&finished&=&(&loopCounter&&&LOOP_COUNT&); &&
&&&else&{ &&
&&&&&elapsed&=&System.currentTimeMillis()&-& &&
&&&&&midlet.exitCanvas(&exitStatus&); &&
...此外我们还需要声明并得到一个Display的句柄:&& Display display = Display.getDisplay( midlet );没有了对wait()的调用,现在我的i85s在10180毫秒内完成了代码的运行---差不多省了40%。你可能希望更大的性能提升,在得知我们刚刚消除了100次50毫秒的对wait()的调用,但是请记住这个技术也是关于用户输入响应的。再次,让我强调一下这个动画方法需要小心使用。在使他在NOKIA上工作的时候我遇到的麻烦,而且他们所有的示例代码(即使是游戏代码)都使用了wait()技术。即使是在Motorola上,我在动画Canvas上添加了命令对象的时候,使用callSerially()的时候也遇到了问题。在你在家尝试这个之前请小心测试。
其他的技术一个我不能在我的示例程序中包含的技术是,最佳的使用switch()。Switch非常普遍的用于实现有限状态自动机(Finite State Machines),在为非玩家角色的行为控制做人工智能的代码时。在你使用switch的时候,像这样写代码是一个好的编程习惯:
public&static&final&int&STATE_RUNNING&=&1000; &&
&public&static&final&int&STATE_JUMPING&=&2000; &&
&public&static&final&int&STATE_SHOOTING&=&3000; &&
&switch&(&n&)&{ &&
&&&case&STATE_RUNNING: &&
&&&&&doRun(); &&
&&&case&STATE_JUMPING: &&
&&&&&doJump(); &&
&&&case&STATE_SHOOTING: &&
&&&&&doShoot(); &&
这没有什么不对的,这些变量很不错而且离得很远,万一我们想要加一个在RUNNING和JUMPING之间的变量,像 STATE_DUCKING = 2500。但是显然switch选项可以被编译为一个两字节的代码,如果所用的整数紧靠在一起那么这个两字节的代码会更快,所以这会更好:& public static final int STATE_RUNNING = 1;& public static final int STATE_JUMPING = 2;& public static final int STATE_SHOOTING = 3;在使用定点数学库(Fixed Point math library)的时候,有一些优化你可以做。首先,如果你除了一个相同的数很多次,你应该计算出那个数的倒数然后把运算改变为执行一个乘法。乘法要比除法快一点。所以不是...& int fpP = FP.Div( fpX, fpD );& int fpQ = FP.Div( fpY, fpD );& int fpR = FP.Div( fpZ, fpD );...你应该把它重新写成这样:& int fpID = FP.Div( 1, fpD );& int fpP = FP.Mul( fpX, fpID );& int fpQ = FP.Mul( fpY, fpID );& int fpR = FP.Mul( fpZ, fpID );如果你在每一帧要做数百次的除法,这会有所帮助。第二点,不要默认你的FP数学函数库不错。don't take your FP math library for granted.& 如果你有它的源代码,打开它然后看一下里面发生了什么。保证所有的方法都被声明为final static并看看有没有机会优化它的代码。比如,你可能发现这个乘法方法需要把int强制转换为long然后再转换回来:public static final int Mul (int x, int y) {& long z = (long) x * (long)& return ((int) (z && 16));}那些转换要花时间。冲突检测使用边界圆球或者半球(bounding circles or spheres)包括将int的平方相加。那会产生一些大的可能会溢出你的int数据类型的最大值的数字。要避免这个,你可以写下自己的返回一个long型数的平方函数:&&& public static final long Sqr (int x) {&&&&& long z = (long)&&&&& z *=&&&&& return (z && 16);&&& }这个优化的方法避免了两个转换。如果你要做大量的定点计算,你可能要考虑把所有的主游戏循环中的调用替换为long型的。那会节约大量的方法调用和参数传递。你可能也发现当这个计算被手动写出的时候,你可以减少类型转换所需要的次数。如果你嵌套一些对你的库的调用,这是尤其正确的。比如:&&& int fpA = FP.Mul( FP.toInt(5),&&&&&&&&&&&&&&&&&&& FP.Mul( FP.Div( 1 / fpB ),&&&&&&&&&&&&&&&&&&& FP.Mul( FP.Div( fpC, fpD ),&&&&&&&&&&&&&&&&&&& FP.toInt( 13 ) ) ) );花一些时间Take the time来拆开这些像这样的嵌套调用,然后看你是否能减少类型转换的次数。另一个方式是避免到long类型的转换,如果你知道涉及到的数字足够小以至于他们肯定不会导致溢出。在高级优化上,你应该看一些游戏设计上的文章。大量的已知的游戏设计中问题,比如3D几何和碰撞检测已经被非常优雅和有效地解决了。如果你找不到java源代码,你很可能会找到C源代码或者伪代码来转换。例如,边界检查是一个普通的技术,我们已经在paint()方法中用到了。我们只需要清除帧到帧之间所改变的那部分屏幕,而不是每次都把整个屏幕清除。因为图形程序相对来说较慢,你会发现这额外的管理的代价---需要明了屏幕上哪些部分需要被清除--值得付出。一些手机制造商提供了一些私有的API来帮助程序员们处理一些J2ME表现出来的的限制,比如声音的欠缺,图片透明度的欠缺,等等。
例如, Motorola提供了一个浮点数学库来在芯片上做浮点运算。这个库比最快的定点数学库快很多,精度也高很多。使用这个函数会完全破坏你代码的可移植性,当然,如果在许多不同的手机上运行不是关键,他们是一个可选项。结论*只优化需要的代码*只在有价值的地方优化*用profiler来找要优化的地方*在具体的设备上profiler无能为力,在硬件上使用System timer*在于用低级技术之前,总是先研究你的代码并且试着改进算法*绘图是慢的,所以尽量节俭地使用图形调用*在可以减少绘制区域的地方使用setClip()*尽可能的把东西放到循环之外*拼命地预先计算和暂存*字符串带来垃圾,垃圾不好,所以使用StringBuffers来代替*什么都不假设*可能就使用static final方法,避免synchronized修饰符*传递尽可能少的参数到经常调用的方法*如果可能,完全地去掉函数调用*解开循环*对2的幂的乘除运算用位移运算代替*你可以使用位运算符代替取模运算来实现循环*试着用零来代替和其他数的比较*数组访问比C要慢,所以暂存数组元素*消去公共的子表达式*局部变量要比引用变量快*如果可以callSerially()就不要wait()*在switch()中使用小的变量作选项*检查定点数学库并且优化它*拆开嵌套的FP调用来减少类型转换*除法比乘法慢,所以用乘于倒数来代替除法*用使用过和测试过的算法*为了保护可移植性,小心地使用私有高性能API
下一步去哪里?优化是魔法。任何的计算机的心脏都是CPU,java的心脏在虚拟CPU,JVM(java虚拟机)。要榨干虚拟机的每点性能,你需要了解大量的表层以下的事情是如何工作的。特别地,你需要知道哪些事情JVM可以做得快,哪些慢。寻找有java里层工作的可靠信息的网站。
你不必要学习如何按字节来写程序,但是你懂得越多,就越容易跟上优化你的程序性能的新方式。没有什么能够代替经验。你会及时地发现关于J2ME的性能特性和所开发的手机的你个人的秘密。即使你不能编出有独特特性的代码,你可以用它设计你的下一个游戏。在开发我的游戏的时候,我发现调用5次drawImage()来分别绘制5个有25像素的图片要比调用它一次来绘制一个五倍大小的图片慢得多。这个只是肯定会帮助我设计我的下一个游戏。祝你好运,玩得开心!资源:1. J2ME's official
site contains the latest on what's happening on this front.2. Like wireless games? Read the Wireless Gaming Review.3. Discuss J2ME Game Development at j2me.org4. A great site on many aspects of Java Optimization5. Another great site on Optimization6. Many articles on J2ME performance tuning7. The amazing Graphics Programming Black Book by Michael Abrash8. The Art of Computer Game Design by Chris Crawford关于作者:Mike Shivas has been playing video games since before the advent of the 8-bit home microcomputer. He has been
programming in Java since 1996, has consulted for MasterCard on wireless solutions and is the published
author of several J2ME video games. Readers may contact Mike at .
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场
访问:724153次
积分:8972
积分:8972
排名:第575名
原创:162篇
转载:339篇
评论:139条
(2)(11)(3)(2)(6)(18)(3)(5)(2)(1)(2)(43)(31)(21)(66)(97)(160)(1)(11)(4)(5)(8)
框架,界面,联网
框架,界面,联网
框架,界面,联网

我要回帖

更多关于 psp中文h游戏下载 的文章

 

随机推荐