GT引擎开发教训和技巧

来源:互联网 发布:外汇实时数据接口 编辑:程序博客网 时间:2024/05/16 19:22
适合新手和老手看,高手请指点
不用猜了,我不是计算机专业的,不过出于对游戏编程的热爱,一直都很想写出自己的游戏。可是用OpenGL还是DirectX呢?我要写自己的引擎,不能依赖它们!可是当我把数据结构、物体剔除函数、不可见面消除函数、坐标变换函数、裁剪函数等都写好后,才发现我不会Cg编程,就是说不能直接操控我的GF4 MX440!不过不要紧,可以用DX和GL啊。郁闷的是我搭建的游戏平台是编译成功了,可是当我添加代码后(第一次用DX的ddraw),每当一锁住directdrawsurface时就出错了:《我已经调试过N次了!
时间不多了,下个月就要赶回学校了,唯有先放弃DX,改用GL,哎,GL的平台是建好了,运行时也不错,可是。。。GL编程我其实不会!!!幸好的是有可运行的支持GL的代码,作为一个程序员,搞懂一些基本没怎么注解的代码还是可以的。程序员就是喜欢和子弹赛跑,光阴似箭啊。不是骗你,这段日子里(现在共用了我23天)我每天都是03:00才睡的,早上9点起来搞搞理论,11:30吃完饭后继续我的历程。。。中午睡不成(我妹妹中午不睡觉,老是要我陪她玩,小孩子嘛,没办法:》)。。。日复一日,就是这么过的,连早上跑步的时间都没有了(9点后跑?总不能叫我在人来人往的时候跑步吧)。GT引擎的编写暂时停下来吧,我还要回去拿学位证和毕业证啊!总不能为了GT而放下学业不管吧。可是不做点东西出来还是有点很遗憾的。所以用了别人的代码:《现在那程序可以载入*.3ds了。虽然GT开发要停下来了,不过收获还是蛮多的,可以算是游戏编程的技巧吧。和网友们分享一下技巧和心得吧。
1用DIK们!在我弄的(不敢说是我写的,我只是大大的修改!)那程序中,render是用GL的,键盘处理除了windows提供的虚拟键VK_*,还用了DInput的虚拟键DIK_*,主要原因是DInput是与HAL(硬件仿真层)打交道的,换句话说就是与硬件无关,和DDraw一样。而且处理消息时比较灵活,我们可以把所有消息都映射到一个数据库里,用一个综合函数把消息处理,把结果填写到那个数据库中作为返回输出。用DInput独占鼠标,键盘设为前台属性。VK们没有0~9和A到Z的虚拟键。VK_F等是错误的。还有就是VK们要在msg里排队!有时不一定能及时的响应(这样的情况很少出现)本人觉得DIK们方便可靠一些。我的建议是这样的,不是经常按下的就交由VK们处理,那些A,D,W,S啊就交给DIK吧。
2优化抛物线函数!编程时不要害怕抛物线函数会拖慢你的速度,因为我们要它们听话。用2次二项差分法来处理就KO了它们。在复杂的AI系统中,这种做法很管用。比如说做个跳跃处理程序块:当按下空格键时,人物就跳起来。给1分钟时间你思考怎样编程吧。。。。。。没你想的那么容易。我们先假设起跳的速度是v,地球的重力加速度是g(注意,方向是负的啊),人物起跳位置高度是s,这些都是已知量啊,根据牛顿的第2定律,那么跳起的t瞬间位置高度S(t)为:S(t)=s+v×t+0.5×g×t×t天啊,简单优化后最起码要进行2次乘法和2次加法,就这样吗?不要忘了t+=dt; dt是时间增量,大小取决你的刷新频率F,dt=1/F;就是说dt就是每一帧所花的时间,一帧占了dt秒。要等到S(t)<=s为止跳跃动作才结束。我们来算一下一个跳跃动作要进行多少次乘法开销吧:假如刷新频率是30fps(这是最基本的刷新率,现在一般都可以到达80fps了),那么人跳起到落下来的时间要2v/g秒,大概1秒吧,就是说要进行
30×2=60次乘法开销,一个物体60次,20个人就1200次啦!要是刷新率为80fps的话,单一个人就80×2=160(当然动作很自然)。。。。。。
有没有想过S(t+dt)-S(t)=?答案是:(v+g×t)×dt+0.5×g×dt×dt我们设
S(t+dt)-S(t)=dS(t),则dS(t+dt)-dS(t)=g×dt×dt,哈哈,看到常数了吧,计算机处理等差数列很easy。初始化时,我们定义ds=v*dt-0.5*g*dt*dt;(注意,这里的加速度g是正数),const dds=g*dt*dt;S=s;则有:S+=ds;ds+=dds;其中只有S和ds是变量
具体的举例子吧:g=-9.87 , s=1.5 ,v=8,dt=0.02(50帧频)则有以下程序:
初始化时:float   S=1.5; dds=-9.87*0.02*0.02; ds=8*0.02+0.5*dds; kds=ds;
          bool jump;
进入游戏循环时:if(KEYDOWN(space))//要是按下空格键
{ jump=true;}//可以跳
if(jump)
{
S+=ds;
ds+=dds;
}
if(S<s){S=s;ds=kds;jump=false;}//跳完了
以上代码可以修改为模拟在水中游泳的情况:只要在jump=true;后加一句ds=kds;还有就是加个判断 if(S>maxS)S=maxS;// maxS水面高度
当然初始化时要修改参数啦,建议g=-3,v=1.8。
想实现僵尸跳(连跳)则只需要把jump=false;这句去掉。你们谁有兴趣的话,把sin()函数展开为抛物线看看,把结果告诉我吧。
3可能会流行的裁剪方法:GT1248裁剪法 这方法可能早已有了,又或者是我发明的,管它呢?代码我就不全写上去了,我只说原理:》基于大多的游戏引擎裁剪都要用到2D裁剪的,3D的也会进行屏幕矩形的裁剪,GT1248就是用矩形对3角形进行裁剪的。我们先把矩形的4条线段分别标上数字代码1,2,4,8,并给它们标上指向里面的方向,所指向的一方为正方,背向的为负方。把矩形里面的区域标为0。如下图所示:

V0
0
2

1
V1

 

4

 
 
 

8

 

V2

 

定义标志数组int mark[5]={0};
给3个顶点设置标志mark[0], mark[1], mark[2]。
用于存储临时结果的标志mark[3]=mark[0]+mark[1]+mark[2];
最终的结果标志为mark[4];
从最左边的一条直线开始(数字代码为1)对3角形进行裁剪判断:
顶点位于正方的标为标0,位于负方的标1。然后计算mark[3]。mark[3]为0时表示整个3角形不用裁剪,但我们这样判断 if(mark[3]==3)continue; mark[3]为3时,3角形不用渲染,直接跳出。否则,else if(mark[3]!=0)mark[4]+=1;//累加直线代码(这里加上的1是直线数字代码)。然后顺时针分别用2,4,8直线对3角形进行裁剪判断。我们要的是最后结果mark[4]它有16种情况!先不讨论结果,先给点代码大家:
不给原代码,给伪代码,这样大家更好理解
 
for(处理3角形队列)
{//直线1判断
if(顶点v0位于直线1的左边)mark[0]=1;
if(顶点v1位于直线1的左边)mark[1]=1;
if(顶点v2位于直线1的左边)mark[2]=1;
mark[3]=mark[0]+mark[1]+mark[2];
if (mark[3]==3)continue;//剔除不可见3角形
else if(mark[3]!=0)mark[4]+=1;//因为我们的对手是1,2,4,8 我们可以用 或 运算
//直线2判断
if(顶点v0位于直线2的上边)mark[0]=1;
if(顶点v1位于直线2的上边)mark[1]=1;
if(顶点v2位于直线2的上边)mark[2]=1;
mark[3]=mark[0]+mark[1]+mark[2];
if (mark[3]==3)continue;//剔除不可见3角形
else if(mark[3]!=0)mark[4]+=2;//有没有发现代码很相似?我们只需复制,粘贴,修改
//直线4判断
。。。。。。。
//直线8判断
。。。。。。。
//上面的代码你可以用for来解决,不过循环次数不超过3的就尽量展开
switch(mark[4])
{case 0:
case 1:
。。。
}
//其他代码
。。。。。。
}
好了,我们来分析结果吧,mark[4]的可能值是0~15。mark[4]为0时,表明3角形不用裁剪,直接渲染;为1则表面用直线1来裁剪就可以了,为2时就只需要直线2裁剪,为3时就只用直线1和2裁剪,。。。,结果列出来给你们看看:
mark[4]值           裁剪直线
0                                                                   不用裁剪
1                                                                   直线1
2                                                                   直线2
3                                                                   直线1、2
4                                                                   4
5                                                                   1、4
6                                                                   2、4
7                                                                   1、2、4
8                                                                   8
9                                                                   1、8
10                                                               2、8
11                                                               1、2、8
12                                                               4、8
13                                                               1、4、8
14                                                               2、4、8
15                                                               1、2、4、8
难不成要我们写出16块裁剪代码???才不!我们只要根据结果调用水平裁剪函数和垂直裁剪函数。你可能想说倒不如对每个3角形进行2次水平裁剪和2次垂直裁剪,在裁剪前加点判断不就更好?这样的效果和我的GT1248是差不多的,不过我可以担保我的GT1248的平均开销绝对比你那样做要低!因为如上图所示,粉红色3角形是不用裁剪的,不过你那方法就要裁剪一次了。而且3角形被裁剪后不一定就是3角形!往往会多出顶点,这样你的判断就要增加了,可是GT1248的判断是固定的,多的是加法。你可能会想到,倒不如先判断3角形是否都在左边,或者都在右边,或者都在上面,又或者都在下面,是的话就剔除,然后再进行2次水平裁剪和2次垂直裁剪,问题是在裁剪函数里面还是要添加判断的,判断3角形是否需要该函数裁剪。开销和GT1248差不多,因为原理是一样的,只不过是GT1248判断后就知道3角形的具体情况。另外在多人开发引擎时,写出16块裁剪代码也是可能的,因为知道3角形具体位置,每块的裁剪代码都得到优化,专用的代码总比通用的效率高。
现在0:45,睡不着!我的demo有声音了,还可以显示中文,虽然也没什么好高兴的,不过,的确游戏编程离不开策划。我现在想不出应该给GTgame添加什么内容了,时间越来越少了,想在里面添加AI,不过我认为一个好的AI系统关键是统筹策划好,在有限的时间里我不可能做这么多东西的,第一次给自己带来了尴尬。各位不好意思啊,我的原则是这样的:要么不干,一动手就要做到最好,尽自己的全力把事情办好。直到实在干不下去了,就该回头看看,然后决定下一步该怎么走。我的电脑拿回家了,要是回到学校就没有电脑用了,哎,还要找工作呢,真麻烦。所以我打算这样:停止GT Engine的开发,尽力把demo搞好,先到深圳或广州找找工作,再回学校把毕业设计完成,要是不能请假的话,就得听话啦――做为测控头号人物(我是1班1号)责无旁贷。
说说我的建议吧:
1一个人开发引擎其实不是不可能的,关键是时间和经验;
2开发引擎是件大事,我当初就太少看它了,根本没有什么引擎计划书!这是一个严重的错误啊!
3 demo其实很简单,我就用了一个多星期就搞了个类CS游戏的demo;
4再次声明:demo应该多从成功的代码中抽取函数,demo关注的是演示效果,个人认为demo只是反映你开发游戏的综合能力,它应该具有特色,但谁关心你的代码呢?example就具有普遍性,以便表明你的代码可用性高。测试引擎时做出来的应该叫example,不叫demo!引擎就不同了,不但要能作出examples,还有就是要别人很容易就看懂里面的代码,详细的解析是小不了的,还有就是不要把代码写成大块大块的,除非你是有意不想别人看穿。不过太多的函数可能会让你发疯!
5开发时多听点鼓励性的歌曲,毕竟我们都是人,人总要学会提高自己的士气。喜欢写代码而不喜欢音乐的人是不可能完成游戏开发的;我就喜欢听郭富成的《强》,刘德华的《大胜无限番》(我只有MTV,谁有wav的话发给我啊,谢谢),还有伊健的《战无不胜》。
6保存好你的草稿!我很后悔把草稿仍给了“旺财”(你应该知道它是条狗吧);
7建议你用进度表记下你的进度,我好像把进度写进代码的注析里了;
8坚持坚持再坚持,相信自己一定能成功的。
9不要一开始就使用线程,还有就是汇编。
10在引擎开发过程中,写API时最头痛的是给它们起名字,所以建议用“金山英文写作助手”输入法,其实我反而觉得紫光有点麻烦,不过我习惯了用它。
11不要每天都编程,保持实践和理论的进展,对于我们这些没什么经验的人理论一旦是错的话,程序也是白写了。
12不要写完一条代码就立刻注析,这其实是不好的习惯,应该写完一小段或一个函数才注析,注析过程其实也算是检查代码的过程,另外,写一条注一条的话不但很累,而且会打断你的突发奇想。
13要是可以的话,锻炼一下自己,看看能否象电脑一样并行处理问题:)
14还是那句:身体是革命的本钱!幸好我身体还好。
15一定要了解你编译器的脾气,有问题的就尽量不用,尽量用英文版的。我的VC++编译器的End User License Agreement项已经生效了,说是不允许我生成应用程序,结果还是可以用啊,哈哈,不要告诉微软。
16引擎是不应该依赖GL和DX的,应该学学Cg
17写API时,不一定非要用interface,我们有类嘛!
18写结构体时,要是编译器说redefined,就在struct们前面(不是每个struct前)加一句using namespace xxx
19要是没网线的建议装上最新版的MSDN,我就后悔没有装。
20游戏技术资料一定要够,我的资料已经突破4G了。不过不要再向我要了,网上很多有的下,虽然有的需要自己破解下载地址。当然我是守法的人,才破解过一次,结果下了本垃圾书《xxx宝典》。然而当你看到这文章的时候,我的电脑早已断了网线,想给也给不了。
21英语学不好不要紧,就像我一样,有金山怕它什么呢。要是有一天编译器完全支持中文编程就好了:)
22不要光是想,要把想到的记下来,一步一步的实现它
23要学会备份。我就专门建了2个文件夹,一个放test的,一个放success的。当然success的我就压缩成rar,并且把文件夹的图片也改了。
24先搭建好测试平台,我是后来才搭建的,想来真笨,当时我还测试了N次自己的代码,后来才发现是平台问题。
25平台搭建好后,就是数据库和类一起计划好,我就后悔没有这样做,结果数据库不断的更新,类也跟着改了又改。要有个清晰的数据,把全局数据和局部数据、数据和类、类和类间的关系用图画出来。而且把它们的名字起好。
26 24~25这些建议可能会占了你比较多的时间,不要让它们超过一个星期。
27计划render流水线时要有2个版本,一个是最简单的,一个是最复杂的。复杂版是简单版的最终扩展版本。我们开始时只要实现最简单的版本,等引擎更新时才向最复杂版进军。(然而,最复终版的最有吸引力)
28最终的最终建议:活着 是为了证明自己存在的痕迹,见证你我的奇迹!
                                                       
 
感谢家人给我一个很好的编程环境
谢谢大家看此文
 
                                                        06年2月26号
                                                        武汉理工大学
曹劲林