﻿<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:trackback="http://madskills.com/public/xml/rss/module/trackback/" xmlns:wfw="http://wellformedweb.org/CommentAPI/" xmlns:slash="http://purl.org/rss/1.0/modules/slash/"><channel><title>BlogJava-游戏策划咨讯-随笔分类-编程技术</title><link>http://www.blogjava.net/yangsail/category/372.html</link><description>做一个游戏并不难，难的是做一个好游戏；完美在于积累！
</description><language>zh-cn</language><lastBuildDate>Tue, 27 Feb 2007 12:15:41 GMT</lastBuildDate><pubDate>Tue, 27 Feb 2007 12:15:41 GMT</pubDate><ttl>60</ttl><item><title>[转载]如何编写用TCP/IP的通讯程序</title><link>http://www.blogjava.net/yangsail/archive/2005/07/24/8318.html</link><dc:creator>蓝色雪焰</dc:creator><author>蓝色雪焰</author><pubDate>Sat, 23 Jul 2005 16:19:00 GMT</pubDate><guid>http://www.blogjava.net/yangsail/archive/2005/07/24/8318.html</guid><wfw:comment>http://www.blogjava.net/yangsail/comments/8318.html</wfw:comment><comments>http://www.blogjava.net/yangsail/archive/2005/07/24/8318.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/yangsail/comments/commentRss/8318.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/yangsail/services/trackbacks/8318.html</trackback:ping><description><![CDATA[<H3 style="CURSOR: hand" onclick=switchDisplay(361)>如何编写用TCP/IP的通讯程序</H3>
<DIV id=excerpt_361></DIV>
<DIV id=content_361 style="DISPLAY: block">
<P>&nbsp;&nbsp;&nbsp;最近在网上不断的看到有人问如何用BCB的TServerSocket和TClientSocket进行编程的问题，所以决定把我的一些编程经验告诉给大家,让大家能够尽快的掌握他们的用法。<BR><BR>首先要讲一下他们的一些设置（属性）：<BR>TServerSocket 的几个属性<BR>Active&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<BR>&nbsp;&nbsp;&nbsp;&nbsp;是否是工作状态，可以把它设置为ture或false来启动和停止侦听。<BR>Port<BR>&nbsp;&nbsp;&nbsp;&nbsp;本机侦听的端口，可以设置为一大于1024的数；<BR>ServerType&nbsp;&nbsp;&nbsp;<BR>&nbsp;&nbsp;&nbsp;&nbsp;服务端的工作方式，有两个选择，一个是stNonBlocking 非阻塞方式，一个是stThreadBlocking线程阻塞方式，默认是非阻塞方式。用非阻塞方式编程比较简单（我个人认为），用阻塞方式对每个连接必须自己用线程来控制收发。<BR>ThreadCacheSize&nbsp;&nbsp;<BR>&nbsp;&nbsp;&nbsp;&nbsp;缓冲的线程个数 当ServerType是stThreadBlocking时有效。<BR>TClientSocket 的几个属性<BR>Active&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<BR>&nbsp;&nbsp;&nbsp;&nbsp;是否是工作状态，可以把它设置为ture或false来同服务端建立或断开连接。<BR>Host&nbsp;&nbsp;&nbsp;&nbsp;<BR>&nbsp;&nbsp;&nbsp;&nbsp;服务器（ServerSocket）的计算机名，是一个字符串<BR>Address <BR>服务器（ServerSocket）的IP地址<BR>ClientType<BR>&nbsp;&nbsp;&nbsp;客户端的工作方式一种是ctNonBlocking非阻塞方式，一种是ctBlocking阻塞方式，默认是非阻塞方式<BR>Port<BR>同服务端(ServerSocket)连接的端口，这个端口就是ServerSocket的侦听&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;端口<BR>在设置ClientSocket的（Host）主机名和设置（Address）IP地址是等效的，但设主机名需要网络具有DNS（域名解析服务）服务器，而且设主机名要比设主机的IP地址连接的速度慢一些。建议用Address来同ServerSocket进行连接。<BR><BR>设置好所有的属性后，ServerSocket控件就可以通过将它的Active属性置为true来进行侦听了。ClientSocket可以通过设置它的Active属性来同ServerSocket进行连接。连接成功就可以进行通讯了。在这个过程中会产生一些事件，下面说说他们各自的事件。<BR><BR>TServerSocket的几个事件<BR>OnAccept<BR>&nbsp;&nbsp;&nbsp;&nbsp;当一个客户同服务端连接成功后产生这个事件，产生这个事件后这个连接就可用了，可以对这个客户进行发送和接收数据。<BR>OnClientConnect&nbsp;&nbsp;<BR>当一个客户正在同服务端建立连接的时候产生此事件，在这里你可以决定是否接受这个连接。<BR>OnClientDisconnect<BR>当一个客户同服务端的连接断开的时候产生此事件，你需要在这里进行一些处理，如从连接列表中清除次连接释放内存等。<BR>OnClientError<BR>当客户同服务端出现错误时产生此事件，在此事件中你可以通过设置ErrorCode ＝ 0来屏蔽系统的错误提示。这样就可以避免讨厌的英文错误了。根据ErrorEvent的不同的值来得知发生了什么错误，它有一下几中错误类型<BR>eeGeneral&nbsp;&nbsp;&nbsp;未知错误<BR>eeSend&nbsp;&nbsp;&nbsp;&nbsp;发送数据出现错误<BR>eeReceive&nbsp;&nbsp;&nbsp;接收数据出现错误<BR>eeConnect&nbsp;&nbsp;&nbsp;客户请求连接时出现错误<BR>eeDisconnect 客户断开连接时出现错误<BR>eeAccept&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;接受一个客户时发生错误<BR>&nbsp;&nbsp;&nbsp;&nbsp;一般来讲，当发生错误的时候这个客户的连接就已经不可用了，要对这个客户进行连接失败处理。<BR>OnClientRead<BR>当服务端收到客户端发来的数据的时候产生此事件。接收客户端的数据都在这里进行。<BR>OnClientWrite<BR>当服务端发送数据的时候产生此事件。<BR><BR>TClientSocket的几个事件<BR><BR>OnConnect<BR>同服务端（ServerSocket）连接成功后将产生此事件，产生此事件后才说明这个连接可用了，这时才可以向ServerSocket发送数据。<BR>OnConnecting<BR>正在同服务端进行连接是产生此事件。<BR>OnDisconnect<BR>同服务端的连接断开后产生此事件，产生此事件后ClientSocket的Active属性就为false了，这时这个连接就不可用了，必须重新进行连接才能向服务器发送数据。<BR>onError<BR>当Socket发生错误时产生此事件，这个事件的意义和ServerSocket的Error事件完全一样，只是它没有eeAccept错误。<BR>OnRead<BR>当接收到服务端发来的数据后产生此事件。<BR>OnWrite<BR>当向服务端发送数据的时候产生此事件。<BR><BR>上面介绍了一下这两个控件的基本属性和事件，在掌握一些方法就可以用这两个控件进行编写通讯程序了，今天写的有写累了，改天在写它的方法并具体使用这两个控件编写一个通讯程序，有两个选择，一个是局域网的聊天程序，一个是局域网内的控制程序，大家喜欢那一个呢？给点建议吧. </P></DIV><img src ="http://www.blogjava.net/yangsail/aggbug/8318.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/yangsail/" target="_blank">蓝色雪焰</a> 2005-07-24 00:19 <a href="http://www.blogjava.net/yangsail/archive/2005/07/24/8318.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>游戏完成平衡性的技巧</title><link>http://www.blogjava.net/yangsail/archive/2005/07/24/8317.html</link><dc:creator>蓝色雪焰</dc:creator><author>蓝色雪焰</author><pubDate>Sat, 23 Jul 2005 16:16:00 GMT</pubDate><guid>http://www.blogjava.net/yangsail/archive/2005/07/24/8317.html</guid><wfw:comment>http://www.blogjava.net/yangsail/comments/8317.html</wfw:comment><comments>http://www.blogjava.net/yangsail/archive/2005/07/24/8317.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/yangsail/comments/commentRss/8317.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/yangsail/services/trackbacks/8317.html</trackback:ping><description><![CDATA[<H3 style="CURSOR: hand" onclick=switchDisplay(408)>游戏完成平衡性的技巧</H3>
<DIV id=excerpt_408>概要：关于游戏平衡性技巧的资料并不普及。这篇文章有意通过描述游戏平衡和不平衡的性质，以及如何达到游戏平衡的过方法这两个方面来填补这个信息空白。这个方法非常依赖于现有的系统工程技能以及公认的游戏设计理论。大量的案例学习及小故事被采用来帮助将方法和具体的设计结合起来。<BR></DIV>
<DIV id=content_408 style="DISPLAY: block">
<P>翻译：张三丰 语嫣<BR>作者：Tom Cadwell<BR><BR>　一个伟大的设计和一个杰出的游戏之间往往只有一个缺乏游戏平衡性的区别。多数游戏策划要通过反复试验才学会游戏平衡的基本原理。如果他们幸运的话，也许可以得到同事传授的一两个小窍门。精通游戏平衡的人往往警惕地保守着自己的秘密，或者无心与人分享。结果是虽然有关游戏平衡性的信息确实存在，但是可得到的却很少。这篇文章试图阐述一个获得游戏平衡性的方法。<BR><BR>什么是游戏平衡？<BR><BR>　　Sid Meier 曾经说过：“一个游戏是很多有趣的选择的集合。”因此得出的是如果游戏失去平衡，就会减少这些选择而影响游戏性。一个理想的游戏应该经过一系列的选择，最后以胜利或其它完成的条件结束。有时一些选择明显成为唯一的选择，或明显是无效的。如果在某一阶段，游戏出现仅有唯一的选择，而游戏却没有结束，就说明游戏的平衡性有了问题。<BR><BR>　　几乎所有通常所谓的不平衡都来自选择权的减少。例如，在一个策略游戏里，如果某一种部队的作用和费用相比过于划算，就会造成其它的部队几乎或完全没有作用。这种情况不仅只留给玩家一个选择（无从选择），而且使玩家受到很多不相关的干扰。这些干扰实际上让游戏变得比较迷乱，减损了游戏性，而且让玩家感到灰心。<BR><BR>　　游戏大富翁(Monopoly)中就有很好的游戏不平衡性的例子。在游戏的后期，玩家们总是尽量拖长呆在监狱里的时间。显然地，玩家在游戏后期的最好的策略就是进监狱而且不付钱出来，希望别人进入自己的领土而破产。在玩大富翁的最后阶段，无需再作选择，游戏基本结束了。没有人再选择是否购买财产，也很少有机会再根据游戏规则建设新的财产（因为房子已经被用完），而且因为资产已经被几个人集中，所以也不再有交易可<BR>做。一旦产生这种情形，游戏就变成每个玩家有一定的机率获胜而基本上结束了。此时玩家可以做的很少，除非靠运气得胜。这情景与游戏前期及中期大相径庭，那时玩家往往忙于大施战术、巧妙夺取利益、陷害对手或谨慎购买“重量级”黄色、绿色或深蓝色的地产。<BR><BR>　　这只是一个不平衡性的举例说明。在游戏中存在许多不同种类的不平衡性。所有的不平衡性都与没有选择性或缺乏选择性有关。<BR><BR>● 太昂贵却用处不大和便宜而且有效：游戏选择通常与游戏代价相联系，不管是牺牲其它的选择、游戏金钱或其它的商品。当一个选择太昂贵以致用处不大，或者太便宜而成为明显的选择时，游戏的不平衡性就出现了，因为有一些游戏选择无效了。虽然此类不平衡性最为普遍，但是通常经过简单地调整这些选择的价格或者是效果就可以纠正过来。<BR><BR>● 玩家时间的不平衡：大多数游戏平衡性对比的基础是以玩家为一个选择而必须放弃其它的各种不同选择的代价来衡量。我们很容易忽视玩家必须消耗时间执行每一个选择。在一个即时游戏里，玩家在游戏里没有无限的时间，所以时间不仅是一个资源，而且是一个有限的资源。在一个非即时游戏里，游戏时间不受限制，但是玩家的时间是受限制的。这种不平衡性基本上是另一种太昂贵或太廉价不平衡性的表现，只是这里这些游戏的代价不是有形的。游戏星际争霸(Starcraft)里的虫族(Zerg)就是一个很好的这种不平衡的例子。虽然虫族从价格上与其它族类是平衡的，但是就玩家的时间而言他们很容易被制造及使用。主要由于这个特点，虫族在游戏星际争霸发行之后大约长达6个月中，在联赛与竞赛中一直是最受欢迎的种族。<BR><BR>● 技术水平的不平衡性：随着玩家的游戏技能不断提高，不同的游戏选择的相对有效性也会改变。如果一种选择容易操作，而另一种极难操作，则结论是一个资深玩家和一个新玩家的对这两种选择的相对有效性的判断是完全不同的。这是游戏开发者的一个常见的陷阱，因为他们一般比较接近“高级玩家”的水平，所以经常看不见新玩家所要面对的问题。但是从另一方面看，随着操作水平的提高，而游戏性也同时“进化”，通常被认为是一件好事。所以注意到这种平衡性很重要，但是也要认识到上面说的现象也很普遍。<BR><BR>● 强制的劣势或优势：在一个对战的游戏里，一些操作的组合使得某一方更具有优越性。这样不仅是典型的不平衡性（因为有一个选择明显最好），这种状况还是不公平的。在一个多人游戏中，最好避免不公平的情况出现，这也是保证游戏平衡的重要一招。<BR>
<P>　　所有的不平衡性最终归结为没有选择性。只要记住这个原则，就容易区分可校正的不平衡性及根本的不平衡性。<BR><BR>如何达到可平衡性<BR><BR>　　游戏平衡性通常被认为是alpha或beta测试的事情，但事实上就像任何工程，好的准备工作是实现良好游戏平衡的关键。优秀的游戏设计具有极大的可平衡性，也就是指游戏系统可以较容易地调整到平衡的状态。如果系统没有可平衡性，费尽周折也不可能将游戏调整到平衡。<BR><BR>　　一个游戏是一个系统，在设计初期应用良好的系统设计方式将带来较好的可平衡性。好的系统设计方式可以分成三个重要步骤：游戏要素的模块性，连贯的设计宗旨及对复杂性的控制与调节。在设计的早期就采用这些方法将为设计师在游戏测试的alpha和beta阶段节省大量的时间。<BR><BR>游戏要素的模块性<BR><BR>　　游戏要素的模块性归结于每个游戏要素只为了一个特别目的存在，如果可能的话，尽量做到只有一个单一的目的。只要贯彻这个原则，调整一个游戏要素只会改变游戏的某个方面而不是许多方面。<BR><BR>　　有一个很好的例子，说明游戏要素缺乏模块性会造成游戏开发人不必要的麻烦。在星际争霸的beta测试中，暴雪（Blizzard，星际争霸的开发人）有一套相当清晰的伤害系统，其中每一兵种各有三种伤害方式：爆炸性的，标准型的或冲击性的。每种伤害方式都有一个根据外型大小而不同的伤害系数——爆炸性伤害对大型目标最有效，冲击性伤害对小型目标最有效，而标准型伤害可用于任何目标。其中一个兵种——飞龙（Mutalisk），不断给平衡性带来问题，因为就功能性上看，不可以被分为大、中或小型中的一种。如果将飞龙设为中型兵种，则它对于爆炸性武器类型的兵种来说抵抗力太强；如将其设为大型，则使其相对爆炸性武器类型的兵种（这种兵种一般是飞龙的天敌）又过于脆弱。暴雪（Blizzard）不能仅仅修改爆炸性相对于大型兵种或爆炸性相对于中型兵种的伤害系数，因为这样做的话就会影响一大批其它兵种的设置。也无法修改爆炸性武器兵种的攻击值，因为这样会影响其它的很多的设置。<BR><BR>　　更让人困惑的是飞龙有两个重要角色——防空军与防步兵（陆战兵种没有空中攻击能力），并具有相同的基本伤害力，而其他类似的兵团（侦察机－Scout、幽灵战机－Wraith）却有不同的武器系统，可以根据具体角色进行调整。<BR><BR>　　因为在伤害系统和飞龙的设计上缺乏模块性的原因，暴雪直到游戏上市后五个月才使飞龙兵种达到平衡。这并不是因为修正是不可能做到，而是因为缺乏系统模块性而使修正非常困难。飞龙在星际争霸里具有一定独特的用途，如果暴雪将它的平衡参数与其它不相关的兵种分开设计，平衡将大为容易。最简单的方法就是为飞龙（及其它类似兵团）添加一个独立的类型，并给予它一个针对各种伤害的自己的防御系数。如果设计师将飞<BR>龙的空军与地面攻击划分开来，调节平衡也会变得简单。<BR><BR>　　当然，星际争霸的多数设计都有相当程度的模块性。施法者（Spellcaster）兵种具有清晰的用途和相对特殊的角色就是一个很好的例子。事实上许多魔法（Spells），包括寄生虫（Broodling）和EMP振荡波（EMP Blast），具有非常特殊的作用，使调整这些兵种的平衡性就容易得多。<BR><BR>　　良好的系统模块性不仅是游戏平衡性的前提，它还是朝着解决的方向走近一步。有一个良好的模块性可以使设计师针对各种特殊问题轻松进行调整，而不会影响到其它系统。<BR><BR>连贯的设计宗旨<BR><BR>　　连贯的设计宗旨可能是在初始设计阶段要遵守的最重要的原则，但是往往容易因为政策问题、疏忽大意或缺乏良好沟通而被忽视。连贯设计宗旨的定义是如果游戏要素没有根据游戏的大局进行同步设计，最好的结果是它会使玩家偏移主要的游戏感觉，最坏的可能性是它会损害主要的游戏感觉。这种情况存在于缺乏中心控制或开发时间很长的游戏中。<BR><BR>　　较有名的多用户网络游戏（MUD）Duris：Land of Bloodlust（是Everquest—“无尽的任务”的原型Sojourn的姐妹版）就因此带来太多问题。其中一个例子是，某个程序设计人自行编入一个他自己感兴趣的角色类型。虽然这个角色类型本身很有意思，但是它使其它几个类型变得无用或大失威力。这个角色类型拥有了其它种族专有的技能，而正是这些技能的专有性才使得这些种族实用而且好玩。这个程序员还带来很多类似的游<BR>戏平衡性问题。他的主要目的是创造一个他感兴趣的类型。这与多用户网络游戏开发人想要创造有趣、独创的角色并与整个系统相吻合的愿望相冲突。他的类型非但不独特（因为是从其它各类型中各取一小部分特点），还与游戏的其它部分格格不入。<BR><BR></P>
<DIV id=content_409 style="DISPLAY: block">
<P>复杂性控制<BR><BR>　　复杂性控制应概括为：“保持简单、易懂”。过于复杂的游戏系统让人费解，因此，也更难做到平衡。一个过于复杂的系统通常是因为最初的设计太糟糕和无休止的添加补丁（理论上这些补丁是合理，但实际上是不连贯的一团糟），或者是太常见的“太多厨师呆在一个厨房里”的现象，这通常也说明缺乏设计宗旨一致性的问题。复杂性控制的另外一个优点就是它避免了一些潜在的游戏性的问题。尤其是，正如复杂的游戏系统让人费解也因此不好平衡，也更难让玩家理解，甚至从某一程度开始玩家很难再享受游戏。一个很常见的设计错误是为了游戏复杂化而牺牲游戏深度，那将对游戏平衡调整造成极大的困难，并造成对游戏性的困惑和费解。<BR><BR>基本游戏平衡过程<BR><BR>　　除了基本的规则和技巧之外，过程是非常重要的。游戏的平衡过程有几个步骤，每个步骤都有各种各样的技巧。<BR><BR>　　首先要考虑的是让游戏进入一个有趣及可玩的境界，这就需要宏观调控，或者说让游戏中的大部分要素至少达到基本上平衡，而且不存在任何要素过分地不平衡。只要达到这个状态，就可以继续细调游戏要素的具体部分，如RTS游戏里的种族或派系。<BR><BR>　　当然在游戏alpha测试阶段之前通常应已进行了宏观调整，所以可能随着新功能的增加要重新进行调整。家园（Homeworld）的主策划Erin Daly提出，应将相关的功能在同一时间加入，然后做一个宏观调控，基本上这是在整个开发过程中保持游戏可玩性的最有效的方法。<BR><BR>　　一旦实现最后的宏观调整，最好在alpha测试阶段的后期，就可以对游戏进行微观调控1使游戏平衡达到完美的程度。<BR><BR>宏观调控<BR><BR>　　提供一个可平衡的游戏系统显然只是达到游戏平衡的第一个步骤。即便是最完美的设计也需要变成现实，而在实施的过程中错误就会出现，在初期设计中就经常会出现小错误。许多游戏价值在整个游戏实现之后才能被清楚认识到。在这些情况下，设计者必须在alpha测试阶段之前及测试期间运用宏观调控技巧校正平衡值。<BR><BR>　　宏观调控应在微观调开始之前结束；如果游戏的基础还在不断改变时，较小的平衡性的改变将变得没有效果而无用。在进行宏观调时，目的是“找到”在设计案中描述的游戏性目标。当然，在你还不清楚如何表明核心游戏性时是不可能进行游戏细节的调整。<BR><BR>　　为了瞄准核心游戏性，明确地说明核心游戏性及其如何体现是很重要的。只要做到这一步，就可以建立一定的基线，也就是Ensemble Studios1所谓的“定锚”。举例说明，你也许设立游戏速度的基线为“大约10分钟长的游戏”，或者设立角色韧性的基线为“被一个危险怪兽攻击3次是致命的”。一旦你为每个游戏因素（一个地图、一个角色类型，一段对话等等）都找到满意的基线，就可以利用这些游戏要素基线为根据扩展游戏。<BR><BR>平衡性数学<BR><BR>　　一旦完成某一特定要素的宏观调控，在有些情况下可应用平衡性数学将结果复制到类似的要素中去。虽然用平衡性数学改善系统的功效还不确定，原因是很难计算一些微妙的细节，但是它对确定不同游戏要素中的基线还是有效的。公认的、几乎对每一个游戏都有帮助的一个公式是成本效率方程式。<BR><BR>成本效率公式说明，针对一个成分来说，<BR>游戏威力×耐久力=效力<BR><BR>而且：<BR>平方根（游戏威力×耐力÷成本的平方）=成本效力<BR><BR>　　游戏威力有可能是火力（伤害性×发射速度）或点数。耐力可以是使用次数或被击点数。成本代表游戏资源，通常是金子、钱币或回合（如棋赛中走一步的真正成本就是一个回合）。另一个有用的方程式主要适用于策略游戏和其它“战斗”场景的是分解式方程式。“分解式”反应了战斗场景中众多小群体总的有效性虽然与几个大群体的战斗力相等，但效力却不相同的特点。众多小群体通常效力小，当然这是假设不存在其它微妙的因素（如大团体攻击小兵种时，杀伤力太大而造成浪费）。这是因为群体在小个体逐渐死去之后就逐渐丧失威力，而一个大兵种可以支撑较长时间，所以不会因逐渐损失而失去效力。据此，相对有效性的公式被定为：<BR>有效性的减损（相对于较小的个体）=<BR>0.5+0.5×[较大个体的数量÷较小个体的数量（相同的价格）]<BR><BR>所得到数字的倒数则是较大个体有效性的增加。<BR><BR>　　这些公式和其它“平衡性数学”对于初步的平衡性特别有用。最好避免从数学上实现完美的平衡性，除非是相当简单的游戏系统。比如说，因为游戏规则简单，平衡游戏Risk并不是特别困难，且玩家的选择可以作到相当的量化。平衡游戏大富翁（Monopoly）是可能的，但是会比游戏Risk困难，因为随机因素（如滚骰子）相对于Risk来说可造成更普遍的影响，而且也因为大富翁有更多数量的特别游戏因素（运气牌、抵押规则，监狱<BR>等等）。譬如在一个现代的RTS中，能够在这样更多复杂性的情况下得到完美的数学平衡性，就相当于完成了博士论文。<BR>
<DIV></DIV>
<P>　一旦游戏已受宏观调控，游戏的平衡必须要进入细节调校。如果游戏至少达到有点乐趣可言，且不存在明显的问题，则已基本上完成宏观调控并可开始转向微小细节。微观调控是游戏策划为了进一步完美平衡性而实施的小手术。一个小手术一般被定义为：变化值相对于一个“全球”数值（影响许多其它的游戏要素）要少于10%，相对于一个“地方性”数值（一个单一游戏要素）则应少于30-40%。<BR><BR>　　微观调控最大的挑战是找到问题。一旦找到问题，就可以开始稍微调整数值，但要注意不要因此再产生出新的问题。良好的要素模块和预先计划在这一阶段很有效果——没有它们，就可能做不到在一个合理时间范围内完成游戏的平衡。<BR><BR>辨识较小的不平衡性<BR><BR>　　策划有几个技巧可辨识较小的不平衡性。其中最明显的做法是大量地测试游戏，寻找一贯受惠或占优势的方法，或寻找从不被使用的方法。另一个常用方法是与一个试验人或另一个策划讨论假设的情景或与其对战，找到一个一致认为会产生的结果，然后在游戏中测试是否会发生同样的结果。<BR><BR>　　如果策划用第一种方法，只是寻找占优势（或从不被使用）的方法，确定这种情况产生的实际原因是很重要的，并确认事情是否应该这样发展。对不平衡性进行分类，并尝试将其归类到典型的不平衡性中，有助于理解问题。基本上越是了解不平衡性的类型及特征就越能够调整它。<BR><BR>　　近年来，愈加流行的方法是秘密记录（不告诉玩家）游戏成果及统计数据。游戏世纪帝国（Age of Empires），雪乐山（Sierra）发表的几款游戏及斗争阴影（Strifeshadow）都受益于这个技巧。有时这些统计数据具开导性，有时也极具误导性。<BR><BR>　　对所有的数据都应有所保留。有时一个不成熟的测试人群会带来很不正确的结果，只因为他们不熟悉游戏，而且没有机会全面尝试（或只是尝试最容易的部分）。同样的，一个过于成熟的测试人群也有可能忽视其它策略的潜力，或被困在一个很高级却较模糊的不平衡点，而这些不平衡与其它更明显的不平衡点相比显得不那么紧迫。Ethermoon娱乐公司在游戏斗争阴影里应用的一个极为有效的技巧就是夸大在beta阶段的补丁中的游<BR>戏平衡性变化，来怂恿玩家尝试新的战略，而不再继续“抵抗”新的变化。<BR><BR>　　发现不平衡性的第二个方法有时被称为“追逐不平衡性”，也就是一个假定的情景被定义后，而由此产生的各种可能的行动及结果都应是符合设计的。例如，一个坦克部队的冲锋应该被认为打败一个轻型车队的进攻，但同时也应该受到轻度伤害，而面对防坦克步兵团的反攻则应受到重创。如果在实际的游戏中，一个坦克部队的冲锋可完全歼灭一个轻型车队并可以与防坦克步兵团不分胜负，此时坦克部队的过于强大就造成了不平衡<BR>性。追逐不平衡性是十分重要的，如果严格地执行，很容易就可以发现75%以上的较小不平衡性问题。游戏往往不按照策划所愿望的某种特别方式发展，特别是在一个对抗型的多人游戏中，一个“地区性”平衡价值的微小变化就可以造成游戏的平衡与不平衡。<BR><BR>　　要紧记的一条是无论何时进行不平衡性的搜索，在游戏早期所设置的游戏要素，往往比后期的游戏要素要敏感得多。仅仅因为一个早期游戏要素的不平衡性会影响在它之后设置的所有东西，而后期游戏要素能够制造麻烦的时间有限。正如有必要在做微观调控之前先做好游戏的宏观调控，也有必要先平衡早期的游戏因素。<BR><BR>修整较小的不平衡性<BR><BR>　　一旦辨识并证明了不平衡性，是否容易进行修正呢？是的，如果游戏已被设计为容易调整！ 一个非常可调的游戏具备的素质能让设计师在不间接影响其它游戏因素的情况下，专门对付某一不平衡性。<BR><BR>　　校正的重要一点是保持细调的水准（往小的方面想），尤其是升级游戏的时候。一个过于强大的游戏要素容易使其它要素失去效力，而一个过于无力的游戏要素则会被忽略而毫无效果。<BR><BR>　　还很重要的一点是细调时不要影响到其它的游戏数值，譬如，在角色扮演游戏里考虑一个叫做“火球”的符咒，它是火系符咒的一种。如果火球威力过大，策划可做的就是全面降低火系魔法的威力，或将火球降级。很明显你应该做的是选择“地方性”的解决方法，在细调全面火系魔法之前将火球降级。这是一个很简单的例子，在大多情况下，游戏要素之间都存在一定程度的互相依赖。谨慎地考虑一个改变将会带来的冲击力，尝试<BR>使用专门解决问题而不影响其它游戏要素的方法。<BR><BR>　　最后，避免“过度解决”不平衡性。当策划在同一时间运用多重不同的细调方法来解决一个特定问题时就会产生“过度解决”的情形。这样就很难决定变化所带来的效果，因为你应用了多重独立的可变性来影响一个不独立的可变性。“过度解决”也有可能因意外地影响其他游戏要素而带来麻烦。<BR>
<DIV></DIV>
<P>总结<BR><BR>　　开发游戏过程当中，在面对许多细节所带来的庞大冲击时很容易偶尔忽视最终目标。保持真实地对待所想得到的游戏性，从根本上贯彻游戏平衡的原则是很辛苦的，但只有这样才可以保证高质量的游戏平衡，并且避免beta测试拖得太久。多人游戏日渐受到青睐，游戏平衡应尽可能地做到最好。太多充满希望的多人游戏都因为平庸的游戏平衡而黯然失色。 
<DIV></DIV></DIV></DIV><img src ="http://www.blogjava.net/yangsail/aggbug/8317.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/yangsail/" target="_blank">蓝色雪焰</a> 2005-07-24 00:16 <a href="http://www.blogjava.net/yangsail/archive/2005/07/24/8317.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>Java2基础 问&amp;答（一）</title><link>http://www.blogjava.net/yangsail/archive/2005/04/09/3046.html</link><dc:creator>蓝色雪焰</dc:creator><author>蓝色雪焰</author><pubDate>Sat, 09 Apr 2005 12:05:00 GMT</pubDate><guid>http://www.blogjava.net/yangsail/archive/2005/04/09/3046.html</guid><wfw:comment>http://www.blogjava.net/yangsail/comments/3046.html</wfw:comment><comments>http://www.blogjava.net/yangsail/archive/2005/04/09/3046.html#Feedback</comments><slash:comments>1</slash:comments><wfw:commentRss>http://www.blogjava.net/yangsail/comments/commentRss/3046.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/yangsail/services/trackbacks/3046.html</trackback:ping><description><![CDATA[1.什么是字节码？它对Java的Internet程序设计为何十分重要？<BR>字节码是一种高度优化的指令集，由Java运行时解释器执行，可以帮助Java获得可移植性和安全性。<BR><BR>2.面向对象程序设计的3个重要原则是什么？<BR>封装、多态性和继承。<BR><BR>3.什么是变量？<BR>变量是一种命名的内存地址，变量的内容可以在程序运行时修改。<BR><BR>4.什么样的变量名无效？<BR>变量名只可以是 字母（A/a）、下划线（_）及美圆符号（$）开头，数字做开头的变量无效。<BR><BR>5.如何创建单行注释与多行注释？<BR>单行注释以“//开始”，在行尾结束。多行注释以“/*”开始，以“*/”结束。<BR><BR>6.语句在一行中的放置位置有限制吗？<BR>没有限制。Java是一种形式自由的语言。<BR><BR>7.Java为什么要严格指定其简单类型的取值范围和行为？<BR>Java严格指定其简单类型的取值范围和行为是为了确保跨平台的可移植性。<BR><BR>8.Java的字符类型是什么？它与其他大多数程序设计语言的字符类型的不同之处是什么？<BR>Java的字符类型是char。Java字符采用Unicode编码格式而不是ASCII格式，后者是大多数其他计算机语言采用的格式。<BR><BR>9.因为任何非0值都为true，所以boolean值可以取任何你想要的值，对吗？<BR>不对，boolean值只能是true或者false。<BR><BR>10.解释自增运算符的前缀形式与后缀形式的不同？<BR>当自增运算符在其操作数之前，Java将会先执行对应的操作，然后获得操作数的值用语表达式的其他部分。如果运算符位于操作数之后，Java 将会在自增之前获得操作数的值。<BR><BR>11.在表达式中，byte和short升级为什么类型？<BR>在表达式中，byte和short升级为 int 类型。<BR><BR>12.总述什么时候需要强制转换？<BR>当在不兼容的类型之间转换时，或者窄域转换发生时，需要使用强制转换。<img src ="http://www.blogjava.net/yangsail/aggbug/3046.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/yangsail/" target="_blank">蓝色雪焰</a> 2005-04-09 20:05 <a href="http://www.blogjava.net/yangsail/archive/2005/04/09/3046.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>漂亮的游戏开始画面</title><link>http://www.blogjava.net/yangsail/archive/2005/04/08/3006.html</link><dc:creator>蓝色雪焰</dc:creator><author>蓝色雪焰</author><pubDate>Fri, 08 Apr 2005 11:01:00 GMT</pubDate><guid>http://www.blogjava.net/yangsail/archive/2005/04/08/3006.html</guid><wfw:comment>http://www.blogjava.net/yangsail/comments/3006.html</wfw:comment><comments>http://www.blogjava.net/yangsail/archive/2005/04/08/3006.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/yangsail/comments/commentRss/3006.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/yangsail/services/trackbacks/3006.html</trackback:ping><description><![CDATA[<STRONG>漂亮的游戏开始画面<BR></STRONG>
<P>介绍一个游戏开始的画面。<BR>先建两个类 MenuScreen.java&nbsp;&nbsp;SimpleCustomMenuWithBGFont.java用于测试</P><BR>
<P>首先说下SimpleCustomMenuWithBGFont.java</P><BR>
<P>import javax.microedition.midlet.*;<BR>import javax.microedition.lcdui.*;<BR>//import java.util.*;</P><BR>
<P>public class SimpleCustomMenuWithBGFont extends MIDlet implements CommandListener {</P><BR>
<P>&nbsp;&nbsp;Display display;<BR>&nbsp;&nbsp;Display pauseDisplay;<BR>&nbsp;&nbsp;boolean isSplash = true;<BR>&nbsp;&nbsp;MenuScreen menuScreen;</P><BR>
<P>&nbsp;&nbsp;public SimpleCustomMenuWithBGFont() {<BR>&nbsp;&nbsp;&nbsp;&nbsp;MenuScreen menuScreen = new MenuScreen();<BR>&nbsp;&nbsp;&nbsp;&nbsp;display = Display.getDisplay(this);<BR>&nbsp;&nbsp;&nbsp;&nbsp;display.setCurrent(menuScreen);<BR>&nbsp;&nbsp;}</P><BR>
<P>&nbsp;&nbsp;protected void startApp() throws MIDletStateChangeException&nbsp;&nbsp; {<BR>&nbsp;&nbsp;}</P><BR>
<P>&nbsp;&nbsp;protected void pauseApp() {&nbsp;&nbsp;}<BR>&nbsp;&nbsp;protected void destroyApp (boolean flag) throws MIDletStateChangeException {}</P><BR>
<P>&nbsp;&nbsp;public void commandAction (Command cmd, Displayable dis) {</P><BR>
<P>&nbsp;&nbsp;}<BR>}<BR>这个类很简单,就是作为了测试使用的。</P><BR>
<P>主要来介绍一下MenuScreen.java</P><BR>
<P><BR>import javax.microedition.lcdui.*;</P><BR>
<P>public class MenuScreen extends Canvas implements Runnable {</P><BR>
<P>&nbsp;&nbsp;// 设置字体<BR>&nbsp;&nbsp;static final Font lowFont&nbsp;&nbsp;= Font.getFont (Font.FACE_MONOSPACE, Font.STYLE_PLAIN, Font.SIZE_SMALL);<BR>&nbsp;&nbsp;static final Font highFont = Font.getFont (Font.FACE_MONOSPACE, Font.STYLE_BOLD, Font.SIZE_MEDIUM);<BR>&nbsp;&nbsp;static final int NEW_GAME = 0;<BR>&nbsp;&nbsp;&nbsp;&nbsp;static final int HIGH_SCORE = 1;<BR>&nbsp;&nbsp;&nbsp;&nbsp;static final int SETTINGS = 2;<BR>&nbsp;&nbsp;&nbsp;&nbsp;static final int HELP = 3;<BR>&nbsp;&nbsp;&nbsp;&nbsp;static final int ABOUT = 4;<BR>&nbsp;&nbsp;&nbsp;&nbsp;static final int MENU_ITEM_COUNT = 5;</P><BR>
<P>&nbsp;&nbsp;// 设置颜色<BR>&nbsp;&nbsp;static final int lowColor&nbsp;&nbsp;= 0x0000FF00;&nbsp;&nbsp;&nbsp;&nbsp;// Not Highlighted<BR>&nbsp;&nbsp;static final int highColor = 0x000000FF;&nbsp;&nbsp;&nbsp;&nbsp;// Highlighted<BR>&nbsp;&nbsp;static final int highBGColor = 0x00CCCCCC;&nbsp;&nbsp;// Highlighted Background</P><BR>
<P><BR>&nbsp;&nbsp;static int width;&nbsp;&nbsp; //屏幕宽<BR>&nbsp;&nbsp;static int height;&nbsp;&nbsp;// 屏幕高</P><BR>
<P>&nbsp;&nbsp;static int startHeight;&nbsp;&nbsp;// 菜单开始的高度</P><BR>
<P>&nbsp;&nbsp;static final int spacing = highFont.getHeight()/2;&nbsp;&nbsp;// 菜单项间的距离<BR>&nbsp;&nbsp;// 菜单项<BR>&nbsp;&nbsp;static final String[] mainMenu = {"New Game","High Score","Settings","Help","About"};<BR>&nbsp;&nbsp;// 当前高亮显示的索引号<BR>&nbsp;&nbsp;static int menuIdx;<BR>&nbsp;&nbsp;Thread thread;<BR>&nbsp;&nbsp;// 背景图<BR>&nbsp;&nbsp;Image bgImage;<BR>&nbsp;&nbsp;//构造<BR>&nbsp;&nbsp;public MenuScreen() {<BR>&nbsp;&nbsp;&nbsp;&nbsp;width = getWidth();<BR>&nbsp;&nbsp;&nbsp;&nbsp;height = getHeight();<BR>&nbsp;&nbsp;&nbsp;&nbsp;// 计算菜单开始的高度<BR>&nbsp;&nbsp;&nbsp;&nbsp;startHeight = (highFont.getHeight() * mainMenu.length) + ((mainMenu.length-1) * spacing);<BR>&nbsp;&nbsp;&nbsp;&nbsp;startHeight = (height - startHeight) / 2;<BR>&nbsp;&nbsp;&nbsp;&nbsp;// 默认所选为菜单的第一项<BR>&nbsp;&nbsp;&nbsp;&nbsp;menuIdx = 0;<BR>&nbsp;&nbsp;&nbsp;&nbsp;try {<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;bgImage = Image.createImage("/res/bg.png");<BR>&nbsp;&nbsp;&nbsp;&nbsp;} catch (Exception e) {}</P><BR>
<P>&nbsp;&nbsp;&nbsp;&nbsp;thread = new Thread(this);<BR>&nbsp;&nbsp;&nbsp;&nbsp;thread.start();<BR>&nbsp;&nbsp;}<BR>&nbsp;&nbsp;public void run() {<BR>&nbsp;&nbsp;&nbsp;&nbsp;while(true) {<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;repaint();<BR>&nbsp;&nbsp;&nbsp;&nbsp;}<BR>&nbsp;&nbsp;}<BR>public void paint(Graphics g) {<BR>//清屏<BR>&nbsp;&nbsp;&nbsp;&nbsp;g.setColor(0x00000000);<BR>&nbsp;&nbsp;&nbsp;&nbsp;g.fillRect(0,0,width,height);<BR>// 背景<BR>&nbsp;&nbsp;&nbsp;&nbsp;g.drawImage(bgImage,(width - bgImage.getWidth()) / 2, (height - bgImage.getHeight())/2,20);<BR>&nbsp;&nbsp;&nbsp;&nbsp;for (int i=0; i&lt;mainMenu.length; i++) {<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;if (i==menuIdx) {<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; //g.setColor(highBGColor);<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; //g.fillRect(0,startHeight + (i*highFont.getHeight()) + spacing,width,highFont.getHeight());<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;g.setFont(highFont);<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;g.setColor(highColor);<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;g.drawString(mainMenu,<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; (width - highFont.stringWidth(mainMenu)) / 2,<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;startHeight + (i*highFont.getHeight()) + spacing,<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;20<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; );</P><BR>
<P><BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;} else {<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;g.setFont(lowFont);<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;g.setColor(lowColor);<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;g.drawString(mainMenu,<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; (width - lowFont.stringWidth(mainMenu)&nbsp;&nbsp; ) / 2,<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; startHeight + (i*highFont.getHeight()) + spacing,<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 20<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;);<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}<BR>&nbsp;&nbsp;&nbsp;&nbsp;}<BR>&nbsp;&nbsp;}</P><BR>
<P><BR>&nbsp;&nbsp;protected void keyPressed (int code) {<BR>&nbsp;&nbsp;&nbsp;&nbsp;if (getGameAction(code) == Canvas.UP &amp;&amp; menuIdx - 1 &gt;= 0) {<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;menuIdx--;<BR>&nbsp;&nbsp;&nbsp;&nbsp;} else if (getGameAction(code) == Canvas.DOWN &amp;&amp; menuIdx + 1 &lt; mainMenu.length) {<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;menuIdx++;<BR>&nbsp;&nbsp;&nbsp;&nbsp;}else if (getGameAction(code) == Canvas.FIRE)<BR>&nbsp;&nbsp;&nbsp;&nbsp;switch(menuIdx) {<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; case NEW_GAME:&nbsp;&nbsp; System.out.println("Start New Game"); break;<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; case HIGH_SCORE: System.out.println("Display High Score"); break;<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; case SETTINGS:&nbsp;&nbsp; System.out.println("Display Settings"); break;<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; case HELP:&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; System.out.println("Display Help"); break;<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; case ABOUT:&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;System.out.println("Display About Info."); break;<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }</P><BR>
<P>&nbsp;&nbsp;}<BR>}</P><BR>
<P>顺便这里介绍一下J2ME中的字体:<BR>字体的属性由：字体类型，风格和字体大小构成，请注意颜色并不是字体的属性。字体<BR>类型由Form类中的静态常量进行定义，可能的取值:<BR>FACE_MONOSPACE: 等宽字体<BR>FACE_PROPORTIONAL: 比例字体，非常宽<BR>FACE_SYSTEM: 系统字体</P><BR>
<P>字体风格由Font 类中的静态常量进行定义，字体风格是可以多选的，可能的取值为：<BR>STYLE_BOLD : 加粗<BR>STYLE_ITALIC ：斜体<BR>STYLE_PLAIN ：常规<BR>STYLE_UNDERLINED：带下划线字体</P><BR>
<P>字体由Font 类中的静态常量进行定义，可能的取值为：<BR>SIZE_LARGE: 大号字体<BR>SIZE_MEDIUM: 中号字体<BR>SIZE_SMALL: 小号字体</P><BR>
<P>创建字体时并不是通过Font 类的构造方法来创建，而是利用Font类的静态方法<BR>static Font getFont(int face,int style,int size)来创建字体。或者利用<BR>static font getDefauleFont()来创建系统默认字体。<BR>在MIDP v2.0中，为Font类增加了新的一个方法用于创建字体，即 static Font<BR>getFont(int fontSpecifier),参数fontSpecifier的取值范围被定义为Font类 <BR>的静态常量，目前只能由俩种取值：<BR>FONT_STATIC_TEXT:静态文本字体,定义了屏幕显示时设备采用的字体<BR>FONT_INPUT_TEXT:输入文体,定义了在用户输入时设备采用的字体<BR>在显示文字时,如果文字过长而超过当前屏幕的宽度,那么多余的部分将无法显示,<BR>所以为了有效地显示文字可以用检测字符或者字符串的宽度的方法,由程序判断每<BR>行输出的字符数,来达到更好的显示效果.</P><img src ="http://www.blogjava.net/yangsail/aggbug/3006.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/yangsail/" target="_blank">蓝色雪焰</a> 2005-04-08 19:01 <a href="http://www.blogjava.net/yangsail/archive/2005/04/08/3006.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>J2ME游戏中的图片处理[转贴]</title><link>http://www.blogjava.net/yangsail/archive/2005/04/08/3005.html</link><dc:creator>蓝色雪焰</dc:creator><author>蓝色雪焰</author><pubDate>Fri, 08 Apr 2005 10:58:00 GMT</pubDate><guid>http://www.blogjava.net/yangsail/archive/2005/04/08/3005.html</guid><wfw:comment>http://www.blogjava.net/yangsail/comments/3005.html</wfw:comment><comments>http://www.blogjava.net/yangsail/archive/2005/04/08/3005.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/yangsail/comments/commentRss/3005.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/yangsail/services/trackbacks/3005.html</trackback:ping><description><![CDATA[<P align=center><STRONG>J2ME游戏中的图片处理</STRONG></P>
<P>&nbsp;</P>
<P>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &nbsp;图片资源乃是游戏的外衣，直接影响一个游戏是否看上去很美。在J2ME游戏开发中，由于受到容量和内存的两重限制，图片使用受到极大的限制。在这种环境中，处理好图片的使用问题就显得更加重要。<BR>&nbsp;本文从容量和内存两个方面谈谈J2ME游戏图片处理的基本方法。</P>
<P>一 减少图片容量</P>
<P>方法1：将多张png图片集成到一张图片上。<BR>&nbsp;这是最基本也是最有效的减少png图片容量的办法了。比如你有10张png图片，每张10×15，现在你可以把它集成到一张100×15或者10×150或者X×X的图片上去。这张大png图片的容量比10张png图片的总容量小很多。这是因为省去了9张图片的文件头，文件结束数据块等等，而且合并了调色板（如果10张图片的调色板恰好相同，则省去了9张图片的调色板所占的容量！这是个不小的数字）</P>
<P>方法2：减少图片的颜色数<BR>&nbsp;减少颜色也算是一个方法？我想说的是什么时候减，谁去减。如果游戏完成后发现容量超出，此时在用优化工具减少颜色，虽然能降低图片容量，但图片效果可能就不让你满意了。所以，在美工作图时就要确定使用的颜色数，手机游戏使用的是象素图，即一个象素一个象素点出来的图像，所以预先规定调色板颜色数量是可以办到的。不过，最终使用优化工具也是有用的，有时候相差一两种颜色，但效果差别并不大，容量却可以变小一些。呵呵，减少颜色确实可以算是一种方法。</P>
<P>方法3：尽可能使用旋转和翻转<BR>&nbsp;这点不用解释了&nbsp;</P>
<P>方法4：使用换调色板技术和自定义图片格式<BR>&nbsp;如果前两种方法还不能满足你对容量的要求，而你的游戏中恰好使用了很多仅颜色不同的怪物，那么可以试试换调色板技术。J2ME规范中规定手机至少可以支持png格式的图片，每张png都带有调色板数据，如果两张图片除了颜色不同而其他（包括颜色数）完全相同，则只要保存一张图片和其他图片的调色板，这相对于保存多张图片来说节省了不少容量。不过这个方法挺麻烦，你得了解png文件格式，然后做一个工具提取出调色板数据和调色板数据块在png文件中的偏移。内存中保存图像仍使用Image,如果要换调色板，则将png文件读入到一个字节数组中，根据调色板数据块在png中的偏移，用新的调色板代替原来的调色板数据，然后用这个字节数组创建出换色后的Image。也许你觉得保存一张png和n份调色板数据的方法有点浪费。至少多保存了1份调色板数据啊！如果直接将图像数据提取出来，在加上n份调色板数据，岂不是更节省容量。但是使用上面的方法，我们还可以用drawImage渲染。如果这样自定义了图片格式，那只有自己写个渲染函数了，这倒还可以，只不过put pixel的速度在某些机器上非常慢。或者自己构造png格式数据,再使用Image.如果你真得决定这么做，我还有个小建议，不要对图像数据进行压缩，zip压缩大多数时候比你写得压缩算法好(参见J2ME Game开发笔记－压缩还是不压缩)。论坛上有位朋友提过使用bmp格式代替png格式，jar中图片容量更小，也是一个道理。</P>
<P>二 减少图片所占内存</P>
<P>1 图片所占内存的计算<BR>&nbsp;png图片所占用的内存并不对应于图片容量。图片占用的内存的计算为：width*height*bpp。bpp即为系统内置的颜色位数。以Nokia 6600为例，象素格式为565共16位。所以一张100*100的图片占用100*100*(16/8)=20000字节，约为19.5k的内存。象素格式是固定的无法改变，所以只有减少图片的宽和高才能降低其消耗的内存。</P>
<P>2 减少Image对象数量可节约大量内存<BR>&nbsp;减少Image对象数量不等于减少图片数量。我的意思是说，将一张集成图保存在一个Image对象中，通过setClip的方法从这个Iamge对象中选取你需要的图像渲染。不过这个方法牺牲了一点速度，每帧都从集成图Image中减切图像的速度比无减切的渲染慢。但对于数目不多的渲染，比如精灵，使用这个方法没问题。这个方法还有一个问题就是不能释放集成图中不需要的图片，这就要看你集成的程度了。从图片容量和内存管理的角度综合考虑，我一般使用二次集成的方法。比如有n个精灵,先将各精灵所有的图片集成到一张集成图中，得到n张集成图，然后将这n张集成图再次集成到一张更大的集成图中。这样在jar中只存在一张集成图。使用时，先将大集成图分割载入到n个Image对象中即可。这样各个精灵的图片可以单独管理了。</P>
<P>3 使用旋转和翻转<BR>&nbsp;只保存一个原始的Image,需要时再旋转或翻转</P>
<P><BR>&nbsp;</P><img src ="http://www.blogjava.net/yangsail/aggbug/3005.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/yangsail/" target="_blank">蓝色雪焰</a> 2005-04-08 18:58 <a href="http://www.blogjava.net/yangsail/archive/2005/04/08/3005.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>新手策划如何快速的提高自己的水平 </title><link>http://www.blogjava.net/yangsail/archive/2005/03/25/2455.html</link><dc:creator>蓝色雪焰</dc:creator><author>蓝色雪焰</author><pubDate>Fri, 25 Mar 2005 15:02:00 GMT</pubDate><guid>http://www.blogjava.net/yangsail/archive/2005/03/25/2455.html</guid><wfw:comment>http://www.blogjava.net/yangsail/comments/2455.html</wfw:comment><comments>http://www.blogjava.net/yangsail/archive/2005/03/25/2455.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/yangsail/comments/commentRss/2455.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/yangsail/services/trackbacks/2455.html</trackback:ping><description><![CDATA[想要提高自己的策划水平，我觉得最重要的还是玩其他人的游戏，并不要求有多长时间，但是玩了之后一定要写自己的心得，比如这个游戏设计的优点、缺点；有那些方面的优点、缺点，注意在写的时候不要太笼统，能有多细就写的多细，这样，写多了，自己的水平也得到了一定的提高。再定期的回去看看以前自己写的文档，有时会发现自己的意识有了一定的进步，这个时候不要删除原来的文档，而是在边上标出现在自己的想法，并标明日期。<BR><BR>另外就是多看看一些其他人的心得，而且心得这个东西是随着自己水平的提高，感受也在不断提高的。所以暂时看到一篇你不以为然的文章不要先忙着否定，也写下自己为什么不认同他的原因，如果有可能最好是能和其他人讨论一下，说服别人的同时你也在考证着自己的理论！<BR><BR>最后：能够迅速提高自己的水平的还是参加实践的工作，但是已经参加工作的要注意，不要把自己每天都陷入进琐碎的事情中，半个或者一个月的时候应该总结一下自己，平时注意写开发日记，主要是记录当时自己的心得，等项目做完之后，用市场的反应再回头来对照自己的开发心得，我想您的进步肯定是阶段性的，也就是说你的水平会有一个非常大的进步！<BR><img src ="http://www.blogjava.net/yangsail/aggbug/2455.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/yangsail/" target="_blank">蓝色雪焰</a> 2005-03-25 23:02 <a href="http://www.blogjava.net/yangsail/archive/2005/03/25/2455.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>关于连连看寻路算法的思路[转载CSDN]</title><link>http://www.blogjava.net/yangsail/archive/2005/03/25/2450.html</link><dc:creator>蓝色雪焰</dc:creator><author>蓝色雪焰</author><pubDate>Fri, 25 Mar 2005 13:19:00 GMT</pubDate><guid>http://www.blogjava.net/yangsail/archive/2005/03/25/2450.html</guid><wfw:comment>http://www.blogjava.net/yangsail/comments/2450.html</wfw:comment><comments>http://www.blogjava.net/yangsail/archive/2005/03/25/2450.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/yangsail/comments/commentRss/2450.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/yangsail/services/trackbacks/2450.html</trackback:ping><description><![CDATA[<SPAN id=lblContent><SPAN id=ArticleContent1_ArticleContent1_lblContent><FONT size=2>图－：<BR>0, 0, 0, 0, 0, 0, 0, 0 , 0, 0<BR>0, 8, 0, 0, 0, 0, 0, 0 , 0, 0<BR>0, 0, 0, 0, 0, 0, 0, 0 , 0, 0<BR>0, 0, 0, 0, 0, 0, 0, 0 , 0, 0<BR>0, 0, 0, 0, 0, 0, 0, 0 , 0, 0<BR>0, 0, 0, 0, 0, 0, 0, 0 , 0, 0<BR>0, 0, 0, 0, 0, 0, 0, 0 , 0, 0<BR>0, 0, 0, 0, 0, 0, 0, 0 , 9, 0<BR>0, 0, 0, 0, 0, 0, 0, 0 , 0, 0 </FONT>
<P><BR><FONT size=2>　　这是一张连连看的地图，假设标8和9的部分是两张相同的牌。<BR>　　在数组矩阵中，0表示没有牌，大于1表示有牌。至于是什么牌，那是随机的了。<BR>不要告诉我，你说的“布局算法”是指怎么把牌刚刚好放上去，那个无所谓什么<BR>算法，你只要首先在地图数组中准备好偶数个1，在布牌时保证每种牌是偶数个<BR>（不同种类的牌用大于1的数来表示），相应地放入每个1的位置上就可以了。</FONT></P>
<P><FONT size=2>一、计算地图上这两张牌能不能连通(当然能了，哈哈）。</FONT></P>
<P><FONT size=2>这是连连看寻路算法的第一步。<BR>先定义一下两张牌能连的充分条件：<BR>1.两张牌是同一种。<BR>2.两张牌之间有一条全是0的路可以连通。<BR>3.这一条路不能有两个以上的拐角(corner)<BR>满足这三个条件，就可以认为这两张牌是可以连的。</FONT></P>
<P><FONT size=2>首先，我们依据前两个条件来完成一个基本的寻路算法。<BR>我们的目的是从8到9找出一条可以连通的路来。<BR>那么很明显从8到9的第一步一其有四个方向可以选择，分别是东，南，西，北<BR>（e, s, w, n以中国地图方向为标准）四个方向，在第一步中我们首先假设四<BR>个方面没有任何优劣，那么我可以任意选择一个方向移动，那就是东面吧。<BR>图二：<BR>0, 0, 0, 0, 0, 0, 0, 0 , 0, 0<BR>0, 8, －8, 0, 0, 0, 0, 0 , 0, 0<BR>0, 0, 0, 0, 0, 0, 0, 0 , 0, 0<BR>0, 0, 0, 0, 0, 0, 0, 0 , 0, 0<BR>0, 0, 0, 0, 0, 0, 0, 0 , 0, 0<BR>0, 0, 0, 0, 0, 0, 0, 0 , 0, 0<BR>0, 0, 0, 0, 0, 0, 0, 0 , 0, 0<BR>0, 0, 0, 0, 0, 0, 0, 0 , 9, 0<BR>0, 0, 0, 0, 0, 0, 0, 0 , 0, 0<BR>我从8向东移动了一步，所以到达了-8的位置，我之所以可以移到-8位置，很明显，<BR>是因为-8的位置上原来是一个0，表示没有牌阻挡。<BR>那么现在寻路的问题就变成了，如何从－8找连通9的路了！<BR>说到这里应该明白了吧，好多费话，有点像娘们在说话。</FONT></P>
<P><FONT size=2>所以目前的寻路算法归结为一个递归算法的基本问题。<BR>先从8到找到下一个结点－8，再用同样的规则，从－8找到下一个结点，比如－88。。。<BR>图三：<BR>0, 0, 0, 0, 0, 0, 0, 0 , 0, 0<BR>0, 8, －8, －88, 0, 0, 0, 0 , 0, 0<BR>0, 0, 0, 0, 0, 0, 0, 0 , 0, 0<BR>0, 0, 0, 0, 0, 0, 0, 0 , 0, 0<BR>0, 0, 0, 0, 0, 0, 0, 0 , 0, 0<BR>0, 0, 0, 0, 0, 0, 0, 0 , 0, 0<BR>0, 0, 0, 0, 0, 0, 0, 0 , 0, 0<BR>0, 0, 0, 0, 0, 0, 0, 0 , 9, 0<BR>0, 0, 0, 0, 0, 0, 0, 0 , 0, 0</FONT></P>
<P><FONT size=2>如果一直都能OK，没有阻碍的话，最后找到了9，就算成功以，如要有一步不能走下去了，<BR>就再退回上个结点，向别的方向发展，都不行，就再退回上级结点，再向别的方向发展，<BR>这里的逻辑就是递归的思想了。</FONT></P>
<P><FONT size=2>用这样的方法写出来的算法已经能在最优的情形下用了，比如从8，到－88，哈哈。<BR>但在稍微复杂的情况下，会产生奇多的递归结点。P4机也跑不动啊。我试过，哈哈。</FONT></P>
<P><FONT size=2>那么第二步就是为（e,s,w,n）四个方向加权，也就是让它们之间有一个优先权，说白了就<BR>是先试哪一条路。决定的法则应该有好几个吧，比如以9号的位置来看，它处于8号的东南面，<BR>那试路时当然应当优先选择东面和南面，再比如9号如果牌8号的正东面，那当然是选择正东了。<BR>再比如，当走到－8的位置时，明显只能再走三个方向，因为它是不能回头的。</FONT></P>
<P><FONT size=2>经过这样的处理，递归算法生成的结点数会明显变少，会更快的找到成功的路。但性能在最坏情况<BR>下没有本质改变。</FONT></P>
<P><FONT size=2>接下来，第三步，我们把第三个充分条件加进来，来解决根本问题。<BR>3.这一条路不能有两个以上的拐角(corner)</FONT></P>
<P><FONT size=2>按照面向对象的思想，很自然的，我给每个在递归算法中生成的位置结点加上了个corner的属性，<BR>来记录这条路到目前为止拐了几个角。<BR>这样一下子就好办了啊。如果发现这个结点已经拐了两个弯时，如果要再拐弯，或者到达9之前注定<BR>要再增加cornor时，就果断over，返回上级结点。</FONT></P>
<P><FONT size=2>注意，要把二、三两步的条件综合起来详细规划一个个可能性，尽可能提早让不可能的结点OVER，<BR>这就是提高性能的关键吧。算法预见性越强，性能就越高吧。</FONT></P>
<P><FONT size=2>我们的算法在赛扬500，256M的机器上，10万次平均结果是一次运算花时不超过0.1毫秒,算得还不<BR>精确，速度确实很快，因为在很坏的情形下，产生的最大结点数是690几个，这样必然会很快的<BR>，详细的数据已经记不清了。</FONT></P>
<P><BR><FONT size=2>说了这么多了，应当明白第一步连通算法的思路了吧，我所知道的，都已经尽可能的讲出来了。<BR>这个算法完全是自己按照连连看的特点，度身定做的。因为是一步步test出来的，所以我个人<BR>觉得是很自然的，没有任何高深的地方，完成后，性能也很好，这才觉得是如此的简单。相信大家<BR>仔细看看就能写出自己的算法来的吧。</FONT></P>
<P><BR><FONT size=2>二、整个地图有没有解？？？可以连通的总牌数？<BR>　　这是一个问题。<BR>　　解决这个问题之前，我们先来解决提示功能(hint）就是为玩家提供提示，哪一对牌可以连<BR>通。<BR>　　我的做法是，先把整个地图中相同牌归到一起，用数组也好，链表也好。<BR>　　像这样，<BR>&nbsp;4,4,4,4<BR>&nbsp;5,5,5,5<BR>&nbsp;6,6,6,6<BR>&nbsp;......<BR>&nbsp;&nbsp;&nbsp; 然后计算比如4个4之间有哪两对可以连，至于如何判断能不能连，第一步算法已经实现了吧，哈哈。<BR>　　一发现有可以连的牌就退出来，告诉玩家这两张牌可以连啊！<BR>　　这就OK了。<BR>　　这完全是建立在第一步算法的基础上实现的。</FONT></P>
<P><FONT size=2>　　好的，hint功能可以了，<BR>　　那么，整个地图没有解的算法是不是出来了？<BR>　　是的，如果找不到可以hint的牌，那当然就是没有解了！<BR>　　把所有可以hint的对数记下来，就是总的可以连通数了。</FONT></P>
<P><FONT size=2>　　至此，与连连看所有算法有关的问题解决完毕。<BR>第二步算法的实现，明显开销要大很多，最坏情况应当会是单次连通算法计算量的大约50倍以上<BR>（与牌的总数量和摆的位置有关）还好在一般的服务器上单次连通计算花的时间实在太少了，<BR>实际运行中完全可以很流畅。以上数据都是我估计的理论值，因为实际测试时一直没有问题，<BR>我也懒得计算出真正比较可靠的均值了。<BR>　　<BR>　　这一部分也是我认为可以有所改进的部分，公开出来，也希望大家能提供一些更好，更巧妙<BR>的方法，我觉得我计算连通数和有无解的方法是比较笨的方法，几乎是仗着基本的算法快，一<BR>个一个算出来的。有没有更好的方法呢？期待中...</FONT></P></SPAN></SPAN><img src ="http://www.blogjava.net/yangsail/aggbug/2450.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/yangsail/" target="_blank">蓝色雪焰</a> 2005-03-25 21:19 <a href="http://www.blogjava.net/yangsail/archive/2005/03/25/2450.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>J2ME学习笔记整理</title><link>http://www.blogjava.net/yangsail/archive/2005/03/13/1979.html</link><dc:creator>蓝色雪焰</dc:creator><author>蓝色雪焰</author><pubDate>Sun, 13 Mar 2005 03:01:00 GMT</pubDate><guid>http://www.blogjava.net/yangsail/archive/2005/03/13/1979.html</guid><wfw:comment>http://www.blogjava.net/yangsail/comments/1979.html</wfw:comment><comments>http://www.blogjava.net/yangsail/archive/2005/03/13/1979.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/yangsail/comments/commentRss/1979.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/yangsail/services/trackbacks/1979.html</trackback:ping><description><![CDATA[<font size="4"><strong>J2ME学习笔记整理（一）</strong></font><br>
http://www.j2medev.com/Article_Show.asp?ArticleID=249<br>
<br>
<span style="font-family: SimSun;"> 一直想写点东西为我学习</span><span lang="EN-US"><font face="Times New Roman">J2ME</font></span><span style="font-family: SimSun;">做个总结，自从上次笔记本在寝室莫名其妙弄丢后，就再没写过</span><span lang="EN-US"><font face="Times New Roman">J2ME</font></span><span style="font-family: SimSun;">的学习笔记，今天终于股起勇气再动笔，同时也为准备学习</span><span lang="EN-US"><font face="Times New Roman">J2ME</font></span><span style="font-family: SimSun;">的兄弟姐妹起个抛砖引玉的作用，呵呵，希望不让大家失望。</span><br>
<br>
<font size="4"><strong>J2ME学习笔记整理（二）<br>
<font size="3"><span style="font-weight: normal;">http://www.j2medev.com/Article_Show.asp?ArticleID=250</span></font><br>
</strong></font><br>
接下来我们来介绍一下LCDUI这个包及其相关内容，大家都知道，在J2SE里面我们用AWT或者SWING来开发用户界面，但是在J2ME里面，开发用户界面并非上述的AWT或者SWING，我们使用的是全新的针对移动设备设计的LCDUI包（即Limited 
Configuration Device User Interface）。<br>
<img src ="http://www.blogjava.net/yangsail/aggbug/1979.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/yangsail/" target="_blank">蓝色雪焰</a> 2005-03-13 11:01 <a href="http://www.blogjava.net/yangsail/archive/2005/03/13/1979.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>3D效果的Applet小游戏</title><link>http://www.blogjava.net/yangsail/archive/2005/03/10/1927.html</link><dc:creator>蓝色雪焰</dc:creator><author>蓝色雪焰</author><pubDate>Thu, 10 Mar 2005 13:38:00 GMT</pubDate><guid>http://www.blogjava.net/yangsail/archive/2005/03/10/1927.html</guid><wfw:comment>http://www.blogjava.net/yangsail/comments/1927.html</wfw:comment><comments>http://www.blogjava.net/yangsail/archive/2005/03/10/1927.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/yangsail/comments/commentRss/1927.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/yangsail/services/trackbacks/1927.html</trackback:ping><description><![CDATA[<P><FONT size=2><FONT face=Arial><STRONG>3D效果的Applet小游戏</STRONG> <BR><A href="http://www.javaresearch.org/code/thread.jsp?thread=21723">http://www.javaresearch.org/code/thread.jsp?thread=21723</A><BR><BR>有代码</FONT></FONT></P><img src ="http://www.blogjava.net/yangsail/aggbug/1927.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/yangsail/" target="_blank">蓝色雪焰</a> 2005-03-10 21:38 <a href="http://www.blogjava.net/yangsail/archive/2005/03/10/1927.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>基于Nokia S60的游戏开发</title><link>http://www.blogjava.net/yangsail/archive/2005/03/03/1629.html</link><dc:creator>蓝色雪焰</dc:creator><author>蓝色雪焰</author><pubDate>Thu, 03 Mar 2005 01:57:00 GMT</pubDate><guid>http://www.blogjava.net/yangsail/archive/2005/03/03/1629.html</guid><wfw:comment>http://www.blogjava.net/yangsail/comments/1629.html</wfw:comment><comments>http://www.blogjava.net/yangsail/archive/2005/03/03/1629.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/yangsail/comments/commentRss/1629.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/yangsail/services/trackbacks/1629.html</trackback:ping><description><![CDATA[<SPAN class=f14><FONT size=2><STRONG>本文是基于Nokia Series 60和Symbian OS技术，指导开发者在现在的Series 60移动电话终端上开发高级的移动游戏。</STRONG><BR><BR>　　注意：本文档中讨论的技术路线都是以Symbian OS GT 6.1作为Series 60平台的基本操作系统。这适用于Series 60平台v1.x。Series 60平台v2.0基于Symbian OS GT 7.0s。本文档主要适用于Series 60 v1.x版，所以可能会有部分不兼容Series 60 v2.0版。 <BR><BR>　　Symbian操作系统简介<BR><BR>　　Symbian操作系统是所有Symbian OS电话共享的应用编程接口( API)技术的公共核心。 这个核心被命名为"通用技术"（generic technology，简称为GT），它被分成不同的版本。 GT包括一个多任务核心、通讯、数据管理和图表中间件，低级图形用户界面框架和应用程序引擎。 <BR><BR>　　象智能电话这样的小型手持设备，通常是资源非常紧张的设备。 这种设备的尺寸和制造费用限制了可用的存储器，处理速度和电池寿命。 尽管以上这些资源缺乏，但是这些设备还是需要能够稳定运行相当长的一段时间，甚至数个月。 一旦出现资源溢出的错误，对于系统来说重要的事情就是返回到前一个稳定的状态，而不失去任何重要的数据。 这使得完全地捕捉和处理每个运行错误对于系统和应用程序来说是非常重要的。 <BR><BR>　　由于资源溢出而出现的错误，和所有的运行期错误一样被称为异常。 在标准的C++中，这些异常是使用try-catch-throw机制处理的，但是因为它造成了代码长度的负面影响，所以Symbian OS提供了它自己的称为trap harness的机制。<BR><BR>　　Symbian开发他们自己的异常处理程序的另一个原因是开发Symbian OS的时候，try - catch - throw机制还不是C++标准的一部分。trap- harness的概念是使用一个TRAP宏把可能引发异常的函数封装起来。这个宏可用于捕获多个函数，并且这些函数可以嵌套。万一出现一个异常，导致异常的函数的执行就会通过User::Leave函数终止，相当于标准的C++异常处理中的throw。这被称为一个leave（离开），它将返回程序执行到结束的TRAP宏，在那里相应的恢复动作可以被执行。<BR><BR>　　Symbian OS还提供一个工具，以便清除异常。一个cleanupstack被用于引用只被一个自动变量引用，并且如果出现一个leave的时候需要被释放的对象。TRAP宏将销毁cleanupstack中自动变量分配的内存。<BR><BR>　　Symbian OS提供一个用于在单一线程之内非抢先式多任务处理技术的系统。这个包括活动对象和活动调度程序的系统的设计目标是降低运行时间成本和解决与优先计划线程冲突的同步问题。Symbian OS中的每个应用程序由一个活动调度程序和一个或多个活动对象组成。这个调度程序封装一个需要异步的服务和依照它们的优先权安排活动对象的等待循环。活动对象封装实际的异步服务。 <BR></FONT></SPAN><img src ="http://www.blogjava.net/yangsail/aggbug/1629.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/yangsail/" target="_blank">蓝色雪焰</a> 2005-03-03 09:57 <a href="http://www.blogjava.net/yangsail/archive/2005/03/03/1629.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>学习J2ME编程需要掌握的几种技术</title><link>http://www.blogjava.net/yangsail/archive/2005/02/25/1506.html</link><dc:creator>蓝色雪焰</dc:creator><author>蓝色雪焰</author><pubDate>Fri, 25 Feb 2005 06:39:00 GMT</pubDate><guid>http://www.blogjava.net/yangsail/archive/2005/02/25/1506.html</guid><wfw:comment>http://www.blogjava.net/yangsail/comments/1506.html</wfw:comment><comments>http://www.blogjava.net/yangsail/archive/2005/02/25/1506.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/yangsail/comments/commentRss/1506.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/yangsail/services/trackbacks/1506.html</trackback:ping><description><![CDATA[<P>&nbsp;&nbsp;&nbsp;&nbsp;J2ME编程是Java在智能家电领域的应用，主要包含以下技术：</P>
<P>
<SCRIPT id=ad_text_pcjob src="http://www.pconline.com.cn/script/ad_text_pcjob.js" defer></SCRIPT>
&nbsp;&nbsp;&nbsp;&nbsp; <B><SPAN style="COLOR: #cc0000">1、&nbsp; 高级用户界面</SPAN></B></P>高级用户界面是指J2ME编程中用到的Form(窗体)、TextBox(文本框)、List(列表框)和Alert(提示信息框)等的使用，以及其中的一些控件的使用，当然也包括相应的事件处理。应用在一般的如登陆窗体、关于窗体和提示等。 
<P></P>
<P>&nbsp;&nbsp;&nbsp;&nbsp;<B><SPAN style="COLOR: #cc0000"> 2、&nbsp; 低级用户界面</SPAN></B></P>
<P>&nbsp;&nbsp;&nbsp;&nbsp; 低级用户界面指Canvas类和Graphics类等的使用，以及相应的事件处理，应用与游戏编程以及特殊界面的绘制等。</P>
<P>&nbsp;&nbsp;&nbsp;&nbsp; <B><SPAN style="COLOR: #cc0000">3、&nbsp; 记录存储系统</SPAN></B></P>
<P>&nbsp;&nbsp;&nbsp;&nbsp; 记录存储系统是手机中支持的用于数据永久保存的技术。因为手机中没有数据文件的概念，所以一般需要保存的数据只能以记录的形式保存。</P>
<P>&nbsp;&nbsp;&nbsp;&nbsp; <B><SPAN style="COLOR: #cc0000">4、&nbsp; 声音处理系统</SPAN></B></P>
<P>&nbsp;&nbsp;&nbsp;&nbsp; MIDP1.0不支持声音处理，但是很多手机厂商如Nokia、Siemens等都支持，所以播放声音也是一项需要掌握的技术。该技术使用的API多和手机厂商相关。但是MIDP2.0就提供了通用的支持。</P>
<P>&nbsp;&nbsp;&nbsp;&nbsp; <B><SPAN style="COLOR: #cc0000">5、&nbsp; 网络编程</SPAN></B></P>
<P>&nbsp;&nbsp;&nbsp;&nbsp; 网络编程指在手机中通过GPRS或者CDMA网络以HTTP协议或者SOCKET的形式连接网络。现在的手机支持HTTP网络编程的占大多数，支持SOCKET的相对很少。所以网络编程暂时也就是使用HTTP协议进行编程。</P>
<P>&nbsp;&nbsp;&nbsp;&nbsp;<B><SPAN style="COLOR: #cc0000"> 6、&nbsp; 多线程</SPAN></B></P>
<P>&nbsp;&nbsp;&nbsp;&nbsp; 多线程是J2ME应用中比较核心的技术之一，因为进行网络编程和低级用户界面编程是为了响应迅速，都需要处理成多线程。所以也必须熟练掌握。</P>
<P>&nbsp;&nbsp;&nbsp;&nbsp; <B><SPAN style="COLOR: #cc0000">7、&nbsp; 短信息编程</SPAN></B></P>
<P>&nbsp;&nbsp;&nbsp;&nbsp; 很多手机都提供了用于发送短信息的API，如NOKIA、SIEMENS、SAMSUNG等，所以在J2ME中发送短信息也是一个比较常用的技术。</P>
<P>&nbsp;&nbsp;&nbsp;&nbsp; <B><SPAN style="COLOR: #cc0000">8、&nbsp; 其他</SPAN></B></P>
<P>&nbsp;&nbsp;&nbsp;&nbsp; 当然根据厂商的不同还提供了其他的一些技术，如Siemens的API中支持文件，Nokia的API支持读取系统通讯录等等。</P><img src ="http://www.blogjava.net/yangsail/aggbug/1506.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/yangsail/" target="_blank">蓝色雪焰</a> 2005-02-25 14:39 <a href="http://www.blogjava.net/yangsail/archive/2005/02/25/1506.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>[转载]优化J2ME程序大小</title><link>http://www.blogjava.net/yangsail/archive/2005/02/20/1374.html</link><dc:creator>蓝色雪焰</dc:creator><author>蓝色雪焰</author><pubDate>Sun, 20 Feb 2005 10:21:00 GMT</pubDate><guid>http://www.blogjava.net/yangsail/archive/2005/02/20/1374.html</guid><wfw:comment>http://www.blogjava.net/yangsail/comments/1374.html</wfw:comment><comments>http://www.blogjava.net/yangsail/archive/2005/02/20/1374.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/yangsail/comments/commentRss/1374.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/yangsail/services/trackbacks/1374.html</trackback:ping><description><![CDATA[<P><SPAN class=myp111><SPAN class=myp111><FONT id=zoom>要把J2ME程序与J2SE程序区分开来，其依据就是J2ME运行所处的受限环境。多数J2ME系统的主要受限条件就是可以存储和运行程序所需内存的大小。例如，许多MIDP设备限制应用程序的尺寸不大于50K，这远远不及Server端J2SE运行环境下那些成兆的程序。实际应用中，程序会很容易超出这些限制条件。通过本篇您将学到一些减小程序尺寸大小的技巧，并在下面的例子中实践这些技术。这个例子MIDlet仅仅显示一个文本框并在其内容改变时发声。 <BR><BR><CCID_NOBR>
<TABLE cellSpacing=0 borderColorDark=#ffffff cellPadding=2 width=400 align=center borderColorLight=black border=1>
<TBODY>
<TR>
<TD class=code style="FONT-SIZE: 9pt" bgColor=#e6e6e6><PRE><CCID_CODE>package com.j2medeveloper.techtips; 
import javax.microedition.lcdui.*; 
public class BeforeSizeOptimization extends 
                                      BasicMIDlet { 
 public static final Command exitCommand = 
                      new Command( "Exit", 
                                 Command.EXIT, 1 ); 
 public BeforeSizeOptimization(){ 
 } 
 protected void initMIDlet(){ 
     getDisplay().setCurrent( new Mainform() ); 
 } 
 public class Mainform extends form { 
     public Mainform(){ 
         super( "Mainform" ); 
         addCommand( exitCommand ); 
         append( textf ); 
         setCommandListener( new CommandListener(){ 
             public void commandAction( Command c, 
                                    Displayable d ){ 
                 if( c == exitCommand ){ 
                     exitMIDlet(); 
                 } 
             } 
           } 
         ); 
         setItemStateListener( 
                           new ItemStateListener() { 
             public void itemStateChanged( 
                                        Item item ){ 
                 if( item == textf ){ 
                     AlertType.INFO.playSound( 
                                     getDisplay() ); 
                 } 
             } 
           } 
         ); 
     } 

     private TextField textf = 
               new TextField( "Type anything", null, 
                              20, 0 ); 
 } 
}</CCID_CODE></PRE></TD></TR></TBODY></TABLE></CCID_NOBR><BR><BR>虽然这个MIDlet在此仅作为一个例子，但使用的尺寸优化技巧可以适用于任一J2ME的profile上。</FONT></SPAN><BR><BR></SPAN><SPAN class=myp111>注意，上面的MIDlet类需要下面的辅助类： <BR><BR><CCID_NOBR>
<TABLE cellSpacing=0 borderColorDark=#ffffff cellPadding=2 width=400 align=center borderColorLight=black border=1>
<TBODY>
<TR>
<TD class=code style="FONT-SIZE: 9pt" bgColor=#e6e6e6><PRE><CCID_CODE>package com.j2medeveloper.techtips; 
import javax.microedition.lcdui.*; 
import javax.microedition.midlet.*; 
public abstract class BasicMIDlet extends MIDlet { 
 private Display display; 
 public BasicMIDlet(){ 
 } 
 protected void destroyApp( boolean unconditional ) 
                 throws MIDletStateChangeException { 
     exitMIDlet(); 
 } 
 public void exitMIDlet(){ 
     notifyDestroyed(); 
 } 
 public Display getDisplay(){ return display; } 
 protected abstract void initMIDlet(); 
 protected void pauseApp(){ 
 } 
 protected void startApp() 
                 throws MIDletStateChangeException { 
     if( display == null ){ 
         display = Display.getDisplay( this ); 
         initMIDlet(); 
     } 
 } 
}</CCID_CODE></PRE></TD></TR></TBODY></TABLE></CCID_NOBR><BR><BR>用J2ME WTK打包时，本例子MIDlet占用4K空间。 <BR><BR><BR>减小尺寸的首要步骤就是通过修正程序的功能实现来去掉多余的类。程序的所有功能确实必须都实现吗？用户可以不需要这些“附属功能”吗？要设计尽可能小的程序，这里的MIDlet例子已经相当小了。 <BR><BR>第二步就是深入考察程序定义的内部类，特别是匿名类。记住，每个类文件都有一定量的与之相关的系统开销。即便最普通的类也有系统开销。 <BR><BR><CCID_NOBR>
<TABLE cellSpacing=0 borderColorDark=#ffffff cellPadding=2 width=400 align=center borderColorLight=black border=1>
<TBODY>
<TR>
<TD class=code style="FONT-SIZE: 9pt" bgColor=#e6e6e6><PRE><CCID_CODE>public class foo { 
     // nothing here 
 }</CCID_CODE></PRE></TD></TR></TBODY></TABLE></CCID_NOBR><BR><BR>编译上边的类，生成的类文件大约200byte大小。比如实现一个事件监听器，就是对匿名类的常见使用。在例子MIDlet中就定义了两个此类的监听器。接下来进行的最简单的优化就是，让主MIDlet类实现CommandListener和ItemStateListener接口，并把监听器代码移至此处。记住，多个对象可以使用同样的监听器。必要时，可以使用传递至commandAction和itemStateChanged方法的参变量来区分它们。 <BR><BR>内部类也可使代码过大，因为编译器必须生成特殊的变量和方法，以便内部类可以访问包含它们的类的私有内容。请参考内部类的规范以获取更多信息。 <BR><BR>第三步，尽量使用现有的类。例如，基于CLDC的profile没有构造集合类，所以我们可以用内建的Hashtable和Vector类来实现之。构造MIDP程序时也可采用此法。例子MIDlet中定义了一个form字类来生成主表，可以容易的如下直接生成： <BR><BR><CCID_NOBR>
<TABLE cellSpacing=0 borderColorDark=#ffffff cellPadding=2 width=400 align=center borderColorLight=black border=1>
<TBODY>
<TR>
<TD class=code style="FONT-SIZE: 9pt" bgColor=#e6e6e6><PRE><CCID_CODE>mainform = new form( "Mainform" ); 
 mainform.addCommand( okCommand ); 
 mainform.setCommandListener( listener );</CCID_CODE></PRE></TD></TR></TBODY></TABLE></CCID_NOBR><BR><BR>这里没有正确或者错误的答案，只是要推敲。 <BR><BR>第四步就是破坏程序的继承关系。你也许把相关的代码放到一个或多个抽象类中，这是OOD中为提高程序间代码重用的推荐做法。虽然破坏继承关系与你所学知识相违背，但简化的继承关系更有意义。特别的，当你的的抽象类――可能来自其他项目――仅仅被继承一次时，破坏继承关系的结果不言而喻。例如，例子MIDlet继承了BasicMIDlet类，但两者合并为一个类。 <BR><BR>第五步就是要缩短名字长度，如包名、类名、方法名和数据元素名。看起来有些蠢，但一个类文件确实包含太多的符号信息。缩短各量的名字可以缩小生成的类文件尺寸。这种节省不会特别明显，但多个类中进行总加的结果还是可观的。包名对减小尺寸来讲特别合适。MIDP程序是完全自我包容的，完全可以不使用包名，因为在手持设备上包名根本不可能与其他类名冲突。例子MIDlet中，可以把com.j2medeveloper.tchtips包名去掉。 <BR><BR>注意，一般来讲，缩短名字不需要手工去做，要用一个“混淆器”去做。“混淆器”的主要功能是“隐藏”程序代码，使之不能通过反编译读出。它的副作用是减小了程序的尺寸。因为隐藏过程主要通过更改方法和数据成员的名字来完成。有一个开源的混淆器称为RetroGuard，可以免费从http://www.retrologic.com得到。也有一些商业包可用。(当为基于CLDC的profile混淆时，记得在预校验之前混淆，否则混淆器将使类文件中的预校验数据失效。) <BR><BR>最后，深入数组的初始化。(例子MIDlet没有做数组初始化，但对程序来说初始化是重要的一步) 在编译时，一个数组初始化声明如下所示： <BR><BR><CCID_NOBR>
<TABLE cellSpacing=0 borderColorDark=#ffffff cellPadding=2 width=400 align=center borderColorLight=black border=1>
<TBODY>
<TR>
<TD class=code style="FONT-SIZE: 9pt" bgColor=#e6e6e6><PRE><CCID_CODE>int arr[] = { 0, 1, 2, 3 }; 
而实际生成代码的过程如下所示： 
&lt;ccid_nobr&gt;
&lt;table width="400" border="1" cellspacing="0" cellpadding="2" 
 bordercolorlight = "black" bordercolordark = "#FFFFFF" align="center"&gt;
&lt;tr&gt;
    &lt;td bgcolor="e6e6e6" class="code" style="font-size:9pt"&gt;
    &lt;pre&gt;&lt;ccid_code&gt; arr[0] = 0; 
 arr[1] = 1; 
 arr[2] = 2; 
 arr[3] = 3;</CCID_CODE></PRE></TD></TR></TBODY></TABLE></CCID_NOBR><BR><BR>这个过程可以通过使用Java 2 SDK中附带的javap工具把二进制代码反编译成类文件去看(使用-c选项)。也许你会诧异于看到的内容，特别当你希望看到的是一排排二进制常数时。有两种方法可以让你看不到反编译的程序代码，(1)把数据编码为字符串，运行时解码之，或者(2)把数据存为二进制文件并与程序打包，用类装载器的getResourceAsStream方法在运行时存取之。 <BR><BR>以上只是一些指导性的方法，对每个J2ME程序而言，这里没有具体到步骤。但是多数方法可以应用的本例。优化后的MIDlet如下所示： <BR><BR><CCID_NOBR>
<TABLE cellSpacing=0 borderColorDark=#ffffff cellPadding=2 width=400 align=center borderColorLight=black border=1>
<TBODY>
<TR>
<TD class=code style="FONT-SIZE: 9pt" bgColor=#e6e6e6><PRE><CCID_CODE>import javax.microedition.lcdui.*; 
import javax.microedition.midlet.*; 
public class ASO extends MIDlet 
              implements CommandListener, 
                         ItemStateListener { 
 private Display   display; 
 private form      mainform; 
 private TextField mainformTF = 
               new TextField( "Type anything", null, 
                              20, 0 ); 
 public static final Command exitCommand = 
                      new Command( "Exit", 
                                  Command.EXIT, 1 ); 
 public ASO(){ 
 } 
 public void commandAction( Command c, 
                            Displayable d ){ 
     if( c == exitCommand ){ 
         exitMIDlet(); 
     } 
 } 
 protected void destroyApp( boolean unconditional ) 
                 throws MIDletStateChangeException { 
     exitMIDlet(); 
 } 
 public void exitMIDlet(){ 
     notifyDestroyed(); 
 } 
 public Display getDisplay(){ return display; } 
 protected void initMIDlet(){ 
     mainform = new form( "Mainform" ); 
     mainform.addCommand( exitCommand ); 
     mainform.setCommandListener( this ); 
     mainform.setItemStateListener( this ); 
     mainform.append( mainformTF ); 
     getDisplay().setCurrent( mainform ); 
 } 
 public void itemStateChanged( Item item ){ 
     if( item == mainformTF ){ 
         AlertType.INFO.playSound( getDisplay() ); 
     } 
 } 
 protected void pauseApp(){ 
 } 
 protected void startApp() 
                 throws MIDletStateChangeException { 
     if( display == null ){ 
         display = Display.getDisplay( this ); 
         initMIDlet(); 
     } 
 } 
}</CCID_CODE></PRE></TD></TR></TBODY></TABLE></CCID_NOBR><BR><BR><B>关于作者：</B>Eric Giguere是来自Sybase下属iAnywhere Solutions的软件开发人员。他致力于手持设备和无线计算领域的Java技术。他是滑铁卢大学的数学学士和数学硕士，写了很多有关计算的文章。 <BR></CCID_CODE></SPAN></P><img src ="http://www.blogjava.net/yangsail/aggbug/1374.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/yangsail/" target="_blank">蓝色雪焰</a> 2005-02-20 18:21 <a href="http://www.blogjava.net/yangsail/archive/2005/02/20/1374.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>翻开手机游戏产业的三张底牌</title><link>http://www.blogjava.net/yangsail/archive/2005/02/20/1373.html</link><dc:creator>蓝色雪焰</dc:creator><author>蓝色雪焰</author><pubDate>Sun, 20 Feb 2005 10:15:00 GMT</pubDate><guid>http://www.blogjava.net/yangsail/archive/2005/02/20/1373.html</guid><wfw:comment>http://www.blogjava.net/yangsail/comments/1373.html</wfw:comment><comments>http://www.blogjava.net/yangsail/archive/2005/02/20/1373.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/yangsail/comments/commentRss/1373.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/yangsail/services/trackbacks/1373.html</trackback:ping><description><![CDATA[<FONT size=2>　　手机游戏产业价值几何？对于包括移动运营、内容和服务提供在内的整条移动应用产业链而言，在所谓手机游戏产业的宏观概念下到底潜藏着一个多大规模的市场？可以挖掘到哪些市场亮点和创新的应用种类？又有多少亦明亦暗的产业障碍还亟待跨越？ <BR><BR>　　表面看来，手机游戏正在成为运营商迅速创收的新渠道，并且似乎即将成为语音呼叫和短信息之外存在的又一个潜力巨大的移动服务市场。运营商通过收取游戏下载费用来增加ARPU值并同时拉动数据服务消费量实现增长。服务和内容提供商(SP和CP)们则通过出卖游戏下载版权和提供下载服务平台赚取更多的利润提成。 <BR><BR>　　事实上，手机游戏产业早在上世纪90年代后期就已经初具雏形。当时，GSM手机基本都内嵌了一些简单的黑白游戏，比如经典不衰的“贪吃蛇”和“俄罗斯方块”。在当时，是否内置有这些游戏已经成为一款畅销手机的必备要素。紧接着，短信息(SMS)服务所取得的巨大成功(世界各地每个月在SMS服务上花费近300亿美元)使得运营商们看到了移动数据服务市场蕴含的庞大潜力，并且萌生了将手机游戏培养成移动数据服务的下一代杀手应用的冲动。 <BR><BR>　　现在就让我们来揭开这位“杀手”的神秘底牌吧。 <BR><BR></FONT><STRONG>
<CENTER><FONT size=2>底牌一：到底有多少游戏形式可供挖掘？</FONT></CENTER></STRONG><BR><BR><FONT size=2>　　迄今为止，业界所能挖掘到的手机游戏种类大致有五种类型(这里不包含N-GAGE这种纯粹是游戏机和手机的融合产物)，不同的游戏类型在开发难度和市场容量方面存在着较大差别。 <BR><BR>　　<STRONG>嵌入式游戏</STRONG> <BR><BR>　　手机出厂时固化的嵌入式游戏无疑是所有手机游戏的起源。Nokia算是始作俑者之一，当时它的开发人员向其经典的6100系列手机添加了“贪吃蛇”游戏。与短消息非常相似，“贪吃蛇”游戏自然而然地流行起来：没有主动的营销，只是凭借口碑的传诵就可以流行起来，而6100和3310系列手机也间接受益成为了当时的畅销手机。 <BR><BR>　　对于这一意外的成功(Nokia认为大约每周会有100万手机用户在经常玩这款游戏)，让许多运营商开始相信手机游戏的确拥有强劲的发展潜力。唯一的不足在于这些游戏无法更新，因此很容易被时尚的潮流所厌倦和抛弃。手机厂商们必须不断地开发和销售更多的新游戏来取代它们……就这样，手机游戏市场终于诞生了。 <BR><BR>　　<STRONG>SMS游戏和MMS游戏</STRONG> <BR><BR>　　从易用性、吸引力等角度看来，短信息(SMS)完全不适合被作为一种游戏类型。但是，SMS却适用于那些早期的文字MUD(网游的前身)游戏，游戏的玩家相互发送简短的文本消息，用以控制其游戏角色的行为。此外，对于一些吸引力颇高的“投票式游戏”以及其他智力测验类游戏，SMS也是相当适用的游戏手段。 <BR><BR>　　我们可以看到，尽管SMS并非理想的游戏媒介，但它是让手机游戏用户和潜在用户习惯于使用手机进行游戏娱乐的绝妙方式。 <BR><BR>　　另一方面，我们看到多媒体消息服务(MMS)已经成为SMS发展的下一阶段，它为用户简单的文本体验增加了图形、照片乃至视频片段等新内容元素。 <BR><BR>　　但是从玩游戏的观点看，MMS在功能本质上相比SMS并没有实现根本性的提升。业界认为MMS仍可以被用作实现某些比较初级的互动游戏，但总体而言，移动游戏业并不看好MMS平台，相反注意到更有效和更具活力的Java/J2ME技术已经日趋成熟，而手机游戏的发展为此也即将登上新的高峰。 <BR><BR>　　<STRONG>Java/J2ME：手机游戏的未来？</STRONG> <BR><BR>　　Java/J2ME技术是推动手机游戏向更高层次进一步发展的关键动力。 <BR><BR>　　这些技术是移动运营商、手机制造商和应用开发商进军手机游戏领域时一直期待的基础元素。通过Java/J2ME实现拥有动画和声音的可下载游戏，这就是手机游戏应用下一阶段的发展方向。 <BR><BR>　　由Java/J2ME技术构建的可下载游戏不仅使手机游戏可以更为轻易地吸引到用户的兴趣，同时也有利于创造更为多样化的计费形式。例如对于一些售价低廉的游戏，可以参照类似街机游戏的业务模型进行销售。用户可以先付费，然后一直玩这款游戏直到他们的预付费或游戏时间用完为止。对于另外一些手机游戏，运营商可以允许用户一次花费更多的钱而获得无限制的游戏时间，用户甚至还可以购买某一游戏，然后依照版权规定的相关协议将此款游戏转让给其他玩家。 <BR><BR>　　当前，具有Java应用下载功能的手机已经在市场上逐渐流行起来，甚至可能从2004年起将被作为新型手机的标准配置。另一方面，随着功能更为强大的手机芯片、更多容量的内存以及正在大规模开发中的Java游戏的出现，未来的手机无疑将成为一个真正的游戏平台。 <BR><BR>　　<STRONG>Flash：甚至比Java更好？</STRONG> <BR><BR>　　当然，并不是所有人都把Java(特别是移动版的J2ME)看作手机游戏发展的真正方向。反对派的意见认为，Java游戏目前的性能表现很难激起游戏玩家的真正兴趣。同时，我们看到如今在互联网上几乎所有的Web游戏已经开始被Flash游戏所统治，Java游戏的辉煌正在逐渐褪去。所以Flash的支持者们相信，同样的情形必将在手机游戏身上重演。 <BR><BR>　　除了J2ME程序处理周期长，对于手机芯片的运算能力要求较高以及运行过程相当耗电等缺点之外，在游戏方面J2ME的最大缺点的确让人难以想象—竟然是玩家在游戏中无法同时按下两个功能键。无论是游戏老手还是缺乏经验的新手，这都是玩家所厌恶的一个致命缺陷。所幸Flash不仅克服了这一缺点，而且对于更为复杂的图形与声音处理也应付自如。有专业咨询师认为，在目前Java当道的情形下，敢于将Flash嵌入到手机和手机游戏中的厂商还寥寥无几，但是不可否认Flash最终将成为所有手机游戏应用的标准操作环境。 <BR><BR></FONT><STRONG>
<CENTER><FONT size=2>底牌二：手机游戏市场究竟有多大？</FONT></CENTER></STRONG><BR><BR><FONT size=2>　　我们首先将手机游戏分为两类，然后再分析各自的市场容量。第一类是包括智力测验、文本问答等在内的基于短信息(SMS或MMS服务)的游戏。第二类是“复杂的可下载游戏”，这其中包括了绝大多数未来的游戏应用。 <BR><BR>　　<STRONG>基于短信息的游戏</STRONG> <BR><BR>　　基于短信息的游戏往往定价也偏低，因此这类游戏的收入潜力可能不如人们最初想像的那般乐观。但是考虑到使用短信息的庞大用户群就是这种游戏的潜在市场，因此我们仍将最终得到一个巨大的市场容量模型。 <BR><BR>　　在全球部分发达地区，基于短信息的手机游戏服务已经开始赢利。特别是在欧洲，已经初步形成了一个规模达数十亿美元的行业市场。然而反观北美市场的规模，目前则只有令人失望的8亿美元而已，在2008年，北美市场规模预期将超越10亿美元大关。 <BR><BR>　　<STRONG>复杂的可下载游戏</STRONG> <BR><BR>　　未来几年中，可下载的手机游戏市场预计将出现跨国和跨地域的发展趋势。在一些地区，如果2.5G和3G手机以及相关网络的渗透率较高，那么可下载的手机游戏市场份额也会相应出现明显的增幅。当然，我们不能忽视短信息游戏所蕴含的市场能量，其中许多的游戏很可能将得到用户的广泛接受甚至成为社会文化的一部分。这些积极因素都将为第三方游戏提供商和提供游戏下载的移动运营商带来丰厚的利润。 <BR><BR>　　尽管今后几年中手机游戏下载的价格将呈整体下降的趋势，但这一市场的赢利前景仍然非常乐观。据Juniper Resource的研究显示，目前全球范围内可下载的手机游戏已经形成了一个价值30亿美元的巨大市场。今后几年中，随着市场领域的不断扩大以及中高端手机价格的相应下降，其市场规模将实现翻番，向60亿美元迅速靠近。这的确是一次真正的商机！ <BR><BR>　　当然，Juniper Resource表示，若想让这一切预测成为现实，那么必须满足两方面的前提条件：始终有关键的市场推动因素以及有益于市场规模化的运营管制和计费基础。 <BR><BR></FONT><STRONG>
<CENTER><FONT size=2>底牌三：有多少产业障碍亟待解除？</FONT></CENTER></STRONG><BR><BR><FONT size=2>　　<STRONG>手机外形因素</STRONG> <BR><BR>　　所有移动娱乐服务的明显缺点是屏幕尺寸过小和分辨率太低。直到不久前，手机仍不是专门为玩游戏而设计的。彩屏技术的出现增加了对使用者的吸引力，事实上，彩屏也是目前新手机销售的最大推动因素。但是，生产适合于彩色屏幕性能的手机游戏完全取决于游戏生产商。嵌入式游戏和某些复古游戏最适合于基本的移动电话屏幕，因为它们的黑白屏幕分辨率不高而且符合用户对某种传统体验的期待心理。但对于其他大部分手机游戏而言，则需要更高分辨率的屏幕。 <BR><BR>　　<STRONG>手机内存制约因素</STRONG> <BR><BR>　　有限的手机内存可满足今天大多数普通游戏和其他娱乐应用的需要，但是在新一代游戏上则成为最大的障碍。例如，一般手机(甚至是某些市面上更先进的手机)一次也只能保存4个游戏。这意味着玩家的游戏体验已经受到限制。他们必须做出选择：要么保留喜爱的游戏并有效地保存它们，要么在他们希望尝试新游戏时必须放弃他们以前喜爱的游戏。 <BR><BR>　　<STRONG>新型手机的普及</STRONG> <BR><BR>　　目前的一些游戏在设计上几乎可以适合大部分手机。但是，如果游戏市场想拥有一个持续繁荣的未来，那么能够处理彩色图像、复合音色并最终可处理Java下载的手机数量必须大大增加和普及。也许，只有2.5G和3G手机具有这种可能性，并引发新一波互动游戏的浪潮。 <BR><BR>　　<STRONG>网络技术</STRONG> <BR><BR>　　目前，多数用户拥有基于2G的手机，它们一般适合于通过SMS提供简单的智力竞赛类的游戏和服务。为了获得更丰富的游戏体验，用户将需要WAP、2G乃至3G手机，但距离规模应用仍有很长的路要走。此外，在消费者心目中，WAP在许多方面的形象不佳，性能不可靠，速度又慢得令人难以忍受。但是WAP可能是早期提供手机游戏的首选平台。当然，这必须是一种新的、改进的WAP版本，当在2.5G或3G网络上使用时，它的性能与WAP 1.0早期广告承诺的“无线Internet”非常像。当然，考虑到它的形像缺损，未来的市场推广应尽量避免提到“WAP因素”。</FONT><img src ="http://www.blogjava.net/yangsail/aggbug/1373.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/yangsail/" target="_blank">蓝色雪焰</a> 2005-02-20 18:15 <a href="http://www.blogjava.net/yangsail/archive/2005/02/20/1373.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>用J2ME在移动设备上实现动画</title><link>http://www.blogjava.net/yangsail/archive/2005/02/17/1286.html</link><dc:creator>蓝色雪焰</dc:creator><author>蓝色雪焰</author><pubDate>Thu, 17 Feb 2005 12:04:00 GMT</pubDate><guid>http://www.blogjava.net/yangsail/archive/2005/02/17/1286.html</guid><wfw:comment>http://www.blogjava.net/yangsail/comments/1286.html</wfw:comment><comments>http://www.blogjava.net/yangsail/archive/2005/02/17/1286.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/yangsail/comments/commentRss/1286.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/yangsail/services/trackbacks/1286.html</trackback:ping><description><![CDATA[<FONT size=2>使用MIDP(Mobile Information Device Profile)的开发人员经常会抱怨用些什么办法才可以在一个MIDlet上显示动画。MIDP 1.0 没有直接提供对动画的支持(正在开发中的MIDP 2.0支持)，但真要是自己去实现，其实也并非是一件很难的事。 </FONT>
<P><FONT size=2>任何动画的最基本的前提，是要在足够快的时间内显示和更换一张张的图片，让人的眼睛看到动的画面效果。图片必须按照顺序画出来。从一张图片到下一张图片之间的变化越小，效果会越好。 <BR><BR>首先要做的，是使用你的图片处理软件（比如ps或者firework）创建一系列相同大小的图片来组成动画。每张图片代表动画一帧。<BR></FONT></P>
<P><FONT size=2>你需要制作一定数量的祯－－越多的帧会让你的动画看上去越平滑。制作好的图片一定要保存成PNG(Portable Network Graphics) 格式，MIDP唯一支持的图片格式；（有两个办法让你刚做好的图片在MIDlet上变成动画。第一，把图片都放到一个web服务器上，让MIDlet下载他们，MIDP内置的HTTP支持。第二个办法更简单，把图片用MIDlet打包成jar文件。如果你使用的是J2ME开发工具，把PNG文件放在你的项目文件里面就可以了。 <BR><BR>动画的过程其实更像帐本记录：显示当前帧，然后适当地更换到下一帧。那么使用一个类来完成这个工作应该是很恰当的，那好，我们就先定义一个AnimatedImage类：</FONT></P>
<P><FONT color=#0000ff size=2>import java.util.*; <BR>import javax.microedition.lcdui.*; <BR>// 定义了一个动画，该动画其实只是一系列相同大小的图片 <BR>// 轮流显示，然后模拟出的动画 <BR>public class AnimatedImage extends TimerTask {; <BR>private Canvas canvas; <BR>private Image[] images; <BR>private int[][] clipList; <BR>private int current; <BR>private int x; <BR>private int y; <BR>private int w; <BR>private int h; </FONT></P>
<P><FONT color=#0000ff size=2>// Construct an animation with no canvas. </FONT></P>
<P><FONT color=#0000ff size=2>public AnimatedImage( Image[] images ){; <BR>this( null, images, null ); <BR>}; </FONT></P>
<P><FONT color=#0000ff size=2>// Construct an animation with a null clip list. </FONT></P>
<P><FONT color=#0000ff size=2>public AnimatedImage( Canvas canvas, Image[] <BR>images ){; this( canvas, images, null ); <BR>}; </FONT></P>
<P><FONT color=#0000ff size=2>// Construct an animation. The canvas can be null, <BR>// but if not null then a repaint will be triggered <BR>// on it each time the image changes due to a timer <BR>// event. If a clip list is specified, the image is <BR>// drawn multiple times, each time with a different <BR>// clip rectangle, to simulate transparent parts. </FONT></P>
<P><FONT color=#0000ff size=2>public AnimatedImage( Canvas canvas, Image[] images, <BR>int[][] clipList ){; <BR>this.canvas = canvas; <BR>this.images = images; <BR>this.clipList = clipList; </FONT></P>
<P><FONT color=#0000ff size=2>if( images != null &amp;&amp; clipList != null ){; <BR>if( clipList.length &lt; images.length ){; <BR>throw new IllegalArgumentException(); <BR>}; <BR>}; </FONT></P>
<P><FONT color=#0000ff size=2>if( images != null &amp;&amp; images.length &gt; 0 ){; <BR>w = images[0].getWidth(); <BR>h = images[0].getHeight(); <BR>}; <BR>}; </FONT></P>
<P><FONT color=#0000ff size=2>// Move to the next frame, wrapping if necessary. </FONT></P>
<P><FONT color=#0000ff size=2>public void advance( boolean repaint ){; <BR>if( ++current &gt;= images.length ){; <BR>current = 0; <BR>}; </FONT></P>
<P><FONT color=#0000ff size=2>if( repaint &amp;&amp; canvas != null &amp;&amp; canvas.isShown() <BR>){; <BR>canvas.repaint( x, y, w, h ); <BR>canvas.serviceRepaints(); <BR>}; <BR>}; </FONT></P>
<P><FONT color=#0000ff size=2>// Draw the current image in the animation. If <BR>// no clip list, just a simple copy, otherwise <BR>// set the clipping rectangle accordingly and <BR>// draw the image multiple times. </FONT></P>
<P><FONT color=#0000ff size=2>public void draw( Graphics g ){; <BR>if( w == 0 || h == 0 ) return; </FONT></P>
<P><FONT color=#0000ff size=2>int which = current; </FONT></P>
<P><FONT color=#0000ff size=2>if( clipList == null || clipList[which] == null <BR>){; <BR>g.drawImage( images[which], x, y, <BR>g.TOP | g.LEFT ); <BR>}; else {; <BR>int cx = g.getClipX(); <BR>int cy = g.getClipY(); <BR>int cw = g.getClipWidth(); <BR>int ch = g.getClipHeight(); </FONT></P>
<P><FONT color=#0000ff size=2>int[] list = clipList[which]; </FONT></P>
<P><FONT color=#0000ff size=2>for( int i = 0; i + 3 &lt;= list.length; i += <BR>4 ){; <BR>g.setClip( x + list[0], y + list[1], <BR>list[2], list[3] ); <BR>g.drawImage( images[which], x, y, <BR>g.TOP | g.LEFT ); <BR>}; </FONT></P>
<P><FONT color=#0000ff size=2>g.setClip( cx, cy, cw, ch ); <BR>}; <BR>}; </FONT></P>
<P><FONT color=#0000ff size=2>// Moves the animation's top left corner. </FONT></P>
<P><FONT color=#0000ff size=2>public void move( int x, int y ){; <BR>this.x = x; <BR>this.y = y; <BR>}; </FONT></P>
<P><FONT color=#0000ff size=2>// Invoked by the timer. Advances to the next frame <BR>// and causes a repaint if a canvas is specified. </FONT></P>
<P><FONT color=#0000ff size=2>public void run(){; <BR>if( w == 0 || h == 0 ) return; </FONT></P>
<P><FONT color=#0000ff size=2>advance( true ); <BR>}; <BR>};</FONT></P><img src ="http://www.blogjava.net/yangsail/aggbug/1286.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/yangsail/" target="_blank">蓝色雪焰</a> 2005-02-17 20:04 <a href="http://www.blogjava.net/yangsail/archive/2005/02/17/1286.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>图块象素化的边缘抗锯齿处理</title><link>http://www.blogjava.net/yangsail/archive/2005/02/17/1283.html</link><dc:creator>蓝色雪焰</dc:creator><author>蓝色雪焰</author><pubDate>Thu, 17 Feb 2005 10:59:00 GMT</pubDate><guid>http://www.blogjava.net/yangsail/archive/2005/02/17/1283.html</guid><wfw:comment>http://www.blogjava.net/yangsail/comments/1283.html</wfw:comment><comments>http://www.blogjava.net/yangsail/archive/2005/02/17/1283.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/yangsail/comments/commentRss/1283.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/yangsail/services/trackbacks/1283.html</trackback:ping><description><![CDATA[<P><FONT size=2><STRONG><FONT size=3>图块象素化的边缘抗锯齿处理<BR></FONT></STRONG>由于通常用作游戏画面的人物肖像、背景等需要做扣图处理的图片在处理前底色都是白色，如果直接这样的话，很可能做出来的象素化后的图片（在256色的环境下，整个图块颜色少于40色）会有明显的边缘锯齿。<BR>克服锯齿或者减弱锯齿效应以前使用的方法是修边，即用深色，例如纯黑，把图块的周围描绘一遍，这样做可以很好的减少锯齿并能提高象素的味道，但是缺点有：对于极细的线条的处理，例如一条线只有1个象素宽，那么描边过后就有3象素宽，这在某些场合是不可取的，很可能会破坏原由画面的味道。而对于手机上的图象处理，这样的结果肯定不行。<BR>那么，就只好进行没有描边的处理方式。<BR>查看了日本最新格斗游戏kof2003的图片内容，kof系列的人物从来都是不描边的，即完全用色块来表现角色。这对于32*32尺寸以内的小图片来说，直接在方格内照原画用象素画法画一遍就可以了，但是如果尺寸是大于32*32的或者战局大半个手机屏幕的一幅图呢？例如人物的特写，rpg中会用到的头像？这个时候如果还用象素的方式来描绘很可能会跟原画有相当大的出入，而且修改起来也很麻烦。<BR>因此，可以利用原画来进行加工！<BR>把底色设置为蓝色（0,0,255）然后缩小图片尺寸，这样图块的周围一圈就很好的和兰色融合在了一起，然后通过反复的在索引色及rgb色之间的转换减少原色，同时对一些相邻色进行删减（把相邻的颜色用灌桶填成同一种颜色，例如121,34,133与121，34，144就可以合成为一个颜色）。这样最终做出来的图片就是以兰色为底色的颜色很少但失真并不严重的图片，把加工的图块扣取出来，放到背景里面，发现融合程度很好，边缘锯齿弱化很多。<BR></FONT><A href="http://www.move2008.com/bbs/UploadFile/200421313425417831.gif" target=_blank><FONT size=2><IMG alt="" src="http://www.move2008.com/bbs/UploadFile/200421313425417831.gif" onload="javascript:if(this.width>screen.width-333)this.width=screen.width-333" border=0 dypop="按此在新窗口浏览图片"></FONT></A><FONT size=2>&nbsp;</FONT></P>
<P><FONT size=2><B>这是以白色为幕布做出来的图片----</B>锯齿非常明显。<BR></FONT><A href="http://www.move2008.com/bbs/UploadFile/200421313495885142.gif" target=_blank><FONT size=2><IMG alt="" src="http://www.move2008.com/bbs/UploadFile/200421313495885142.gif" onload="javascript:if(this.width>screen.width-333)this.width=screen.width-333" border=0 dypop="按此在新窗口浏览图片"></FONT></A></P>
<P><FONT size=2><STRONG>将完成的原画背静改为兰色------</STRONG>是这样样子滴！<BR></FONT><A href="http://www.move2008.com/bbs/UploadFile/200421313504382357.gif" target=_blank><FONT size=2><IMG alt="" src="http://www.move2008.com/bbs/UploadFile/200421313504382357.gif" onload="javascript:if(this.width>screen.width-333)this.width=screen.width-333" border=0 dypop="按此在新窗口浏览图片"></FONT></A></P>
<P><FONT size=2><STRONG>将上图减小尺寸过后------</STRONG>就是这个样子滴！<BR></FONT><A href="http://www.move2008.com/bbs/UploadFile/200421313513283131.gif" target=_blank><FONT size=2><IMG alt="" src="http://www.move2008.com/bbs/UploadFile/200421313513283131.gif" onload="javascript:if(this.width>screen.width-333)this.width=screen.width-333" border=0 dypop="按此在新窗口浏览图片"></FONT></A></P>
<P><FONT size=2>此时再用你所能想到的任何方法，在不明显失真的前提下进行颜色压缩，最后把兰色去掉，就得到成品啦。哈哈啊。</FONT></P>
<P><FONT size=2><STRONG>用另外一个女生的图片做的图<BR></STRONG>同样的方法，可以取得同样良好的结果。<BR></FONT><A href="http://www.move2008.com/bbs/UploadFile/20042131401036261.gif" target=_blank><FONT size=2><IMG alt="" src="http://www.move2008.com/bbs/UploadFile/20042131401036261.gif" onload="javascript:if(this.width>screen.width-333)this.width=screen.width-333" border=0 dypop="按此在新窗口浏览图片"></FONT></A></P>
<P><FONT size=2><STRONG>反复进行不同背静的测试，<BR></STRONG>发现锯齿问题解决得很好，兰色的背静幕布在抠图过后能与其他颜色很好的融合。<BR></FONT><A href="http://www.move2008.com/bbs/UploadFile/20042131414772402.gif" target=_blank><FONT size=2><IMG alt="" src="http://www.move2008.com/bbs/UploadFile/20042131414772402.gif" onload="javascript:if(this.width>screen.width-333)this.width=screen.width-333" border=0 dypop="按此在新窗口浏览图片"></FONT></A></P><A href="http://www.move2008.com/bbs/UploadFile/200421313513283131.gif" target=_blank></A><img src ="http://www.blogjava.net/yangsail/aggbug/1283.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/yangsail/" target="_blank">蓝色雪焰</a> 2005-02-17 18:59 <a href="http://www.blogjava.net/yangsail/archive/2005/02/17/1283.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>编程技巧在动画方面</title><link>http://www.blogjava.net/yangsail/archive/2005/02/17/1281.html</link><dc:creator>蓝色雪焰</dc:creator><author>蓝色雪焰</author><pubDate>Thu, 17 Feb 2005 10:07:00 GMT</pubDate><guid>http://www.blogjava.net/yangsail/archive/2005/02/17/1281.html</guid><wfw:comment>http://www.blogjava.net/yangsail/comments/1281.html</wfw:comment><comments>http://www.blogjava.net/yangsail/archive/2005/02/17/1281.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/yangsail/comments/commentRss/1281.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/yangsail/services/trackbacks/1281.html</trackback:ping><description><![CDATA[<P><FONT size=2>动画方面.</FONT></P>
<P><FONT size=2>前面的朋友说了动画的制作,一个动画是由很多帧组在的.,您说的方法把一帧帧的动画做成一幅幅的图象,再一幅幅地载入,这样做的效率不高.应该把各幅员的动画放在一起,做成一幅大的图,加载一幅大图比加载多幅小图效率要高,一幅大图也比多幅小图容易管理得多.那么怎样做呢?</P>
<P><BR>做法和前面朋友说的差不多,关键是这个方法:<BR>drawImage(Image&nbsp;img,dx1,dy1,dx2,dy2,sx1,sy1,sx1,sy1,this)<BR>其中Image&nbsp;img是来源图象,<BR>&nbsp;&nbsp;&nbsp;&nbsp;dx1是目的区域的===左上===顶点的===X====坐标; </FONT></P>
<P></P>
<P><FONT size=2>&nbsp;&nbsp;&nbsp;&nbsp;dy1是目的区域的===左上===顶点的===Y====坐标;</FONT></P>
<P><FONT size=2>&nbsp;&nbsp;&nbsp;&nbsp;dx2是目的区域的===右下===顶点的===X====坐标;</FONT></P>
<P><FONT size=2>&nbsp;&nbsp;&nbsp;&nbsp;dy2是目的区域的===右下===顶点的===Y====坐标;</FONT></P>
<P><FONT size=2>&nbsp;&nbsp;&nbsp;&nbsp;sx1是来源区域的===左上===顶点的===X====坐标;</FONT></P>
<P><FONT size=2>&nbsp;&nbsp;&nbsp;&nbsp;sy1是来源区域的===左上===顶点的===Y====坐标;</FONT></P>
<P><FONT size=2>&nbsp;&nbsp;&nbsp;&nbsp;sx2是来源区域的===右下===顶点的===X====坐标;</FONT></P>
<P><FONT size=2>&nbsp;&nbsp;&nbsp;&nbsp;sy2是来源区域的===右下===顶点的===Y====坐标.<BR>================================================================<BR>下面是一个动画的程序代码.<BR><FONT color=#0000ff>import&nbsp;java.awt.*;<BR>import&nbsp;java.applet.*;<BR>public&nbsp;class&nbsp;Seriallmage4&nbsp;extends&nbsp;Applet&nbsp;implements&nbsp;Runnable<BR>{<BR>&nbsp;&nbsp;int&nbsp;SPF;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;//用来表示线程暂停的时间<BR>&nbsp;&nbsp;int&nbsp;sx,sy;<BR>/******************************************************************<BR>sx,sy用来表示来源区域的左上顶点.因为每一帧我们都做成了长是80宽87的图,再把它们放在一起做成一幅大的图,<BR>所以来源区域的右下顶点的坐标等于左上顶点坐标加上长和高.<BR>式子为:sx+80;sy+87<BR>******************************************************************/&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<BR>&nbsp;&nbsp;Image&nbsp;Animation;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;//来源图象<BR>&nbsp;&nbsp;MediaTracker&nbsp;MT;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;//图像追踪器,用来检查图像是否完全加载<BR>&nbsp;&nbsp;Thread&nbsp;newThread;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;//新的线程.<BR>&nbsp;&nbsp;Image&nbsp;OffScreen;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;//后台图像;<BR>&nbsp;&nbsp;Graphics&nbsp;drawOffScreen;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;//绘制后台图像的实体(实例).<BR>//======================init()函数===========================<BR>&nbsp;&nbsp;public&nbsp;void&nbsp;init()<BR>&nbsp;&nbsp;{<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;SPF=100;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;//线程暂停的时间是100豪秒,<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;MT=new&nbsp;MediaTracker(this);&nbsp;//实体化图像追踪器.<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Animation=getImage(getDocumentBase(),"lunisEx.gif");//加载图像.<BR>//======================//追踪图像=============================<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;MT.addImage(Animation,0);&nbsp;&nbsp;<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;try<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;{<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;showStatus("图象加载中....");&nbsp;&nbsp;&nbsp;//在状态栏中显示信息<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;MT.waitForAll();&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;//等待加载<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;catch(InterruptedException&nbsp;E){}<BR>//============================================================<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;OffScreen=createImage(300,300);&nbsp;&nbsp;//建立后台画面的实体<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;drawOffScreen=OffScreen.getGraphics();&nbsp;&nbsp;//取得后台画面的绘制类<BR>&nbsp;&nbsp;}<BR>//===========================================================<BR>&nbsp;&nbsp;public&nbsp;void&nbsp;start()&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;//start函数<BR>&nbsp;&nbsp;{<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;newThread=new&nbsp;Thread(this);&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;//建立线程<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;newThread.start();&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;//启动线程<BR>&nbsp;&nbsp;}<BR>&nbsp;&nbsp;public&nbsp;void&nbsp;stop()&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;//stop函数<BR>&nbsp;&nbsp;{<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;newThread=null;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;//线程设为null<BR>&nbsp;&nbsp;}<BR>//==========================================================<BR>&nbsp;&nbsp;public&nbsp;void&nbsp;paint(Graphics&nbsp;g)<BR>&nbsp;&nbsp;{<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;drawOffScreen.setColor(Color.green);&nbsp;&nbsp;&nbsp;&nbsp;//设置后台画面的背景色,<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;drawOffScreen.fillRect(0,0,300,300);&nbsp;&nbsp;&nbsp;&nbsp;//画一次背景,作用是清除后台画面,如果背景是一幅画<BR>//则要用这幅图来画.<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;drawOffScreen.drawImage(Animation,20,20,250,250,sx,sy,sx+80,sy+87,this);//在后台画面绘制<BR>//的图像,<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;g.drawImage(OffScreen,0,0,300,300,this);//把后台画面画到前台画面<BR>&nbsp;&nbsp;}<BR>&nbsp;&nbsp;public&nbsp;void&nbsp;update(Graphics&nbsp;g)<BR>&nbsp;&nbsp;{<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;paint(g);&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<BR>&nbsp;&nbsp;}<BR>//====================新动画循环在此========================50<BR>&nbsp;&nbsp;&nbsp;public&nbsp;void&nbsp;run()&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<BR>&nbsp;&nbsp;&nbsp;{<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;while(newThread&nbsp;!=null)&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;//如果线程不等于null<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;{<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;repaint();&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;//&nbsp;重画一次.<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;try<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;{<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Thread.sleep(SPF);&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;//让线程暂停<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;sx+=80;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;//画了一次后,改变来源区域,使其到下一帧.<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;if(sx==800)&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;//我的图像是一幅800*170的,有两行,每行十帧,如果sx走到了<BR>//800就说明播放完第一行了,<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;{<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;sy+=85;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;//播放完第一行后,改变来源区域的左上顶点使其跑到第二行.<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;sx=0;<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;if(sy==170)&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;//如果播放完第二行后,又让它回到第一行,<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;{<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;sy=0;<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;catch(InterruptedException&nbsp;E){}<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}<BR>&nbsp;&nbsp;&nbsp;&nbsp;}</FONT></FONT></P>
<P><FONT size=2><FONT color=#0000ff>}<BR></FONT>动画每秒播放多少帧好呢?<BR>电影的是每秒24帧,这样的速度不仅能产生视觉停留,而且让人感觉到画面非常流畅.那么是不是我们做游戏也要用这个速度呢?答案是否定的,实际上每秒播放10帧就足已产生视觉停留,如果设置高了会消耗更多的资源.至命的是,有一些手机连每秒10帖都做不到.</FONT></P><img src ="http://www.blogjava.net/yangsail/aggbug/1281.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/yangsail/" target="_blank">蓝色雪焰</a> 2005-02-17 18:07 <a href="http://www.blogjava.net/yangsail/archive/2005/02/17/1281.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>J2ME游戏开发实例讲解</title><link>http://www.blogjava.net/yangsail/archive/2005/02/17/1263.html</link><dc:creator>蓝色雪焰</dc:creator><author>蓝色雪焰</author><pubDate>Wed, 16 Feb 2005 17:02:00 GMT</pubDate><guid>http://www.blogjava.net/yangsail/archive/2005/02/17/1263.html</guid><wfw:comment>http://www.blogjava.net/yangsail/comments/1263.html</wfw:comment><comments>http://www.blogjava.net/yangsail/archive/2005/02/17/1263.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/yangsail/comments/commentRss/1263.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/yangsail/services/trackbacks/1263.html</trackback:ping><description><![CDATA[<P><SPAN class=f14><STRONG><FONT size=2>本文通过使用J2ME开发华容道游戏，介绍了J2ME游戏开发的基本模式....</FONT></STRONG></SPAN></P>
<P><SPAN class=f14><FONT size=2><STRONG>一、序言<BR><BR></STRONG>　　昨天在网上闲逛，发现一篇讲解用delphi实现华容道游戏的文章，颇受启发．于是，产生了将华容道游戏移植到手机中去的冲动．现在手机游戏琳琅满目，不一而足，华容道的实现版本也很多．正巧不久前笔者对J2ME下了一番功夫，正想借这个机会小试牛刀。选用J2ME的原因还有一个就是目前Java开发大行其到，无线增殖业务迅猛发展，J2ME的应用日渐活跃起来，也希望我的这篇文章能够为J2ME知识的普及和开发团队的壮大推波助澜。由于长期受ISO规范的影响，这次小试牛刀我也打算遵照软件工程的要求，并采取瀑布式的开发模式来规划项目，也希望借此机会向各位没有机会参与正式项目开发的读者介绍一下软件开发的流程。</FONT></SPAN></P>
<P><SPAN class=f14><FONT size=2>　　这里我们先定义项目组的人员体制(其实只有我一个人)：技术调研、需求分析、概要设计、详细设计、编码、测试均有笔者一人担任；美工这里我找了个捷径，盗用网上现成的图片，然后用ACDSee把它由BMP转换成PNG格式(我出于讲座的目的，未做商业应用，应该不算侵权吧)；至于发布工作，由于缺少OTA服务器，此项工作不做(但是我会介绍这步如何做)。<BR><BR>　　接下来，我们规划一下项目实现的时间表，以我个人经验，设想如下：技术调研用2天(这部分解决项目的可行性和重大技术问题，时间会长一些)，需求分析用半天(毕竟有现成的东东可以参照，只要理清思路就行了，况且还有很多以前用过的设计模式和写好的代码)，概要设计再用半天(有了需求，概要只不够是照方抓药)，详细设计要用2天(这一步要把所有的问题想清楚，还要尽可能的准确描述出来)，编码用2天(其实1天就够了，技术已经不是问题，多计划出一天来应付突发事件)，测试用2天(测试应该至少占全部项目的四分之一，不过这个项目只是一个Demo，也太简单了)，发布也要用上半天(尽管我们不去实际发布它，但是还要花点时间搞清楚应该如何做)，最后就是项目总结和开庆功会(时间待定)。<BR><BR>　　<B>二.利其器</B><BR><BR>　　“公欲善其事，必先利其器”，做项目之前第一步是前期调研．我们要做的华容道这个东东随处可见，我们要调研的是两个方面：<BR><BR>　　1、游戏的内容：游戏本身很简单，就是有几个格子，曹操占据其中一个较大的格子，然后被几个格子包围，这些格子形状不一定相同，但是挡住了曹操移动的方向．游戏者需要挪动这些格子最终把曹操移动到一个指定的位置才算是过关．更具体的分析我们放在后面需求分析和概要设计中讨论。<BR><BR>　　2、技术储备：谈到技术，这里简单介绍一下J2ME.Java有三个版本，分别是J2ME（微型版）.J2SE（标准版）.J2EE（企业版）．J2ME是一个标准，采用３层结构设计．最低层是配置层（Configuration）也就是设备层，其上是简表层（Profile）,再上是应用层（Application）.MIDP就是移动信息设备简表，目前主流手机支持MIDP1.0，最新的是MIDP2.0,它比前一个版本增加了对游戏的支持，在javax.microedition.lcdui.game包中提供了一些类来处理游戏中的技术，比如我们后面会用到的Sprite类，它是用来翻转图片的.权衡再三，笔者决定使用MIDP2.0来做开发．首先需要安装一个J2ME的模拟器，我们就用Sun公司的WTK2.0，我觉得Sun的东西最权威．当然你也可以使用Nokia.Siemens或是Motolora等其他模拟器，但是他们的JDK不尽相同，写出来的程序移植是比较麻烦的．Sun公司的WTK2.0可以到＜A href="http://here/下"＞http://here/下＜/A＞载，当然要想成功下载的前提是你要先注册成为Sun的会员（其实这样对你是有好处的）．当下来之后就是按照提示一步一步的安装．安装好了之后，我们用一个"Hello World"程序开始你的J2ME之旅．我们启动WTK2.0工具集中的KToolBar，然后点击New Project按钮，在弹出的输入框中输入Project Name为HelloWorld,MIDlet Class Name为Hello,然后点击Create Project，开始生成项目，工具会弹出MIDP配置简表，这里接受生成的默认值（以后还可以修改）点击OK，工具提示我们把写好的Java源程序放到[WTK_HOME]\apps\HelloWorld\src目录之下．我们编辑如下代码，并保存在上述目录之下，文件名为Hello.java。<BR><BR></FONT></P>
<P>
<TABLE width="100%" bgColor=#ffffff>
<TBODY>
<TR>
<TD><FONT color=#0000ff size=2>import javax.microedition.midlet.*;<BR>import javax.microedition.lcdui.*;<BR>public class Hello extends MIDlet<BR>{<BR>private Display display;<BR>public Hello(){<BR>display =Display.getDisplay(this);<BR>}<BR>public void startApp(){<BR>TextBox t = new TextBox("Hello","Hello",256,0);<BR>display.setCurrent(t);<BR>}<BR>public void pauseApp(){<BR>}<BR>public void destroyApp(boolean unconditional){<BR>}<BR>}</FONT></TD></TR></TBODY></TABLE></P>
<P><BR><FONT size=2>　　保存好了之后，点击Build按钮，工具会为你编译程序，如无意外再点击Run按钮，会弹出一个手机界面，剩下的就不用我教了吧（用鼠标对手机按键一顿狂点）。呵呵，你的第一个J2ME程序已经OK了.什么？你还一点都没懂呢（真是厉害，不懂都能写出J2ME程序来，果然是高手）．我这里主要是介绍WTK2.0工具的使用，程序并不是目的，不懂的话后面还会有详细的解说，这里只是带你上路．什么？你不懂Java！那也没有关系，后面我再讲得细一点。<BR><BR>　　跳过J2ME，我们先来讲点游戏的理论．具体到华容道这个游戏，主要有三个方面，贴图．游戏操作．逻辑判断．这里讲讲贴图，其他两方面放在概要设计和详细设计里讲．所谓的贴图，其实就是画图，就是在要显示图形的位置上输出一副图片，（要是牵扯到动画就要麻烦一些，可以使用TimerTask.Thread或Rannable之类的技术)，这副图片可以是事先准备好的也可以是临时处理的．在J2ME中有一个Image类,专门用于管理图片，它有createImage()方法，可以直接读取图片文件（J2ME只支持PNG格式的图片），也可以截取已有的图片的一部分（这样我们可以把很多图片放在一起，然后一张一张的截下来，好处是节省存储空间和文件读取时间，对于手机这两者都是性能的瓶颈）．J2ME还有一个Graphics类，专门用于绘图，它有drawImage()方法，可以把一副图片在指定的位置上显示出来，它还有drawRect()方法和setColor()方法，这两个方法在后面我们进行游戏操作时就会用到，这里先交代一下．有了图片和绘图的方法，还需要知道把图画到谁身上，J2ME提供了一个Canvas类，字面意思就是画布，它有一个paint()方法用于刷新页面，还有一个repaint()方法用于调用paint()方法．听着有些糊涂是吧，不要紧，我来结合具体程序讲解一下．为了今后编程的方便，我们创建两个类Images和Draw,Images用于保存一些常量值和图片，Draw主要是用于画图，这两个类的源代码如下。<BR><BR>　　Images类的源代码如下：<BR><BR></FONT>
<TABLE width="100%" bgColor=#ffffff>
<TBODY>
<TR>
<TD><FONT color=#0000ff size=2>package huarongroad; </FONT>
<P><FONT color=#0000ff size=2>import javax.microedition.lcdui.*;<BR>import javax.microedition.lcdui.game.*;</FONT></P>
<P><FONT color=#0000ff size=2>public class Images {//保存常量<BR>//绘图位置常量<BR>public static final int UNIT = 32;//方块的单位长度<BR>public static final int LEFT = 10;//画图的左边界顶点<BR>public static final int TOP = 9;//画图的上边界顶点<BR>//地图位置常量<BR>public static final int WIDTH = 4;//地图的宽度<BR>public static final int HEIGHT = 5;//地图的高度<BR>//地图标记常量<BR>public static final byte CAOCAO = (byte) 'a'; ＜A href="file://曹"＞file://曹＜/A＞操的地图标记<BR>public static final byte MACHAO = (byte) 'b';//马超的地图标记<BR>public static final byte HUANGZHONG = (byte) 'c';//黄忠的地图标记<BR>public static final byte GUANYU = (byte) 'd';//关羽的地图标记<BR>public static final byte ZHANGFEI = (byte) 'e';//张飞的地图标记<BR>public static final byte ZHAOYUN = (byte) 'f';//赵云的地图标记<BR>public static final byte ZU = (byte) 'g';//卒的地图标记<BR>public static final byte BLANK = (byte) 'h';//空白的地图标记<BR>public static final byte CURSOR = (byte) 'i';//光标的地图标记<BR>//地图组合标记常量<BR>public static final byte DLEFT = (byte) '1'; ＜A href="file://组"＞file://组＜/A＞合图形左边标记<BR>public static final byte DUP = (byte) '2'; ＜A href="file://组"＞file://组＜/A＞合图形上边标记<BR>public static final byte DLEFTUP = (byte) '3'; ＜A href="file://组"＞file://组＜/A＞合图形左上标记<BR>//图片常量<BR>public static Image image_base;//基本图片<BR>public static Image image_Zhaoyun;//赵云的图片<BR>public static Image image_Caocao;//曹操的图片<BR>public static Image image_Huangzhong;//黄忠的图片<BR>public static Image image_Machao;//马超的图片<BR>public static Image image_Guanyu;//关羽的图片<BR>public static Image image_Zhangfei;//张飞的图片<BR>public static Image image_Zu;//卒的图片<BR>public static Image image_Blank;//空白的图片<BR>public static Image image_Frame;//游戏框架的图片</FONT></P>
<P><FONT color=#0000ff size=2>public Images() {//构造函数<BR>}</FONT></P>
<P><FONT color=#0000ff size=2>public static boolean init() {//初始化游戏中用到的图片<BR>try {<BR>image_base = Image.createImage("/huarongroad/BITBACK.png");<BR>image_Frame = Image.createImage(image_base, 126, 0, 145, 177,<BR>Sprite.TRANS_NONE);<BR>//Sprite类是用来翻转图片的，是MIDP2.0新新增加的支持游戏的特性<BR>image_Zhaoyun = Image.createImage(image_base, 0, 0, UNIT, 2 * UNIT,<BR>Sprite.TRANS_NONE);<BR>image_Caocao = Image.createImage(image_base, UNIT, 0, 2 * UNIT,<BR>2 * UNIT, Sprite.TRANS_NONE);<BR>image_Huangzhong = Image.createImage(image_base, 3 * UNIT, 0, UNIT,<BR>2 * UNIT,<BR>Sprite.TRANS_NONE);<BR>image_Machao = Image.createImage(image_base, 0, 2 * UNIT, UNIT,<BR>2 * UNIT,<BR>Sprite.TRANS_NONE);<BR>image_Guanyu = Image.createImage(image_base, UNIT, 2 * UNIT,<BR>2 * UNIT, UNIT,<BR>Sprite.TRANS_NONE);<BR>image_Zhangfei = Image.createImage(image_base, 3 * UNIT, 2 * UNIT,<BR>UNIT, 2 * UNIT,<BR>Sprite.TRANS_NONE);<BR>image_Zu = Image.createImage(image_base, 0, 4 * UNIT, UNIT, UNIT,<BR>Sprite.TRANS_NONE);<BR>image_Blank = Image.createImage(image_base, 1 * UNIT, 4 * UNIT,UNIT,<BR>UNIT,<BR>Sprite.TRANS_NONE);</FONT></P>
<P><FONT color=#0000ff size=2>return true;<BR>}catch (Exception ex) {<BR>return false;<BR>}<BR>}<BR>}</FONT></P></TD></TR></TBODY></TABLE></P>
<P><FONT size=2>　　Draw类的源代码如下：<BR></FONT></P>
<P>
<TABLE width="100%" bgColor=#ffffff>
<TBODY>
<TR>
<TD>
<P><FONT color=#0000ff size=2>package huarongroad;</FONT></P>
<P><FONT color=#0000ff size=2>import javax.microedition.lcdui.*;</FONT></P>
<P><FONT color=#0000ff size=2>public class Draw {<BR>//绘制游戏中的图片<BR>public Draw(Canvas canvas) {//构造函数<BR>}</FONT></P>
<P><FONT color=#0000ff size=2>public static boolean paint(Graphics g, byte img, int x, int y) {<BR>//在地图的x,y点绘制img指定的图片<BR>try {<BR>paint(g, img, x, y, Images.UNIT);//把地图x,y点转化成画布的绝对坐标，绘图<BR>return true;<BR>}<BR>catch (Exception ex) {<BR>return false;<BR>}<BR>}</FONT></P>
<P><FONT color=#0000ff size=2>public static boolean paint(Graphics g, byte img, int x, int y, int unit) {<BR>try {<BR>switch (img) {<BR>case Images.CAOCAO://画曹操<BR>//变成绝对坐标，并做调整<BR>g.drawImage(Images.image_Caocao, Images.LEFT + x * unit,<BR>Images.TOP + y * unit,<BR>Graphics.TOP | Graphics.LEFT);<BR>break;<BR>case Images.GUANYU://画关羽<BR>g.drawImage(Images.image_Guanyu, Images.LEFT + x * unit,<BR>Images.TOP + y * unit,<BR>Graphics.TOP | Graphics.LEFT);<BR>break;<BR>case Images.HUANGZHONG://画黄忠<BR>g.drawImage(Images.image_Huangzhong, Images.LEFT + x * unit,<BR>Images.TOP + y * unit,<BR>Graphics.TOP | Graphics.LEFT);<BR>break;<BR>case Images.MACHAO://画马超<BR>g.drawImage(Images.image_Machao, Images.LEFT + x * unit,<BR>Images.TOP + y * unit,<BR>Graphics.TOP | Graphics.LEFT);<BR>break;<BR>case Images.ZHANGFEI://画张飞<BR>g.drawImage(Images.image_Zhangfei, Images.LEFT + x * unit,<BR>Images.TOP + y * unit,<BR>Graphics.TOP | Graphics.LEFT);<BR>break;<BR>case Images.ZHAOYUN://画赵云<BR>g.drawImage(Images.image_Zhaoyun, Images.LEFT + x * unit,<BR>Images.TOP + y * unit,<BR>Graphics.TOP | Graphics.LEFT);<BR>break;<BR>case Images.ZU://画卒<BR>g.drawImage(Images.image_Zu, Images.LEFT + x * unit,<BR>Images.TOP + y * unit,<BR>Graphics.TOP | Graphics.LEFT);<BR>break;<BR>case Images.BLANK://画空白<BR>g.drawImage(Images.image_Blank, Images.LEFT + x * unit,<BR>Images.TOP + y * unit,<BR>Graphics.TOP | Graphics.LEFT);<BR>break;<BR>case Images.CURSOR://画光标<BR>g.drawRect(Images.LEFT + x * unit,<BR>Images.TOP + y * unit,Images.UNIT,Images.UNIT);<BR>break;<BR>}<BR>return true;<BR>}catch (Exception ex) {<BR>return false;<BR>}<BR>}<BR>} </FONT></P></TD></TR></TBODY></TABLE></P>
<P><FONT size=2>　　其中Images类存的是绘图位置常量（也就是在画图时每个格子的长度和相对坐标原点位置要进行的调整）、地图位置常量（地图的长、宽），地图标记常量（人物对应的记号），地图组合标记常量（后面会细说），图片常量（存放人物的图片）；Draw类主要负责在制定的位置画出人物图片。下面我来说说Images类中的地图标记常量和地图组合标记常量。为了能够灵活的安排各个关面的布局，我们决定把游戏布局的信息存储在外部文件中，然后程序启动后把它读进来。这样我们制定了一套存储图片的代码，这就是地图标记常量，如上面Images类中定义的Caocao(曹操)用a字符来表示，当程序读到a字符时就能将它转化成曹操对应的图片，并在读到a字符的位置上进行显示。但是从实际观察中我们发现所有的图片并不是统一大小的，有的占4个格子，有的占2个格子，还有的占1个格子，而且即便同是占两个格子的图片还有横、竖之分。有鉴于此，我们引入了地图组合标记常量，就是说在遇到占有多个格子的时候，值1(也就是Images.LEFT)表示它的左边是一个真正的地图标记，值2(也就是Images.UP)表示它的上边是一个真正的地图标记，值1(也就是Images.LEFTUP)表示它的左上边是一个真正的地图标记。地图组合标记常量其实就是用来占位置的，与实际显示无关，当后面我们将到移动时还会再来分析组合标记的使用。<BR><BR>　　Draw类主要是用来在画布上画出图形，它有两个paint方法，这是很常见的函数重载。但是程序中实际上只用到了4个参数的paint方法，它直接获得要画图片的相对坐标位置信息，然后调用5个参数的paint方法。5个参数的paint方法将相对坐标位置信息转换成绝对位置，并实际调用Graphics.drawImage()方法，将Images中的图片画了出来。这种实现方法的好处是灵活和便于扩展，但你需要画图的位置并不能够对应到格子中的相对坐标位置时，你就可以直接调用5个参数的paint方法，而不必再去修改这各类；但你添加新的图片时，只要在Images中增加对应的常量，然后向Draw中5个参数的paint方法添加一条处理就可以了。<BR>写到这里，两天的时间刚好用完。<BR><BR></FONT><SPAN class=f14><FONT size=2>　　<B>三、需求分析</B><BR><BR>　　这部分叫做需求分析，听起来挺吓人的，其实就是搞清楚我们要做什么，做成什么样，那些不做。下面我引领着大家共同来完成这一步骤。首先，我们要做一个华容道的游戏，华容道的故事这里不再赘述了，但其中的人物在这里限定一下，如上面Images类里的定义，我们这个版本只提供曹操(Caocao)、关羽(Guanyu)、张飞(Zhangfei)、赵云(Zhaoyun)、黄忠(Huangzhong)、马超(Machao)和卒(Zu)。我们这里也限定一下游戏的操作方法：首先要通过方向键选择一个要移动的区域(就是一张图片)，被选择的区域用黑色方框框住；选好后按Fire键(就是确定键)将这块区域选中，被选中的区域用绿色方框框住；然后选择要移动到的区域，此时用红色方框框住被选择的区域；选好要移动到的区域之后按Fire键将要移动的区域(图片)移到要移动到的区域，并去掉绿色和红色的方框。这里需要强调的概念有选择的区域、选中的区域、要移动的区域和要移动到的区域，这四个概念请读者注意区分，当然也应当把这一部分记入数据字典之中。为了使文章的重点突出(介绍如何制作一个J2ME的收集游戏)，我们这里限定一些与本主题无关的内容暂不去实现：过关之后的动画(实现时要用到TimerTask或Thread类，后续的系列文章中我会详细介绍动画方面的知识)、关面之间的切换(其实很简单，当完成任务之后重新再做一边)、暂停和保存等操作(这部分的内容介绍的资料很多，我也写不出什么新的东东来，难免抄袭，故此免掉)。<BR><BR>　　需求分析基本完成，离下午还有一段时间，马上动手用ACDSee把从网上找来的BMP文件，调整其大小为271*177(我的这个图片是两个部分合在一起，所以比手机实际屏幕大了)，另存为PNG格式。半天时间刚刚好，不但搞清楚了要做的东东，还把要用的图片准备好了。<BR><BR>　　<B>四、概要设计</B><BR><BR>　　概要设计是从需求分析过渡到详细设计的桥梁和纽带，这一部分中我们确定项目的实现方法和模块的划分。我们决定将整个项目分成五个部分，分别是前面介绍的Images、Draw，还有Map和Displayable1和MIDlet1。Images和Draw类功能简单、结构固定，因此很多项目我们都使用这两各类，这里直接拿来改改就能用了，前面已经介绍过这里不再赘述。Map类是用来从外部文件读入地图，然后保存在一个数组之中，这部分的内容是我们在本阶段讨论的重点。Displayable1是一个继承了Canvas类的画布，它用来处理程序的主要控制逻辑和一部分控制逻辑所需的辅助函数，主要函数应该包括用来绘图的paint()函数、用来控制操作的keyPressed()函数、用来控制选择区域的setRange()函数、用来控制选择要移动到区域的setMoveRange()函数、用来移动选中区域的Move()函数和判断是否完成任务的win()函数，更具体的分析，我们放到详细设计中去细化。MIDlet1实际上就是一个控制整个J2ME应用的控制程序，其实也没有什么可特别的，它和我们前面介绍的"Hello World"程序大同小异，这里就不展开来说了，后面会贴出它的全部代码。<BR><BR>　　Map类主要应该有一个Grid[][]的二维数组，用来存放华容道的地图，还应该有一个read_map()函数用来从外部文件读取地图内容填充Grid数据结构，再就是要有一个draw_map()函数用来把Grid数据结构中的地图内容转换成图片显示出来(当然要调用Draw类的paint方法)。说到读取外部文件，笔者知道有两种方法：一种是传统的定义一个InputStream对象，然后用getClass().getResourceAsStream()方法取得输入流，然后再从输入流中取得外部文件的内容，例如<BR></FONT></P>
<P><FONT size=2></FONT></P>
<P>
<TABLE width="100%" bgColor=#ffffff>
<TBODY>
<TR>
<TD><FONT color=#0000ff size=2>InputStream is = getClass().getResourceAsStream("/filename");<BR>if (is != null) {<BR>byte a = (byte) is.read();<BR>}</FONT></TD></TR></TBODY></TABLE></P>
<P><FONT color=#0000ff size=2>　　这里请注意文件名中的根路径是相对于便以后的class文件放置的位置，而不是源文件(java)。第二种方法是使用onnector.openInputStream方法，然后打开的协议是Resource，但是这种方法笔者反复尝试都没能调通，报告的错误是缺少Resource协议，估计第二种方法用到J2ME的某些扩展类包，此处不再深究。由于以前已经做过一些类似华容道这样的地图，这里直接给出Map类的代码，后面就不再详细解释Map类了，以便于我们可以集中精力处理Displayable1中的逻辑。Map类的代码如下：<BR></FONT></P>
<P>
<TABLE width="100%" bgColor=#ffffff>
<TBODY>
<TR>
<TD>
<P><FONT color=#0000ff size=2>package huarongroad;</FONT></P>
<P><FONT color=#0000ff size=2>import java.io.InputStream;<BR>import javax.microedition.lcdui.*;</FONT></P>
<P><FONT color=#0000ff size=2>public class Map {<BR>//处理游戏的地图，负责从外部文件加载地图数据，存放地图数据，并按照地图数据绘制地图</FONT></P>
<P><FONT color=#0000ff size=2>public byte Grid[][];//存放地图数据</FONT></P>
<P><FONT color=#0000ff size=2>public Map() {//构造函数，负责初始化地图数据的存储结构<BR>this.Grid = new byte[Images.HEIGHT][Images.WIDTH];<BR>//用二维数组存放地图数据，注意第一维是竖直坐标，第二维是水平坐标<BR>}</FONT></P>
<P><FONT color=#0000ff size=2>public int[] read_map(int i) {<BR>＜A href="file://从"＞file://从＜/A＞外部文件加载地图数据，并存放在存储结构中，返回值是光标点的位置<BR>//参数是加载地图文件的等级<BR>int[] a = new int[2];//光标点的位置，0是水平位置，1是竖直位置<BR>try {<BR>InputStream is = getClass().getResourceAsStream(<BR>"/huarongroad/level".concat(String.valueOf(i)));<BR>if (is != null) {<BR>for (int k = 0; k ＜ Images.HEIGHT; k++) {<BR>for (int j = 0; j ＜ Images.WIDTH; j++) {<BR>this.Grid[k][j] = (byte) is.read();<BR>if ( this.Grid[k][j] == Images.CURSOR ) {<BR>//判断出光标所在位置<BR>a[0] = j;//光标水平位置<BR>a[1] = k;//光标竖直位置<BR>this.Grid[k][j] = Images.BLANK;//将光标位置设成空白背景<BR>}<BR>}<BR>is.read();//读取回车（13）,忽略掉<BR>is.read();//读取换行（10）,忽略掉<BR>}<BR>is.close();<BR>}else {<BR>//读取文件失败<BR>a[0] = -1;<BR>a[1] = -1;<BR>}<BR>}catch (Exception ex) {<BR>//打开文件失败<BR>a[0] = -1;<BR>a[1] = -1;<BR>}<BR>return a;<BR>}</FONT></P>
<P><FONT color=#0000ff size=2>public boolean draw_map(Graphics g) {<BR>//调用Draw类的静态方法，绘制地图<BR>try {<BR>for (int i = 0; i ＜ Images.HEIGHT; i++) {<BR>for (int j = 0; j ＜ Images.WIDTH; j++) {<BR>Draw.paint(g, this.Grid[i][j], j, i);//绘制地图<BR>}<BR>}<BR>return true;<BR>}catch (Exception ex) {<BR>return false;<BR>}<BR>}<BR>}</FONT></P></TD></TR></TBODY></TABLE></P>
<P><FONT size=2>　　对于像华容道这样的小型地图可以直接用手工来绘制地图的内容，比如：<BR><BR>fa1c<BR>2232<BR>bd1e<BR>2gg2<BR>gihg<BR><BR>　　但是，如果遇到像坦克大战或超级玛莉那样的地图，就必须另外开发一个地图编辑器了(我会在后续的文章中介绍用vb来开发一个地图编辑器)。</FONT></P>
<P><SPAN class=f14><FONT size=2>　　<B>五、详细设计</B><BR><BR>　　详细设计是程序开发过程中至关重要的一个环节，好在我们在前面的各个阶段中已经搭建好了项目所需的一些工具，现在这个阶段中我们只需集中精力设计好Displayable1中的逻辑。(两天的时间当然不只干这点活，还要把其他几个类的设计修改一下)<BR><BR>　　Displayable1这个类负责处理程序的控制逻辑。首先，它需要有表示当前关面的变量level、表示当前光标位置的变量loc、表示要移动区域的变量SelectArea、表示要移动到的区域的变量MoveArea、表示是否已有区域被选中而准备移动的变量Selected和Map类的实例MyMap。然后，我们根据用户按不同的键来处理不同的消息，我们要实现keyPressed()函数，在函数中我们处理按键的上下左右和选中(Fire)，这里的处理需要我展开来讲一讲，后面我很快会把这一部分详细展开。<BR><BR>　　接下来，是实现paint()函数，我们打算在这一部分中反复的重画背景、地图和选择区域，这个函数必须处理好区域被选中之后的画笔颜色的切换，具体讲就是在没有选中任何区域时要用黑色画笔，当选重要移动的区域时使用绿色画笔，当选择要移动到的区域时改用红色画笔(当然附加一张流程图是必不可少的)。<BR><BR>　　再下面要实现的setRange()函数和setMoveRange()函数，这两个函数用来设置要移动的区域和要移动到的区域，我的思路就是利用前面在Images类中介绍过的地图组合标记常量，当移动到地图组合标记常量时，根据该点地图中的值做逆向变换找到相应的地图标记常量，然后设置相应的loc、SelectArea和MoveArea,其中setMoveRange()函数还用到了一个辅助函数isInRange(),isInRange()函数是用来判断给定的点是否在已选中的要移动的区域之内,如果isInRange()的返回值是假并且该点处的值不是空白就表明要移动到的区域侵犯了其他以被占用的区域。有了setRange()和setMoveRange()函数，Move()函数就水到渠成了,Move()函数将要移动的区域移动到要移动到的区域,在移动过程中分为三步进行:<BR><BR>　　第一.复制要移动的区域；<BR><BR>　　第二.将复制出的要移动区域复制到要移动到的区域(这两步分开进行的目的是防止在复制过程中覆盖掉要移动的区域)；<BR><BR>　　第三.用isInRange2()判断给定的点是否在要移动到的区域内,将不在要移动到的区域内的点设置成空白。<BR><BR>　　下面我们详细的分析一下keyPressed()函数的实现方法:首先,keyPressed()函数要处理按键的上下左右和选中(Fire),在处理时需要用Canvas类的getGameAction函数来将按键的键值转换成游戏的方向,这样可以提高游戏的兼容性(因为不同的J2ME实现,其方向键的键值不一定是相同的)。<BR><BR>　　接下来,分别处理四个方向和选中.当按下向上时,先判断是否已经选定了要移动的区域(即this.selected是否为真),如果没有选中要移动区域则让光标向上移动一格,然后调用setRange()函数设置选择要移动的区域,再调用repaint()函数刷新屏幕,否则如果已经选中了要移动的区域,就让光标向上移动一格,然后调用setMoveRange()函数判断是否能够向上移动已选中的区域,如果能移动就调用repaint()函数刷新屏幕,如果不能移动就让光标向下退回到原来的位置。<BR><BR>　　当按下向下时,先判断是否已经选定了要移动的区域,如果没有选中要移动的区域则判断当前所处的区域是否为两个格高,如果是两个格高则向下移动两格,如果是一个格高则向下移动一格,接着再调用setRange()函数设置选择要移动的区域,而后调用repaint()函数刷新屏幕,否则如果已经选中了要移动的区域,就让光标向下移动一格,然后调用setMoveRange()函数判断是否能够向下移动已选中的区域,如果能移动就调用repaint()函数刷新屏幕,如果不能移动就让光标向上退回到原来的位置.按下向左时情况完全类似向上的情况,按下向右时情况完全类似向下的情况,因此这里不再赘述,详细情况请参见程序的源代码。<BR><BR>　　当按下选中键时,先判断是否已经选中了要移动的区域,如果已经选中了要移动的区域就调用Move()函数完成由要移动的区域到要移动到的区域的移动过程,接着调用repaint()函数刷新屏幕,然后将已选择标记置成false,继续调用win()函数判断是否完成了任务,否则如果还没有选定要移动的区域则再判断当前选中区域是否为空白,如果不是空白就将选中标记置成true,然后刷新屏幕.这里介绍一个技巧,在开发程序遇到复杂的逻辑的时候,可以构造一格打印函数来将所关心的数据结构打印出来以利调试,这里我们就构造一个PrintGrid()函数,这个函数纯粹是为了调试之用,效果这得不错.至此我们完成了编码前的全部工作。</FONT></SPAN></P>
<P><SPAN class=f14><SPAN class=f14><FONT size=2>　　<B>六.编码</B><BR><BR>　　整个项目共有五个类,有四个类的代码前面已经介绍过了,而且是在其他项目中使用过的相对成熟的代码.现在只需全力去实现Displayable1类.Displayable1类的代码如下:<BR></FONT></P>
<P><FONT size=2></FONT></P>
<P>
<TABLE width="100%" bgColor=#ffffff>
<TBODY>
<TR>
<TD>
<P><FONT color=#0000ff size=2>package huarongroad;</FONT></P>
<P><FONT color=#0000ff size=2>import javax.microedition.lcdui.*;</FONT></P>
<P><FONT color=#0000ff size=2>public class Displayable1 extends Canvas implements CommandListener {</FONT></P>
<P><FONT color=#0000ff size=2>private int[] loc = new int[2]; ＜A href="file://光"＞file://光＜/A＞标的当前位置，0是水平位置，1是竖直位置<BR>private int[] SelectArea = new int[4];//被选定的区域，即要移动的区域<BR>private int[] MoveArea = new int[4];//要移动到的区域<BR>private Map MyMap = new Map();//地图类<BR>private boolean selected;//是否已经选中要移动区域的标志<BR>private int level;//但前的关面<BR>public Displayable1() {//构造函数<BR>try {<BR>jbInit();//JBuilder定义的初始化函数<BR>}catch (Exception e) {<BR>e.printStackTrace();<BR>}<BR>}<BR>private void Init_game(){<BR>//初始化游戏，读取地图，设置选择区域，清空要移动到的区域<BR>this.loc = MyMap.read_map(this.level);//读取地图文件，并返回光标的初始位置<BR>//0为水平位置，1为竖直位置<BR>this.SelectArea[0] = this.loc[0];//初始化选中的区域<BR>this.SelectArea[1] = this.loc[1];<BR>this.SelectArea[2] = 1;<BR>this.SelectArea[3] = 1;<BR>this.MoveArea[0] = -1;//初始化要移动到的区域<BR>this.MoveArea[1] = -1;<BR>this.MoveArea[2] = 0;<BR>this.MoveArea[3] = 0;<BR>}<BR>private void jbInit() throws Exception {//JBuilder定义的初始化函数<BR>＜A href="file://初"＞file://初＜/A＞始化实例变量<BR>this.selected = false;//设置没有被选中的要移动区域<BR>this.level = 1;<BR>Images.init();//初始化图片常量<BR>Init_game();//初始化游戏，读取地图，设置选择区域，清空要移动到的区域<BR>setCommandListener(this);//添加命令监听，这是Displayable的实例方法<BR>addCommand(new Command("Exit", Command.EXIT, 1));//添加“退出”按钮<BR>}</FONT></P>
<P><FONT color=#0000ff size=2>public void commandAction(Command command, Displayable displayable) {<BR>//命令处理函数<BR>if (command.getCommandType() == Command.EXIT) {//处理“退出”<BR>MIDlet1.quitApp();<BR>}<BR>}</FONT></P>
<P><FONT color=#0000ff size=2>protected void paint(Graphics g) {<BR>//画图函数，用于绘制用户画面，即显示图片，勾画选中区域和要移动到的区域<BR>try {<BR>g.drawImage(Images.image_Frame, 0, 0,<BR>Graphics.TOP | Graphics.LEFT);//画背景<BR>MyMap.draw_map(g);//按照地图内容画图<BR>if ( this.selected )<BR>g.setColor(0,255,0);//如果被选中，改用绿色画出被选中的区域<BR>g.drawRect(this.SelectArea[0] * Images.UNIT + Images.LEFT,<BR>this.SelectArea[1] * Images.UNIT + Images.TOP,<BR>this.SelectArea[2] * Images.UNIT,<BR>this.SelectArea[3] * Images.UNIT);//画出选择区域，<BR>＜A href="file://如"＞file://如＜/A＞果被选中，就用绿色<BR>＜A href="file://否"＞file://否＜/A＞则，使用黑色<BR>g.setColor(255,255,255);//恢复画笔颜色<BR>if (this.selected) {//已经选中了要移动的区域<BR>g.setColor(255, 0, 255);//改用红色<BR>g.drawRect(this.MoveArea[0] * Images.UNIT + Images.LEFT,<BR>this.MoveArea[1] * Images.UNIT + Images.TOP,<BR>this.MoveArea[2] * Images.UNIT,<BR>this.MoveArea[3] * Images.UNIT);//画出要移动到的区域<BR>g.setColor(255, 255, 255);//恢复画笔颜色<BR>}<BR>}catch (Exception ex) {<BR>}<BR>System.out.println(Runtime.getRuntime().freeMemory());<BR>System.out.println(Runtime.getRuntime().totalMemory());<BR>}</FONT></P>
<P><FONT color=#0000ff size=2>private void setRange() {<BR>//设置移动后能够选中的区域<BR>//调整当前光标位置到地图的主位置，即记录人物信息的位置<BR>if (this.MyMap.Grid[this.loc[1]][this.loc[0]] == Images.DLEFT) {<BR>this.loc[0] -= 1;//向左调<BR>}else if (this.MyMap.Grid[this.loc[1]][this.loc[0]] == Images.DUP) {<BR>this.loc[1] -= 1;//向上调<BR>}else if (this.MyMap.Grid[this.loc[1]][this.loc[0]] == Images.DLEFTUP) {<BR>this.loc[0] -= 1;//向左调<BR>this.loc[1] -= 1;//向上调<BR>}<BR>this.SelectArea[0] = this.loc[0];//设置光标的水平位置<BR>this.SelectArea[1] = this.loc[1];//设置光标的竖直位置<BR>//设置光标的宽度<BR>if (this.loc[0] + 1 ＜ Images.WIDTH) {<BR>this.SelectArea[2] = this.MyMap.Grid[this.loc[1]][this.loc[0] + 1] != (byte) '1' ?<BR>1 : 2;<BR>}else {<BR>this.SelectArea[2] = 1;<BR>}<BR>//设置光标的高度<BR>if (this.loc[1] + 1 ＜ Images.HEIGHT) {<BR>this.SelectArea[3] = this.MyMap.Grid[this.loc[1] + 1][this.loc[0]] != (byte) '2' ?<BR>1 : 2;<BR>}else {<BR>this.SelectArea[3] = 1;<BR>}<BR>}</FONT></P>
<P><FONT color=#0000ff size=2>private boolean setMoveRange() {<BR>//设置要移动到的区域，能够移动返回true,否则返回false<BR>for (int i = 0; i ＜ this.SelectArea[2]; i++) {<BR>for (int j = 0; j ＜ this.SelectArea[3]; j++) {<BR>if (this.loc[1] + j ＞= Images.HEIGHT ||<BR>this.loc[0] + i ＞= Images.WIDTH ||<BR>(!isInRange(this.loc[0] + i, this.loc[1] + j) &amp;&amp;<BR>this.MyMap.Grid[this.loc[1] + j][this.loc[0] + i] !=<BR>Images.BLANK)) {<BR>return false;<BR>}<BR>}<BR>}<BR>this.MoveArea[0] = this.loc[0];<BR>this.MoveArea[1] = this.loc[1];<BR>this.MoveArea[2] = this.SelectArea[2];<BR>this.MoveArea[3] = this.SelectArea[3];<BR>return true;<BR>}</FONT></P>
<P><FONT color=#0000ff size=2>private boolean isInRange(int x, int y) {<BR>//判断给定的（x，y）点是否在选定区域之内，x是水平坐标，y是竖直坐标<BR>if (x ＞= this.SelectArea[0] &amp;&amp;<BR>x ＜ this.SelectArea[0] + this.SelectArea[2] &amp;&amp;<BR>y ＞= this.SelectArea[1] &amp;&amp;<BR>y ＜ this.SelectArea[1] + this.SelectArea[3]) {<BR>return true;<BR>}else {<BR>return false;<BR>}<BR>}</FONT></P>
<P><FONT color=#0000ff size=2>private boolean isInRange2(int x, int y) {<BR>//判断给定的（x，y）点是否在要移动到的区域之内，x是水平坐标，y是竖直坐标<BR>if (x ＞= this.MoveArea[0] &amp;&amp;<BR>x ＜ this.MoveArea[0] + this.MoveArea[2] &amp;&amp;<BR>y ＞= this.MoveArea[1] &amp;&amp;<BR>y ＜ this.MoveArea[1] + this.MoveArea[3]) {<BR>return true;<BR>}else {<BR>return false;<BR>}<BR>}</FONT></P>
<P><FONT color=#0000ff size=2>protected void keyPressed(int keyCode) {<BR>//处理按下键盘的事件，这是Canvas的实例方法<BR>switch (getGameAction(keyCode)) {//将按键的值转化成方向常量<BR>case Canvas.UP://向上<BR>if (!this.selected) {//还没有选定要移动的区域<BR>if (this.loc[1] - 1 ＞= 0) {//向上还有移动空间<BR>this.loc[1]--;//向上移动一下<BR>setRange();//设置光标移动的区域，该函数能将光标移动到地图主位置<BR>repaint();//重新绘图<BR>}<BR>}else {//已经选定了要移动的区域<BR>if (this.loc[1] - 1 ＞= 0) {//向上还有移动空间<BR>this.loc[1]--;//向上移动一下<BR>if (setMoveRange()) {//能够移动，该函数能够设置要移动到的区域<BR>repaint();//重新绘图<BR>}else {//不能移动<BR>this.loc[1]++;//退回来<BR>}<BR>}<BR>}<BR>break;<BR>case Canvas.DOWN://向下<BR>if (!this.selected) {//还没有选定要移动的区域<BR>if (this.loc[1] + 1 ＜ Images.HEIGHT) {//向下还有移动空间<BR>if (this.MyMap.Grid[this.loc[1] + 1][this.loc[0]] ==<BR>Images.DUP){//该图片有两个格高<BR>this.loc[1]++;//向下移动一下<BR>if (this.loc[1] + 1 ＜ Images.HEIGHT) {//向下还有<BR>＜A href="file://移"＞file://移＜/A＞动空间<BR>this.loc[1]++;//向下移动一下<BR>setRange();//设置光标移动的区域，<BR>＜A href="file://该"＞file://该＜/A＞函数能将光标移动到地图主位置<BR>repaint();//重新绘图<BR>}else {//向下没有移动空间<BR>this.loc[1]--;//退回来<BR>}<BR>}else {//该图片只有一个格高<BR>this.loc[1]++;//向下移动一下<BR>setRange();//设置光标移动的区域，<BR>＜A href="file://该"＞file://该＜/A＞函数能将光标移动到地图主位置<BR>repaint();//重新绘图<BR>}<BR>}else {<BR>}<BR>}else {//已经选定了要移动的区域<BR>if (this.loc[1] + 1 ＜ Images.HEIGHT) {//向下还有移动空间<BR>this.loc[1]++;//向下移动一下<BR>if (setMoveRange()) {//能够移动，该函数能够设置要移动到的区域<BR>repaint();//重新绘图<BR>}else {//不能移动<BR>this.loc[1]--;//退回来<BR>}<BR>}<BR>}<BR>break;<BR>case Canvas.LEFT://向左<BR>if (!this.selected) {//还没有选定要移动的区域<BR>if (this.loc[0] - 1 ＞= 0) {//向左还有移动空间<BR>this.loc[0]--;//向左移动一下<BR>setRange();//设置光标移动的区域，该函数能将光标移动到地图主位置<BR>repaint();//重新绘图<BR>}<BR>}else {//已经选定了要移动的区域<BR>if (this.loc[0] - 1 ＞= 0) {//向左还有移动空间<BR>this.loc[0]--;//向左移动一下<BR>if (setMoveRange()) {//能够移动，该函数能够设置要移动到的区域<BR>repaint();//重新绘图<BR>}else {//不能移动<BR>this.loc[0]++;//退回来<BR>}<BR>}<BR>}<BR>break;<BR>case Canvas.RIGHT://向右<BR>if (!this.selected) {//还没有选定要移动的区域<BR>if (this.loc[0] + 1 ＜ Images.WIDTH) {//向右还有移动空间<BR>if (this.MyMap.Grid[this.loc[1]][this.loc[0] + 1] ==<BR>Images.DLEFT) {//该图片有两个格宽<BR>this.loc[0]++;//向右移动一下<BR>if (this.loc[0] + 1 ＜ Images.WIDTH) {//向右还有<BR>＜A href="file://移"＞file://移＜/A＞动空间<BR>this.loc[0]++;//向右移动一下<BR>setRange();//设置光标移动的区域，<BR>＜A href="file://该"＞file://该＜/A＞函数能将光标移动到地图主位置<BR>repaint();//重新绘图<BR>}else {//向右没有移动空间<BR>this.loc[0]--;//退回来<BR>}<BR>}else {//该图片只有一个格宽<BR>this.loc[0]++;//向右移动一下<BR>setRange();//设置光标移动的区域，<BR>＜A href="file://该"＞file://该＜/A＞函数能将光标移动到地图主位置<BR>repaint();//重新绘图<BR>}<BR>}else {<BR>}<BR>}else {//已经选定了要移动的区域<BR>if (this.loc[0] + 1 ＜ Images.WIDTH) {//向右还有移动空间<BR>this.loc[0]++;//向右移动一下<BR>if (setMoveRange()) {//能够移动，该函数能够设置要移动到的区域<BR>repaint();//重新绘图<BR>}else {//不能移动<BR>this.loc[0]--;//退回来<BR>}<BR>}<BR>}<BR>break;<BR>case Canvas.FIRE:<BR>if (this.selected) {//已经选定了要移动的区域<BR>Move();//将要移动的区域移动到刚选中的区域<BR>repaint();//重新绘图<BR>this.selected = false;//清除已选定要移动区域的标志<BR>if ( win()) {<BR>System.out.println("win");<BR>}<BR>}else {//还没有选定要移动的区域<BR>if (this.MyMap.Grid[this.loc[1]][this.loc[0]] ==<BR>Images.BLANK) {//要移到的位置是一个空白<BR>}else {//要移到的位置不是空白<BR>this.selected = true;//设置已选定要移动区域的标志<BR>}<BR>repaint();//重新绘图<BR>}<BR>break;<BR>}<BR>}</FONT></P>
<P><FONT color=#0000ff size=2>private boolean win(){<BR>＜A href="file://判"＞file://判＜/A＞断是否已经救出了曹操<BR>if ( this.MyMap.Grid[Images.HEIGHT - 2 ][Images.WIDTH - 3 ] == Images.CAOCAO )<BR>return true;<BR>else<BR>return false;<BR>}</FONT></P>
<P><FONT color=#0000ff size=2>private void PrintGrid(String a) {<BR>＜A href="file://打"＞file://打＜/A＞印当前地图的内容，用于调试<BR>System.out.println(a);<BR>for (int i = 0; i ＜ Images.HEIGHT; i++) {<BR>for (int j = 0; j ＜ Images.WIDTH; j++) {<BR>System.out.print( (char)this.MyMap.Grid[i][j]);<BR>}<BR>System.out.println("");<BR>}<BR>}</FONT></P>
<P><FONT color=#0000ff size=2>private void Move() {<BR>＜A href="file://将"＞file://将＜/A＞要移动的区域移动到刚选中的区域<BR>if (this.MoveArea[0] == -1 || this.MoveArea[1] == -1 ||<BR>this.SelectArea[0] == -1 || this.SelectArea[1] == -1) {//没有选中区域<BR>}else {//已经选中了要移动的区域和要移动到的区域<BR>byte[][] temp = new byte[this.SelectArea[3]][this.SelectArea[2]];<BR>＜A href="file://复"＞file://复＜/A＞制要移动的区域，因为这块区域可能会被覆盖掉<BR>for (int i = 0; i ＜ this.SelectArea[2]; i++) {<BR>for (int j = 0; j ＜ this.SelectArea[3]; j++) {<BR>temp[j][i] =<BR>this.MyMap.Grid[this.SelectArea[1] +j]<BR>[this.SelectArea[0] + i];<BR>}<BR>}<BR>＜A href="file://PrintGrid"＞file://PrintGrid＜/A＞("1"); // 调试信息<BR>＜A href="file://将"＞file://将＜/A＞要移动的区域移动到刚选中的区域（即要移动到的区域）<BR>for (int i = 0; i ＜ this.SelectArea[2]; i++) {<BR>for (int j = 0; j ＜ this.SelectArea[3]; j++) {<BR>this.MyMap.Grid[this.MoveArea[1] + j]<BR>[this.MoveArea[0] + i] = temp[j][i];<BR>}<BR>}<BR>＜A href="file://PrintGrid"＞file://PrintGrid＜/A＞("2");// 调试信息<BR>＜A href="file://将"＞file://将＜/A＞要移动的区域中无用内容置成空白<BR>for (int i = 0; i ＜ this.SelectArea[3]; i++) {<BR>for (int j = 0; j ＜ this.SelectArea[2]; j++) {<BR>if (!isInRange2(this.SelectArea[0] + j,<BR>this.SelectArea[1] + i)) {//该点是不在要移动到<BR>＜A href="file://的"＞file://的＜/A＞区域之内，需置空<BR>this.MyMap.Grid[this.SelectArea[1] + i]<BR>[this.SelectArea[0] + j] = Images.BLANK;<BR>}else {<BR>}<BR>}<BR>}<BR>＜A href="file://PrintGrid"＞file://PrintGrid＜/A＞("3");// 调试信息<BR>this.SelectArea[0] = this.MoveArea[0];//重置选中位置的水平坐标<BR>this.SelectArea[1] = this.MoveArea[1];//重置选中位置的竖直坐标<BR>this.MoveArea[0] = -1;//清空要移动到的位置<BR>this.MoveArea[1] = -1;//清空要移动到的位置<BR>this.MoveArea[2] = 0;//清空要移动到的位置<BR>this.MoveArea[3] = 0;//清空要移动到的位置<BR>}<BR>}<BR>}</FONT></P></TD></TR></TBODY></TABLE></P>
<P><FONT size=2>　　代码的相关分析,在详细设计阶段已经讲过,代码中有比较相近的注释,请读者自行研读分析.将全部的代码写好,用wtk2.0自带的Ktoolbar工具建立一个工程,接下来把去不源文件放到正确位置下,然后点击build,再点run,就完成了程序的编写.当然如果有错误还要修改和调试.<BR><BR></FONT><SPAN class=f14><FONT size=2>　　</FONT><FONT size=2><B>七、测试<BR></B><BR>　　作为一个真正的产品要经过单体测试、结合测试和系统测试。由于项目本身简单,而且大部分代码已经是相对成熟的,我们跳过单体测试；又由于笔者的实际环境所限,无法搞到Java手机,无法架设OTA服务器,因此我们也只能放弃系统测试。那么就让我们开始结合测试吧。测试之前要先出一个测试式样书,也就是测试的计划。我们将它简化一下,只测试如下几种情况:第一、对各种形状的区域的选择和移动;第二、临近边界区域的选择和移动;第三、同一区域的反复选择和反复移动;第四、非法选择和非法移动。有了测试的目标,接下来的工作就是用wtk2.0自带的Run MIDP Application工具进行测试。打开这个工具,加载huarongRoad的jad文件,程序就会自动运行,选择launch上MIDlet1这个程序,华容道游戏就会跃然屏幕之上,接下来的工作就是左三点.右三点,拇指扭扭,来做测试。测试过程中发现任何的问题,立刻发一个bug票给自己,然后就又是痛苦的调试和修正bug,如此如此。<BR><BR>　　<B>八.发布</B><BR><BR>　　谈到发布,其实是个关键,再好的产品不能很好的发布出去也只是个产品而已,变不成商品也就得不到回报.由于笔者的条件所限,这里只能是纸上谈兵,不过还是希望能够使读者对这一过程有所了解(网上的资料也很多)。<BR><BR>　　J2ME的程序发布一般都是通过OTA(Over The Air),你只需要一台有公网IP的主机和一个普通的web Server就可以了(尽管要求很低,但笔者还是没有)，这里我们以apache为例介绍一下OTA服务的配置，首先是安装好了apache服务器，然后在conf目录下找到mime.types文件，在该文件中加入如下两行<BR><BR>application/java-archive jar <BR>text/vnd.sun.j2me.app-descriptor jad <BR><BR>　　然后重起apache服务器就可以了。接下来的工作就是修改jad文件中MIDlet-Jar-URL:后面的参数，将它改为URL的绝对路径，即＜A href="http://***/"＞http://***/＜/A＞huarongroad.jar(其中***是你的域名或IP地址)。在下面就是用java手机下载jad文件，它会自动部署相应的jar文件并加载它。剩下的工作就和在模拟器上操作是一样的了。<BR><BR>　　<B>九、项目总结</B> </FONT></P>
<P><FONT size=2></FONT></P>
<P><FONT size=2>　　至此，我们已经完成了一个J2ME游戏的全部开发过程，程序中涉及到了调研、分析、设计、编码、测试和发布等方面的问题，其实在实际的工作中还有很多更为具体的问题，毕竟技术只在软件开发过程中占据很有限的一部分，这里限于篇幅的限制无法一一具体展开。今后，笔者计划再写一篇使用J2ME开发手机屏保的文章，借此机会向读者展示J2ME动画技术；然后再写一篇J2ME网络应用的文章，做一个类似开心辞典那样的知识问答游戏，以便向读者展示J2ME的网络技术；待这两方面的技术交待清楚之后，我将引领读者制作一个稍大一些的游戏。</FONT></P></SPAN></SPAN></SPAN></SPAN></SPAN><img src ="http://www.blogjava.net/yangsail/aggbug/1263.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/yangsail/" target="_blank">蓝色雪焰</a> 2005-02-17 01:02 <a href="http://www.blogjava.net/yangsail/archive/2005/02/17/1263.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>韩国系MMORPG和EverQuest在怪物智能上的简单对比</title><link>http://www.blogjava.net/yangsail/archive/2005/02/16/1239.html</link><dc:creator>蓝色雪焰</dc:creator><author>蓝色雪焰</author><pubDate>Tue, 15 Feb 2005 16:00:00 GMT</pubDate><guid>http://www.blogjava.net/yangsail/archive/2005/02/16/1239.html</guid><wfw:comment>http://www.blogjava.net/yangsail/comments/1239.html</wfw:comment><comments>http://www.blogjava.net/yangsail/archive/2005/02/16/1239.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/yangsail/comments/commentRss/1239.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/yangsail/services/trackbacks/1239.html</trackback:ping><description><![CDATA[<H3 style="CURSOR: hand" onclick=switchDisplay(93)><FONT size=2>韩国系MMORPG和EverQuest在怪物智能上的简单对比</FONT></H3>
<DIV id=excerpt_93><FONT size=2></FONT></DIV>
<DIV id=content_93 style="DISPLAY: block">
<DIV>
<P><FONT size=2>现在，让我们假设诸位正在设计一款MMO ARPG中的怪物行动模式。</FONT></P>
<P><FONT size=2>在主流的韩国系MMO ARPG中，这些问题一般被如下处理：<BR>1. 敌人是否会主动攻击玩家人物？如何判断？<BR>分为主动攻击和被动攻击两种类型。前者当玩家人物进入自身周围一定范围之后激发攻击行为。后者当自身被攻击后激发攻击行为。</FONT></P>
<P><FONT size=2>2. 当怪物面对多个玩家的时候，如何选择自己的攻击对象？<BR>主流的处理方式是攻击第一个攻击自己的玩家。</FONT></P>
<P><FONT size=2>3. 战斗过程中，怪物能够改变自己的攻击目标吗？如何判断？<BR>基本上不改变。</FONT></P>
<P><FONT size=2>4. 当处于不利的情况下，怪物会作出什么行动？<BR>死战到底，有些游戏中有逃跑的设定。</FONT></P>
<P><FONT size=2>5. 怪物之间能否协作？<BR>一拥而上。</FONT></P>
<P><FONT size=2>在欧美经典MMORPG EverQuest中，是这样处理的：<BR>1. 敌人是否会主动攻击玩家人物？如何判断？<BR>通过一个关系表来决定，如果玩家和自身阵营关系恶劣，那么主动攻击。否则是被动攻击。（这个关系表是动态的，会根据玩家的行为而改变）</FONT></P>
<P><FONT size=2>2. 当怪物面对多个玩家的时候，如何选择自己的攻击对象？<BR>攻击首先进入自己视野或者攻击自己的玩家。</FONT></P>
<P><FONT size=2>3. 战斗过程中，怪物能够改变自己的攻击目标吗？如何判断？<BR>战斗中，怪物有一个隐藏的仇恨列表。怪物会攻击处于自己仇恨列表顶端的玩家，而玩家会增加仇恨的动作包括攻击、挑衅、医疗同伴、施放魔法……等等。换言之，如果一个法师疯狂的施放攻击法术，那么一定会被怪物首先料理。同样的，如果牧师频繁的医疗同伴，也会被怪物特殊照顾。（正确的战斗方式是让肉盾类角色保证自己永远处于仇恨列表最顶端，当然这很困难，但是却很有必要，也很有趣）</FONT></P>
<P><FONT size=2>4. 当处于不利的情况下，怪物会作出什么行动？<BR>逃跑，而且速度相当快。鉴于怪物之间能够呼唤，所以让怪物成功跑掉往往比较可怕。有一些法师系的敌人会用施放传送门的方式逃跑。</FONT></P>
<P><FONT size=2>5. 怪物之间能否协作？<BR>怪物之间的仇恨可以传递。如果两个怪物距离很近，那么很可能一个发现到了你而两个都冲过来。法师系的怪物也会给同伴加血和一些防御性法术。</FONT></P>
<P><BR><FONT size=2>简单的说，EQ在“人工智能”的范畴上并没有走多远，但是在游戏上却做的很出色。特别是“仇恨列表”，并不复杂的规则衍生出了很多的变化，堪称天才的设计。当你体会到因为这个限制而衍生出的精彩刺激的游戏过程，便能理解到玩家想要的其实并不仅仅是经验值而已。</FONT></P>
<P><FONT size=2>试试看将“仇恨列表”导入你的游戏，你觉得会发生什么？ </FONT></P></DIV></DIV><img src ="http://www.blogjava.net/yangsail/aggbug/1239.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/yangsail/" target="_blank">蓝色雪焰</a> 2005-02-16 00:00 <a href="http://www.blogjava.net/yangsail/archive/2005/02/16/1239.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>j2me游戏引擎程序结构</title><link>http://www.blogjava.net/yangsail/archive/2005/02/13/1115.html</link><dc:creator>蓝色雪焰</dc:creator><author>蓝色雪焰</author><pubDate>Sun, 13 Feb 2005 03:47:00 GMT</pubDate><guid>http://www.blogjava.net/yangsail/archive/2005/02/13/1115.html</guid><wfw:comment>http://www.blogjava.net/yangsail/comments/1115.html</wfw:comment><comments>http://www.blogjava.net/yangsail/archive/2005/02/13/1115.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/yangsail/comments/commentRss/1115.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/yangsail/services/trackbacks/1115.html</trackback:ping><description><![CDATA[<SPAN id=lblContent><FONT size=2>&nbsp;&nbsp;&nbsp; </FONT><FONT size=2><SPAN lang=EN-US><SPAN style="mso-tab-count: 1">&nbsp;&nbsp;&nbsp; </SPAN></SPAN><SPAN style="FONT-FAMILY: 宋体; mso-ascii-font-family: Times New Roman; mso-hansi-font-family: Times New Roman">游戏引擎的结构很多，不过基本上都是在一个游戏主循环内实现。程序里面的主循环包含了程序框架的最主要的结构体。</SPAN><SPAN lang=EN-US>J2me</SPAN><SPAN style="FONT-FAMILY: 宋体; mso-ascii-font-family: Times New Roman; mso-hansi-font-family: Times New Roman">的程序一般都包含两个</SPAN><SPAN lang=EN-US>class</SPAN><SPAN style="FONT-FAMILY: 宋体; mso-ascii-font-family: Times New Roman; mso-hansi-font-family: Times New Roman">文件，一个是</SPAN><SPAN lang=EN-US>MIDlet,</SPAN><SPAN style="FONT-FAMILY: 宋体; mso-ascii-font-family: Times New Roman; mso-hansi-font-family: Times New Roman">一个是</SPAN><SPAN lang=EN-US>Displayable</SPAN><SPAN style="FONT-FAMILY: 宋体; mso-ascii-font-family: Times New Roman; mso-hansi-font-family: Times New Roman">。一般我都是把游戏的主要代码放在</SPAN><SPAN lang=EN-US>Displayable</SPAN><SPAN style="FONT-FAMILY: 宋体; mso-ascii-font-family: Times New Roman; mso-hansi-font-family: Times New Roman">这个类里面。这个类是基于事件驱动的程序，有三个主要相应函数</SPAN><SPAN lang=EN-US>void paint(Graphics g),void keyPressed(int keyCode),void keyReleased(int keyCode)</SPAN><SPAN style="FONT-FAMILY: 宋体; mso-ascii-font-family: Times New Roman; mso-hansi-font-family: Times New Roman">。</SPAN></FONT>
<P class=MsoNormal style="MARGIN: 0cm 0cm 0pt"><SPAN lang=EN-US><FONT size=2>&nbsp;<?xml:namespace prefix = o ns = "urn:schemas-microsoft-com:office:office" /><o:p></o:p></FONT></SPAN></P>
<P class=MsoNormal style="MARGIN: 0cm 0cm 0pt 21pt; TEXT-INDENT: -21pt; mso-list: l0 level1 lfo1; tab-stops: list 21.0pt"><FONT size=2><B><SPAN lang=EN-US>1.<SPAN style="FONT: 7pt Times New Roman">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </SPAN></SPAN></B><B><SPAN style="FONT-FAMILY: 宋体; mso-ascii-font-family: Times New Roman; mso-hansi-font-family: Times New Roman">使用</SPAN><SPAN lang=EN-US>Runnable</SPAN></B><B><SPAN style="FONT-FAMILY: 宋体; mso-ascii-font-family: Times New Roman; mso-hansi-font-family: Times New Roman">和创建线程的主循环</SPAN><SPAN lang=EN-US><o:p></o:p></SPAN></B></FONT></P>
<P class=MsoNormal style="MARGIN: 0cm 0cm 0pt"><FONT size=2><SPAN style="FONT-FAMILY: 宋体; mso-ascii-font-family: Times New Roman; mso-hansi-font-family: Times New Roman">一般主体的做法就是让</SPAN><SPAN lang=EN-US>Displayable</SPAN><SPAN style="FONT-FAMILY: 宋体; mso-ascii-font-family: Times New Roman; mso-hansi-font-family: Times New Roman">这个类实现</SPAN><SPAN lang=EN-US>Runnable</SPAN><SPAN style="FONT-FAMILY: 宋体; mso-ascii-font-family: Times New Roman; mso-hansi-font-family: Times New Roman">这个接口，然后在其构造函数中创建一个线程，启动其</SPAN><SPAN lang=EN-US>run()</SPAN><SPAN style="FONT-FAMILY: 宋体; mso-ascii-font-family: Times New Roman; mso-hansi-font-family: Times New Roman">函数</SPAN><SPAN lang=EN-US>,</SPAN><SPAN style="FONT-FAMILY: 宋体; mso-ascii-font-family: Times New Roman; mso-hansi-font-family: Times New Roman">而</SPAN><SPAN lang=EN-US>run</SPAN><SPAN style="FONT-FAMILY: 宋体; mso-ascii-font-family: Times New Roman; mso-hansi-font-family: Times New Roman">函数里面就包含了游戏的主循环。下面是我在仙剑里面的片断代码。</SPAN></FONT></P>
<P class=MsoNormal style="MARGIN: 0cm 0cm 0pt"><SPAN lang=EN-US><FONT size=2>&nbsp;<o:p></o:p></FONT></SPAN></P>
<P class=MsoNormal style="MARGIN: 0cm 0cm 0pt"><SPAN lang=EN-US><FONT size=2>public class GameMIDlet extends MIDlet {</FONT></SPAN></P>
<P class=MsoNormal style="MARGIN: 0cm 0cm 0pt"><SPAN lang=EN-US><FONT size=2><SPAN style="mso-spacerun: yes">&nbsp; </SPAN>static GameMIDlet instance;</FONT></SPAN></P>
<P class=MsoNormal style="MARGIN: 0cm 0cm 0pt"><SPAN lang=EN-US><FONT size=2><SPAN style="mso-spacerun: yes">&nbsp; </SPAN>Display display;</FONT></SPAN></P>
<P class=MsoNormal style="MARGIN: 0cm 0cm 0pt"><SPAN lang=EN-US><FONT size=2><SPAN style="mso-spacerun: yes">&nbsp; </SPAN>GameDisplayable displayable = null;</FONT></SPAN></P>
<P class=MsoNormal style="MARGIN: 0cm 0cm 0pt"><SPAN lang=EN-US><FONT size=2>&nbsp;<o:p></o:p></FONT></SPAN></P>
<P class=MsoNormal style="MARGIN: 0cm 0cm 0pt"><SPAN lang=EN-US><FONT size=2><SPAN style="mso-spacerun: yes">&nbsp; </SPAN>public GameMIDlet() {</FONT></SPAN></P>
<P class=MsoNormal style="MARGIN: 0cm 0cm 0pt"><SPAN lang=EN-US><FONT size=2><SPAN style="mso-spacerun: yes">&nbsp;&nbsp;&nbsp; </SPAN>instance = this;</FONT></SPAN></P>
<P class=MsoNormal style="MARGIN: 0cm 0cm 0pt"><SPAN lang=EN-US><FONT size=2><SPAN style="mso-spacerun: yes">&nbsp;&nbsp;&nbsp; </SPAN>display =<SPAN style="mso-spacerun: yes">&nbsp; </SPAN>Display.getDisplay(this);</FONT></SPAN></P>
<P class=MsoNormal style="MARGIN: 0cm 0cm 0pt"><SPAN lang=EN-US><FONT size=2><SPAN style="mso-spacerun: yes">&nbsp;&nbsp;&nbsp; </SPAN>displayable = new GameDisplayable();</FONT></SPAN></P>
<P class=MsoNormal style="MARGIN: 0cm 0cm 0pt"><SPAN lang=EN-US><FONT size=2><SPAN style="mso-spacerun: yes">&nbsp; </SPAN>}</FONT></SPAN></P>
<P class=MsoNormal style="MARGIN: 0cm 0cm 0pt"><SPAN lang=EN-US><FONT size=2>&nbsp;<o:p></o:p></FONT></SPAN></P>
<P class=MsoNormal style="MARGIN: 0cm 0cm 0pt"><SPAN lang=EN-US><FONT size=2><SPAN style="mso-spacerun: yes">&nbsp; </SPAN>public void startApp() {</FONT></SPAN></P>
<P class=MsoNormal style="MARGIN: 0cm 0cm 0pt"><SPAN lang=EN-US><FONT size=2><SPAN style="mso-spacerun: yes">&nbsp;&nbsp;&nbsp; </SPAN>display.setCurrent(displayable);</FONT></SPAN></P>
<P class=MsoNormal style="MARGIN: 0cm 0cm 0pt"><SPAN lang=EN-US><FONT size=2><SPAN style="mso-spacerun: yes">&nbsp; </SPAN>}</FONT></SPAN></P>
<P class=MsoNormal style="MARGIN: 0cm 0cm 0pt"><SPAN lang=EN-US><FONT size=2>&nbsp;<o:p></o:p></FONT></SPAN></P>
<P class=MsoNormal style="MARGIN: 0cm 0cm 0pt"><SPAN lang=EN-US><FONT size=2><SPAN style="mso-spacerun: yes">&nbsp; </SPAN>public void pauseApp() {</FONT></SPAN></P>
<P class=MsoNormal style="MARGIN: 0cm 0cm 0pt"><SPAN lang=EN-US><FONT size=2><SPAN style="mso-spacerun: yes">&nbsp; </SPAN>}</FONT></SPAN></P>
<P class=MsoNormal style="MARGIN: 0cm 0cm 0pt"><SPAN lang=EN-US><FONT size=2>&nbsp;<o:p></o:p></FONT></SPAN></P>
<P class=MsoNormal style="MARGIN: 0cm 0cm 0pt"><SPAN lang=EN-US><FONT size=2><SPAN style="mso-spacerun: yes">&nbsp; </SPAN>public void destroyApp(boolean unconditional) {</FONT></SPAN></P>
<P class=MsoNormal style="MARGIN: 0cm 0cm 0pt"><SPAN lang=EN-US><FONT size=2><SPAN style="mso-spacerun: yes">&nbsp;&nbsp;&nbsp; </SPAN>displayable.running = false;</FONT></SPAN></P>
<P class=MsoNormal style="MARGIN: 0cm 0cm 0pt"><SPAN lang=EN-US><FONT size=2><SPAN style="mso-spacerun: yes">&nbsp; </SPAN>}</FONT></SPAN></P>
<P class=MsoNormal style="MARGIN: 0cm 0cm 0pt"><SPAN lang=EN-US><FONT size=2>&nbsp;<o:p></o:p></FONT></SPAN></P>
<P class=MsoNormal style="MARGIN: 0cm 0cm 0pt"><SPAN lang=EN-US><FONT size=2><SPAN style="mso-spacerun: yes">&nbsp; </SPAN>public static void quitApp() {</FONT></SPAN></P>
<P class=MsoNormal style="MARGIN: 0cm 0cm 0pt"><SPAN lang=EN-US><FONT size=2><SPAN style="mso-spacerun: yes">&nbsp;&nbsp;&nbsp; </SPAN>instance.destroyApp(true);</FONT></SPAN></P>
<P class=MsoNormal style="MARGIN: 0cm 0cm 0pt"><SPAN lang=EN-US><FONT size=2><SPAN style="mso-spacerun: yes">&nbsp;&nbsp;&nbsp; </SPAN>instance.notifyDestroyed();</FONT></SPAN></P>
<P class=MsoNormal style="MARGIN: 0cm 0cm 0pt"><SPAN lang=EN-US><FONT size=2><SPAN style="mso-spacerun: yes">&nbsp;&nbsp;&nbsp; </SPAN>instance = null;</FONT></SPAN></P>
<P class=MsoNormal style="MARGIN: 0cm 0cm 0pt"><SPAN lang=EN-US><FONT size=2><SPAN style="mso-spacerun: yes">&nbsp; </SPAN>}</FONT></SPAN></P>
<P class=MsoNormal style="MARGIN: 0cm 0cm 0pt"><SPAN lang=EN-US><FONT size=2>}</FONT></SPAN></P>
<P class=MsoNormal style="MARGIN: 0cm 0cm 0pt"><SPAN lang=EN-US><FONT size=2>&nbsp;<o:p></o:p></FONT></SPAN></P>
<P class=MsoNormal style="MARGIN: 0cm 0cm 0pt"><SPAN lang=EN-US><FONT size=2>public class GameDisplayable extends FullCanvas implements Runnable {</FONT></SPAN></P>
<P class=MsoNormal style="MARGIN: 0cm 0cm 0pt"><FONT size=2><SPAN lang=EN-US><SPAN style="mso-spacerun: yes">&nbsp; </SPAN>/** </SPAN><SPAN style="FONT-FAMILY: 宋体; mso-ascii-font-family: Times New Roman; mso-hansi-font-family: Times New Roman">主控制线程</SPAN><SPAN lang=EN-US> */</SPAN></FONT></P>
<P class=MsoNormal style="MARGIN: 0cm 0cm 0pt"><SPAN lang=EN-US><FONT size=2><SPAN style="mso-spacerun: yes">&nbsp; </SPAN>Thread MainThread = null;</FONT></SPAN></P>
<P class=MsoNormal style="MARGIN: 0cm 0cm 0pt"><FONT size=2><SPAN lang=EN-US><SPAN style="mso-spacerun: yes">&nbsp; </SPAN>/** </SPAN><SPAN style="FONT-FAMILY: 宋体; mso-ascii-font-family: Times New Roman; mso-hansi-font-family: Times New Roman">游戏时钟间隔</SPAN> <SPAN style="FONT-FAMILY: 宋体; mso-ascii-font-family: Times New Roman; mso-hansi-font-family: Times New Roman">毫秒为单位</SPAN><SPAN lang=EN-US> */</SPAN></FONT></P>
<P class=MsoNormal style="MARGIN: 0cm 0cm 0pt"><SPAN lang=EN-US><FONT size=2><SPAN style="mso-spacerun: yes">&nbsp; </SPAN>public static long timeinterval = 20;</FONT></SPAN></P>
<P class=MsoNormal style="MARGIN: 0cm 0cm 0pt"><SPAN lang=EN-US><FONT size=2><SPAN style="mso-spacerun: yes">&nbsp; </SPAN>public static boolean Isstable = true;</FONT></SPAN></P>
<P class=MsoNormal style="MARGIN: 0cm 0cm 0pt"><SPAN lang=EN-US><FONT size=2>&nbsp;<o:p></o:p></FONT></SPAN></P>
<P class=MsoNormal style="MARGIN: 0cm 0cm 0pt"><FONT size=2><SPAN lang=EN-US><SPAN style="mso-spacerun: yes">&nbsp; </SPAN>/* </SPAN><SPAN style="FONT-FAMILY: 宋体; mso-ascii-font-family: Times New Roman; mso-hansi-font-family: Times New Roman">用于游戏时钟的变量</SPAN><SPAN lang=EN-US> */</SPAN></FONT></P>
<P class=MsoNormal style="MARGIN: 0cm 0cm 0pt"><SPAN lang=EN-US><FONT size=2><SPAN style="mso-spacerun: yes">&nbsp; </SPAN>public static long timeold = 0;</FONT></SPAN></P>
<P class=MsoNormal style="MARGIN: 0cm 0cm 0pt"><SPAN lang=EN-US><FONT size=2><SPAN style="mso-spacerun: yes">&nbsp; </SPAN>public static long timenow = 0;</FONT></SPAN></P>
<P class=MsoNormal style="MARGIN: 0cm 0cm 0pt"><SPAN lang=EN-US><FONT size=2><SPAN style="mso-spacerun: yes">&nbsp; </SPAN>public long interval = 0;</FONT></SPAN></P>
<P class=MsoNormal style="MARGIN: 0cm 0cm 0pt"><SPAN lang=EN-US><FONT size=2><SPAN style="mso-spacerun: yes">&nbsp; </SPAN>public static long frames_per_second = 0;</FONT></SPAN></P>
<P class=MsoNormal style="MARGIN: 0cm 0cm 0pt"><SPAN lang=EN-US><FONT size=2><SPAN style="mso-spacerun: yes">&nbsp; </SPAN>int count = 0;</FONT></SPAN></P>
<P class=MsoNormal style="MARGIN: 0cm 0cm 0pt"><SPAN lang=EN-US><FONT size=2><SPAN style="mso-spacerun: yes">&nbsp; </SPAN>long second = 0;</FONT></SPAN></P>
<P class=MsoNormal style="MARGIN: 0cm 0cm 0pt"><SPAN lang=EN-US><FONT size=2><SPAN style="mso-spacerun: yes">&nbsp; </SPAN>public static boolean running = true;</FONT></SPAN></P>
<P class=MsoNormal style="MARGIN: 0cm 0cm 0pt; TEXT-INDENT: 10.5pt; mso-char-indent-count: 1.0; mso-char-indent-size: 10.5pt"><SPAN lang=EN-US><FONT size=2>public GameDisplayable() {</FONT></SPAN></P>
<P class=MsoNormal style="MARGIN: 0cm 0cm 0pt"><FONT size=2><SPAN lang=EN-US><SPAN style="mso-spacerun: yes">&nbsp; </SPAN><SPAN style="mso-spacerun: yes">&nbsp;&nbsp;&nbsp;&nbsp;</SPAN>// </SPAN><SPAN style="FONT-FAMILY: 宋体; mso-ascii-font-family: Times New Roman; mso-hansi-font-family: Times New Roman">开始主线程</SPAN></FONT></P>
<P class=MsoNormal style="MARGIN: 0cm 0cm 0pt"><SPAN lang=EN-US><FONT size=2><SPAN style="mso-spacerun: yes">&nbsp;&nbsp;&nbsp; </SPAN>Thread MainThread = new Thread(this);</FONT></SPAN></P>
<P class=MsoNormal style="MARGIN: 0cm 0cm 0pt"><SPAN lang=EN-US><FONT size=2><SPAN style="mso-spacerun: yes">&nbsp;&nbsp;&nbsp; </SPAN>MainThread.start();</FONT></SPAN></P>
<P class=MsoNormal style="MARGIN: 0cm 0cm 0pt"><SPAN lang=EN-US><FONT size=2><SPAN style="mso-spacerun: yes">&nbsp; </SPAN>}</FONT></SPAN></P>
<P class=MsoNormal style="MARGIN: 0cm 0cm 0pt; TEXT-INDENT: 10.5pt; mso-char-indent-count: 1.0; mso-char-indent-size: 10.5pt"><SPAN lang=EN-US><FONT size=2>public void run() {</FONT></SPAN></P>
<P class=MsoNormal style="MARGIN: 0cm 0cm 0pt"><SPAN lang=EN-US><FONT size=2><SPAN style="mso-spacerun: yes">&nbsp;&nbsp; </SPAN><SPAN style="mso-spacerun: yes">&nbsp;</SPAN>while (running) {</FONT></SPAN></P>
<P class=MsoNormal style="MARGIN: 0cm 0cm 0pt"><SPAN lang=EN-US><FONT size=2><SPAN style="mso-spacerun: yes">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </SPAN>timenow = System.currentTimeMillis();</FONT></SPAN></P>
<P class=MsoNormal style="MARGIN: 0cm 0cm 0pt"><SPAN lang=EN-US><FONT size=2><SPAN style="mso-spacerun: yes">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </SPAN>interval = timenow - timeold;</FONT></SPAN></P>
<P class=MsoNormal style="MARGIN: 0cm 0cm 0pt"><SPAN lang=EN-US><FONT size=2>&nbsp;<o:p></o:p></FONT></SPAN></P>
<P class=MsoNormal style="MARGIN: 0cm 0cm 0pt"><SPAN lang=EN-US><FONT size=2><SPAN style="mso-spacerun: yes">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </SPAN>if (interval &gt;= timeinterval) {</FONT></SPAN></P>
<P class=MsoNormal style="MARGIN: 0cm 0cm 0pt"><SPAN lang=EN-US><FONT size=2><SPAN style="mso-spacerun: yes">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </SPAN>timeold = timenow;</FONT></SPAN></P>
<P class=MsoNormal style="MARGIN: 0cm 0cm 0pt"><SPAN lang=EN-US><FONT size=2><SPAN style="mso-spacerun: yes">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </SPAN><SPAN style="mso-spacerun: yes">&nbsp;</SPAN>Game_Process();</FONT></SPAN></P>
<P class=MsoNormal style="MARGIN: 0cm 0cm 0pt"><SPAN lang=EN-US><FONT size=2><SPAN style="mso-spacerun: yes">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </SPAN>if (second != (System.currentTimeMillis() / 1000)) {</FONT></SPAN></P>
<P class=MsoNormal style="MARGIN: 0cm 0cm 0pt"><SPAN lang=EN-US><FONT size=2><SPAN style="mso-spacerun: yes">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </SPAN><SPAN style="mso-spacerun: yes">&nbsp;&nbsp;&nbsp;&nbsp;</SPAN>second = System.currentTimeMillis() / 1000;</FONT></SPAN></P>
<P class=MsoNormal style="MARGIN: 0cm 0cm 0pt"><SPAN lang=EN-US><FONT size=2><SPAN style="mso-spacerun: yes">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </SPAN>frames_per_second = count;</FONT></SPAN></P>
<P class=MsoNormal style="MARGIN: 0cm 0cm 0pt"><SPAN lang=EN-US><FONT size=2><SPAN style="mso-spacerun: yes">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </SPAN>count = 1;</FONT></SPAN></P>
<P class=MsoNormal style="MARGIN: 0cm 0cm 0pt"><SPAN lang=EN-US><FONT size=2><SPAN style="mso-spacerun: yes">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </SPAN>}</FONT></SPAN></P>
<P class=MsoNormal style="MARGIN: 0cm 0cm 0pt"><SPAN lang=EN-US><FONT size=2><SPAN style="mso-spacerun: yes">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </SPAN>else</FONT></SPAN></P>
<P class=MsoNormal style="MARGIN: 0cm 0cm 0pt"><SPAN lang=EN-US><FONT size=2><SPAN style="mso-spacerun: yes">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </SPAN>count++;</FONT></SPAN></P>
<P class=MsoNormal style="MARGIN: 0cm 0cm 0pt"><SPAN lang=EN-US><FONT size=2><SPAN style="mso-spacerun: yes">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </SPAN>}</FONT></SPAN></P>
<P class=MsoNormal style="MARGIN: 0cm 0cm 0pt"><SPAN lang=EN-US><FONT size=2><SPAN style="mso-spacerun: yes">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </SPAN>lib.sleep(30);</FONT></SPAN></P>
<P class=MsoNormal style="MARGIN: 0cm 0cm 0pt"><SPAN lang=EN-US><FONT size=2><SPAN style="mso-spacerun: yes">&nbsp;&nbsp;&nbsp; </SPAN>}</FONT></SPAN></P>
<P class=MsoNormal style="MARGIN: 0cm 0cm 0pt"><SPAN lang=EN-US><FONT size=2>&nbsp;<o:p></o:p></FONT></SPAN></P>
<P class=MsoNormal style="MARGIN: 0cm 0cm 0pt"><SPAN lang=EN-US><FONT size=2><SPAN style="mso-spacerun: yes">&nbsp;</SPAN>}</FONT></SPAN></P>
<P class=MsoNormal style="MARGIN: 0cm 0cm 0pt"><SPAN lang=EN-US><FONT size=2>&nbsp;<o:p></o:p></FONT></SPAN></P>
<P class=MsoNormal style="MARGIN: 0cm 0cm 0pt"><FONT size=2><SPAN style="COLOR: red; FONT-FAMILY: 宋体; mso-ascii-font-family: Times New Roman; mso-hansi-font-family: Times New Roman">其中关于控制主循环速度的代码可以不要，但是</SPAN><SPAN lang=EN-US style="COLOR: red">lib.sleep(30)</SPAN><SPAN style="COLOR: red; FONT-FAMILY: 宋体; mso-ascii-font-family: Times New Roman; mso-hansi-font-family: Times New Roman">必须保留，因为在</SPAN><SPAN lang=EN-US style="COLOR: red">Nokia 60</SPAN><SPAN style="COLOR: red; FONT-FAMILY: 宋体; mso-ascii-font-family: Times New Roman; mso-hansi-font-family: Times New Roman">的手机上，如果去除了</SPAN><SPAN lang=EN-US style="COLOR: red">sleep(30),</SPAN><SPAN style="COLOR: red; FONT-FAMILY: 宋体; mso-ascii-font-family: Times New Roman; mso-hansi-font-family: Times New Roman">那么游戏将无法切换回来。同时，在游戏中任何一个内部循环中，也必须加入</SPAN><SPAN lang=EN-US style="COLOR: red">sleep(30)</SPAN><SPAN style="COLOR: red; FONT-FAMILY: 宋体; mso-ascii-font-family: Times New Roman; mso-hansi-font-family: Times New Roman">这个等待，才能让游戏可以切换回来，至于为什么这样做，我暂时还不清楚。</SPAN><SPAN lang=EN-US style="COLOR: red">30ms</SPAN><SPAN style="COLOR: red; FONT-FAMILY: 宋体; mso-ascii-font-family: Times New Roman; mso-hansi-font-family: Times New Roman">是我测试过没有问题的数值，可能比</SPAN><SPAN lang=EN-US style="COLOR: red">30ms</SPAN><SPAN style="COLOR: red; FONT-FAMILY: 宋体; mso-ascii-font-family: Times New Roman; mso-hansi-font-family: Times New Roman">还小的值也是没有问题的。</SPAN><SPAN lang=EN-US style="COLOR: red"><o:p></o:p></SPAN></FONT></P>
<P class=MsoNormal style="MARGIN: 0cm 0cm 0pt"><SPAN lang=EN-US style="COLOR: red"><FONT size=2>&nbsp;<o:p></o:p></FONT></SPAN></P>
<P class=MsoNormal style="MARGIN: 0cm 0cm 0pt"><FONT size=2><SPAN style="COLOR: red; FONT-FAMILY: 宋体; mso-ascii-font-family: Times New Roman; mso-hansi-font-family: Times New Roman">同时，在</SPAN><SPAN lang=EN-US style="COLOR: red">MOTO</SPAN><SPAN style="COLOR: red; FONT-FAMILY: 宋体; mso-ascii-font-family: Times New Roman; mso-hansi-font-family: Times New Roman">的手机上，必须将游戏的主循环放在一个线程中，游戏才能切换回来，不过可以不加上面说的</SPAN><SPAN lang=EN-US style="COLOR: red">sleep(30)</SPAN><SPAN style="COLOR: red; FONT-FAMILY: 宋体; mso-ascii-font-family: Times New Roman; mso-hansi-font-family: Times New Roman">延时。</SPAN><SPAN lang=EN-US style="COLOR: red"><o:p></o:p></SPAN></FONT></P>
<P class=MsoNormal style="MARGIN: 0cm 0cm 0pt"><SPAN lang=EN-US><FONT size=2>&nbsp;<o:p></o:p></FONT></SPAN></P>
<P class=MsoNormal style="MARGIN: 0cm 0cm 0pt 21pt; TEXT-INDENT: -21pt; mso-list: l0 level1 lfo1; tab-stops: list 21.0pt"><FONT size=2><B><SPAN lang=EN-US>2.<SPAN style="FONT: 7pt Times New Roman">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </SPAN></SPAN></B><B><SPAN style="FONT-FAMILY: 宋体; mso-ascii-font-family: Times New Roman; mso-hansi-font-family: Times New Roman">不使用线程的主循环办法</SPAN><SPAN lang=EN-US><o:p></o:p></SPAN></B></FONT></P>
<P class=MsoNormal style="MARGIN: 0cm 0cm 0pt"><FONT size=2><SPAN style="FONT-FAMILY: 宋体; mso-ascii-font-family: Times New Roman; mso-hansi-font-family: Times New Roman">这个办法只能在</SPAN><SPAN lang=EN-US>Nokia</SPAN><SPAN style="FONT-FAMILY: 宋体; mso-ascii-font-family: Times New Roman; mso-hansi-font-family: Times New Roman">的平台上实现，而我只建议在</SPAN><SPAN lang=EN-US>Nokia 40</SPAN><SPAN style="FONT-FAMILY: 宋体; mso-ascii-font-family: Times New Roman; mso-hansi-font-family: Times New Roman">的平台上做，这样不需要线程，道理上来说节约了一些内存，如果不是内存很紧张的机型，那么最好还是使用上一种办法。</SPAN></FONT></P>
<P class=MsoNormal style="MARGIN: 0cm 0cm 0pt"><SPAN lang=EN-US><FONT size=2>&nbsp;<o:p></o:p></FONT></SPAN></P>
<P class=MsoNormal style="MARGIN: 0cm 0cm 0pt"><FONT size=2><SPAN style="FONT-FAMILY: 宋体; mso-ascii-font-family: Times New Roman; mso-hansi-font-family: Times New Roman">游戏的主循环放在</SPAN><SPAN lang=EN-US>MIDlet</SPAN><SPAN style="FONT-FAMILY: 宋体; mso-ascii-font-family: Times New Roman; mso-hansi-font-family: Times New Roman">的</SPAN><SPAN lang=EN-US>class</SPAN><SPAN style="FONT-FAMILY: 宋体; mso-ascii-font-family: Times New Roman; mso-hansi-font-family: Times New Roman">里面，具体做法如下：</SPAN></FONT></P>
<P class=MsoNormal style="MARGIN: 0cm 0cm 0pt"><SPAN lang=EN-US><FONT size=2>public class GameMIDlet extends MIDlet {</FONT></SPAN></P>
<P class=MsoNormal style="MARGIN: 0cm 0cm 0pt"><SPAN lang=EN-US><FONT size=2><SPAN style="mso-spacerun: yes">&nbsp; </SPAN>GameDisplayable displayable = null;</FONT></SPAN></P>
<P class=MsoNormal style="MARGIN: 0cm 0cm 0pt"><SPAN lang=EN-US><FONT size=2>&nbsp;<o:p></o:p></FONT></SPAN></P>
<P class=MsoNormal style="MARGIN: 0cm 0cm 0pt"><FONT size=2><SPAN lang=EN-US><SPAN style="mso-spacerun: yes">&nbsp; </SPAN>/** </SPAN><SPAN style="FONT-FAMILY: 宋体; mso-ascii-font-family: Times New Roman; mso-hansi-font-family: Times New Roman">游戏时钟间隔</SPAN> <SPAN style="FONT-FAMILY: 宋体; mso-ascii-font-family: Times New Roman; mso-hansi-font-family: Times New Roman">毫秒为单位</SPAN><SPAN lang=EN-US> */</SPAN></FONT></P>
<P class=MsoNormal style="MARGIN: 0cm 0cm 0pt"><SPAN lang=EN-US><FONT size=2><SPAN style="mso-spacerun: yes">&nbsp; </SPAN>public static long<SPAN style="mso-spacerun: yes">&nbsp; </SPAN>timeinterval = 0;</FONT></SPAN></P>
<P class=MsoNormal style="MARGIN: 0cm 0cm 0pt"><FONT size=2><SPAN lang=EN-US><SPAN style="mso-spacerun: yes">&nbsp; </SPAN>//</SPAN><SPAN style="FONT-FAMILY: 宋体; mso-ascii-font-family: Times New Roman; mso-hansi-font-family: Times New Roman">用于游戏时钟的变量</SPAN></FONT></P>
<P class=MsoNormal style="MARGIN: 0cm 0cm 0pt"><SPAN lang=EN-US><FONT size=2><SPAN style="mso-spacerun: yes">&nbsp; </SPAN>public static long timeold = 0;</FONT></SPAN></P>
<P class=MsoNormal style="MARGIN: 0cm 0cm 0pt"><SPAN lang=EN-US><FONT size=2><SPAN style="mso-spacerun: yes">&nbsp; </SPAN>public static long timenow = 0;</FONT></SPAN></P>
<P class=MsoNormal style="MARGIN: 0cm 0cm 0pt"><SPAN lang=EN-US><FONT size=2><SPAN style="mso-spacerun: yes">&nbsp; </SPAN>public long interval = 0;</FONT></SPAN></P>
<P class=MsoNormal style="MARGIN: 0cm 0cm 0pt"><SPAN lang=EN-US><FONT size=2><SPAN style="mso-spacerun: yes">&nbsp; </SPAN>public static long frames_per_second=0;</FONT></SPAN></P>
<P class=MsoNormal style="MARGIN: 0cm 0cm 0pt"><SPAN lang=EN-US><FONT size=2><SPAN style="mso-spacerun: yes">&nbsp; </SPAN>int count=0;</FONT></SPAN></P>
<P class=MsoNormal style="MARGIN: 0cm 0cm 0pt"><SPAN lang=EN-US><FONT size=2><SPAN style="mso-spacerun: yes">&nbsp; </SPAN>long second =0;</FONT></SPAN></P>
<P class=MsoNormal style="MARGIN: 0cm 0cm 0pt"><SPAN lang=EN-US><FONT size=2><SPAN style="mso-spacerun: yes">&nbsp; </SPAN>public static boolean running = false;</FONT></SPAN></P>
<P class=MsoNormal style="MARGIN: 0cm 0cm 0pt"><SPAN lang=EN-US><FONT size=2><SPAN style="mso-spacerun: yes">&nbsp; </SPAN>static boolean exitApp =false;</FONT></SPAN></P>
<P class=MsoNormal style="MARGIN: 0cm 0cm 0pt"><SPAN lang=EN-US><FONT size=2>&nbsp;<o:p></o:p></FONT></SPAN></P>
<P class=MsoNormal style="MARGIN: 0cm 0cm 0pt"><SPAN lang=EN-US><FONT size=2><SPAN style="mso-spacerun: yes">&nbsp; </SPAN>public GameMIDlet() {</FONT></SPAN></P>
<P class=MsoNormal style="MARGIN: 0cm 0cm 0pt"><SPAN lang=EN-US><FONT size=2><SPAN style="mso-spacerun: yes">&nbsp;&nbsp;&nbsp; </SPAN>displayable = new GameDisplayable();</FONT></SPAN></P>
<P class=MsoNormal style="MARGIN: 0cm 0cm 0pt"><SPAN lang=EN-US><FONT size=2><SPAN style="mso-spacerun: yes">&nbsp;&nbsp;&nbsp; </SPAN>running =true;</FONT></SPAN></P>
<P class=MsoNormal style="MARGIN: 0cm 0cm 0pt"><SPAN lang=EN-US><FONT size=2><SPAN style="mso-spacerun: yes">&nbsp; </SPAN>}</FONT></SPAN></P>
<P class=MsoNormal style="MARGIN: 0cm 0cm 0pt"><SPAN lang=EN-US><FONT size=2>&nbsp;<o:p></o:p></FONT></SPAN></P>
<P class=MsoNormal style="MARGIN: 0cm 0cm 0pt"><SPAN lang=EN-US><FONT size=2><SPAN style="mso-spacerun: yes">&nbsp; </SPAN>public void startApp() {</FONT></SPAN></P>
<P class=MsoNormal style="MARGIN: 0cm 0cm 0pt"><SPAN lang=EN-US><FONT size=2><SPAN style="mso-spacerun: yes">&nbsp;&nbsp;&nbsp; </SPAN>running =true;</FONT></SPAN></P>
<P class=MsoNormal style="MARGIN: 0cm 0cm 0pt"><SPAN lang=EN-US><FONT size=2><SPAN style="mso-spacerun: yes">&nbsp;&nbsp;&nbsp; </SPAN>Display.getDisplay(this).setCurrent(displayable);</FONT></SPAN></P>
<P class=MsoNormal style="MARGIN: 0cm 0cm 0pt"><SPAN lang=EN-US><FONT size=2><SPAN style="mso-spacerun: yes">&nbsp;&nbsp;&nbsp; </SPAN>while(running) {</FONT></SPAN></P>
<P class=MsoNormal style="MARGIN: 0cm 0cm 0pt"><SPAN lang=EN-US><FONT size=2><SPAN style="mso-spacerun: yes">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </SPAN>timenow = System.currentTimeMillis();</FONT></SPAN></P>
<P class=MsoNormal style="MARGIN: 0cm 0cm 0pt"><SPAN lang=EN-US><FONT size=2><SPAN style="mso-spacerun: yes">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </SPAN>interval = timenow - timeold;</FONT></SPAN></P>
<P class=MsoNormal style="MARGIN: 0cm 0cm 0pt"><SPAN lang=EN-US><FONT size=2><SPAN style="mso-spacerun: yes">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </SPAN>if (interval &gt;= timeinterval) {</FONT></SPAN></P>
<P class=MsoNormal style="MARGIN: 0cm 0cm 0pt"><SPAN lang=EN-US><FONT size=2><SPAN style="mso-spacerun: yes">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </SPAN>timeold = timenow;</FONT></SPAN></P>
<P class=MsoNormal style="MARGIN: 0cm 0cm 0pt"><SPAN lang=EN-US><FONT size=2><SPAN style="mso-spacerun: yes">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </SPAN>displayable.Game_Process();</FONT></SPAN></P>
<P class=MsoNormal style="MARGIN: 0cm 0cm 0pt"><SPAN lang=EN-US><FONT size=2><SPAN style="mso-spacerun: yes">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </SPAN>if(second != (System.currentTimeMillis() /1000)){</FONT></SPAN></P>
<P class=MsoNormal style="MARGIN: 0cm 0cm 0pt"><SPAN lang=EN-US><FONT size=2><SPAN style="mso-spacerun: yes">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </SPAN>second = System.currentTimeMillis()/1000;</FONT></SPAN></P>
<P class=MsoNormal style="MARGIN: 0cm 0cm 0pt"><SPAN lang=EN-US><FONT size=2><SPAN style="mso-spacerun: yes">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </SPAN>frames_per_second = count;</FONT></SPAN></P>
<P class=MsoNormal style="MARGIN: 0cm 0cm 0pt"><SPAN lang=EN-US><FONT size=2><SPAN style="mso-spacerun: yes">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </SPAN>count = 1;</FONT></SPAN></P>
<P class=MsoNormal style="MARGIN: 0cm 0cm 0pt"><SPAN lang=EN-US><FONT size=2><SPAN style="mso-spacerun: yes">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </SPAN>}else</FONT></SPAN></P>
<P class=MsoNormal style="MARGIN: 0cm 0cm 0pt"><SPAN lang=EN-US><FONT size=2><SPAN style="mso-spacerun: yes">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </SPAN>count ++;</FONT></SPAN></P>
<P class=MsoNormal style="MARGIN: 0cm 0cm 0pt"><SPAN lang=EN-US><FONT size=2><SPAN style="mso-spacerun: yes">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </SPAN>}</FONT></SPAN></P>
<P class=MsoNormal style="MARGIN: 0cm 0cm 0pt"><SPAN lang=EN-US><FONT size=2><SPAN style="mso-spacerun: yes">&nbsp;&nbsp;&nbsp; </SPAN>}</FONT></SPAN></P>
<P class=MsoNormal style="MARGIN: 0cm 0cm 0pt"><SPAN lang=EN-US><FONT size=2><SPAN style="mso-spacerun: yes">&nbsp;&nbsp;&nbsp; </SPAN>if(exitApp) {</FONT></SPAN></P>
<P class=MsoNormal style="MARGIN: 0cm 0cm 0pt"><SPAN lang=EN-US><FONT size=2><SPAN style="mso-spacerun: yes">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </SPAN>destroyApp(true);</FONT></SPAN></P>
<P class=MsoNormal style="MARGIN: 0cm 0cm 0pt"><SPAN lang=EN-US><FONT size=2><SPAN style="mso-spacerun: yes">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </SPAN>notifyDestroyed();</FONT></SPAN></P>
<P class=MsoNormal style="MARGIN: 0cm 0cm 0pt"><SPAN lang=EN-US><FONT size=2><SPAN style="mso-spacerun: yes">&nbsp;&nbsp;&nbsp; </SPAN>}</FONT></SPAN></P>
<P class=MsoNormal style="MARGIN: 0cm 0cm 0pt"><SPAN lang=EN-US><FONT size=2>&nbsp;<o:p></o:p></FONT></SPAN></P>
<P class=MsoNormal style="MARGIN: 0cm 0cm 0pt"><SPAN lang=EN-US><FONT size=2><SPAN style="mso-spacerun: yes">&nbsp; </SPAN>}</FONT></SPAN></P>
<P class=MsoNormal style="MARGIN: 0cm 0cm 0pt"><SPAN lang=EN-US><FONT size=2>&nbsp;<o:p></o:p></FONT></SPAN></P>
<P class=MsoNormal style="MARGIN: 0cm 0cm 0pt"><SPAN lang=EN-US><FONT size=2><SPAN style="mso-spacerun: yes">&nbsp; </SPAN>public void pauseApp() {</FONT></SPAN></P>
<P class=MsoNormal style="MARGIN: 0cm 0cm 0pt"><SPAN lang=EN-US><FONT size=2><SPAN style="mso-spacerun: yes">&nbsp;&nbsp;&nbsp; </SPAN>running =false;</FONT></SPAN></P>
<P class=MsoNormal style="MARGIN: 0cm 0cm 0pt"><SPAN lang=EN-US><FONT size=2><SPAN style="mso-spacerun: yes">&nbsp; </SPAN>}</FONT></SPAN></P>
<P class=MsoNormal style="MARGIN: 0cm 0cm 0pt"><SPAN lang=EN-US><FONT size=2>&nbsp;<o:p></o:p></FONT></SPAN></P>
<P class=MsoNormal style="MARGIN: 0cm 0cm 0pt"><SPAN lang=EN-US><FONT size=2><SPAN style="mso-spacerun: yes">&nbsp; </SPAN>public void destroyApp(boolean unconditional) {</FONT></SPAN></P>
<P class=MsoNormal style="MARGIN: 0cm 0cm 0pt"><SPAN lang=EN-US><FONT size=2><SPAN style="mso-spacerun: yes">&nbsp;&nbsp;&nbsp; </SPAN>running = false;</FONT></SPAN></P>
<P class=MsoNormal style="MARGIN: 0cm 0cm 0pt"><SPAN lang=EN-US><FONT size=2><SPAN style="mso-spacerun: yes">&nbsp; </SPAN>}</FONT></SPAN></P>
<P class=MsoNormal style="MARGIN: 0cm 0cm 0pt"><SPAN lang=EN-US><FONT size=2>&nbsp;<o:p></o:p></FONT></SPAN></P>
<P class=MsoNormal style="MARGIN: 0cm 0cm 0pt"><SPAN lang=EN-US><FONT size=2><SPAN style="mso-spacerun: yes">&nbsp; </SPAN>public static void quitApp() {</FONT></SPAN></P>
<P class=MsoNormal style="MARGIN: 0cm 0cm 0pt"><SPAN lang=EN-US><FONT size=2><SPAN style="mso-spacerun: yes">&nbsp;&nbsp;&nbsp; </SPAN>running =false;</FONT></SPAN></P>
<P class=MsoNormal style="MARGIN: 0cm 0cm 0pt"><SPAN lang=EN-US><FONT size=2><SPAN style="mso-spacerun: yes">&nbsp;&nbsp;&nbsp; </SPAN>exitApp =true;</FONT></SPAN></P>
<P class=MsoNormal style="MARGIN: 0cm 0cm 0pt"><SPAN lang=EN-US><FONT size=2><SPAN style="mso-spacerun: yes">&nbsp; </SPAN>}</FONT></SPAN></P>
<P class=MsoNormal style="MARGIN: 0cm 0cm 0pt"><SPAN lang=EN-US><FONT size=2>&nbsp;<o:p></o:p></FONT></SPAN></P>
<P class=MsoNormal style="MARGIN: 0cm 0cm 0pt"><SPAN lang=EN-US><FONT size=2>}</FONT></SPAN></P></SPAN><img src ="http://www.blogjava.net/yangsail/aggbug/1115.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/yangsail/" target="_blank">蓝色雪焰</a> 2005-02-13 11:47 <a href="http://www.blogjava.net/yangsail/archive/2005/02/13/1115.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>角色扮演游戏引擎的设计原理</title><link>http://www.blogjava.net/yangsail/archive/2005/02/10/1067.html</link><dc:creator>蓝色雪焰</dc:creator><author>蓝色雪焰</author><pubDate>Thu, 10 Feb 2005 13:43:00 GMT</pubDate><guid>http://www.blogjava.net/yangsail/archive/2005/02/10/1067.html</guid><wfw:comment>http://www.blogjava.net/yangsail/comments/1067.html</wfw:comment><comments>http://www.blogjava.net/yangsail/archive/2005/02/10/1067.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/yangsail/comments/commentRss/1067.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/yangsail/services/trackbacks/1067.html</trackback:ping><description><![CDATA[<P><FONT size=2>角色扮演游戏引擎的设计原理</FONT></P>
<P><FONT size=2>　　角色扮演游戏(RPG)是深受广大游戏迷们喜爱的一种游戏,&nbsp;它以独特的互动性和故事性吸引了无数的玩家。它向人们提供了超出现实生活的广阔的虚拟世界，使人们能够尝试扮演不同的角色，去经历和体验各种不同的人生旅程或奇幻经历。这些体验都是在现实生活中无法实现的。在玩过许多游戏后，许多玩家都不再仅仅满足于一个游戏玩家的身份，而会思考游戏是如何制作的，并且打算制作一个自己的游戏，网上的各种游戏制作小组更是如雨后春笋般涌现。下面我就给大家介绍一下角色扮演游戏引擎的原理与制作，希望能对游戏制作爱好者有所帮助。 </FONT></P>
<P></P>
<P><FONT size=2>一&nbsp;游戏引擎的原理</FONT></P>
<P><FONT size=2>　　说到引擎，游戏迷们都很熟悉。游戏引擎是一个为运行某一类游戏的机器设计的能够被机器识别的代码（指令）集合。它象一个发动机，控制着游戏的运行。一个游戏作品可以分为游戏引擎和游戏资源两大部分。游戏资源包括图象，声音，动画等部分，列一个公式就是：游戏=引擎（程序代码）+资源（图象，声音，动画等）。游戏引擎则是按游戏设计的要求顺序的调用这些资源。</FONT></P>
<P><FONT size=2>二&nbsp;角色扮演游戏的制作</FONT></P>
<P><FONT size=2>　　一个完整的角色扮演游戏的制作从大的分工来说可以分为：策划，程序设计，美工，音乐制作以及项目管理，后期的测试等。<BR>　　策划主要任务是设计游戏的剧情，类型以及模式等，并分析游戏的复杂性有多大，内容有多少，策划的进度要多快等因素。<BR>　　程序设计的任务是用某种编程语言来完成游戏的设计，并与策划配合，达到预期的目的。<BR>　　美工主要是根据游戏的时代背景与主题设计游戏的场景及各种角色的图象。<BR>　　音乐制作是根据游戏的剧情和背景制作游戏的音乐与音效。<BR>　　项目管理主要是控制游戏制作的进程，充分利用现有的资源（人员，资金，设备等），以达到用尽量少的资金实现最大的收益。<BR>　　后期的测试也是非常重要的一个环节，对于一个几十人花费几个月甚至是几年时间制作的游戏，测试往往能找到许多问题，只有改进程序才能确保游戏的安全发行。<BR>　　由于文章主要是讲解游戏程序的制作的，所以策划，美工，音乐制作等方面请读者参考其它文章，下面我就讲讲游戏程序的设计。</FONT></P>
<P><FONT size=2>(一)&nbsp;开发工具与主要技术</FONT></P>
<P><FONT size=2>1．件开发工具</FONT></P>
<P><FONT size=2>　　游戏程序开发工具有很多，在不同游戏平台上有不同的开发工具。在个人计算机上，可以用目前流性的软件开发工具，比如：C，C++，VC++，Delphi，C++&nbsp;Builder等。由于Windows操作系统的普及和其强大的多媒体功能，越来越多的游戏支持Windows操作系统。由于VC是微软的产品，用它来编写Windows程序有强大的程序接口和丰富的开发资源的支持，加之VC严谨的内存管理，在堆栈上良好的分配处理，生成代码的体积小，稳定性高的优点，所以VC++就成为目前游戏的主流开发工具。</FONT></P>
<P><FONT size=2>2．DirectX组件的知识</FONT></P>
<P><FONT size=2>　　谈到Windows系统下的游戏开发，我们就要说一下微软的DirectX&nbsp;SDK。<BR>　　Windows系统有一个主要优点是应用程序和设备之间的独立性。然而应用程序的设备无关性是通过牺牲部分速度和效率的到的，Windows在硬件和软件间添加了中间抽象层，通过这些中间层我们的应用程序才能在不同的硬件上游刃有余。但是，我们因此而不能完全利用硬件的特征来获取最大限度的运算和显示速度。这一点在编写Windows游戏时是致命的，DirectX便是为解决这个问题而设计的。DirectX由快速的底层库组成并且没有给游戏设计添加过多的约束。微软的DirectX软件开发工具包（SDK）提供了一套优秀的应用程序编程接口（APIs），这个编程接口可以提供给你开发高质量、实时的应用程序所需要的各种资源。<BR>　　DirectX的6个组件分别是：<BR>　　　　DirectDraw：&nbsp;使用页面切换的方法实现动画，它不仅可以访问系统内存，还可以访问显示内存。<BR>　　　　Direct3D：&nbsp;提供了3D硬件接口。<BR>　　　　DirectSound：&nbsp;立体声和3D声音效果，同时管理声卡的内存。<BR>　　　　DirectPlay：&nbsp;支持开发多人网络游戏，并能处理游戏中网络之间的通信问题。<BR>　　　　DirectInput：&nbsp;为大量的设备提供输入支持。<BR>　　　　DirectSetup：&nbsp;自动安装DirectX驱动程序。<BR>　　随着DirectX版本的提高，还增加了音乐播放的DirectMusic。</FONT></P>
<P><FONT size=2>3．AlphaBlend&nbsp;技术</FONT></P>
<P><FONT size=2>　　现在许多游戏为了达到光影或图象的透明效果都会采用AlphaBlend&nbsp;技术。所谓AlphaBlend技术，其实就是按照"Alpha"混合向量的值来混合源像素和目标像素，一般用来处理半透明效果。在计算机中的图象可以用R(红色)，G(绿色)，B(蓝色)三原色来表示。假设一幅图象是A，另一幅透明的图象是B，那么透过B去看A，看上去的图象C就是B和A的混合图象，设B图象的透明度为alpha(取值为0-1，0为完全透明，1为完全不透明)，Alpha混合公式如下：<BR>　　　　R(C)=alpha*R(B)+(1-alpha)*R(A)<BR>　　　　G(C)=alpha*G(B)+(1-alpha)*G(A)<BR>　　　　B(C)=alpha*B(B)+(1-alpha)*B(A)<BR>　　R(x)、G(x)、B(x)分别指颜色x的RGB分量原色值。从上面的公式可以知道，Alpha其实是一个决定混合透明度的数值。应用Alpha混合技术，可以实现游戏中的许多特效，比如火光、烟雾、阴影、动态光源等半透明效果。</FONT></P>
<P><FONT size=2>4．A*算法</FONT></P>
<P><FONT size=2>　　在许多游戏中要用鼠标控制人物运动，而且让人物从目前的位置走到目标位置应该走最短的路径。这就要用到最短路径搜索算法即A*算法了。<BR>　　A*算法实际是一种启发式搜索，所谓启发式搜索，就是利用一个估价函数评估每次的的决策的价值，决定先尝试哪一种方案。如果一个估价函数可以找出最短的路径，我们称之为可采纳性。A*算法是一个可采纳的最好优先算法。A*算法的估价函数可表示为：<BR>　　　　f(n)&nbsp;=&nbsp;g(n)&nbsp;+&nbsp;h(n)<BR>　　这里，f(n)是节点n的估价函数，g(n)是起点到终点的最短路径值，h(n)是n到目标的最断路经的启发值。由于A*算法比较复杂，限于篇幅，在此简单介绍一下，具体理论朋友们可以看人工智能方面的书籍了解详细的情况。</FONT></P>
<P><FONT size=2>　　其它技术还有粒子系统，音频与视频的调用，图象文件的格式与信息存储等，大家可以在学好DirectX的基础上逐渐学习更多的技术。</FONT></P>
<P><FONT size=2>（二）游戏的具体制作</FONT></P>
<P><FONT size=2>1．地图编辑器的制作</FONT></P>
<P><FONT size=2>　　RPG游戏往往要有大量的场景，场景中根据需要可以有草地，湖泊，树木，房屋，家具等道俱，由于一个游戏需要很多场景且地图越来越大，为了节省空间，提高图象文件的可重用性，RPG游戏的画面采用很多重复的单元（可以叫做“图块”）所构成的，这就要用到地图编辑器了。我们在制作游戏引擎前，要完成地图编辑器的制作。在&nbsp;RPG游戏里，场景的构成，是图块排列顺序的记录。首先制定一个场景构成文件的格式，在这个文件里记录构成场景所需要的图块的排列顺序，因为我们已经为每个图块建立了索引，所以只需要记录这些索引就可以了。一个场景的构成，是分成几层来完成的：地面，建筑和植物，家具摆设，和在场景中活动的人物或者物体（比如飘扬的旗帜），按照一定的顺序把它们依次显示到屏幕上，就形成了一个丰富多采的场景。我们可以用数组来表示地图场景的生成过程。</FONT></P>
<P><FONT size=2>MapData[X][Y]；&nbsp;//地图数据，X表示地图宽度，Y表示地图高度<BR>Picture[num]；&nbsp;//道具的图片，num表示道具的总数<BR>void&nbsp;MakeBackGround()&nbsp;//生成场景函数<BR>{<BR>　int&nbsp;n;<BR>　for(&nbsp;int&nbsp;i=0;&nbsp;i&lt;Y;&nbsp;i++)&nbsp;//共Y行<BR>　for(&nbsp;int&nbsp;j=0;&nbsp;j&lt;X;&nbsp;j++)&nbsp;//共X列<BR>　{<BR>　　n=MapData[&nbsp;i&nbsp;][&nbsp;j&nbsp;];&nbsp;//取得该位置的道具编号<BR>　　Draw(&nbsp;j*32,&nbsp;i*32,&nbsp;Picture[n]);&nbsp;//在此位置(j*32,i*32)画道具<BR>　}<BR>}</FONT></P>
<P><FONT size=2>2．游戏的模块的划分</FONT></P>
<P><FONT size=2>　　游戏按功能分为：消息处理系统、场景显示及行走系统、打斗系统三大主要部分。其中又以消息处理系统为核心模块，其余部分紧紧围绕它运行。</FONT></P>
<P><FONT size=2>一：消息处理系统</FONT></P>
<P><FONT size=2>　　消息处理系统是游戏的核心部分。游戏用到的消息处理系统先等待消息，然后根据收到的消息转到相应的函数进行处理。比如：主角碰到敌人后，我们就让程序产生‘打斗消息’，消息处理系统收到这个消息后就会马上转到打斗模块中去。消息处理的大体框架如下：</FONT></P>
<P><FONT size=2>//定义程序中要用到的变量<BR>DWORD&nbsp;Message;&nbsp;//消息变量<BR>WinMain()&nbsp;//进入程序<BR>{<BR>　初始化主窗口;<BR>　初始化DirectDraw环境，并调入程序需要的图形、地图数据；<BR>　while(&nbsp;1&nbsp;)&nbsp;//消息循环<BR>　{<BR>　　switch(&nbsp;Message&nbsp;)<BR>　　{<BR>　　　case&nbsp;行走消息:&nbsp;行走模块();<BR>　　　case&nbsp;打斗消息：&nbsp;打斗模块();<BR>　　　case&nbsp;事件消息：&nbsp;事件模块();<BR>　　}<BR>　}<BR>}</FONT></P>
<P><FONT size=2>二：场景显示及行走系统</FONT></P>
<P><FONT size=2>　　作为RPG游戏，其所有事件的发生几乎都是和场景有关，例如：不同的地方会碰到不同的敌人、与不同的人对话得知不同的事情等。鉴于这部分的重要性，我们可再将它划分为：背景显示、行走&nbsp;和&nbsp;事件发生&nbsp;三个子模块，分别处理各自的功能。下面进行具体分析。</FONT></P>
<P><FONT size=2>（一）背景显示<BR>　　程序运行后，先读取前面地图编辑器制作的场景所需要的图块的排列顺序，按照排列顺序将图象拼成一个完整的场景，一般做法是：在内存中开辟一到两个屏幕缓存区，事先把即将显示的图象数据准备在缓存区内，然后一次性搬家：把它们传送到真正的屏幕缓冲区内。<BR>　　游戏用到的图片则事先制作好并存于另外的图形文件中。地图编辑器制作的场景文件仅仅是对应的数据，而不是真正的图片。在游戏中生成场景就是地图编辑的逆过程，一个是根据场景生成数据，而另一个是根据数据生成场景。</FONT></P>
<P><FONT size=2>（二）行走<BR>　　要让主角在场景中行走，至少要有上、下、左、右四个行走方向，每个方向4幅图（站立、迈左腿、迈右腿、迈左腿），如图：游戏中一定要将图片的背景设为透明，这样在画人物的时候就不会覆盖上背景色了（这一技术DirectDraw中只要用SetColorKey（）函数将原图片背景色过滤掉就行了）。我们让主角位置不动，而使场景移动，即采用滚屏技术来实现角色在场景上移动。这样角色一直保持在屏幕的正中间，需要做的工作只是根据行走方向和步伐不停变换图片而已。行走时的障碍物判断也是每一个场景中必定要有的，有一些道具如树木、房屋等是不可跨越的。对此我主要用一个二维数组来对应一个场景，每一个数组值代表场景的一小格(见图3)。有障碍的地方，该数组的对应值为1，可通过的地方的值为0。</FONT></P>
<P><FONT size=2>（三）事件发生<BR>　　事件发生原理就是把相应事件的序号存储在地图的某些格子中，当主角一踏入这个格子就会触发对应事件。例如：在游戏开始时，主角是在他的家里。他要是想出去的话，就需要执行场景切换这个处理函数。我们假定该事件的编号为001，那么在地图上把家门外路口处的格子值设为001。这样主角走到路口时，编号为001的场景切换函数就会被触发，于是主角便到了下一个场景中。程序具体如下：</FONT></P>
<P><FONT size=2>void&nbsp;MessageLoop(&nbsp;int&nbsp;Msg&nbsp;)&nbsp;//消息循环<BR>{<BR>　switch(&nbsp;Msg&nbsp;)<BR>　{<BR>　　char&nbsp;AddressName[16];&nbsp;//数组AddressName[16]用来存储主角所在地点的名称<BR>　　case&nbsp;ADDRESS&nbsp;==&nbsp;001:&nbsp;//&nbsp;由ADDRESS的值决定场景值（出门）<BR>　　ScreenX=12;&nbsp;ScreenY=0;&nbsp;//初始化游戏背景位置<BR>　　Hero.x=360;&nbsp;Hero.y=80;//主角坐标<BR>　　Move();//主角移动函数<BR>　　//以下程序用来显示主角所在地点<BR>　　sprintf(AddressName,"下一幅游戏场景的名称");<BR>　　PrintText(lpDDSPrimary,&nbsp;280,&nbsp;330,AddressName&nbsp;,&nbsp;RGB(255,255,255));//在屏幕上显示出场景的名称<BR>　　break;<BR>　}<BR>}</FONT></P>
<P><FONT size=2>三：打斗系统</FONT></P>
<P><FONT size=2>　　绝大多数的RPG都是有战斗存在的，因此，打斗系统就成为RPG系统中很重要的一环。有不少RPG游戏采用回合制打斗方式，因为实现起来较为简单。和打斗紧密相关的是升级，通常在一场战斗结束后，主角的经验值都会增加。而当经验值到达一定程度时，角色就升级了。<BR>　　上面我简要的介绍了角色扮演游戏的制作，由于写这篇文章的目的是让读者对角色扮演游戏的制作有一个基本的了解，所以读者朋友们可以研究相关资料。</FONT></P><img src ="http://www.blogjava.net/yangsail/aggbug/1067.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/yangsail/" target="_blank">蓝色雪焰</a> 2005-02-10 21:43 <a href="http://www.blogjava.net/yangsail/archive/2005/02/10/1067.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item></channel></rss>