2009年8月29日
在深圳的这家WY公司做了一年多了,主要都是搞底层的算法,感觉有这么一些体会应该写下来,期待与哪位高手切磋:
1.数学为本:游戏设计里的很多问题的本质都能归结到一个数学问题,有意思的是这里用到的数学实在是太初级了,无非就一些矩阵,向量,四元数,曲线,插值等等;其实数学的功能极其强大,有微积分,组合数学,概率论,多项式,集合论,运筹学等等,只是感叹没机会用啊,最复杂的任务也只用到过一个最小二乘平面而已;
2.测试驱动:做算法的,考虑的是先让用例通过,然后提高效率,比如做的地平线裁减算法吧,用例出奇的多,这时只能搞可视化调试了,我的经验是宁愿多花时间写测试环境,也不要仓促下手,否则维护起来实在是太痛苦了。再比如寻路算法,每秒要被服务器调用上万次,坐标可能是任何数值,一不小心就可以哪里死循环了造成服务器不响应,另外效率要求还极其严格,所以一有机会就要及时return,呵呵;
3.注重调试:游戏模块一般都很大,自己只能负责部分,为了便于跟问题,一定要写便于调试的代码,例如:和别人模块交互一定要通过接口,因为这边是下断点的绝佳位置;日志是王道,非法返回的地方一定要写;要把复杂的代码统一写在一个地方,这样改错只要改一个地方等等;
先写这三点,希望同道中人不吝赐教,顺便把本人的博客“复活”一下,好久没更新啦
2007年8月29日
考虑一个问题:函数内和函数外需要通过一块内存来交互(例如字符串),如果该内存是以局部变量的方式在函数内栈上分配的,那么随着函数的返回这个内存将被弹栈释放,所以,需要返回一块函数外部还有效的内存。解决这个问题有三种方式:
(1)在函数内部通过malloc或new在堆上分配内存,然后把这块内存的地址作为返回值返回(因为在堆上分配的内存是全局可见的)。这样将导致潜在的内存问题。因为调用者需要知道函数内部的实现,如果返回出去的内存不释放,那么就是内存泄露。或者是被多次释放,从而造成程序的崩溃。这两个问题都相当的严重,原因就是它不符合谁申请内存谁负责释放这一原则。
(2)在函数内部声明static型的变量,static的栈内存一旦分配,那这块内存不会随着函数的返回而释放,它实际上是全局可见的,只要你有这块内存的地址就能使用。所以,有一些函数使用了static的这个特性,即不用使用堆上的内存,又不需要用户传入一个缓冲区地址和其长度。从而使得自己的函数长得很漂亮,也很容易使用。但是全局的东西可以被任何人更改,安全性无法保证。
(3)让调用者通过函数参数传入一块他自己申请的内存地址,而在函数中写这块内存。很多Windows API函数或是标准C函数都需要你传入一个缓冲区的地址和长度。这种方式的好处就是由调用者来维护这块内存,比较直观,问题是在调用上要进行严格的初始化。不过相对来说这种方式把犯错误的机率减到了最低。
2007年7月7日
接触极限编程一段时间,找到以下四点反驳它的理由:
[1]代码质量
极限编程运用测试驱动开发(TDD),其理论基础是需求应该是可测试的,其目的在于保证软件系统的正确性和健壮性(测试用例足够充分的话)。可以这么认为:极限编程关心的是结果,不关心过程。因此它忽略了软件系统的结构性和开放性。我们知道结构性有助于修改,开放性有助于扩展,而极限编程却放弃这种追求,导致的结果就是产生一大堆丑陋的代码,而且随时有可能被彻底抛弃。
极限编程解决效率,结构性和开放性问题的对策是重构,它宣称重构无处不在,但是重构是一种补救的方式,为什么不在设计初期进行预防呢?极限编程回避不了这些问题,而只是将它们推到了后面的阶段,但是付出的代价可能会更高。
[2]工作进度
极限编程直接将代码作为文档,弱化传统文档的作用。既然如此,那么代码就应该有规范的格式和详尽的注释,以便提高它的可读性,但是由于极限编程采用的是团队合作方式,代码规范很难得到统一。那么通过注释吧,可是极限编程认为注释是一种负担,无法适应频繁修改的代码。
极限编程解决沟通问题的对策是结对编程,它认为频繁的沟通胜过面面俱到的文档,但是文档是永久的,沟通却是短暂的,大家可以看同一份文档,却要进行多次两两沟通,所需时间也许并不比写文档的时间少。更糟糕的是,经常地切换搭档将极大地破坏工作的延续性,只能拖慢进度。
[3]工作量
测试驱动开发具体应该怎么做呢?测试驱动决不是说代码从测试写起,在写测试用例之前,肯定要对需求有完整的了解,否则测试无从写起,其实这就是需求分析以及设计,还是与瀑布模型一样的流程,只不过没有文档化而已。唯一不同的是极限编程要求需求都是可测试的,因此要把这些需求翻译成系统测试用例,集成测试用例,和单元测试用例。由于写程序必须同时写它的测试,因此如果改程序则必须改测试,这将达到两倍的工作量。
[4]目的
极限编程认为需求是不断变化的,因此软件能满足当前需求就好,没有必要构造框架之类可复用的东西,它认为这是一种过度设计。这种思想是极端的,因为框架就是为了解决需求变化问题而出现的。举个例子,MFC就是一套框架(尽管我厌恶它),但是基于MFC却可以开发网络,多媒体,数据库甚至游戏应用程序。面向对象的目的就是为了复用,而且好的框架能够做到隔离变化,依赖抽象,如果认为软件系统的一切东西都是暂时的,无疑是与面向对象思想背道而驰的。
2007年3月27日
本人将AFX从TGE版本移植到TSE版本,目前AFX的官方网站并没有推出该版本,以下介绍一下移植过程中主要的改动:
(1)粒子系统。TGE的粒子系统来自三个类:ParticleEngine,ParticleEmitter,Particle。三者的关系是前者管理后者。ParticleEmitter以一个链表来存储Particle,ParticleEngine则负责Particle的创建和删除工作。TSE的粒子系统将ParticleEngine取消,Particle的创建和删除转由ParticleEmitter执行,粒子的存储方式改为数组方式,以C语言的方式动态地扩展空间。
(2)地形网格。考虑这个问题的原因是要将纹理贴到地表(不同于Decal),主要用于形成光环。TGE的地形网格是以索引数组的方式存储,若干个连续的索引组成一个单位,每个单位或者是三角形Triangle,或者是一个扇形TriangleFan,前者直接存储三角形的三个顶点索引,后者则先存储共用顶点,再存储单独的顶点。TSE中将两种类型的数据统一成Triangle,并使用TerrBatch类封装了大量针对地形的操作。
(3)渲染方式。TGE使用OpenGL进行渲染,TSE使用D3D进行渲染。TSE提供了PrimBuild命名空间,能够很方便地将OpenGL语句以类似的方式移植过来,OpenGL中的各种渲染状态设置语句在D3D中也有相应的语句相对应。
(4)材质纹理
TSE为了支持Shader,将材质单独提取出来。因此在移植模型动画特效的时候,要在模型类里做一些材质方面的处理。
2007年2月3日
Torque Shader Engine只提供了简单的几个类来支持效果,只能是小打小闹,要运用于商业运营,必须自己整合一套架构。我参考了AFX的实现方式,认为它的架构大致可以这样描述:
AFX是面向TGE的一套效果系统,它由一个效果引擎和一个DEMO构成。构成效果引擎的单元被成为组件效果(Component Effect),它们有的封装了TGE的类,有的是AFX自定义的。所谓组件效果是指它们与效果引擎是相对独立的,这意味着AFX即可以支持在TGE中定义的一些原有效果,也可以通过修改和扩展各种适配器来定制新效果。
效果引擎使用效果管理器(afxChoreographer)来管理效果,目前已实现了三种效果管理器:选元(afxSelectron)、效元(afxEffectron)、魔法(afxMagicSpell),其中比较完整的是魔法,它将效果分为若干阶段(afxPhrase),使用载体(afxMagicMissile)进行抛射。效果管理器管理若干统一格式的效果包装器(afxEffectWrapper),并使用限制器(afxConstraint)来将包装器应用于特定目标,例如地点或人物。
包装器的作用是隐藏具体效果的底层细节,使得所有的效果在外部看起来都一样,用面向对象的话来说就是实现了多态性,这样AFX支持的效果不仅可以是图形的,也可以是声音和脚本的。效果分为两种:被动效果和主动效果,两者的区别在于后者会改变其它的对象而前者不会。因此被动效果可以只在客户端运行,而主动效果必须通过服务器来运行。包装器使用修改器(afxXfmModBase)调整效果的位置、方向和视点,每个包装器可以使用若干个修改器。
限制器的作用是将效果与特定的游戏实体关联,关联目标可以是一个点,一个模型,或者模型的某个部分。限制器提供历史记录功能,因此能够很容易重用效果的限制记录。
受AFX启发,我将效果分成粒子,贴花和模型三种类型,配合动画来实现效果。重点考虑效果的组合、限制和修改机制,便于将来的扩展。目前已经正式开始动手,希望能够早点有所收获。
2006年11月28日
Torque Shader Engine
是最新出现的一套商业游戏引擎,它的前身是
Torque Game Engine
,如今已是
XNA
框架的有机组成部分。
TSE
秉承了
TGE
的脚本系统,文件目录结构清晰,它对
TGE
的改进之处主要在于将
Material
独立出来,使用脚本配置材质属性和附加
Shader
,可以利用最新的硬件实现特效。
但是
TSE
只是一款
FPS
类型的游戏引擎,代码很零乱,性能不稳定,存在不少
BUG
。看过它的类图之后,我认为该引擎效果不错,但是结构不合理,给人一种拼凑的感觉。它的主框架,效果系统和物理系统的实现并不高明,当前甚至不支持
OpenGL
!
Garagegames
的那帮老外写代码到底是急功近利还是尚未优化,真叫人拿不准,如此质量,难怪只卖
100$
。
2006年10月20日
说来惭愧,学了这么久的图形学,今天才动手编了自己的第一个Cg程序,参考的就是那本《The Cg Tutorial》。Cg的GPU概念彻底改变了我对图形学的看法,传统的程序都是由CPU执行的,这种观念在其它方向的程序员看来天经地义,但是对于图形程序员来说,从2002年Cg诞生开始就已经颠覆了。面向GPU编程,让CPU解放出来,能够极大地提高了渲染速度。还有,它实现了可编程的渲染,对于游戏来说,就意味着能够以脚本的形式来渲染场景了,如果再配合那些AI脚本,游戏引擎基本上就能够建立在脚本之上了!大部分的C++程序员都会对Cg有种似曾相识的感觉,因为它是面向图形的C语言,配置和编程都很符合习惯。对于使用DirectX的游戏程序员来说,Cg与HLSL其实上同一种语言。我个人认为DirectX的所有组件其实都可以使用别的工具代替,而Cg则是其中最高级的替代品,决定用它了。
2006年10月7日
面向对象技术推崇的是分而治之的思想,每个类既有属性又有操作。它构建了模块化的模型结构,但是也带来网络式的消息机制。面向对象技术的难点就在于对控制流的建模,下面举两个例子。例一,一个组合之间各个单元之间的交互,例如页面之间的切换。过程式技术可以设置一个组合代理,通过代理这个中介来切换单元。如果使用面向对象技术的消息通信机制,不仅要大刀用斧使用线程,而且每个单元都要感知其它单元的存在!例二,为一个类附加一个功能,例如页面切换时执行载入或者统计。过程式技术可以使用函数映射表的方法,而面向对象技术只能使用虚拟函数和继承机制了,除去生成很多琐碎的类不说,如果功能函数涉及到多个对象,同样存在着感知的问题。写类谁都会,关键是怎样把他们组织起来以便使用,因此要想学好面向对象技术,多在接口(公有属性及值域、公有方法及参数等)上花些工夫吧。
2006年9月28日
毕业论文的初稿已经出来了,暂时可以先喘一口气,我做的是《射击类三维游戏引擎》,总的感觉是“麻雀虽小,五脏倶全”。我将引擎分成了七块:界面包、资源包、设备包、实体包、实用包、工具包和一个程序框架。界面包负责显示视图,资源包负责调度资源,设备包负责虚拟设备,实体包管理游戏中有意义的实体,实用包包括物理系统、效果系统、媒体系统和脚本系统,工具包包含一些数学和图形学的数据结构,程序框架采用Windows的消息驱动和消息映射机制。体系结构比较清晰,但是技术含量还有待提高。当前主流的游戏引擎都支持图形两套引擎(OpenGL和Direct3D)以及一门高级渲染语言Cg,而我的引擎只用了OpenGL,只能感叹自己学艺不精。图形学方面从来就不缺牛人,而且很多都是从数学转过来的,不过我也是从数学科班出身,也许几年之后...不管那么多,向人家取经吧。
2006年9月19日
软件复用程度的级别可以按以下准则来衡量(级别从低到高):
(1)函数:将那些重复或者类似的程序集中起来放在函数里,对外提供参数以利复用,是功能型的重用;
(2)实在类:将若干经常使用的函数集中起来,将函数的参数转化为类的成员变量,自定义构造和销毁函数,是资源型的重用;
(3)抽象类:使用纯虚函数对外提供接口,这些接口仅仅声明了基类的功能,而将实现拖延到子类中去,是强调可变性的资源型重用;
(4)包:将若干耦合度较大的类集中起来,统一地对外提供接口,是一种有机的资源型重用;
(5)模板:将变量的类型通用化,它是一种强调通用性的资源型重用,可以应用在上面四个层次上;
(6)组件:封装了数据和方法的可执行代码,上面五种都属于代码级的复用,而组件属于软件级的重用。