2005年08月25日

  了解选择理论的人们都知道哈丁的“公地灾难”、不知何人首先发明的“囚犯难题”以及奥尔森的“集体行动的逻辑”。这些理论模型都说明,个人的理性行动最终导致的却是集体的非理性的结果。许多分析家均认为,除非彻底私有化,或者通过强权的控制,人类几乎难以摆脱这些悲剧性的梦魇。但是,事实又表明,人类社会中虽然到处都是公共的悲剧,但许多人却自主地摆脱了公共选择的悲剧,从而改善了福利。美国著名行政学家、政治经济学家埃莉诺•奥斯特罗姆《公共事物的治理之道:集体行动制度的演进》一书,着眼于小规模公共池塘资源问题,在大量的实证案例研究的基础上,开发了自主组织和治理公共事物的制度理论,从而在企业理论和国家理论的基础上进一步发展了集体行动的理论,同时也为面临公共选择悲剧的人们开辟了新的路径,为避免公共事物的退化、保护公共事物、可持续地利用公共事物从而增进人类的福利提供了自主治理的制度基础。该书的研究宗旨是有限的,就如作者所总结的,“本项研究的中心问题是一群相互依赖的委托人如何才能把自己组织起来,进行自主治理,从而能够在所有人都面对搭便车、规避责任或其他机会主义行为诱惑的情况下,取得持久的共同收益。必须同时解决的问题是如何对变量加以组合,以便⑴增加自主组织的初始可能性,⑵增强人们不断进行自主组织的能力,或⑶增强在没有某种外部协助的情况下通过自主组织解决公共池塘资源问题的能力。”并且由于作者是美国学者,其所处的学术背景是美国的学术传统,所探讨的理论问题主要是美国学术界所探讨的热门话题,所提出的理论发展也是对美国现有学术研究的发展,这一切都有别于我们中国。但是,这并不意味着我们不能从中国政治学、行政学尤其是公共政策研究发展的需要出发,用中国人的眼光,着眼于中国人所面临的公共选择问题,来阅读《公共事物的治理之道》,并由此来思考我们中国人所面临的集体行动理论的发展,以及思考中国人所面临的公共选择的实践问题,尤其是思考如何避免中国公共事物的退化,并挽救已经在退化的公共事物,如断流时间越来越长、很可能永远成为内陆河的黄河、污染日重的淮河和太湖、日益退化的沿海渔场、砍伐殆尽的森林,甚至是日益滑坡的道德等。本译序的目的是根据笔者阅读该书的心得,用中国学者的眼光,尽可能简去原书所具有的“美国特色”,概括、重述本书的理论要义,以使我们更好地把握其中的智慧的内核。

 

一、政府与市场之外 
 
  在本书的第一章,埃莉诺•奥斯特罗姆教授首次系统地总结了人们用之以分析公共事物解决之道的理论模型,它们是哈丁的“公地悲剧”、普遍使用的“囚犯的难题”和奥尔森的“集体行动的逻辑”。这些理论模型都说明了特定情况下的公共事物总是得不到关怀的必然的悲剧性结果,即亚里士多德所说的“凡是属于最多数人的公共事物常常是最少受人照顾的事物,人们关怀着自己的所有,而忽视公共的事物;对于公共的一切,他至多只留心到其中对他个人多少有些相关的事物。”对此,人们已经提出了若干所为“唯一”的方案,即以强有力的中央集权或者彻底的私有化来解决公共事物的悲剧,除此之外,别无选择。 埃莉诺•奥斯特罗姆运用博弈论分析了这些理论模型所隐含的博弈结构,并从博弈的角度探索了在理论上可能的政府与市场之外的自主治理公共池塘资源的可能性。她提出了“自筹资金的合约实施博弈”,认为没有彻底的私有化,没有完全的政府权力的控制,公共池塘资源的使用者可以通过自筹资金来制定并实施有效使用公共池塘资源的合约。

     利维坦和私有化,都不是解决公共池塘资源的灵丹妙药,那么自主治理的制度是否就是灵丹妙药呢?埃莉诺•奥斯特罗姆教授认为,“自筹资金的合约实施博弈不是万应灵药。这样的制度安排在许多场景中都具有不少弱点。牧人可能高估或低估草地的负载能力。他们自己的监督制度可能出现故障。外来的执行人在事先承诺将按某种方式行事后,可能又不实施。在实际场景中,各种问题都可能发生,就如理想化的集中管制制度和私有财产制度中的情况一样。” 这表明,理论模型可能在特定的情况下是有效的,但是未必一定能够提供理想的政策选择方案。大量的经验性个案研究表明,实际的制度安排,要比任何简单的博弈结构要复杂。这说明,政策分析家除了要进行理论思维之外,还需要以认真细致的经验研究为基础。这就对于政策分析家提出了一个重大的挑战:“如何以对人类在处理与公地悲剧部分相关或完全相关的各种情形中表现出来的能力和局限的实际评估为基础,去发展人类组织的理论。经验上可靠的人类组织的理论,能够把组织人类活动的各种不同方法及其后果传递给决策层,从而成为政策科学的实质性组成部分。理论探讨包括对规则的研究,它包括对现实场景中复杂事物的抽象,并确定作为所观察到的复杂性之基础的理论变量。为了对封闭系统中各种变量之间的逻辑关系作更细致的分析,还需要对一种特定的理论模型作进一步的抽象和简化。”埃莉诺•奥斯特罗姆认为,这就是政策科学的核心。 埃莉诺•奥斯特罗姆教授认为,政策分析家的工具箱中有各种各样的工具,但还缺少“一种具体明确的集体行动理论。凭借这一理论,一群当事人能够自愿地组织起来,以保持自己努力所形成的剩余。”自主组织的实例到处存在,但分析家却没有很好地在理论上总结它们。在此,理论分析是重要的,因为“所有的组织安排都面临着压力,有它的偏好和不足。没有合适的自主组织的集体行动理论,人们既不能预见或解释在什么时候个人单靠自身的组织不能解决公共问题,也不能确定在协助解决某一特定问题时,何种干预策略可能是有效的。”她写作本书的目的就是“对发展由经验支持的自主组织和自主治理的集体行动理论作出贡献”。它的特色是把许多新制度主义学者所使用的策略与由生物学家为促进从理论上更好理解生物世界而开展的经验研究中所采用的策略结合起来。制度分析与经验研究相结合,成了本书研究方法上的基本特色。

二、制度分析的方法

 

    本书第二章界定了一些基本概念,以及用于公共池塘资源问题分析的基本理论。奥斯特罗姆教授认为,公共池塘资源是一种人们共同使用整个资源系统但分别享用资源单位的公共资源。在这种资源环境中,理性的个人可能导致资源使用拥挤或者资源退化的问题。这时,理性的个人的问题就是如何通过组织避免独立行动的不利后果。根据既有的企业理论和国家理论,这个问题是由外部代理人解决的。这些理论以企业或国家作为一种组织工具,解释了如何供给新制度,如何获得承诺,以及如何有效监督代理人和其他主体的行动。但是,一群委托人,如一个社群的公民,如何才能将自己组织起来解决制度供给、承诺和监督问题,仍然是一个理论难题。由于一些人解决了这个难题,另一些人未解决这个难题,因此研究解决公共池塘资源问题中成功的和不成功的经验都应该探讨与集体行动理论和开发更好的公共池塘资源政策相关的重要问题。在这些探讨的基础上,埃莉诺•奥斯特罗姆提出了本书所运用的制度分析的框架,即要解决公共池塘资源的集体行动问题,需要解决三个问题:⑴新制度的供给问题,⑵可信承诺问题,⑶相互监督问题。就既有的理论来看,一组委托人不可能自己解决这三个问题。但是在实践上,已经有人解决了这三个问题。如何在理论上概括这些人是如何解决这些问题的,或者进一步在理论上探讨某些人为什么没有解决这些问题?这对制度分析学者提出了理论的和实地研究的挑战。 奥斯特罗姆教授认为,研究公共池塘资源的问题,理论与实践都是重要的。“理解个人如何解决实际场景中的特定问题,要求采用从理论世界到行为世界,又从行为世界到理论世界的策略。没有理论,人们决不可能理解作用于不同情形中的许多外在现象的一般的基本机制。如果不利用理论来解决经验难题,理论工作只能对理论本身有所创造,很少会对现实世界的状况有所反映。”而当理论预测与经验观察的不一致时,就需要对理论作出校正。 理论的重要性,“不只是影响解释中所使用的特定假设,还影响问题的设计方式。问题的设计方式影响询问什么问题和在实证调查中了解什么。”到目前为止,分析集体行动困境的学者,其结论之所以过分悲观,是因为他们所运用的理论假设存在着局限性,如他们认为囚犯困境博弈始终是基本的结构,并且一个层次的分析就已足够。埃莉诺•奥斯特罗姆认为,公共池塘资源中个人所面临的博弈结构并非是单一的囚犯困境博弈结构,并且所面临的问题也包括占用和提供两个方面的问题,这些问题的结构取决于基本参数的值,并因场景不同而不同。在这些方面,囚犯困境的博弈结构是可能存在的,但并不是唯一的,保证合作的博弈结构等,都是现实可能存在的结构。 另外,现有的集体行动分析,一般只着眼于操作层次的分析。实际上,影响集体行动的制度并不只限于操作层次的制度。“通常,需要区别长期影响使用公共池塘资源时的行为和结果的三个层次的规则。操作规则直接影响占用者有关下述问题的日常决策:何时、何地及如何提取资源单位,谁来监督并如何监督其他人的行动,何种信息必须进行交换,何种信息不能发布,对行为和结果的不同组合如何进行奖励或制裁等。集体选择规则间接影响操作选择。通常由占用者及其公务人员或外部当局在就如何管理公共池塘资源制定政策──操作规则──时使用。宪法选择规则通过决定谁具有资格决定用于制定影响集体选择规则的特殊规则影响操作活动和结果。人们能够想到在这些规则与人类对其进行选择、采取行动的相关分析层次之间的联系。占用、提供、监督和强制实施的过程发生在操作层次。政策决策的制定、管理和评判的过程发生在集体选择层次。宪法决策的规划设计、治理、评判和修改发生在宪法层次。”对这三个层次的行动规则来说,“一个层次的行动规则的变更,是在较之更高层次上的一套固定‘规则’中发生的。更高层次上的规则的变更通常更难以完成,成本也更高,因此提高了根据规则行事的个人之间相互预期的稳定性。” 除了要对规则进行多层次分析之外,还需要看到论坛与规则选择和执行之间的关系。在实践上,正式的集体选择论坛包括全国性的、区域性的或地方性的论坛,非正式的集体选择论坛包括非正式的集会、占用者团队、民间协会等,这些论坛,以及诸如立法机构、管制机构和法庭等治理机构对规则的正式监督和实施活动、日常使用的操作性规则以及非正式的监督和实施活动,都有着密切的关系。 最后,奥斯特罗姆教授还指出,公共池塘资源的使用规则并非只有法律上的规则,实际上,非正式的规则,也可能是有效的规则。“缺乏国家的、正式的规制公共池塘资源占用与提供的法律与缺乏有效的规则并不是等同的。”通过把非正式的规则纳入制度分析的范围,就进一步开拓了制度分析的视野。 
     

三、经验研究的进程
    
 

    埃莉诺•奥斯特罗姆教授用三章的篇幅,探讨了三个方面的案例。这些案例分布在世界各国,具有相当的代表性。 第一类案例涉及两个特色,一是占用者已经为控制对公共池塘资源的使用,设计、应用并监督实施了一套自己的规则;二是资源系统以及相应的制度,都已存续了很长的时间,存在时间最短的超过100年,历史最长的已超过1000年。这些案例涉及瑞士和日本的山地牧场及森林的公共池塘资源,以及西班牙和菲律宾群岛的灌溉系统的组织状况。

     在案例分析的基础上,奥斯特罗姆探索了其中所包含的“设计原则”。在此,设计原则是指“实质要素或条件,它们有助于说明这些制度在维持公共池塘资源、保证占用者世世代代遵守所使用的规则中的成功原因。”她界定了其中的八项原则:

     一、清晰界定边界。公共池塘资源本身的边界必须予以明确规定,有权从公共池塘资源中提取一定资源单位的个人或家庭也必须予以明确规定。      二、占用和供应规则与当地条件保持一致。规定占用的时间、地点、技术和/或资源单位数量的占用规则,要与当地条件及所需劳动、物资和/或资金的供应规则相一致。 
     三、集体选择的安排。绝大多数受操作规则影响的个人应该能够参与对操作规则的修改。
     四、监督。积极检查公共池塘资源状况和占用者行为的监督者,或是对占用者负有责任的人,或是占用者本人。
     五、分级制裁。违反操作规则的占用者很可能要受到其他占用者、有关官员或他们两者的分级的制裁(制裁的程度取决于违规的内容和严重性)。 
     六、冲突解决机制。占用者和他们的官员能够迅速通过低成本的地方公共论坛,来解决占用者之间或占用者和官员之间的冲突。      七、对组织权的最低限度的认可。占用者设计自己制度的权利不受外部政府威权的挑战。
     八、分权制企业。在一个多层次的分权制企业中,对占用、供应、监督、强制执行、冲突解决和治理活动加以组织。 
     
     埃莉诺•奥斯特罗姆教授认为,这些原则是在案例分析的基础上总结出来的,它们是长期有效的公共池塘资源自主组织、自主治理制度的基本构件,“这些设计原则对公共池塘资源及其相关制度的存续性提供了一种可信的解释。……这些设计原则能影响激励,使占用者能够自愿遵守在这些系统中设计的操作规则,监督各自对规则的遵守情况,并把公共池塘资源的制度安排一代一代地维持下去。”

    在第一批案例分析中,奥斯特罗姆的分析主要集中在承诺和相互监督问题。由于这些制度持续了漫长的时间,没有资料表明这些制度是如何得到供给的。为了说明人们是如何为自己供给新的制度来解决公共池塘资源问题的,需要进一步的案例分析。于是,埃莉诺•奥斯特罗姆转向了第二批案例分析,专门讨论自主治理公共池塘资源制度的供给问题。这就是第四章案例分析的任务。

    第四章的案例分析主要涉及制度供给问题。对此,需要回答许多问题,如“多少参与者?群体的内部结构如何?创新行动的成本由谁来支付?参与者拥有何种类型的有关他们境况的信息?各种参与者面临着怎样的危机和风险?参与者在制定规则时涉及到何等广泛的制度背景?”而既有的制度分析案例研究文献对此并未给出适当的回答。带着这一问题,奥斯特罗姆考察了美国大洛杉矶地区南部一系列地下水流域的制度的起源。她分析了该地区发生的抽水竞赛导致的地下水资源退化、因此而引起的诉讼博弈以及企业家的努力。在这些案例中,奥斯特罗姆看到,水资源生产者为了摆脱恶劣的抽水竞赛,在大量的诉讼无法解决问题之后,终于自主地建立了公共企业,对地下水资源进行适当的管理。在多个公共企业基础上,终于形成了“多中心公共企业博弈”的格局。

     制度分析学者一般认为,抽水者无法终止抽水竞赛(一阶困境),自然也难以为终止抽水竞赛提供制度安排(二阶困境)。但是,现实表明,加州的抽水者却给予制度供给提供了大量的投入。“他们创建了新的民间协会。他们为分配抽水量而提起的诉讼支付了大笔费用。他们起草了法案,把它介绍给州立法机构,并在其他水生产者那里寻求到了足够的支持,使法案得以通过。他们创立了特别行政区,向所有在那些流域抽取的地下水收税,也向所有在地下水之上的财产收税。他们花费了似乎是无穷无尽的时间向他们自己提供有关流域地质结构的信息,以及各种热点问题、各方打算和未来可能性的信息。”

     那么,为什么会如此呢?埃莉诺•奥斯特罗姆认为,这与地方自治的政治制度的类型有关。这种政治制度有许多制度设施减少地方制度供给的成本,而抽水者就是因为对新制度供给进行了渐进而持续的投资,各个地区之间相互仿效、学习,各个流域建立民间性的协会,开发适当的信息,了解各种策略所可能引起的成本收益。在此基础上,埃莉诺•奥斯特罗姆认为,为理解这些地下水流域渐进、连续和自主转化的制度变迁过程,制度分析家们应该重新考虑将制度供给问题理论化的方法。它“涉及若干微妙而又重复的变化,即要考虑到制度的规则及其起源和变迁。”把制度供给与制度变革统一在同一个分析框架中,把宪法选择与集体选择统一起来思考问题。

     第三、第四章以成功的案例为基础,分析了公共池塘资源自主组织和自主治理的制度何以能够长期持续,并且这些制度何以能够由当事人自主地供给的问题。但是,从现实来看,许多公共池塘资源的当事人却没有提供一套行之有效的制度安排,或者所提供的制度安排难以持续,从而导致了公共池塘资源的退化。这又是什么道理呢?埃莉诺•奥斯特罗姆详细地分析了土耳其近海渔场、加利福尼亚的部分地下水流域、斯里兰卡渔场、斯里兰卡水利开发工程和新斯科舍近海渔场制度失败的具体情况。在此基础上与前文所分析的成功的案例相比较,得出了公共池塘资源治理失败的制度原因。对于土耳其的两个渔场来说,它们在制度上缺乏第三章所概括的八项原则。斯里兰卡的科林迪奥亚灌溉工程只符合一条原则,即边界清晰。1938年租金散失成为严重问题后的马维尔渔场符合两条原则,即一致原则和监督。制度变迁之前的雷蒙德流域、西部流域和中部流域符合两条原则,即冲突解决机制和被认可的组织权。莫哈韦案例符合三条原则,即集体选择的论坛,冲突解决机制,和被认可的组织权。经过分析,奥斯特罗姆发现,在公共池塘资源的占用者显然不能解决他们所面临的问题的案例中,没有一个符合三条以上的设计原则。
 
    奥斯特罗姆教授认为,加拿大莱蒙隆港的公共池塘资源制度是脆弱的,土耳其阿兰亚的制度尽管有创造力但是脆弱的,斯里兰卡加勒亚工程的制度也是脆弱的。对此,奥斯特罗姆的解释是:虽然在阿兰亚设计的制度提供了一条解决分配问题的第一流方法,但他们并没有提供解决地方渔场进入限制问题的方法。在当时,愿意到阿兰亚捕鱼的渔民数量还没有威胁到渔场的生存,但是如果有更多的人想要进入渔场,在马维尔发生的租金散失问题就可能在阿兰亚重演。过去,集体选择部分是通过当地的协调机制、部分是通过在当地咖啡屋中的讨论决定的。但是,没有一个用于集体选择的经常性论坛,阿兰亚的渔民要在未来条件变化时调整他们的规则就将是困难的。

     关于加勒亚,边界和成员已清楚界定,设计了合适的原则,原则的执行情况得到监督,集体选择的论坛也已建立。然而,由于农民权利没有得到明确的认可和保证,没有合适的冲突解决机制,奥斯特罗姆认为这些制度也不是强有力的。在长期中央控制的情况下,如果水利部门的一个重要变化是任命那些认为农民们几乎未曾提供过任何合作的人作为水利部的工程师,那么加勒亚的农民要继续进行有组织的努力就将是困难的。就设计原则而言,脆弱制度的案例属于中间案例。虽然这些原则足以使公共池塘资源的占用者解决他们直接面临的公共池塘资源问题,但是除非有进一步的制度发展,除非制度安排基本符合整套设计原则,否则人们仍很难预测这些制度是否是强有力的。 
     

     四、自主治理的框架
    
  

  在本书的最后一章,埃莉诺•奥斯特罗姆又回到了理论探讨。她认为,第一章所介绍的有关集体行动的理论模型并没有什么错误。“当现实条件逼近模型中的假设条件时,实际的行为和结果将与预测的行为和结果非常接近。”但是,把这些理论模型运用于分析小规模的公共池塘资源时,却显示了其三个问题: 
   

  1.没有反映制度变迁的渐进性和制度自主转化的本质;
     2.在分析内部变量是如何影响规则的集体供给时,没有注意外部政治制度特征的重要性;
     3.没有包括信息成本和交易成本。

     所以,现有的集体行动理论“不适于为小规模公共池塘资源中的制度变迁提供政策分析的基础”。埃莉诺•奥斯特罗姆教授认为,“认识到了这些问题,我们就能接下去考虑如何在现行的集体行动理论和公共池塘资源情形中集体行动的经验案例之间构架一座桥梁,以便为政策分析建立更为相关的制度变迁理论。” 接下来,埃莉诺•奥斯特罗姆开始了制度选择分析框架的探讨。她认为,如果存在三个条件,即每一个总和变量都有准确的汇总方法,个人能把有关净收益和净成本的信息完全而准确地转化为预期收益和预期成本,个人以直截了当的而非策略的方式行事,制度分析人员在预测个人策略时,就只需确定总和变量的价值。但“不幸的是,对分析人员来说,几乎没有现实场景是以这三个条件,甚至其中的一个或两个条件为特征的。诸如使用一套替代规则的收益或监督和执行一套规则的成本这些变量,很少以分析人员(或进行制度选择的人)通过简单计算便可求解的形式记载下来。”因此,埃莉诺•奥斯特罗姆认为,“在对政策背景进行分析时,必须抛弃总和变量而使用影响总和变量的环境变量。”她的分析框架就是以此展开的。

     一套新的制度替代旧的制度,主要取决于这两套制度的收益-成本比较。在此,要计算制度纯粹的收益总和是不可能的,这就需要确定影响收益评价的环境变量。埃莉诺•奥斯特罗姆认为,要估价一套制度的总收益,需要确定9个环境变量:占用者人数、公共池塘资源规模、资源单位在时空上的变动性、公共池塘资源的现有条件、资源单位的市场条件、冲突的数量和类型、这些变量资料的可获得性、所使用的现行规则以及所提出的规则。这些环境变量影响有关所建议之规则之净收益的信息。这说明,个人了解新制度的收益,并不是一件简单的事情。埃莉诺认为,要了解一套制度规则所可能产生的收益,取决于公共池塘资源的客观条件、当前制度安排所形成的并对人们公开的信息的类型以及替代方案所提出的制度规则。“规则改变后能否获得收益并不是一个简单存在于世界上的‘事实’,可以被任何希望增进福利的人──占用者、分析人员或公共官员──所使用。有关收益的信息必须通过搜寻、组织和分析才能得到。”对于收益评估是如此,对于成本评价也是如此。 
 
    一套新的制度取代旧的制度,这可以界定为制度变迁。制度变迁的成本包括转换成本,也包括监督和实施成本。影响转换成本的环境变量包括决策者的人数、利益的异质性、为改变规则所使用的规则、领导者的技能和资本、所提出的规则、占用者以往的策略和改变规则的自主权等变量,并且还包括当地占用者以往的制度决策以及外部政府所制定的要求。影响监督和实施成本评价的环境变量包括公共池塘资源的规模和结构、排他技术、占用技术、市场安排、所提出的规则以及所使用的规则的合法性等。

     除了上述环境变量影响制度的收益和成本评价之外,共有规范以及其他机会评估也通过影响当事人的贴现率从而影响制度的收益和成本。当事人居住地与公共池塘资源的远近、当事人对可能拥有的其他机会的了解程度等,都会影响他们的贴现率,从而改变现有的收益和成本评价。
 
    在这些分析的基础上,埃莉诺•奥斯特罗姆进一步分析了制度变迁的过程问题。她认为,市场的效率在于使企业家有积极性采取有效的制度,采取有效制度的企业家无疑会利于不败之地。但是对于公共池塘资源来说,市场竞争反而会给公共池塘资源带来毁灭性的恶果。这时,公共池塘资源的制度变迁理论就应该有别于标准市场经济中的制度变迁理论。就如奥斯特罗姆教授所说的,“一个较好的理论态度不是把规则变更的决策视为机械的计算过程,而是把制度选择视为对不确定的收益和成本进行有根据的评估过程。”这样,我们就可以运用社会心理学家的研究成果,来分析人们对成本与收益的评估偏差。这些偏差主要表现在如下几个方面:一是人们对潜在损失的重视程度要高于对潜在收益的重视程度。相应的,人们对避免未来损害的预期收益的重视大大胜过对生产未来产品的收益的重视。二是当存在资源恶化指标并被普遍认可作为未来资源损害度的准确的预测指标时,或当领袖们能够使其他人相信“危机”迫近时,人们便会愿意接受限制他们使用资源活动的新规则。三是与长期不确定的收益和成本相比,前期转换成本的计算不仅要容易些,而且有时会有实质的区别。所有占用者更为关注的是即时成本而不是未来收益。在决策者往往更强调预期损失而不是预期收益的情况下,转换成本在占用者判断是否要改变规则时便具有了进一步的重要性。如果规则改革的预期贴现净收益并不大,公共池塘资源的占用者就极不可能为改变规则支付即刻发生的转换成本。四是人们对以频率为基础的概率进行准确估计的能力也非常有限。他们对近期事件的重视程度往往要远远高于对很久以前发生的事件的重视程度。五是占用者或其他人所设计的一套特定规则很少包括了可用于治理运作环境的全部可能的规则。他们所提出的规则很可能来自反对者已经熟悉的规则大全。在规则的任何变革都与大量不确定性相连的情况下,人们不太可能采用不熟悉的规则,而乐于接受其他人已在相似环境中使用过并被证明效果较好的规则。在对不同规则进行了大量实证研究的情况下,占用者可以通过分析在相似的公共池塘资源环境中使用不同规则所得到的经验,来了解不同规则的效果。
 
    最后,还需要注意每一个制度选择都可能产生完全不同的利益分配和成本配置结果。这些制度选择的分配效应对所有参与者来说不可能完全是一样的,这显然也影响制度变革的进程。

     在制度变迁过程中,当事人自主治理不见得一定会失败。但是,如果制度变迁的收益超过成本,当事人也并不一定会自动地抛弃旧制度,选择新制度。无论如何,“设计和采纳新制度来解决公共池塘资源问题是困难的任务,无论占用者群体是如何同质,无论协会的成员们对他们的公共池塘资源的状况掌握了何等多的信息,也无论互惠的共识是多么根深蒂固。当人们面对公共池塘资源问题时,通常都会有很强的规避责任、搭便车和以机会主义方式行事的诱惑。在这样的条件下,我们决不能保证这些问题可以得到解决。没有任何强大的外部压力能驱使人们对这些问题形成积极的解决方案。如果存在着走向一个独特结局的强大压力,那么这个结局很可能就是第一章讨论的三个模型中不完全均衡的结局。我们知道人们有可能运用他们的能力,去思考、交流和承诺设计解决公共池塘资源问题的新的规则,但是我们不能断定这种情况必然会发生。并且,如果人们觉得现有规则运作得还不错,他们就不会有什么动力去继续那个寻求更好规则的昂贵的过程。‘如果还没有坏掉,就不要去修它’,这种观念不仅适用于物质资本,而且适用于制度资本。”

     那么,人们何以选择具有净收益的新制度呢?除了特定的环境变量之外,政治制度的性质显然具有很大的影响。对此,埃莉诺•奥斯特罗姆进一步分析了不同类型政治制度中自主治理公共池塘资源制度的供给问题。
 
    首先,奥斯特罗姆教授认为,在偏远的地区,外在的政治制度无论如何,对内部选择来说都是“零条件”,这时公共池塘资源占用者在操作规则上采取一系列渐进变革来提高共同福利的可能性,与以下内部特征呈依次序正相关:1.大多数占用者都认为:如果不采取替代规则,他们就将受到损害。2.所提出的规则变更对大多数占用者会有类似的影响。3.大多数占用者对在公共池塘资源上继续生产活动给予高度评价,即他们的贴现率较低。4.占用者所面临的信息成本、转换成本和实施成本较低。5.大多数占用者有互惠的共识,并相信这种共识能作为初始的社会资本。6.使用公共池塘资源的群体相对较小,也较稳定。

     其次,奥斯特罗姆教授认为,在非偏远地区政治统治制度的导向对地方占用者能否为他们自己提供制度,或能否不依赖外部政府、独立解决他们的问题,具有实质性的影响。比如,假定政治制度允许有实质性地方自治,在这种政治制度背景中,地区性的和全国的政府都可以起到积极的作用,比如提供专业性的信息服务,提供解决冲突所需要的公正的法庭程序,提供一定的法律基础,承担督察的责任等。显然,政府在这种制度中的作用与一般理论模型所设想的集中控制的作用显然存在着巨大的差别。

     那么,当政治制度不允许存在实质性的地方自治,所有问题都只有上级政府才能解决时,又会发生什么情况呢?埃莉诺•奥斯特罗姆对此又进行了两个层次的探讨。 
    
  首先,假设官员是正直的,非常愿意帮助解决公共池塘资源问题。这时,面临公共池塘资源退化问题的当事人就会等待政府来解决问题。对于当地资源的占用者来说,其主要问题是如何向不了解当地情况但又有权、有积极性来解决当地问题的官员介绍当地的“事实”,以引导官员创造一种可以使一些人比另一些人境况更好的制度安排。那些拥有能向外部官员充分表达自己立场的资源的人,便最有可能赢得最有利于他们的规则(或例外解决办法)。从可能性上来说,正直、勤勉的地区或国家官员完全可能在一些他们管辖的公共池塘资源区提供很适合当地情形的新的公共池塘资源制度。但是,这种制度供给方式也会出现消极后果,“试图把一套规则强加于整个辖区,而不是制定适合辖区内各地情况的特殊规则,会使官员们在建立和实施那些对当地占用者似乎是有效而公正的规则时遇到极大的困难。试图使当地占用者承诺遵守那些被他们认为是低效率、不公正的规则是困难的。监督和实施这样的规则的成本必然要高于监督和实施由占用者参与制定的、适合当地情形的规则的成本。” 
 
    其次,假设官员是腐败的,不是正直的,这时制度供给问题就会变得更加困难。当地占用者也许有可能在法律框架之外创立他们自己的地方制度。然而,这些占用者必须是非常同质的,对他们的公共池塘资源的状况很了解,对他们同伴的行为很了解,贴现率低,并在整体上具有前面所列举的、在这个极端上所希望有的全部特征。更有可能的结局是如公地悲剧、囚犯难题和集体行动的逻辑等模型所描述的悲剧性结果,任何人都不与他人合作,所有的人都生活在恶梦之中。 
     

五、理论与实践之间
    

     在学术世界中,社会科学家的成就表现在理论模型的建设以及发展上。在实践世界中,实践者的成就表现在现实问题的解决和良好的后果上。实践需要理论的指导,但是社会科学家是否就可以用理论模型来直接分析现实世界所面临的问题呢?埃莉诺•奥斯特罗姆认为,社会科学家的理论模型至多可以当作分析框架,因为“无法在一个模型中容纳下此等复杂的情形。当在模型关系中选择时,往往只能包括一个子变量群,即使如此,通常还会将其中的某些变量再设为零或某个绝对值。典型的假设还包括完全信息、独立行动、利益的完全对称,无人的失误,无互惠准则、监督和实施的零成本,以及环境无自主转化能力。这些假设所导致的是非常特殊的模型,而不是一般的理论。”

     但是,实际上政策分析家往往以模型为基础,这已经导致了一些严重的问题,就如奥斯特罗姆教授所说:“完全依赖模型作为政策分析基础这一做法存在着一个认识上的陷阱,这就是学者会因此认为他们是无所不知的观察家,能够通过对系统的某些方面的规范化描述,领悟复杂的动态系统运作的真谛。有了这种自以为万能的错误自信,学者在向政府提建议时感到非常得心应手,而政府也相信学者的模型是万能的,能够纠正一切场景下存在的不完善的问题。”奥斯特罗姆教授进一步指出,以公共池塘资源问题的分析来说,社会科学家的模型化分析,对支持政府的更加集权化发生了三个方面的不良影响,一是使用公共池塘资源的人被视为短期利益最大化的追求者,不会使用增进长期共同利益的合作战略。二是那些人似乎落入了陷阱之中,没有外部政府的解决方案便不能从陷阱中解脱出来。三是把那些人可能建立的制度搁置一旁,或被作为低效率的制度加以拒绝,而不考虑如何使这些制度能有助于他们获得信息,减少监督和实施成本,公正地分配占用权和供应义务。四是所提出的由政府强制实施的解决方案本身,是以理想化的市场或理想化的国家模型为基础的。本书的案例研究表明,这些不良影响所内涵的理论假设实际上是脱离实际的。 ( 燕南, h     在结束本书之时,埃莉诺•奥斯特罗姆教授进一步分析了理论模型与经验研究之间的关系,她指出:“理论事业要求社会科学家进行模型的建构,但这并不意味着把理论探索限制在一些特定层次的问题的论述上。我们必须珍视霍布斯、孟德斯鸠、休漠、斯密、麦迪逊、汉密尔顿、托尔维克和其他许多伟大学者以往重要的理论贡献所提供给我们的分析手段。公共和集体选择理论、交易成本经济学、新制度经济学、法和经济学、博弈理论、以及许多相关领域的最新研究也正在做出重要的贡献。这些贡献需要在用理论指导的、实验室和现实场景的实证研究中,进一步向前推进。”通过系统的经验研究,无疑可以检验这些理论的可行性,减少理论模型对复杂的实践问题的误导,并进一步推动理论研究的发展。奥斯特罗姆的这些论述,对于我们思考理论与实践之间的关系,无疑是颇有助益的。

 

六、作者其人
    

     最后,让我们认识一下本书的作者埃莉诺•奥斯特罗姆教授。埃莉诺•奥斯特罗姆,美国印第安纳大学阿瑟•F•本特利政治科学讲座教授,印第安纳大学政治理论与政策分析研究所所长,印第安纳大学公共与环境事务学院(SPEA)兼职教授。曾任美国政治学会主席,美国公共选择学会会长,美国中西部政治学会会长。一生著作丰富,其中重要的有《社群组织与警察服务的提供》、《诚实服务的提供:变革的结果》、《美国大城市地区的警察服务》、《大城市地区警察之道》、《警察绩效的基本问题》、《政治研究的方法》、《美国的地方政府》、《公共事物的治理之道:集体行动制度的演进》、《自主治理灌溉系统的行业制度》、《制度激励与可持续发展:基础设施政策透视》、《规则、博弈与公共池塘资源》、《地方公共事物与全球相互依赖:两个领域的差异与合作》等十余本著作。重要的学术论文100余篇。她的研究特色是在富有系统性理论的基础上进行深入广泛的实证研究,主要集中在运用公共选择与制度分析理论和方法,分析公共事务,尤其是警察服务、公共池塘资源的自主治理问题,她对于公共选择与制度分析的理论和方法的发展,对于公共政策研究和新政治经济的研究,作出了举世瞩目的杰出贡献。1991年她入选为美国人文与科学学院院士,1996年获得美国政策研究组织颁发的杰出妇女奖,尤其难得的是,她作为政治学家、行政学家和政策分析学家,于1997年获得了具有世界声誉的弗兰克•E•塞德曼(Frank E. Seidman)政治经济学大奖。到1997年她得奖时为止,已经有24位政治经济学大师获得了该奖,其中有5位大师随后获得了诺贝尔经济学奖,他们是加纳•卡尔•缪尔达尔(Gunnar Karl Myrdal)、威廉•S•维克里(William S. Vickery)、罗伯特•M•索罗(Robert M Solow)、詹姆斯•M•布坎南(James M. Buchanan)和斯坦利•贝克尔(Stanley Becker)。或许因此之故,也因为她对公共选择与制度分析研究的杰出贡献,许多人都预料,埃莉诺•奥斯特罗姆在不远的将来很可能成为诺贝尔经济学奖的有力竞争者。

     1997年9月27日,在塞德曼政治经济学奖颁发大会上,著名的阿罗不可能定理发明者、1972年诺贝尔经济学奖获得者、斯坦福大学经济学教授、塞德曼政治经济学奖遴选委员会委员肯尼思•J•阿罗发表演讲。他的讲话,系统地阐述了埃莉诺•奥斯特罗姆教授对于政治经济学的杰出贡献: ( 燕南,

     “实际上,埃莉诺•奥斯特罗姆就是公共经济学研究的始作俑者之一。该领域的研究追述到17世纪托马斯•霍布斯在《利维坦》一书中所描绘的国家。霍布斯认为,没有社会的人,注定要陷入贫困和冲突。因此,男男女女都把其自己交给全能的国家,以避免恐怖的自然状态。当然,后来的思想家大大修改了对自然状态的刻板的恐惧和对全能主权者的需要。在霍布斯时代的100年之后,人们日益认识到,尤其亚当•斯密的思想最著名且最有说服力。另一方面,国家也不需要拥有无限的权力。可以把创造霍布斯所设想的国家的社会契约解释成公民控制国家,也就是如约翰•洛克和让-雅克•卢梭等19世纪的作家所所设想的民主控制的基础。公民要求有一个对抗国家权力的庇护所。 (

     福利经济学是经济学的一个分支,一些福利经济学家试图把经济理论应用于公共政策的塑造,他们在一定程度上采用了霍布斯的思想。市场有效地解决大量的资源配置问题,但它不解决所有的问题。在最近若干年里,一些学者运用“囚犯难题”、“公地悲剧”等富有色彩的模型确定了市场失败。这些模型揭示了所有人的利益,或者至少是绝大多数人的利益,并不符合任一个人的利益。就拿奥斯特罗姆教授研究的两个领域,即警察和公共财产资源的使用来说,即使在最为特殊的假设条件下,市场也不最优地解决问题。在这两种情况下,都存在着单个行为者得不到的因而也不会为此而花费足够资源的收益。

   公共财产资源是相当多的个人能够免费利用的资源。它们是稀缺的,市场也没有对其使用进行收费。典型的例子是捕鱼。鱼是稀缺的,一个渔夫捕到的鱼不能为其他人所捕获。但是,海洋不是私人财产;任何拥有渔具的人都可以从中捕鱼。这就会发生过度捕捞的问题,结果就是鱼供给量的减少。地下水灌溉庄稼,也是类似的。地下水是流动的,而不是固定的,任何农夫都可以不付费就抽取他人土地下的水资源。
    
   生物学家加勒特•哈丁用“公地悲剧”比喻无效使用公共财产(实际上,他的具体目标是人口增长,而我认为,就此目的来说这一比喻是极端误导的。所以,我并不接受这一比喻对这一概念的关键用法)。具体地说,他针对的是中世纪在公共土地上放牧的习俗。他没有问为什么公共土地延续了许多世纪。根据他的看法,这显然是一种灾难,并且这就是为什么只有在地主控制公共土地(这是较具有现代味道的财产概念)为自己所用时公共土地才消失的原因
     
  在这些公共事物方面,市场并没有发挥作用;霍布斯的可怕的主权者也没有干预以使其处于正常。即使是福利经济学家所讨论的最低限度的补救,如征税或者可转让的许可证,也不存在。埃莉诺•奥斯特罗姆以及她的合作者综合运用好的理论,进行仔细观察,得到了新的洞见。为了遏制显然的无效率,人们会发展制度和规则,虽然并不总是如此,但经常如此。或许可以把这些看作是小规模的社会契约,但这的确不是把权力交给全能的主权者。相反,他们是为了特定的目的而转让有限的权力。控制渔业和水资源的运用,都是例子,除此之外还有许多公共财产资源的例子。

     奥斯特罗姆的创新之处是把整个体制看成是互动的公共机构构成的体制,而不是由一个人控制的单一的体制。把公共当局看作是一个多元的体制,而不是单一的。当然,各种各样的地方政府已经有了很长的历史,但是人们易于认为它们是较大的国家的创造物。在奥斯特罗姆的体制中并不这样认为。它们是自发创造的秩序,实际上,它们必须与其他公共控制机构在同一层次或者不同层次上综合在一起。 (
 
    这使得奥斯特罗姆教授得出了更为广泛的公共当局之间相互调适的观点。就如市场中的企业那样,公共当局也是竞争者和合作者。诸如规模经济那样的经济上的考虑,与责任的配置高度相关。在两个多世纪里,美国的联邦体制在法律意义上没有什么变化。但是实际上,联邦政府和州政府之间责任的配置发生了剧烈的变化,并且由于条件变化,也由于理想的变化而在发生变化。地方政府的结构更具有可变性,即使在法律形式上也是如此,正是在这一领域里,埃莉诺•奥斯特罗姆及其合作者已经完成了如此具有创造性的工作。” ( 燕南,

     最后,肯尼思•阿罗教授指出,“奥斯特罗姆教授的贡献在于综合政治学和经济学的同时又超越了政治学和经济学。针对市场失败的政治解决方案远远多于简单的新霍布斯福利经济学的观念。”而集中体现了其学术贡献的就是本书,即《公共事物的治理之道》。

     获奖,只是该书获得社会承认的一个方面。该书自1990年初次出版后,每年均有重印,其中1994年还重印两次。该书已经成为90年代美国政治学界引用率最高的学术专著,1996年牛津大学出版社出版的《政治科学新手册》一书,概述了100多年来美国政治学各子学科及其总体的发展。该书各章参考文献共收集了由1630名作者协作的3403本政治学著作,其中最常引用的四本著作依次是安东尼•唐斯的《民主的经济理论》(1957)、曼瑟尔•奥尔森的《集体行动的逻辑》(1965)、埃莉诺•奥斯特罗姆的《公共事物的治理之道》以及道格拉斯•诺斯的《制度、制度变迁与经济绩效》(1990),埃莉诺•奥斯特罗姆的著作名列第三。该书把美国政治学分成政治制度、政治行为、比较政治、国际关系、政治理论、公共政策与行政、政治经济和政治方法八个子学科,埃莉诺•奥斯特罗姆的《公共事物的治理之道》一书,是唯一一本为所有子学科所引用的著作。并且它的影响远远超出美国,远远超出行政学和政治学界,不仅在行政学界、政治学界、政治经济学界产生了巨大的学术影响,而且由于她所分析的案例涉及发展中国家,该书已经成为发展研究的必读文献,更由于该书涉及到自主治理问题,恰好与风靡世界的治道变革潮流(从集权走向分权,或者从单中心集中统治走向多中心自主治理)不谋而合,从而成为现代治道研究的重要文献。该书英文版封底指出:“《公共事物的治理之道》对制度方面的分析性文献作出了重大贡献,对我们对人类合作的理解也作出了重大的贡献。”1997年夏,奥斯特罗姆夫妇应中国社会科学院政治学研究所的邀请来北京进行学术访问,他们了解到中国农村的村民自治正在稳定发展,他们对此很感兴趣,认为中国农村正在发展的村民自治,必将为中国农村的可持续发展提供制度基础,从而为人类文明的发展作出非凡的贡献。相信本书的翻译出版,有助于我们更好地思考自主治理制度与持续发展的关系问题。 
     

七、翻译杂议
    

     本书由浙江大学政治学与行政学系于逊达教授和陈旭东女士合译,本译丛主编毛寿龙校阅了译稿,翻译了作者的中文版序,并按译丛的要求进行了初步的编辑。在术语翻译方面,有几个术语的翻译值得在“译序”中作些讨论。首先是“Commons”的翻译。该词源于哈丁的论文“The Tragedy of the Commons”。在该文中,哈丁主要用它来指称“公共牧地”,但是它的所指并非只限于牧地,而且还指带有普遍性的人口爆炸等问题。许多学者由此出发所进行的研究,其“Commons”的所指也不仅仅指“公地”。在90年代初,笔者曾经用该理论模型思考过“中央与地方的关系”,当时所采用的翻译是“公用地”,后来觉得“公用地”所指过窄,也一度用过“公有物”,后来根据亚里士多德有关公共事物的论述定为“公共事物”。本书的英文名为“Governing the Commons”,奥斯特罗姆教授告诉笔者说,本书以此为题,在很大程度上不是分析性的,而是修饰性的,即作者并没有严格地界定“the Commons”,而是宽泛地指称与公共相关的事物,即除了私益物品之外的所有物品,如公益物品、公共池塘资源、收费物品(俱乐部物品)等。所以,笔者把本书书名译为《公共事物的治理之道》。不过,本书所研究的公共事物却是有所指的,主要着眼于小规模的公共地下水资源、渔场资源、森林资源等,而不涉及纯粹的公益物品,也不涉及收费物品。如果都译为“公共事物”,失之于过于宽泛,可能影响理解;如果随时因地制宜,分别译为“公共事物”或者“公地”,也易于产生不必要的混乱。因此在正文中,本书一般译为“公地”,“the Tragedy of the Commons”也因此而译成了“公地悲剧”,但在本译丛的其他书中,在该词所指比较宽泛的情况下,一般译为“公共事物”,“公地悲剧”也相应地译成“公共事物悲剧”。 
     
     另外,奥斯特罗姆教授在本书中所研究的资源,不是严格意义上的公益物品,也不是私益物品,它们介于两者之间,是难以排他同时又分别享用的物品,作者称之为“Common-pool Resources(CPR)”。在此,该词又是一个易于引起翻译争论的术语,也值得我们在此作一些探讨。      
     从字面上来看,common与公共可以对应,这没有异议;但在英文中,pool有两个意思,一是池塘,二是合伙使用,如car-pool。如果采用第一个意思,则可译为“公共池塘资源”,如果采用第二个意思,则可译为“公用资源”,这两者应该都是可以的。但是,如果通过译文去理解英文,后者可能会引起一些误解。因为“公用资源”在汉语中既可以理解为为公共所用的资源(如国防或者政府大楼),也可以理解为属于公共但为私人所分别享用的资源(如公共住房或者河水),自然也可以理解为分别付费但共同享用的资源(如收费公路、公园)等。本书作者使用CPR时,也没有严格界定,但字里行间显然是指第二者,因为第一者显然属于公益物品的范畴,第三者显然属于收费物品(Toll Goods)或者俱乐部物品(Club Goods)的范畴。如果翻译成“公共资源”,也同样容易出现这一混淆。本丛书为了体现CPR的独特含义,决定统一译为“公共池塘资源”,它有别于不可排他、共同享用的公益物品,有别于可以排他、个人享用的私益物品,同时也有别于可以排他、共同享用的收费物品,它是难以排他但分别享用的物品。显然,无论是“公用资源”的译法,还是“公共资源”的译法,都难以表达这一准确的含义。另外,如果我们把CPR想像成一个无主的向任何人开放的池塘中的水,谁都可以去取水,但池塘中的水一旦为谁所取得,水就成了私人拥有、私人享用的物品,显然这种水就是奥斯特罗姆教授所指的CPR。因此,把CPR译成“公共池塘资源”可以说既可以在分析意义上区别关键的几个词汇,避免混淆,同时也可以感性上使人把握其含义,是比较恰当的。不过,埃莉诺•奥斯特罗姆告诉笔者说,即使对CPR,她也并非界定得非常清楚,因为她没有说明这类资源的产权是否在法律上是如何界定的,她仅仅说明了其普遍的特征是对一定范围的公众来说是开放性的。因此,CPR又有别于“Common Property Resources”,后者的产权显然是公共的,所有可以翻译为“公有资源”或者“公共财产资源”。不过进一步说,公共池塘资源和公共财产资源是否真的如此泾渭分明呢?恐怕不见得。因为从国际学术界来看,公共池塘资源研究属于公共财产资源范围的范畴,公共池塘资源是公共财产资源研究领域的。在这种情况下,公共池塘资源与公共财产资源的关系就难以说得清楚了。 在世界上,中文具有悠久的独具特色的传统;在最近几十年里,中文世界与西文世界的学术交往有了很大的发展,但是相对来说还是不够的。这使得中国学术世界所存在的话语体系与西方学术世界依然存在着很大的差异,西方学术世界所运用的许多词汇在中文中难以找到对应词。这给翻译带来了巨大的困难。在这一进程中,对术语的翻译产生分歧是正常的,并且根据笔者的经验,如果在视觉上不感到别扭,一本著作中关键词汇的译法在一定意义上是没有关系的,如无论把CPR译成“公共池塘资源”,还是“公共资源”或者“公用资源”,甚至是“共用资源”,无论把“Commons”译成“公共地”、“公用地”,还是“公共事物”、“公物”,甚至是“公共牧场”,读者如果把握了关键概念的核心含义,依然不难准确地把握本书的核心思想。不过,如果尽可能采用能够确切表达原文含义的词汇,并且在一套译丛中能够统一关键词汇的译名,无疑可以降低对“制度分析与公共政策”问题感兴趣但又不懂英文的朋友的阅读成本。相信作者、译者和读者都能够理解本丛书编者的用心所在。 
     
     本书的翻译得到了埃莉诺•奥斯特罗姆教授的悉心帮助。从中文版权的取得,到对关键词汇的把握,埃莉诺•奥斯特罗姆都在百忙之中给予了宝贵的帮助。在今天,许多经济学者的制度分析文献,如道格拉斯•诺斯、罗纳德•科斯和德姆塞茨等的著作,以及公共选择的文献,如布坎南、缪勒和阿罗等的著作,已经陆续翻译成了中文,并且对于政治学和行政学的研究产生了广泛的影响。印第安纳大学政治理论与政策分析研究所的学者们,与这些经济学者一样,都属于共同的公共选择与制度分析传统,相互之间非常熟悉,属于同行和朋友。相信该研究所的创始人和主持人之一的政治学家、行政学家埃莉诺•奥斯特罗姆教授的著作,在世界各地已经产生了巨大影响之后,也会受到中国政治学者、行政学者和政策分析学者的关注,从而为中国学术的发展提供适当的资料,为中国境内的公共池塘资源研究,乃至政府与市场关系的研究,提供可资借鉴的文献。如果是这样的话,我们的翻译也就达到了预期的目的。

  一、游戏开发策略

  1 游戏动作(Action)的使用

  MIDP的Canvas类让程序员处理某些按键事件,要么作为特定的低级键控代码事件要么作为抽象的游戏动作。FullCanvas是Nokia的全屏画布(FullCanvas)类,它是从Canvas类继承而来的。

  在回合制游戏或者不需要超过四个方向键(上、下、左、右)的游戏中,最好使用直接的键盘代码来控制游戏动作。可以使用抽象游戏动作的游戏例子包括国际象棋和知识测验以及解谜游戏,这些都是使用方向键来滚动屏幕或者移动。

  游戏动作应该只在不需要快速反应的游戏中使用。这是因为所选择的设备的游戏动作映射可能对于要求快速动作的游戏并不适用。并且,如果一个游戏要求斜的方向键或者这个游戏是一个快节奏的动作游戏,那时游戏者一只手需要移动游戏角色,另一只手需要执行其他操作,比如射击、开门等等,那么就需要使用直接键盘代码。原因是在MIDP中没有斜向的游戏动作,并且游戏动作映射是为一只手使用设计的。

  当使用直接键盘代码事件的时候,必须特别注意应用程序的可移植性。

  在不同的设备之间键盘的布局有很大区别(参见图1,是两种手机不同的键盘布局)。开发者可以通过允许用户在游戏中自己定义按键的方式来解决这个问题。这可以在游戏开始之前或者在游戏的"选项"页面中完成。

  键盘代码和游戏动作在同一个应用程序中决不应该混合使用。 


图1、不同手机的键盘布局

  2 关于游戏动作的说明

  一个MIDlet应用程序通过调用Canvas方法来探测哪些键盘代码映射到运行的应用程序中的抽象游戏动作:

public static int getGameAction(int keyCode);  

  Canvas类定义抽象游戏动作集:UP、DOWN、LEFT、RIGHT、FIRE等等。

  游戏开发者应该知道MIDP 1.0规范中的一个问题。这个类定义了转化键盘代码到游戏动作的方法,同样也定义了转化游戏动作到键盘代码的方法。

public int getGameAction(int keyCode)
public int getKeyCode(int gameAction) 

  方法getKeyCode(int gameAction)可能会导致一些问题,因为它只能返回基于游戏动作的一个键盘代码,即使MIDP 1.0允许超过一个键盘代码被实现。在Nokia手机中,个别的一些键盘代码被映射到相同的游戏动作,比如"UP键"和"2键"都被映射为向上的游戏动作。而这个方法只能返回其中之一;返回的值是特定的实现。然而,如果方法getGameAction(int KeyCode)使用"UP键"和"2键"的键盘代码作为参数,这个方法将返回正确的向上的游戏动作。下面来看一个不好的例子,以加深我们的印象:

//不好的例子,不要这么做:
class TetrisCanvas extends Canvas {
int leftKey, rightKey, downKey, rotateKey;
void init (){
//FOLLOWING MUST NOT BE DONE
leftKey = getKeyCode(LEFT);
rightKey = getKeyCode(RIGHT);
downKey = getKeyCode(DOWN);
rotateKey = getKeyCode(FIRE);
}
public void keyPressed(int keyCode) {
if (keyCode == leftKey) {
moveBlockLeft();
} else if (keyCode = rightKey) {

}
}
}  

  下面是更好的解决方案:

class TetrisCanvas extends Canvas {
void init (){
}
public void keyPressed(int keyCode) {
int action = getGameAction(keyCode);
switch (action)
{
case Canvas.LEFT:
moveBlockLeft();
break;
case Canvas.RIGHT:
moveBlockRight();
break;
}
}

  这个例子是MIDP 1.0规范中的例子,使用getKeyCode ( int gameAction)处理键盘代码值,只能返回一个值。如果这样的话,其它可能的按键映射就不能在MIDlet中使用了。比如说,在Nokia 7650中就会出现问题,Nokia 7650有五个方向键和一个操纵杆以及普通的键盘布局,上面这个例子就会返回操纵杆的值而不是键盘的值。这是处理事件的一种与设备无关的方法,也是一种不好的方法。更好的解决方法是在keyPressed ()方法内使用getGameAction ( int KeyCode)。通常,应用程序应该避免使用getKeyCode ( int gameAction)方法并且总是使用getGameAction ( int KeyCode)。

3 游戏外壳和游戏动作

  Nokia新型号手机支持的游戏外壳可能会影响游戏动作。Nokia第一款可以使用游戏外壳的手机是Nokia 3510i(参见图2)。如果手机支持游戏外壳,设备的游戏菜单中会有一个设置对话框,允许用户设置游戏外壳上的按键对应的游戏动作。用户必须设置这个按键映射,否则设计使用普通键盘布局的游戏就不能运行。


图2、 Nokia 3510i (左图)原装外壳 (右图)游戏外壳

  4 同时按键

  许多Nokia手机(例如,诺基亚6310i、3410、7210)不支持同时按下多键(也就是说,如果你按住Up键,那么MIDlet就不会知道你是否你按下Fire键)。虽然Nokia 7650支持同时按下多键(例如同时按住"1"和"3"键),但是操纵杆键物理上是无法同时又向左按又向上按的,所以在游戏中不可能有斜向的运动,并且在它被向上、向下、向左或者向右按下的时候是不能记录一个"点击"的。

  5 键名:避免硬编码你自己的文本字符串

  避免把硬编码文本和你的MIDlet中的键盘代码相关联。Canvas类提供了一个返回每个键盘代码关联的文本名称的方法:

public static String getKeyName(int keyCode);

  现在的Nokia设备的问题是这个方法始终返回一个英语字符串,而不是根据用户的语言选择。在未来的Nokia设备中,这个字符串可能会基于用户的语言选择。如果你的目的是开发可移植的游戏,那么必须记住getKeyName在不同的Nokia MIDP设备中返回不同的值。

  6 使用全屏画布(FullCanvas)功能键

  一个基于全屏画布(FullCanvas)游戏屏幕的功能键只能用于从游戏屏幕退出到不同的屏幕状态。带有FullCanvas类的Nokia用户界面应用编程接口明确地提供一个全屏图形区域用于绘图,可用于许多类型的游戏。

  继承于Nokia用户界面应用编程接口类FullCanvas的游戏屏幕将不会把功能键的键盘代码(即KEY_SOFTKEY1、KEY_SOFTKEY2,KEY_SOFTKEY3)用做游戏相关的动作,除非是用于从游戏屏幕返回到菜单中。

  如果一个MIDlet应用程序需要使用功能键命令,那么你必须使用默认的MIDP Canvas类,特别是如果你的MIDlet需要使用标签功能键的时候。当你使用FullCanvas的时候,你不应该提供你自己的功能键标签。

  7 MIDlet国际化

  你有可能需要把你的MIDlet国际化--例如,用于不同的地区和语言。MIDP的系统属性"microedition.locale"定义了设备的当前区域。它的值可以使用System.getProperty.方法取得。MIDP规范允许在这个系统属性中使用一个空值。然而,在Nokia的MIDP实现中是不允许空值的。CLDC的系统属性"microedition.encoding"定义了设备的默认字符编码。它的值可以使用System.getProperty方法取得。
想要了解更多MIDlet本地化问题,可以参阅《Writing World Aware J2ME Applications》http://wireless.java.sun.com/midp/ttips/worldaware/ ;的资源包。目前在MIDP中还没有一个标准机制用来处理资源包。这个文档使用一种简单的途径把用户界面文本从主程序中分离出来。它在Resource 类(二、9节)中被处理。

  把国际化特色加入一个MIDlet非常重要,但是这可能会增加你的MIDlet的大小。如果MIDlet大小对于某种特定MIDP设备来说是一个问题,那么你可能希望产生好几个不同编译版本的MIDlet。每个版本可以为一个或多个区域、语言本地化。

  8 设备特性:声音、振动和灯光

  如果你使用Nokia用户界面应用编程接口类Sound或DeviceControl(振动、灯光),你应该提供一个Options或Settings菜单和一个设置编辑器,来允许最终用户启动或者取消这样的设置。

  然而,如果你使用Nokia用户界面应用编程接口类Sound或DeviceControl(振动、灯光)并且通过设置JAD参数<>来把你的游戏安装到Games菜单(见一、11节),Games菜单中已经提供的设置允许最终用户启动或者取消这些特性。因此应用程序没有必要自己创建这样一个特性。除Nokia UI API技术文档之外,你还可以参考《Nokia UI API Programmer ’ s Guide》,它能提供很多有用的信息。

  9 设备无关性

  Nokia MIDP设备可能在屏幕尺寸、键盘布局和可用API等方面不同。为了创建可移植的游戏,在设计游戏时,这些差异应当被考虑在内。

  应用程序应该向系统询问屏幕的尺寸,并且避免绘制屏幕内容的时候硬编码坐标。可以使用Canvas类的getHeight和getWidth方法来达到这个目的。

  不同的Nokia MIDP设备中应用编程接口变化很大,开发者应该检查所要开发的设备平台上的应用编程接口。这是可以做到的,振动就是一个很好的例子。

try{
Class.forName("com.nokia.mid.ui.DeviceControl");
}
catch(ClassNotFoundException e){
//Can’t use vibration because the API
//is not supported in the device

  使用继承于默认MIDP Canvas类的游戏屏幕代替厂商特定的FullCanvas类,这有助于提高你的MIDlet的可移植性;然而,那就不可能实现全屏幕了。

  10 最优化

  MIDP设备的内存非常有限,所以使用内存时应格外小心。对于游戏来说一个很重要的限制就是有限的堆内存(heap memory):为了节省堆内存,对象引用不再需要被设置为"null",以便这些对象可被垃圾-收集(garbage-collected)。彩屏手机需要更多的内存来处理应用程序中的位图,这与更大的屏幕位深度和相关的内部数据结构有关。因此,虽然一个应用程序可能编写来使用在一个黑白屏幕的手机上,但是在彩屏手机上使用时,它可能消耗更多动态内存:就Nokia 7210来说,它显示一幅图片时比Nokia 6310i多用16倍的内存。
开发者应该在设计应用程序时考虑到这个因素,应该把同时加载的图片数降到最少的程度。例如,闪动屏幕图像应该能够在游戏图形图象创建之前被垃圾收集(通过设置所有到图像对象的引用为"null")。

  11 安装

  默认情况下MIDlet被安装到Nokia设备的Applications菜单下。如果设备有Games菜单的话,MIDlet还可以通过设置MIDlet的.jad文件中的Nokia-MIDlet-Category:Game参数来安装到这个菜单下。
二、实现游戏的步骤

  下图显示的是一个游戏MIDlet在成功安装和运行之后用户界面状态的典型的变化流程。我们想通过一个游戏者的视角来阐述开发移动游戏的过程。


图3 用户界面状态图表

  1 开始游戏

  在用户启动MIDlet之后,将显示游戏特定的闪动屏幕。闪动屏幕是FullCanvas的一个实例。它可用于显示一个公司的标志或者用动画形式介绍游戏。除了End键以外的所有键盘事件(MIDlet可用的)都可以跳过闪动屏幕并显示主菜单。还应该设置一个时间限定,能够在一定的时间过后自动跳出闪动屏幕进入游戏屏幕。
GameMIDlet类是游戏的基本类;它处理MIDlet的生命周期并且处理游戏显示。下面的代码是闪动屏幕和游戏MIDlet类的构架。

//Skeleton for the base class of game
import javax.microedition.midlet.*;
import javax.microedition.lcdui.*;
public class GameMIDlet extends MIDlet {
 private Display display = null;
 //Splash screen that starts the application
 private SplashFullCanvas splash;
 public GameMIDlet() {
  splash = new SplashFullCanvas(this);
 }
 protected void startApp() throws MIDletStateChangeException {
  if (display == null) {
   display = Display.getDisplay(this);
  }
  //splash screen to the display
  setDisplayable(splash);
 }
 protected void pauseApp() {
}

 protected void destroyApp(boolean p0)
 throws MIDletStateChangeException {
 }

 public void setDisplayable(Displayable dl) {
  display.setCurrent(dl);
 }
}

//Skeleton for the splash screen in Nokia Java Game
import javax.microedition.lcdui.*;
import java.util.Timer;
import java.util.TimerTask;
import com.nokia.mid.ui.*;

public class SplashFullCanvas extends FullCanvas {
 private GameMIDlet parent = null;
 private MainMenu menu = null;
 private Timer timer = null;
 public SplashFullCanvas(GameMIDlet parent) {
  this.parent = parent;
  menu = new MainMenu(
   Resources.getString(Resources.ID_GAME_NAME),
   List.IMPLICIT, parent);
   startTimer();
 }
protected void paint(Graphics g) {
 //Do the splash screen here
}
protected void keyPressed(int keyCode) {
 timer.cancel();
 timer = null;
 //All key events received set the main menu to the screen
 parent.setDisplayable(menu);
}

//Timer for the splash screen. Main menu is set to the display
//after 5 seconds.
private void startTimer() {
 TimerTask task =new TimerTask() {
  public void run() {
   parent.setDisplayable(menu);
  }
 };
 timer = new Timer();
 timer.schedule(task, 5000);
}

  2 主菜单(MainMenu)屏幕

  主菜单是包含游戏特定选项的固有目录("Continue"、"New game"、"Options"、"High scores"、"Instructions"、"About"和"Exit game")。"Continue"只有在游戏被暂停的时候才能被显示。当"Continue"显示的时候,它必须是目录列表的第一个元素。主菜单的标题必须是游戏的名称。下面的代码是主菜单的框架。

//Skeleton for the main menu
import javax.microedition.lcdui.*;
public class MainMenu extends List implements CommandListener {
 private GameMIDlet parent = null;
 private GameFullCanvas game = null;
 public MainMenu(String p0, int p1, String[] p2, Image[] p3,
 GameMIDlet parent) {
  super(p0, p1, p2, p3);
  init(parent);
 }
 public MainMenu(String p0, int p1, GameMIDlet parent) {
  super(p0, p1);
  init(parent);
 }
 public void init(GameMIDlet parent) {
  this.parent = parent;
  this.setCommandListener(this);
  //if game paused then "Continue" should be available in
  //selection list
  if (game != null && game.isPaused()) {
   if(!(this.getString(0).equals(
    new String(Resources.getString(
     Resources.ID_GAME_CONTINUE))))) {
      this.insert(0,
       Resources.getString(Resources.ID_GAME_CONTINUE),
       null);
     }
     this.setSelectedIndex(0,true);
    }
   else {
    //These must be with or without icons
    this.append(Resources.getString(Resources.ID_GAME_NEW), null);
    this.append(Resources.getString(Resources.ID_GAME_OPTIONS),null);
    this.append(Resources.getString(
     Resources.ID_GAME_HIGHSCORES), null);
    this.append(Resources.getString(
    Resources.ID_GAME_INSTRUCTIONS), null);
    this.append(Resources.getString(Resources.ID_GAME_ABOUT), null);
    this.append(Resources.getString(Resources.ID_GAME_EXIT), null);
   }
  }
  public void commandAction(Command p0, Displayable p1) {
   List lis = (List) p1;
   String selected =
   lis.getString(lis.getSelectedIndex());
   if (selected.equals(Resources.getString(Resources.ID_GAME_NEW))) {
    game = new GameFullCanvas(parent, this);
    parent.setDisplayable(game);
   }
   else if (selected.equals(
    Resources.getString(Resources.ID_GAME_OPTIONS))) {
     parent.setDisplayable(
      new OptionList(Resources.getString(Resources.ID_GAME_OPTIONS),
       List.IMPLICIT,
       parent, this));
    }
   else if (selected.equals(
    Resources.getString(Resources.ID_GAME_HIGHSCORES))) {
     parent.setDisplayable(new HighScore(parent, this));
    }
   else if (selected.equals(
    Resources.getString(Resources.ID_GAME_INSTRUCTIONS))) {
     parent.setDisplayable(
      new Instructions(
       Resources.getString(Resources.ID_GAME_INSTRUCTIONS),parent,this));
    }
   else if (selected.equals(
    Resources.getString(Resources.ID_GAME_ABOUT))) {
     parent.setDisplayable(
      new About(
       Resources.getString(Resources.ID_GAME_ABOUT),
       parent,
       this));
    }
   else if (selected.equals(
    Resources.getString(Resources.ID_GAME_EXIT))) {
     parent.notifyDestroyed();
   }
   else if (selected.equals(
    Resources.getString(Resources.ID_GAME_CONTINUE))) {
     if (game != null) {
      game.gameContinue();
      parent.setDisplayable(game);
   }
  }
 }

3 游戏屏幕

  如果用户从主菜单中选择"New game",那么开始游戏并且显示游戏屏幕。游戏屏幕使用全屏画布(FullCanvas)。如果按下任何功能键,那么用户界面必须返回主菜单,并且应使游戏暂停。其他的按键对游戏是有效的。注意:游戏不应该在屏幕上创建任何功能键的标签。如果必须使用功能键标签,那么应用程序应该使用默认的Canvas屏幕Commands。示例代码没有解决诸如线程和线程安全等问题,这些问题在设计的时候必须格外注意。下面的代码是游戏屏幕的框架。

import javax.microedition.lcdui.*;
import com.nokia.mid.ui.*;
public class GameFullCanvas extends FullCanvas {
private GameMIDlet parent = null;
private MainMenu menu = null;
private boolean gamePaused = false;
public GameFullCanvas(GameMIDlet parent, MainMenu menu) {
this.parent = parent;
this.menu = menu;
}
protected void paint(Graphics g) {
//Paint the game screen here
}
protected void keyPressed(int keyCode) {
if (keyCode == KEY_SOFTKEY1 || keyCode == KEY_SOFTKEY2
|| keyCode == KEY_SOFTKEY3) {
gamePaused = true;
//main menu to the screen
menu.init(parent);
parent.setDisplayable(menu);
}
}
public void gameContinue() {
gamePaused = false;
}
public boolean isPaused() {
return gamePaused;
}

  4 游戏选项屏幕

  用户可以通过选择主菜单中的"Options"选项改变特定的游戏选项。Options列表是固有的列表,包含处理游戏设置的条目,例如:声音、振动(见一、8节)、音调等等。如果要回到主菜单的话,需要使用Back命令。下面的代码是Options列表的框架。

  注意:如果游戏被安装到Games菜单的话,就不需要lights/sounds设置条目了,因为那些选项已经由Games菜单提供了。

import javax.microedition.lcdui.*;
public class OptionList extends List implements CommandListener {
private GameMIDlet parent = null;
private MainMenu menu = null;
private KeyDefinitions def = null;
private Command back = new Command("", Command.BACK, 2);
public OptionList(String p0, int p1, String[] p2, Image[] p3,
GameMIDlet parent, MainMenu menu) {
super(p0, p1, p2, p3);
this.menu = menu;
init(parent);
}
public OptionList(String p0, int p1, GameMIDlet parent,
MainMenu menu) {
super(p0, p1);
this.menu = menu;
init(parent);
}
private void init(GameMIDlet parent) {
this.parent = parent;
this.addCommand(back);
this.setCommandListener(this);
//These are just a examples for the game specific options
this.append(Resources.getString(Resources.ID_GAME_LEVEL),
null);
this.append(Resources.getString(Resources.ID_GAME_SOUNDS),
null);
this.append(Resources.getString(Resources.ID_GAME_VIBRA),
null);
}
public void commandAction(Command p0, Displayable p1) {
if (p0 == back) {
parent.setDisplayable(menu);
}
else {
List lis = (List) p1;
int idx = lis.getSelectedIndex();
switch (idx) {
case 0:
//TODO
break;
case 1:
//TODO
break;
case 2:
//TODO
break;
case 3:
parent.setDisplayable(def);
break;
//More if needed
default:
break;
}
}
}

5 高分屏幕

  当用户从主菜单中选择"High scores"选项的时候,高分就会显示出来。高分是显示在全屏画布(FullCanvas)实例上的。分数应该在一个屏幕上就显示完,而不要卷动页面,因为这样会给用户带来麻烦。
当然了,高分屏幕也可能会包含一些图片或者动画。用户应该能够通过按下左功能键、右功能键、数字键或者Send键返回主菜单。这个例子不提供任何处理高分的机制。处理高分的两种典型的方法是通过使用记录管理服务(RMS)永久保存分数或者通过HTTP连接把高分保存到服务器中。下面的代码是高分的框架。

import javax.microedition.lcdui.*;
import com.nokia.mid.ui.*;
public class HighScore extends FullCanvas {
private GameMIDlet parent = null;
private MainMenu menu = null;
public HighScore(GameMIDlet parent, MainMenu menu) {
this.parent = parent;
this.menu = menu;
}
protected void paint(Graphics g) {
//Paint the high scores here
}
public void keyPressed(int keyCode) {
if (keyCode != KEY_END) {
//selection list to the screen
parent.setDisplayable(menu);
}
}
}  

  6 教学屏幕

  当用户从主菜单中选择"Instructions"条目的时候,就会显示出游戏的规则。游戏规则文本放置在Form实例中。Form中应该包含用于进入下一条教学规则的命令(例如,标签"More")和回到主菜单的命令(例如,标签"Back")。"More"一般由左功能键控制,而"Back"由右功能键控制。如果教学规则里包含动画,那么这些动画应该使用全屏画布(FullCanvas)来显示。按下左功能键将跳过动画进入下一个教学屏幕。按下右功能键,用户将从动画中回到主菜单。键End将结束应用程序。下面的代码是文字教学规则和动画的框架。

//Text instructions. Text can be written in constructor or in own
method.
//Developer should remember that also instruction texts should be
//internationalized
import javax.microedition.lcdui.*;
public class Instructions extends Form implements CommandListener {
//Command for going next instruction if needed
private Command more = new Command(
Resources.getString(Resources.ID_GAME_MORE),
Command.OK, 1);
//Command for going back to the main menu
private Command back = new Command("", Command.BACK, 2);
private GameMIDlet parent = null;
private MainMenu menu = null;
public Instructions(String title, GameMIDlet parent, MainMenu
menu) {
super(title);
this.parent = parent;
this.menu = menu;
this.addCommand(back);
this.addCommand(more);
this.setCommandListener(this);
}
public void commandAction(Command p0, Displayable p1) {
if (p0 == more) {
//go to the next if needed e.g animation
parent.setDisplayable(new InstructionAnimation(parent));
}
else if (p0 == back) {
parent.setDisplayable(menu);
}
}
}
//Instruction animation
import javax.microedition.lcdui.*;
import com.nokia.mid.ui.*;
public class InstructionAnimation extends FullCanvas {
private GameMIDlet parent = null;
public InstructionAnimation(GameMIDlet parent) {
this.parent = parent;
}
protected void paint(Graphics g) {
//Do the animation here
}
public void keyPressed(int keyCode) {
if (keyCode == KEY_SOFTKEY1) {
//go to the next instruction screen if needed
}
else if (keyCode == KEY_SOFTKEY2) {
//selection list to the screen
parent.setDisplayable(new MainMenu(
Resources.getString (
Resources.ID_GAME_NAME),
List.IMPLICIT, parent));
}
}

  7关于(About)屏幕

  关于(About)屏幕显示游戏制作公司的消息文本或标志。当用户从主菜单中选择"About"选项的时候,就会启动这个屏幕。和教学规则页面一样,关于屏幕页面如果只需要文本信息的话,那么可以使用Form来实现。如果需要图像或动画,那么应该使用Canvas或FullCanvas。

//Text "About" code skeleton
import javax.microedition.lcdui.*;
public class About extends Form implements CommandListener {
//Command for going back to the main menu
private Command back = new Command("", Command.BACK, 1);
private GameMIDlet parent = null;
private MainMenu menu = null;
public About(String title, GameMIDlet parent, MainMenu menu) {
super(title);
this.parent = parent;
this.menu = menu;
this.addCommand(back);
this.setCommandListener(this);
}
public void commandAction(Command p0, Displayable p1) {
if (p0 == back) {
parent.setDisplayable(menu);
}
}

  8 退出

  从主菜单中选择"Exit game"选项来中止游戏并释放所有的资源。 

  9 Resources类

  Resources类不是一个用户界面类,与本文中介绍的其他类不同。这个类处理国际化问题。

/**
* A simple class to simulate a resource bundle.
* Modify the contents of this class according to the
* locales/languages you want your application to support.
* In your application, retrieve a string using code such as the
* following:

* String s = Resources.getString(Resources.ID_GAME_NEW);* 

* Copyright (C) 2002 Nokia Corporation
*/
public class Resources {
// Identifiers for text strings.
public static final int ID_GAME_NEW = 0;
public static final int ID_GAME_OPTIONS = 1;
public static final int ID_GAME_HIGHSCORES = 2;
public static final int ID_GAME_INSTRUCTIONS = 3;
public static final int ID_GAME_ABOUT = 4;
public static final int ID_GAME_CONTINUE = 5;
public static final int ID_GAME_BACK = 6;
public static final int ID_GAME_MORE = 7;
public static final int ID_GAME_EXIT = 8;
public static final int ID_GAME_LEVEL = 9;
public static final int ID_GAME_SOUNDS = 10;
public static final int ID_GAME_VIBRA = 11;
public static final int ID_GAME_NAME = 12;
// List of supported locales.
// The strings are Nokia-specific values
// of the "microedition.locale" system property.
private static final String[] supportedLocales = {
"en", "fi-FI", "fr", "de"
};
//NOTE: default language must be the first one
//for getString to work!
// Strings for each locale, indexed according to the
// contents of supportedLocales
private static final String[][] strings = {
{ "New game", "Settings", "High scores", "Instructions",
"About","Continue", "Back", "More", "Exit game",
"Level", "Sounds","Shakes", "Game name" },
{ "Uusi peli", "Asetukset", "Huipputulokset", "Peliohjeet",
"Tietoja","Jatka", "Poistu", "Jatka", "Poistu",
"Vaikeusaste", "Peli??net", "V?rin?tehosteet",
"Pelin nimi" },
{ "Nouveau jeu", "Paramètres", "Scores", "Instructions",
"A propos","Continuer", "Retour", "Suite", "Sortir",
"Niveau", "Sons", "Vibrations", "Jeu nom" },
{ "Neues Spiel", "Einstellungen", "Rekord", "Anleitung",
"über","Weiter", "Zurück", "Weiter", "Beenden",
"Ebene", "Ton", "Vibrationen", "Spiel name" }
};
/**
* Gets a string for the given key.
* @param key Integer key for string
* @return The string
*/
public static String getString(int key) {
String locale = System.getProperty("microedition.locale");
if (locale == null) {
locale = new String(""); // use empty instead of null
}
// find the index of the locale id
int localeIndex = -1;
for (int i = 0; i < supportedLocales.length; i++) {
if (locale.equals(supportedLocales[i])) {
localeIndex = i;
break;
}
}
// not found
if (localeIndex == -1) {
// defaults to first language, in this example English
return strings[0][key];
}
return strings[localeIndex][key];
}

 


2005年08月20日

队伍组成

开发团队

n              制作人

n              执行制作人

n              策划团队

n              程式团队

n              美术团队

销售团队

测试团队

游戏评论队伍

游戏制作人

n              开发组长(always)

n              资源管理  (Resource  Management)

n              行政管理  (Administration)

n              向上負責  (Upward  Management)

n              专案管理  (Project  Management)

游戏执行制作人

n              专案管理执行  (Project  Management)

n              Daily  運作

n              House  Keeping

n              Not  full-time  job  position

 

游戏策划

n              故事设计  (Story  Telling)

n              脚本设计  (Scripting)

n              玩法设计  (Game  Play  Design)

n              关卡设计  (Level  Design)

n              游戏調適  (Game  Tuning)

n              数值设定  (Numerical  Setup)

n              AI  设计  (Game  AI)

n              音效设定  (Sound  FX  Setup)

n              场景设定  (Scene  Setup)

游戏美术

n              场景  (Terrain)

n              人物  (Character)

n              建模  (Models)

n              材質  (Textures)

n              动作  (Motion  /  Animation)

n              特效  (FX)

n              用户界面User  Interface

游戏程序

n              游戏程序  (Game  Program)

n              游戏开发工具  (Game  Tools)

n              Level  Editor

n              Scene  Editor

n              FX  Editor

n              Script  Editor

n              游戏Data  Exporters  from  3D  Software

n              3dsMax  /  Maya  /  Softimage

n              游戏引擎开发Game  Engine  Development

n              网络游戏服务端开发Online  Game  Server  Development

 

游戏开发流程

创意            提案                            制作                            整合                                          测试      
           赞成          雏形                                                                                    除错                            调试  

                                                                                                         Pre-alpha          Alpha        Beta                      结束
 
n              创意  (Idea)

n              提案  (Proposal)

n              制作  (Production)

n              整合  (Integration)

n              测试  (Testing)

n              除錯  (Debug)

n              调试  (Tuning)

游戏设计(Concept  Design)

n              游戏类型  (Game  Types)

n              游戏世界观  (Game  World)

n              故事  (Story)

n              游戏特色  (Features)

n              游戏玩法  (Game  Play)

n              游戏定位  (Game  Product  Positioning)

n              Target  player

n              Marketing  segmentation  /  positioning

n              风险评估  (Risk)

n              SWOT  (优势Strength/缺点Weakness/机会Opportunity/威胁Threat)

游戏提案  (Proposal)

n              系統分析  (System  Analysis)

n              游戏设计文件撰写  (Game  Design  Document)

n              传播媒介文件撰写  (Media  Design  Document)

n              技术设计文案撰写  (Technical  Design  Document)

n              游戏专案建立  (Game  Project)

n              时间表Schedule    

n              进程/控制Milestones  /  Check  points  

n              管理Risk  management  

n              测试计划书

n              团队建立  (Team  Building)

游戏开发  (Production)

n              美术量产制作

n              (建模)Modeling

n              (结构)Textures

n              (动画)Animation

n              (动作)Motion

n              (特效)FX

n              程序开发  (Coding)

n              策划数值设定

游戏整和  (Integration)

n              关卡串联  (Level  Integration)

n              数值调整  (Number  Tuning)

n              音效置入  (Audio)

n              完成所有美术

n              程旬与美术結合

n              (攻略)Focus  Group  (说明书User  Study)

n              发布一些攻略截图Release  some  playable  levels  for  focus  group

游戏测试  (Testing)

n              Alpha(α)  测试

n              除錯  (Debug)

n              Beta  (β)测试

n              数值微调

n              Game  play  微调

n              对网络游戏而言  (MMOG)

n              封閉测试  (Closed  Beta)

n              開放测试  (Open  Beta)

n              压力(极限)测试  (Critical  Testing)

n              网络游戏才有

关于Bug

 

n              Bug  分級  (Bug  Classification)

n              A  Bug

n              B  Bug

n              C  Bug

n              S  Bug

n              Principles

n              Bug  分級从严

n              Tester(测试对象?—)  vs  Debugger(调试程序)

系统层System  Layer  –  APIs

n              3D  Graphics  API

n              DirectX  9.0  SDK  –  Direct3D

n              OpenGL  2.0

n              2D  API

n              DirectX  9.0  SDK  -  DirectMedia

n              Win32  GDI

n              Input  Device

n              DirectX  9.0  SDK  –  DirectInput

n              Audio

n              DirectX  9.0  SDK  –  DirectSound  /  Direct3DSound  /  DirectMedia

n              OpenAL

n              OS  API

n              Win32  SDK

n              MFC

n              Network

n              DirectX  9.0  SDK  –  DirectPlay

n              Socket  library

引擎层Engine  Layer

n              3D  Scene  Management  System

n              Scene  Graph

n              Shaders

n              2D  Sprite  System

n              Audio  System

n              Gamepad

n              Hotkey

n              Mouse

n              Timers

n              Network

n              DDK  Interface

n              Terrain

n              Advanced  Scene  Management  –  Space  Partition

n              BSP  Tree

n              Octree

n              Character  System

n              Motion  Blending  Techniques

n              Dynamics

n              Collision  Detection

n              SoundFX

n              User  Interface

游戏层Game  Play  Modula

n              NPC  (Non-playable  Characters)

n              Game  AI

n              Path  Finding

n              Finite  State  Machine

n              …

n              Avatar

n              Combat  System

n              FX  System

n              Script  System

n              Trading  System

n              Number  System

n              …

Game  Dev  Tools

n              Visual  C/C++

n              .net  2003

n              Visual  C/C++    6.0+  SP5

n              DirectX

n              Current  9.0c

n              NuMega  BoundsChecker

n              Intel  vTune

n              3D  Tools

n              3dsMax/Maya/Softimage

n              In-house  Tools

 

~~~~~~~~~~结束~~~~~~~~~

游戏分类

n              RPG  (Role  playing  games角色扮演)

n              AVG  (Adventure  games冒险类)

n              RTS  (Real-time  strategy    games既时战略)

n              FPS  (First-person  shooting  games主视觉射击)

n              MMORPG(多人在线角色扮演)

n              SLG  (战棋)

n              Simulation(模拟)

n              Sports(运动)

n              Puzzle  games(解迷)

n              Table  games(棋牌)

2005年08月18日

作者:佚名

小T一边作自己的3D引擎,一边研究half-life2的图形引擎,留下点心得。

整体上half-life2给小T的感觉一是庞大,二就是难读,com方式组织引擎,小T也不敢妄加评价,只是小T觉得整个引擎非常的难读。小T只有借助调试器才勉强对half-life2的图形引擎有个大致的了解,至于怎么编译half-life2,怎么运行,以及怎样调试,小T就不详细的描述了,到网上随便一搜索就能找到一大堆,这里只是简单的看看他的图形系统。

当前的3D环境,想要提高渲染速度,一个最基本的方法就是要减少硬件的渲染状态的切换,今天小T就是讲hl2的渲染状态管理部分的。在hl2里面,渲染状态主要被放在了一个叫ShadowState_t的结构里面,这个结构对应了大多数的硬件渲染状态,渲染系统维护多个ShadowState_t结构,在必要的时候把ShadowState_t的内容真正的设置到硬件上面,从而减少硬件的状态切换。当然实际上却没有这么简单,上面只是个简单的介绍,下面开始详细的解释hl2怎么进行硬件状态管理。

hl2里面渲染状态管理的一个比较重要的类就是CTransitionTable,从名字上面看,这个类描述的是一种转换,他其实就是描述了,渲染过程中硬件渲染状态的转换流程,hl2把硬件状态的管理也放在了这个里面,举个例子来说可能比较清楚。

比如:整个场景里面有2个物品,第一个物品使用一组渲染状态,第二个物品使用另外一组渲染状态,我们先渲染第一个物品,设置成第一个物品的渲染状态,接着渲染第二个物品,设置第二个物品的渲染状态,这个就是正常的操作方式,如果第一个物品和第二个物品的渲染状态有相同的,那么有些设置renderstate的函数就不用调用,我们可以把渲染状态看作是一种状态机,从第一种状态转换到第二种状态,我们要作一些事情,如果我们能建立一张行动表,表里面记录状态转换的时候要执行的动作,那么我们就可以简化这种状态管理模型,这个就是CTransitionTable类完成的工作。

对于场景里面的每个materila(顺便说一句,hl2也是按照material的方式组织渲染动作的),都对应了一种渲染状态,这些渲染状态都被CTransitionTable类记录下来,每一个渲染状态对应一个唯一的id,那么material只用记录他对应的渲染状态id就能完成自己的渲染了。比如我们要渲染一个物品,我们告诉渲染系统我们使用的渲染状态id,渲染系统自动完成渲染状态的设置,自动完成渲染状态切换的最小化任务,然后我们调用一个drawprimitive就ok。实际上的操作也没有这么简单,我们再看看实际一点的东西。

hl2里面的渲染状态操作都被封装到了ShaderRenderState_t类里面
struct ShaderRenderState_t
{
 RenderPassList_t m_Snapshots[4];//代表了一种渲染方式,缓冲4种渲染方式0=默认,1=带color数据,2=带alpha数据,3=color+alpha
int m_Flags;// 标志,
VertexFormat_t m_VertexFormat;// 顶点格式
int m_VertexUsage;
};

struct RenderPassList_t
{
int m_nPassCount; // 渲染pass的数目
StateSnapshot_t m_Snapshot[MAX_RENDER_PASSES]; // 实际是一个short形式,就是渲染状态id
};

解释下RenderPass这个东西,一个material在hl2里面渲染方式分成了4种,每种都对应了一个RenderPassList_t的类,而ShaderRenderState_t把这4种渲染方式都缓存了,嗯,举个例子,比如我现在想要按照默认的方式渲染一种material,我传递一个ShaderRenderState_t的结构给渲染系统,渲染系统根据我要求的渲染方式索引到正确的RenderPassList_t元素,使用里面记录的StateSnapshot_t数组里面的某个正确的id来获取到正确的渲染状态。

我们已经知道了渲染系统的客户端怎样要求渲染系统完成自己的渲染状态设置了,接下来看看渲染系统本身怎样完成这个工作,当渲染系统得到一个渲染状态id以后,就应该要获取到与之对应的渲染状态,并且正确的设置好这个状态,换句话说也就是要完成当前状态到新状态的切换,首先看看渲染系统怎么去找到正确的渲染状态,这就落在了IShaderAPI和CTransitionTable身上了,小T使用的是DX9的ShaderApi,所以这个任务是由CShaderAPIDX8这个类来完成了,在CShaderAPIDX8类里面有一个CTransitionTable类的数据成员m_TransitionTable,每当要获取到一个渲染状态id以后,ShaderApi就告诉m_TransitionTable要使用新的渲染状态了,并且传递新状态的id,到这里先停一下,一直小T都使用渲染状态这几个字在描述,那究竟hl2使用什么样子的数据结构来表示实际的渲染状态呢?ShadowState_t对应的是硬件状态,而还有一个东西对应了vs,ps的状态,他也是要在状态转换的时候进行设置的,渲染状态id对应的其实是SnapshotShaderState_t 结构:

struct SnapshotShaderState_t
{
ShadowShaderState_t m_ShaderState;
ShadowStateId_t m_ShadowStateId;
};

struct ShadowShaderState_t
 {
// The vertex + pixel shader group to use…
VertexShader_t m_VertexShader;
PixelShader_t m_PixelShader;
// The static vertex + pixel shader indices
int m_nStaticVshIndex;
int m_nStaticPshIndex;
};

ShaderApi获得了新的渲染状态id以后,和当前的渲染状态id比较,如果不相同,则获取到ShadowStateId,如果也不相同,那就要设置新的ShadowState了,这个操作下面讲解。然后,ShaderApi保存当前的渲染状态id,保存ShadowStateId,同时设置ShadowShaderState,这些都设置完成了,硬件的渲染状态也就设置完成了,然后的任务就是调用DrawPrimitive等等函数完成绘制的时候了,这个绘制是由各个vs,ps完成的,关于这个部分,小T留到下次讲解。

现在我们来看看shaderapi,怎么设置新的ShadowState,基本的方式就是获得两个状态之间的转换表,执行表里面规定的动作,而实际上也是这样进行的,那现在的重点就落在了转换表上面。

CTransitionTable保存了当前渲染过程中所有可能的ShadowState(关于这些是怎么保存起来的,这些信息是怎么获取到了,下面讲解),然后再这些状态中间拉了一张网,每两个状态之间总有一个弧,同时保存了两个状态进行切换的时候要执行的操作,CTransitionTable的主要数据成员如下:

CUtlVector< ShadowState_t > m_ShadowStateList ; // 全部的ShadowState_t的vector,ShadowState的id作为这个vector的下标
CUtlVector< CUtlVector< TransitionList_t > > m_TransitionTable; // 状态转换表,TransitionList_t就代表了状态转换的操作.

struct TransitionList_t
{
unsigned short m_FirstOperation;
unsigned char m_NumOperations;
unsigned char m_nOpCountInStateBlock;
IDirect3DStateBlock9 *m_pStateBlock;
};

struct TransitionOp_t
{
ApplyStateFunc_t m_Op;
int m_Argument;
};

TransitionOp_t也放到了一个vector里面,而TransitionList_t的第一个和第二个成员能在这个vector里面寻址,从而定位到实际的操作,而TransitionOp_t定义的就是实际的操作,第一个成员是一个函数指针,执行操作就是调用那个函数指针,并且传递两个参数,一个是新的ShadowState_t,一个就是结构里面的另外一个成员。

  总结下,CTransitionTable保存了全部的ShadowState_t,保存全部的TransitionOp,都是使用下表作为索引访问,再全部的ShadowState_t之间建立TransitionList,当要进行状态切换的时候执行状态之间定义的TransitionOp就完成了状态的切换了.那ShaderApi是怎么建立ShadowState_t表格和TransitionOP表格的呢?

  ShadowState_t的表格是在material创建的时候完成创建的,创建了一个新的ShadowState_t了以后就会向转换表里面加入新的节点,并且设置好转换操作,而这动作却是借助Draw动作完成了。

  下面结合源代码看看上面这些功能的具体实现。

  先看看draw的流程,最常用操作就是建立一个DynamicMesh,然后使用一个MeshBuilder修改刚刚建立的DynamicMesh,然后调用mesh的Draw函数,我们就从这里开始。

void CDynamicMeshDX8::Draw( int firstIndex, int numIndices )
  CBaseMeshDX8::DrawMesh(); // 调用自己类的函数,实际上是从父类继承来的
    ShaderAPI()->DrawMesh( this ); // 调用CShaderAPIDX8的函数
      m_pMaterial->DrawMesh( m_pRenderMesh ); // 转调用CMaterial的DrawMesh函数
        ShaderSystem()->DrawElements( m_pShader, m_pShaderParams, &m_ShaderRenderState );//调用CShaderSystem函数
        // 计算当前这次绘制操作的方式:普通?color?alpha?alpha+color?
        int mod = pShader->ComputeModulationFlags( params );
        g_pShaderAPI->SetDefaultState(); // 调用CShaderAPIDX8的函数
        ShaderUtil()->SetDefaultState(); // 实际上回到了CMaterialSystem:SetDefaultState()
        // 一系列的CShaderAPIDX8的函数,这些函数只是比较纪录,并不真正的修改硬件的状态
        // 准备渲染,纪录当前的渲染操作,接下来的CurrentStateSnapshot()函数返回的就是当前渲染的状态id
        PrepForShaderDraw( pShader, params, pRenderState, mod );
        // 调用CShaderAPIDX8的函数,纪录当前要渲染的状态ID
        g_pShaderAPI->BeginPass( CurrentStateSnapshot() );
        // 真正的渲染操作,一般是调用到CBaseShader::DrawElements函数
        pShader->DrawElements( params, mod, 0, g_pShaderAPI );
        // 这里根据第三个参数的有无进行不同的操作,如果0就实际的渲染,否则是要生成ShadowState,
        OnDrawElements( ppParams, pShaderShadow, pShaderAPI );
        // 这个函数调用的地方就不好说了,他是和你当前material使用的shader相关联的,这个函数开始的时候
        // 完成些设置操作以后,调用Draw函数,回到CBaseShader
          CBaseShader::Draw();
          // 如果是设置ShadowState,
          GetShaderSystem()->TakeSnapshot();
          // 否则是渲染,都是调用到CShaderSystem的函数
          GetShaderSystem()->DrawSnapshot();
          // 获取到RenderPass
          RenderPassList_t& snapshotList = m_pRenderState->m_Snapshots[m_nModulation];
          // 渲染这个pass
          g_pShaderAPI->RenderPass();
          // 设置这个渲染状态
          m_TransitionTable.UseSnapshot( m_nCurrentSnapshot );
          // ShadowStateId
          ShadowStateId_t id = m_SnapshotList[snapshotId].m_ShadowStateId;
          if (m_CurrentSnapshotId != snapshotId)
          {
            if ( m_CurrentShadowId != id )
            {
              TransitionList_t& transition = m_TransitionTable[id][m_CurrentShadowId];
              ApplyTransition( transition, id );
              ApplyTransitionList( snapshot, nFirstOp, nOpCount );
              ShadowState_t& shadowState = m_ShadowStateList[snapshot];
              TransitionOp_t* pTransitionOp = &m_TransitionOps[nFirstOp];
              for (int i = 0; i < nOpCount; ++i )
              {
                (*pTransitionOp->m_Op)(shadowState, pTransitionOp->m_Argument);
                ++pTransitionOp;
              }
            }
            m_CurrentSnapshotId = snapshotId;
          }
          // 渲染这个mesh,这个函数就会调用到IDERICET3DDEVICE9::DrawPrimitiveIndex
          m_pRenderMesh->RenderPass();
          DoneWithShaderDraw();

接着说GetShaderSystem()->TakeSnapshot();分支

GetShaderSystem()->TakeSnapshot();
  g_pShaderAPI->TakeSnapshot();
    m_TransitionTable.TakeSnapshot(); // CTransitionTable:TakeSnapshot();
      ShaderShadow()->ComputeAggregateShadowState(); // 计算最后的结果CShaderShadowDX8函数
      shadowStateId = FindShadowState();// 查找,如果不存在就创建
      if(shadowStateId == -1)
      {
        shadowStateId = CreateShadowState();// 创建
        // 创建一个空的连接到以前的所有状态
        for ( i = 0; i < newShaderState; ++i )
        {
          int newElem = m_TransitionTable[i].AddToTail();
          m_TransitionTable[i][newElem].m_FirstOperation = INVALID_TRANSITION_OP;
          m_TransitionTable[i][newElem].m_NumOperations = 0;
          m_TransitionTable[i][newElem].m_nOpCountInStateBlock = 0;
          m_TransitionTable[i][newElem].m_pStateBlock = NULL;
        }
        // 以前有的状态都创建一个空的连接到新的状态
        for ( i = 0; i <= newShaderState; ++i )
        {
          int newElem = m_TransitionTable[newShaderState].AddToTail();
          m_TransitionTable[newShaderState][newElem].m_FirstOperation = INVALID_TRANSITION_OP;
          m_TransitionTable[newShaderState][newElem].m_NumOperations = 0;
          m_TransitionTable[newShaderState][newElem].m_nOpCountInStateBlock = 0;
          m_TransitionTable[newShaderState][newElem].m_pStateBlock = NULL;
        }
      }
      // 新加入的状态,和原来的状态连接到一起
      for (int to = 0; to < shadowStateId; ++to)
      {
        CreateTransitionTableEntry( to, shadowStateId );
      }
      for (int from = 0; from < shadowStateId; ++from)
      {
        CreateTransitionTableEntry( shadowStateId, from );
        // 这个函数就有点复杂了,不再这里列举了,就是根据两个状态的不同设置转换的操作
      }

到这里,小T要罗索的就全部完了,剩下的就要去看源代码了,小T再列几个注意的地方就结束。

  很多的类之间的通讯方式是靠类成员,全局变量完成的,多注意那些准备函数,结束函数,对理解参数的传递有帮组。

  多使用调试器,留意那些指针变量的地址,很多的指针都是指向同一个东西的。


2005年08月01日

炮炮版权所有 2005
http://www.alphasun.org
msn: sunshaking@hotmail.com QQ: 43626070

转载请注明出处。编辑人员请负责地将图片一同转载,不要丢失。如果发生图片丢失请读者到作者个人主页上阅读。< BR>

概念介绍

传统的几何约束求解方法是数值方法。数值方法普适性虽好,却有诸多弊病。比如时间复杂度高,几何约束系统规模稍大就无法实时求解。再者无法求出局部过约束、局部欠约束,这是由于数值求解的整体性造成的。另外数值方法求解过程不稳定,特别是在欠约束情况下即便是不太大的规模收敛也存在困难,即便是收敛了也有可能收敛到一个不合理的解。 基于图论的分解规划方法能够将约束图分解成为可以独立求解的子系统。子系统通过数值方法或者公式法进行求解。分解过程可以在不实际实施数值求解的情况下进行。分解过程的时间复杂度远比数值整体求解的复杂度低。所以分解规划是非常划算的。

约束图是用图论方法表示一个几何约束系统的方法。图中的顶点数约束系统中的几何,其边是约束。满约束是指一个子图形成了内部图元的相对位置、形状不发生改变的一种结构。如果把几何看成未知量,把约束看成方程,那么从代数理论上来讲,一个满约束子图就是未知量数比方程数多3的一个方程组。这个方程组的解空间是三维的。

分解规划的任务就是要将一个约束图分解成一个个的刚体以及小刚体构成的大刚体。这样,一个约束图就形成一个分解规划树。分解规划树的每一个结点都可以在其子节点求解完毕之后进行求解。由此可见,在分解树进行的求解步骤是相当具有独立性的。这样便降低了每一步的数值求解或者公式求解的复杂度。

由于图论计算丝毫不求助于数值,所以分解规划阶段的计算过程具有很好的稳定性,即便几何发生了数值上的改变。

参考资料

Meera Sitharam A Scientist of Computer Graphics
Christoph M. Hoffmann Professor of Computer Science

 

 

 

算法的算例

下面图中的是文献上的经典例子。这个图分为三个部分,每个部分内部结构都相同,并且它们各自都是一个满约束子图。trip01.gif ~ trip05.gif是其中的五为个基本刚体。基本刚体是最小的刚体。基本刚体是很多的。分解规划第一步,是找出所有的基本刚体。

trip01.gif
trip01.gif
trip02.gif
trip02.gif
trip03.gif
trip03.gif
trip04.gif
trip04.gif
trip05.gif
trip05.gif

然后就会进一步找到由基本刚体构成的大刚体,如trip06.gif ~ trip08.gif所示。

trip06.gif
trip06.gif
trip07.gif
trip07.gif
trip08.gif
trip08.gif

直到最后刚体无法进一步归并,这样分解算法就结束了。这个例子会得到一个最大的刚体,它覆盖了整个原始约束图。

trip09.gif
trip09.gif

下面是大图

 

读程序员网游专题云风的文章有感

本文作者:sodme
本文出处:http://blog.csdn.net/sodme
声明:本文可以不经作者同意任意转载、复制、引用。但任何对本文的引用,均须注明本文的作者、出处以及本行声明信息。

  1.勇于承认失败
  国内的游戏厂商,让人觉得能有大家风范的少之又少,炒作、随意夸大游戏品质,好象不吹牛就没人知道他游戏作得烂似的。由于网游市场渐显的各种风险增加,资本市场从2004年底开始,对网游的投入渐趋理性化。在这样一个群雄逐鹿的时代,唯有靠品质才能最终取胜,小的游戏厂商会逐渐被市场淘汰或边缘化。未来可以在市场上存活的,将是为数不多的大游戏厂家。值得说明的,这里的“大厂家”并不是一定就是指现在的某些看起来很大的厂家。
  在游戏业界广泛充斥着浮躁的大氛围下,智冠和网易给我留下了难得的好印象。王俊伯在RO的代理权易手给盛大后,公开承认智冠的RO运营是失败的。而网易的云风,在05年6期的程序员杂志的网游专题文章中,不仅承认大话I是失败的,更是总结了若干失败的教训和经验,这些东西可以帮助多少人少走弯路呀!网易的作风,给外界的感觉,向来是踏实、务实的。在东西没出来之前,他们从来不会怎么高调作什么所谓的花边宣传,这与国内很多游戏厂商的作法大相痉庭。大话系列的游戏品质相信玩过的自有评说。
  承认失败,是需要勇气的,同样也是需要相当自信的。因为他们相信自己将来可以作得更好,所以才会勇敢地承认自己过去作得不好。不管是智冠,还是网易,我想,没有人可以否认他们在国内玩家心中的份量。

  2.结对编程
  结对编程,一个曾经非常时髦的话题,这个概念刚提出来时,我还在一个名不见经传的“教授型”网络教育软件公司。当时,我们的项目负责人也一再提出结对编程的想法,但是,项目组的成员对执行它却丝毫没有兴趣。是的!结对编程的前提是:参与结对编程的两个人,在技术水平上我认为一定要是水平相差不大的人,但也不能水平相同。你跟一个连“面向对象”都还没有弄清楚是什么的人说什么结对编程,那绝对是在扯蛋!技术水平相差太大,是很容易造成歧义的,水平太低的人往往无法正常理解水平高的人的设计思想,除非在结对编程时指定谁是说话算话的老大,就是说当出现意见相左时到底由谁说了算!有人说,应该民主讨论决定谁说了算。我要说的是,这种民主,在一定的范围内是有效的,但越过了范围,当你该拿决断时就一定要拍板决定,不能给项目成员以犹豫不决的印象,否则别人的工作作起来也是心虚的,因为他们不知道自己现在作的东西,到了明天或者后天是否就会被推翻而不得不重写。对于水平相差不大的人,我觉得结对编程是有益的,我之所以这样说,绝不是因为自己多看了几本书就在这里瞎忽悠,因为我曾经跟我过去的同事这样作过,不管是思维的广度还是深度,都要比一个人写的代码更漂亮,也更安全。坐在旁边看的那个人,往往可以指出你现在正在写的这段代码的问题所在。除此之外,结对编程还是对工作效率的有效约束,两个人一起工作时,精力必定是相对集中的,不会出现三心二意或跑神的情况。

  3.阶段性的测试和验收
  在多人参与的项目中,阶段性的测试和验收工作是必须要有的。当然,我们的设计总是随着开发的进程而不断调整的,那种一次设计之后再不改动的完美方案在现实中是几乎不存在的。项目组的成员,在阶段性的工作完成之后,应该由项目负责人召开专门的阶段性成果验收会议或演示,由设计者提供相关的设计文档、演示代码等。而参与验收的人,特别是以后可能要用到这一部分功能的人员,要详细询问、了解它的接口使用方式,设想你在使用这个接口时的可能情况以及可能出现的问题。把后期可能出现的因修改延期风险尽可能提到前面来。

  4.策划内容与程序实现的配合
  游戏的程序人员和策划人员,在很多公司里是“互相鄙视”的。程序鄙视策划整天异想天开,不顾技术实现难度,随意增加、修改变态方案;而策划人员则鄙视程序人员甚至连游戏都不会打,还在那写游戏程序,他们鄙视程序甚至连NPC的确切含义都不知道。而我要说的是,首先,对于程序人员,不管你是作服务器的,作客户端的,还是作数据库的,既然你选择了作游戏,你就得去努力多打一些好游戏。别跟我说实际上你不喜欢游戏,作游戏只是为了混口饭吃,OK!混饭吃可以有很多方式,请不要拿玩家的心情开玩笑。一个游戏公司想成名很难,可是,如果想毁掉则非常容易,他们只要出一两款烂游戏就足矣!而对于策划人员,我要说的是,你们的能想出那些变态方案并不是你们的错,并且我还鼓励你们尽可能地变态,但是,在作案子的同时,我也建议你们尽可能地提高自己的知识量,扩大自己的知识视野,努力多学习一点有关游戏程序方面的知识,我不是让你们学着写程序,但你们可以多与程序沟通,多了解程序的原理。一个方案提出来时,你要考虑到客户端的表现难度以及服务器方面的数据包广播量,当然,限于自己的知识层次,你们可能无法确切知道这些方面的答案,那么,剩下的就是唯一的一条路:多沟通!

  5.项目管理者的角色定位
  通常的项目管理者,会有两种类型:监工型和实干型。前者是只说话,不作具体的编码、设计;而后者,不光是“说话”,还要作具体的编码、设计。从我目前接触到的项目组来看,两者都有,但我更欣赏后者。而作为投资商,我则建议他们选择前者,前提是:投资商有足够的资金可以烧!欣赏后者的原因,是因为程序员广泛比较崇拜技术牛人,他们总希望自己的老大是一个在技术上非常牛的人,这样他们就会觉得自己有一个靠山,有问题时心里也会比较有底,因为他们知道老大可以给他们一些好的建议。而选择前者,是因为监工型的项目管理者,往往可以在投资商和开发团队之间站在一个相对客观的角度来对项目进行进度跟踪和控制,他们明确知道投资商的项目预期,也可以明确知道自己想要的是一个怎么样的开发团队,团队成员出现问题他可以果断地进行替换。

  6.抽象的层次
  表现层和逻辑层的分开,是一个再通俗不过的道理。这一点,在游戏客户端上表现是最为明显的。界面层的代码,不要与逻辑层的代码直接混为一谈。说过最简单的道理:两个EDIT控件,一个加法逻辑。在实现加法逻辑时,有两种方法。一种是把EDIT的值直接转换后拿来直接作加法,另一种是首先作一个加法函数,然后将EDIT的值转成数值再调用这个加法函数。这是再简单不过的界面与逻辑层的分离方式了,可惜的是,很多的懒人却没有这样作,又或者,他们尚且还没有意识到自己为什么要象第二种方法那样去作?

  7.测试自动化的实现
  我从04年4月左右开始作一个休闲游戏,今年又开始作MMORPG。在休闲游戏里,我实现了游戏机器人,就是自己写了个游戏外挂,可以让机器人自己去打游戏。游戏机器人,对于游戏的自动化测试是非常重要的,甚至,我认为是必须的。大量用户的压力测试以及大量游戏逻辑的BUG测试,都需要有一个象游戏机器人的程序自动为我们完成。在云风的文章里,云风为我们提供了一种思路:录制网络数据包行为。这是一个很好的想法,我很赞成。但我想说的是,要作游戏机器人,首先对你的程序设计要有一定的要求,即:你的游戏逻辑设计,不管是服务器端还是客户端都要尽可能地把功能模块化,接口化。这种模块化的粒度越小越好!当然,游戏机器人的实现,更多的,还是依赖于客户端的设计。只要客户端能把每个游戏功能都尽可能地以小粒度加以实现,那种游戏机器人实现起来其实相当容易。一切,只有一个要求:把游戏功能尽可能小的粒度化。

  8.最重要的一点,把游戏首先当作一下正常的“软件”来对待
  我们经常抱怨国内的游戏是多么多么地烂,其实,你只要对他们的开发团队和开发方式稍微了解一下应该就完全不会大惊小怪的了。有相当多的公司,屹今为止,尚没有把游戏当作一个正常的“软件开发”来对待,在项目的管理上,在人员的组织和任用上,都带有相当程度的随意性。所谓的项目延期,品质低劣,BUG频出,归根结底是人的问题!先不要把目标放那么远,要想作出一个好的游戏,请先把游戏当作一个再正常不过的软件项目来开发,来规范,来控制!


分两种,一种是动画放完后sprite消失

void Change_Sp(void)
{
 int i;
 for(i=13;i<17;i++)  
 {
  //Check
  if(Sprite_Buff[i].sprcell==0)
   continue;
  if(Sprite_Buff[i].ActKind==0)//if  ActKind==0 , this Spirte no movement
   continue;
  //Timer of Change Sprites’s Index
  if( ((++Sprite_Buff[i].ChangeIdxCnt)%5)!=0)
   continue;

  
  Sprite_Buff[i].sprcell = Sp_Idx_Tab[ Sprite_Buff[i].SpType ][ Sprite_Buff[i].ActKind ][ Sprite_Buff[i].ActCounter ].sprcell;
    
  if( Sp_Idx_Tab[Sprite_Buff[i].SpType][Sprite_Buff[i].ActKind][Sprite_Buff[i].ActCounter+1].sprcell==(const int *)0xffff)//Check End Flag
  {
   Sprite_Buff[i].ActKind = 0;//Begin Index
   Sprite_Buff[i].sprcell = 0;//when action finish ,sprite disappear
  }
  ++Sprite_Buff[i].ActCounter;
 }
}

sprite 保持最后一桢 不消失

void Change_Sp(void)
{
 int i;
 i=0;
 //Check
 if(Sprite_Buff[i].sprcell==0)
  return;
 if(Sprite_Buff[i].ActKind==0)
  return;

 if( ((++Sprite_Buff[i].ChangeIdxCnt)%5)!=0)
  return;

  
 Sprite_Buff[i].sprcell = Sp_Idx_Tab[ Sprite_Buff[i].SpType ][ Sprite_Buff[i].ActKind ][ Sprite_Buff[i].ActCounter ].sprcell;

 if( Sp_Idx_Tab[Sprite_Buff[i].SpType][Sprite_Buff[i].ActKind][Sprite_Buff[i].ActCounter+1].sprcell==(const int *)0xffff)//Check End Flag
 {
  Sprite_Buff[i].ActKind = 0;//Begin Index
  //Sprite_Buff[i].sprcell = 0;//no disappear
 }
 
 ++Sprite_Buff[i].ActCounter;
}


很奇怪,自己也开始写blog了。哈哈。首先申明,本人不是什么对技术很有研究的人,只是对游戏设计感兴趣而以,个人认为技术只是工具,并不是最重要的。但是目前而言,纯设计师是很少的,一般都是从程序或者策划等上来的。欧美的设计师更是有10多年的原工作经验。(宫本大叔是很少见的。-_-)而在我国就更离奇了。我只记得原金山的裘前辈说过中国的策划都是空降的。那设计师的话那就更难说了。好了。牢骚发完了。我们开始吧。偶是今年刚毕业的,但是对游戏因该确切的说从小学开始就接触了。(当然只是玩游戏而已!)毕业后很有辛的录入一家做手机游戏的公司,(其实我一直很想做单机游戏!)开始了自己的游戏历程。职业是程序员。所以我这次的话题也是站在程序的角度来说明自己所知道的。对游戏前期的策划等不在论述之内。当然我觉得不管你在什么平台,对游戏的理解因该是一样的,(略有偏差!)所以我想把自己所用1个月作出的一个小游戏与大家分享,对于我身边一直很感兴趣的朋友也算是有个交代,也对自己来说是个总结。

1. 很多人一开始就喜欢问我,你现在用什么语言开发啊。其实在我以前没有做游戏之前,这个问题我已经有看法了。那就是跟那些经常喜欢回答这个问题的高手一样。^-^ 就是你看你从事哪个平台和你所熟悉的语言来说吧。有些平台是有限制的。当然也许我说的还不够完整。比如在windows下,我听过的最多的声音就是用c++,但是看过拉莫斯的书好像他一直在用c来演示。呵呵。所以你要做的事情就是你想在什么平台开发游戏,然后在看那个平台支持什么语言,然后如果可以选择的话你要看自己熟悉什么拉。当然如果考虑游戏的实现来说,你可能选择的时候不一定是自己擅长的语言,但是有一点我想说自己的看法,就是目前而言,你要做商业上的游戏,最好学习c++,当然你的c如果也很厉害的话,对你没有什么坏处。

2. 现在开始说自己现在要从事的平台,我是在手机上开发游戏,用的是brew平台,所以我首先考虑的是手机上我可以做什么样的游戏。其实现在的游戏很多都可以移植到手机了。只是考虑的手机的操作性,我们最好可以做那些比较容易操作的游戏。如rpg,puz,stg,slg等。(我知道的就先说这些吧。其实我做的这个算是个puz,但是自己下一个可能是rpg,哈哈.自己最喜欢的类型!)然后我要选择这个平台目前支持的语言类型.我知道的就是c和c++了.但是目前我从事的公司一般都用c,所以我就用c来开发.其实c++我可能还不是很熟悉.哈哈.(当然c也不是那么很容易的东西.)

3. 游戏类型刚才已经说了.我做一个puz,当然是头分配的.对自己刚毕业来说也算是个锻炼.呵呵.因该说这个游戏有一定的娱乐性.这个游戏是属于俄罗斯方块那种类型,只是要求打方块和交换方块,计算有没有可以打的方块.(请允许我用这么模糊的词语来形容,因为不能说的太暴露.要不你们就知道我做的游戏了.哈哈.是要上线的.哈哈.谢谢头这么信任我啊.)对于新手来说因该这个地方是大家很想知道怎么做的吧.那我们就开始吧.首先你要明白怎么在手机屏幕上显示图像.说实话这个问题空扰了我很长的时间.2days.对我来说很浪费自己在公司的时间啊.要说这个,就不能不提现在我们在windows平台是怎么做游戏的。大家因该都知道用sdk(软件开发包)拉了吧。有很多函数直接用。包括图像。当然在手机上也是一样了。brew就是这样的。呵呵。所以你要熟悉sdk,当然你也要熟悉手机上特有的东西。如在brew上经常用的sprite技术。算是对那些经常要移动的对象有很方便的用法。图像平常用的image,bitmap等接口一定要熟悉它是怎么运作的。解决了这些你因该可以看到你的东东可以显示了。哈哈。

4. 现在在看我们游戏的主逻辑。比如游戏中的物体的的移动。考虑我写的是给新手看的。(当然自己也是新手。)所以我用的办法可能比较土。高手就不要说我了。其实图像的实现和数据是分开的,所以我们因该从数据上考虑。你的东西可以移动,说白了就是一个数据在坐标上移动。你只需要把坐标的位置改变,用对应的平台工具实现他就可以了。 我用的是spirte。在来看看移动要改变的位置。如俄罗斯只是向下移动,但是如果方块是从左边发射要碰到边界会向下移动怎么办呢?我们可以设定变量来表示它是否想左移动或者向下移动。对于碰撞我们要考虑是否要移动的方块前面的位置是否有东西。这个判断我用的是数组。既不同的方块用数字标示。个人感觉数组好像在游戏中用的很多,目前我认识的。至于其他的消方块什么的都是判断是否显示和不显示而以。这些东西就是要靠自己看看游戏中经常用的方法。以上就是稍稍的提及了一些游戏中的方法。也对新手而言有个感性认识而已。我做的只能这么多了。

5. 程序怎么写。这个问题是不是很幼稚?不过我感觉刚从事游戏的新手因该有这样的感觉。不知道自己怎么写啊。说实话,刚开始我也不知道怎么写?这个就要学习游戏的架构啦。当然前提要看平台是怎么表达的。brew和windows一样都是基于消息驱动机制的。所以在这个基础上我们在学习一下其他人的代码写法。刚开始大家都在模仿,如果到一定时候,你可能自己根据游戏可以写架构了。我看过同样的游戏用不一样的写法,真的很不错。哈哈。

6. 游戏说到这里可能差不多了。请原谅我从来没有写过东西,可能表达有些东西是模糊的。还有可能逻辑上也有问题。哈哈。但是我只想表达一下自己所看到的东西。如果你有机会看到这些东西对你有用的话那就可以了。如果没有用,你也可以发表自己的看法,算是我起个抛砖的作用吧。呵呵。

7.  希望可以和大家一起交流游戏。因为本人刚毕业。所以也没有什么商业经验,写的不好的地方请大家原谅。我的qq22856273 msn godkiller007@hotmail.

Ogre 是一个图形引擎

作为开源源码的最成功的图形引擎,Ogre确实能用强大来形容。看看它的API参考手册,数万个函数。以及大量的类和枚举就能证明这一点。Ogre支持DirectX9和OpenGL,支持即时3D演算。通过一些库(如SDL)Ogre可用来构建游戏程序,屏幕保护程序,科学演示程序甚至下一代桌面系统。Ogre的最终目标是与商业游戏图形引擎一决雌雄!


Ogre地形演示


神秘的巨石阵

Ogre 不是一个游戏引擎

作为一个游戏引擎必须拥有下列功能:
  • 图形系统

  • 声音系统

  • 网络系统

  • 输入系统

  • 物理碰撞系统

而Ogre没有拥有所有这些功能,因此,它并不能被称为一个“游戏引擎 “。但是在其它系统的帮助下,Ogre仍能用来写游戏。

为什么要使用Ogre

电影的真正发展是在胶片的规范化后才开始的。同样,游戏业的真正发展也将会在游戏开发技术的规范化后开始。由于商业保密,目前的游戏业仍是”手工作坊的师徒“式的培训,生成出来的游戏鱼龙混杂。而Ogre等开放源代码软件,第一次把从商业巨头手里的最高机密:图形引擎的实现,摆在我们面前。如果能够通过它,了解和掌握游戏开发的核心技术,对先天不足的中国游戏业从业人员有极大帮助。
即使不是游戏开发人员,Ogre也能为您服务。科学研究,教学演示,3D桌面系统,还有火热的
数字地图(如Goole Earth),都呼唤着一个强大的图形引擎。一个开放而且免费的图形引擎,能避免 人们受到某些垄断企业的控制,从而前途无量。
Ogre是LGPL 协议下发表的,允许商业软件使用它作为图形引擎。可以用它来作为商业引擎的一个免费版本。
有人说:开源项目有如贷款,要回报开源组织。没有无私的奉献,不会有Ogre的今天和未来。Ogre将给予你 许多来之不易的知识,你是否有回报的准备?

学习Ogre

初学Ogre的人,比如我,会被Ogre的庞大震动。如此大的类库,我什么时候能够学完?
答案是:你不需要完全掌握Ogre系统的方方面面。由于良好的设计,Ogre整个系统相当清晰和一致。从而一本API函数手册(CHM格式或是在线文本)就能解决问题。
作为初学者,你应掌握三种学习的方法:
  • 实例:Ogre拥有许多开放源代码的实例。在Ogre Wiki中你可以看到许多手把手的教程。按照教程所说,一步一步的学习,不久后你就会发现自己的实力提高了。
  • 手册:较之教程,手册上的内容更为枯燥和深奥,但手册上的知识对一个立志”掌握“Ogre引擎,掌握图形引擎实现的人来说是必不可缺的。同时,手册的阅读为未来的源代码阅读打下了基础。这里要求你把数学知识复习一下,不然很容易不知所云。
  • 源代码:阅读源代码是学习图形引擎的不二法门。各种算法的具体实现的了解,最终落实到源代码的了解上。

资源

Ogre官方主页:www.ogre3d.org


记得在一本字典上对于Quake的解释是这样的:一群最有天才的家伙在一起做出的最有天才的射击类游戏。
< BR>1996年7月31日,id software推出了自己的全新作品《Quake》。如果说《DOOM》是FPS的开山之作,那么《Quake》则无疑是一款跨越颠峰的作品。如果你没有在那个时候体验过《Quake》的世界,那么你绝对不会感受到那份足以让人疯狂的冲动。相信即使你现在再一次走进《Quake》中,你也丝毫不会感受到那个时候的3D游戏所拥有的粗糙感和简陋感,要知道,在《Quake》发布的时代,3D显卡对于当时的人们就相当于3D显示器对于我们一样遥望不可及。id software的伟大由此可见一般。


历史上的今天