dos游戏编程21条(转载)

类别:编程语言 点击:0 评论:0 推荐:

中国人是不甘落后的!让我们协起手来为中国的游戏事业奋斗吧! DOS游戏编程二十一条 (The 21 FAQ of PC DOS GAME) 我在一家游戏公司多年,现就自己的编程经验谈一点体会,希望能对大家有点帮助,本文中所有例子均在WATCOM C/C++ 10.6下调试通过。 1、找一种好的编程语言: 当然,游戏可以用任何语言编写,这是可以肯定的,我就使用过Turbo Basic编写过跑马机游戏,还用VB写过一个半成品的网络拱猪游戏,但是,一个好的编程语言能够达到好的效果,这是毋庸置疑的。一个游戏程序员,梦寐以求的就是一个方便、完美、高速的语言。 汇编是一种高速语言,但不够方便,如果要方便,就必须大量使用宏,笔者就曾经在6502汇编语言中大量使用FOR,NEXT,PRINT等语句,全部是宏,但是相应的内存开销,时间开销都加大了,不划算。 WATCOM C/C++是一种好语言,可以访问大内存,速度快,也够方便,但是调试不够方便,只能用自己写的调试函数解决问题。还有每次运行必须调用DOS/4GW这个32位环境程序,既累赘又不方便,还占地方。 MSC7.0也不错,通过它的虚拟内存机制也可以访问大内存,但可惜是16位仿真的,速度太慢。 DJGPP也是很不错,关键它是共享的,同时还带有一个Alleg的共享游戏库,非常好用,推荐使用,但它生成的程序代码太大,不够优化。 作为游戏程序员,我们追求的就是快一点、快一点、再快一点,如果还有更快的语言,希望大家介绍给我。 2、要写专有程序,不要写通用的,通用,意味着慢,哪怕下次重新来过,也不能为了下次耽误这次。同理,凡是系统给你的函数,调用,要有坚决不用的思想准备,要自己写一套。 3、写出来的程序,每秒钟必须刷屏70次以上,再通过时钟限制在30次(不抖),剩下的时间,就是运行你的游戏程序内容的时间,算一算,不多。 4、要有引擎的概念,引擎包含系统底层的程序,数据结构,调用方法等,这些直接限制你以后的游戏好不好编,一般说来,我们做一个游戏半年时间,其中两个月编引擎,两个月编游戏,剩下两个月调试,可见引擎的重要。永远记着,你写的程序,就计算机而言,就是在搬数,把一堆数据提出来,处理一下搬到另外一块地方,就这么简单,那么,搬数的方法有多重要,你知道了吧。 5、尽量少用乘除法、浮点数,必要时使用移位乘法。这里给一个移位乘法的例子,大家可以参考: /*-------------------------------------------------------------------------------*/ //XiaoGe Made under WATCOM C/C++ 10.6 /*-------------------------------------------------------------------------------*/ int count_offest(int _width,int x,int y) //移位乘法计算显示偏移值 { int mode[17]={1,2,4,8,16,32,64,128,256,512,1024,2048,4096,8192,16384,32768,65536}; int i,offest=0; for (i=16;i>=0;i--) { if (_width>=mode[i]) { offest+=(y<<i); _width-=mode[i]; if (_width<1) break; } } return(offest+x); } /*-------------------------------------------------------------------------------*/ 结果=屏幕宽度*y+x。(这仅仅是一个移位乘法的例子,真正用时,要根据具体情况选择使用) 6、没人会用数学描述去写一幅游戏图形,所有的图形都来自于美工画的PCX,BMP图形,去找一个美工,或者偷一批图形吧。读写PCX,BMP的函数是必须的。 7、游戏程序员语录:给我一个画点函数吧,我能描绘出整个世界。任何时候下,一个最高速的画点程序都是必要的,这里给出一个例子(没有乘法的): /*-------------------------------------------------------------------------------*/ //XiaoGe Made under WATCOM C/C++ 10.6 /*-------------------------------------------------------------------------------*/ void point(int x,int y,unsigned char color) //高速画点 { if ((color!=NO_COLOR)&& (x>=0)&&(x<SCR_H)&& (y>=0)&&(y<SCR_V)) { #ifdef VESA_320_200 *(buffer+(y<<8)+(y<<6)+x)=color; #endif #ifdef VESA_640_480 *(buffer+(y<<9)+(y<<7)+x)=color; #endif #ifdef VESA_800_600 *(buffer+(y<<9)+(y<<8)+(y<<5)+x)=color; #endif #ifdef VESA_1024_768 *(buffer+(y<<10)+x)=color; #endif #ifdef VESA_1280_1024 *(buffer+(y<<10)+(y<<8)+x)=color; #endif } } /*-------------------------------------------------------------------------------*/ 8、镂空算法很多,用的都是AND MASK+OR方式,不要去理它,每一个点要处理两遍,包含三次读内存,两次逻辑运算,一次写内存,太慢了,在你的颜色中规定一种透明色,画点时不去管它就行了(上例)。记住,每个点上少处理一次,你至少可以多跳一圈舞。 9、双缓冲是必要的,但也不全是,很多教课书上把双缓冲作为消除屏幕闪烁的唯一方法,这不对,因为只要跟踪了屏幕刷新周期,就不会闪,双缓冲直接带来的就是你的程序画点必须画两次,一次向buffer,另一次重buffer搬到屏幕。我在做优化时,往往首先把双缓冲优化掉。没必要浪费时间,就算有点闪,游戏是可以牺牲效果,换取时间的。这里给一个跟踪屏幕刷新周期的函数,只要在你的刷屏程序前加上,效果基本上就可以了。 /*-------------------------------------------------------------------------------*/ //XiaoGe Made under WATCOM C/C++ 10.6 /*-------------------------------------------------------------------------------*/ void wait (void) //VGA屏幕刷新周期的测试 { while (inp(0x3DA)&0x08); while (!(inp(0x3DA)&0x08)); } /*-------------------------------------------------------------------------------*/ 10、刷屏程序应该包含:背景屏幕刷新、精灵动画刷新、鼠标处理、键盘处理等,并且,每秒钟必须能运行70次以上,如果做不到,优化你的程序。 11、优化是必须的,一个游戏引擎,至少应该优化7-10次,我的一个引擎,就优化了14次,速度从每秒钟12.1屏到70屏。还有,不要使用编译器的优化,除非你想你的用户无法使用你的程序。 12、计算你的每一步使用了几步操作,这一点在C中尤其重要,因为C太方便了,隐瞒了很多细节,如下例: 从 *(Video+k)=*(p[1]+j); k++; 到 *(Video+(k++))=*(p[1]+j); //减少了一次k读内存操作 到 *(Video+(k++))=*(*(p+1)+j); //减少了把p转化成数组操作 13、减少循环,循环中多开销了一次累加(读写内存),一次比较(读内存+1次逻辑),如下例: 从 for (i=0;i<10000;i++) { *(p+i)=0; } 到 for (i=0;i<10000;i+=10) //循环次数减少9000次 { *(p+i+0)=0; *(p+i+1)=0; *(p+i+2)=0; *(p+i+3)=0; *(p+i+4)=0; *(p+i+5)=0; *(p+i+6)=0; *(p+i+7)=0; *(p+i+8)=0; *(p+i+9)=0; } 到 for (i=0;i<10000;i+=10) //20次读变量内存减少为12次读,1次写 { j=p+i; *(j+0)=0; *(j+1)=0; *(j+2)=0; *(j+3)=0; *(j+4)=0; *(j+5)=0; *(j+6)=0; *(j+7)=0; *(j+8)=0; *(j+9)=0; } 到 for (i=0;i<10000;i+=10) //10次读值内存减少为1次,其余为寄存器变量 { j=p+i; *(j+0)=*(j+1)=*(j+2)=*(j+3)=*(j+4)=*(j+5)=*(j+6)=*(j+7)=*(j+8)=*(j+9)=0; } 当然,如果允许,可以写10000个,不过也没必要,减掉一多半就行了。关键在速度和程序容量上达成平衡。另外,DO...WHILE比FOR和WHILE要少一次逻辑比较。 14、具体的说,处理一个图块时,很多人采用x,y两重循环,这是很值得研究的,根据屏幕特点,应该只保留y循环,x方向直接线性累加处理即可。 15、不要节约判断语句,它可能给你带来多一条语句的开销,但是却可能减少几百条语句的开销,1赔100,赌了。 16、别给自己找病,养成良好的书写习惯,让编译程序为你检查错误,如下例: if (i==1) 写成 if (i=1) 编译不出错,但意思错了 写成 if (1=i) 编译就出错,可以检查出来 17、游戏程序没有主循环,主循环往往只是包含刷屏的一个死循环,更多的东东放在时钟里头,要熟练拦截时钟,改变它的频率,你的画面就会动得流畅、自然。下面是一个拦截时钟的例子,因为采用时钟循环,所以必须大量使用switch/case结构,要有思想准备。 /*-------------------------------------------------------------------------------*/ //XiaoGe Made under WATCOM C/C++ 10.6 /*-------------------------------------------------------------------------------*/ #define TIME_KEEPER_INT 0x1c long timer_counter; void (_interrupt far *Old_Time_Isr)(); void timer_program(void); //////////////////////////////////////////////////////////////// //注意:中断函数中不能调用系统输入输出函数,应尽量使用自己的程序 void _interrupt Timer(void) { timer_program(); //调用用户程序 timer_counter++; Old_Time_Isr(); } //////////////////////////////////////////////////////////////// #define CTRL_8253 0x43 #define CTRL_WORD 0x3c #define COUNTER_0 0x40 #define COUNTER_1 0x41 #define COUNTER_2 0x42 #define LOW_BYTE(n) (n&0x00ff) #define HI_BYTE(n) ((n>>8)&0x00ff) #define TIME_18HZ 0xFFFF //改变定时器频率函数 //注意:超过1000Hz,与Windows将发生冲突 void Change_Timer(unsigned short new_count) { outp(CTRL_8253,CTRL_WORD); outp(COUNTER_0,LOW_BYTE(new_count)); outp(COUNTER_0,HI_BYTE(new_count)); } //////////////////////////////////////////////////////////////// //安装时钟 void install_timer(int Hz) { short time_hz; time_hz=short(1193180/Hz); timer_counter=0; Change_Timer(time_hz); Old_Time_Isr=_dos_getvect(TIME_KEEPER_INT); _dos_setvect(TIME_KEEPER_INT,Timer); } //////////////////////////////////////////////////////////////// //卸载时钟 void uninstall_timer() { Change_Timer(TIME_18HZ); _dos_setvect(TIME_KEEPER_INT,Old_Time_Isr); } //////////////////////////////////////////////////////////////// /*-------------------------------------------------------------------------------*/ 18、不要去相信mouse程序会为你做到一切,去读0x33的状态,光标由自己显示,否则,哼哼...... 例子: /*-------------------------------------------------------------------------------*/ //XiaoGe Made under WATCOM C/C++ 10.6 /*-------------------------------------------------------------------------------*/ unsigned short cursor[] = { 0x0000, /*0000000000000000*/ /* 16 words of cursor mask */ 0x4000, /*0100000000000000*/ 0x6000, /*0110000000000000*/ 0x7000, /*0111000000000000*/ 0x7800, /*0111100000000000*/ 0x7c00, /*0111110000000000*/ 0x7e00, /*0111111000000000*/ 0x7f00, /*0111111100000000*/ 0x7c00, /*0111110000000000*/ 0x4600, /*0100011000000000*/ 0x0600, /*0000011000000000*/ 0x0300, /*0000001100000000*/ 0x0300, /*0000001100000000*/ 0x0180, /*0000000110000000*/ 0x0180, /*0000000110000000*/ 0x00c0, /*0000000011000000*/ }; struct Mouse { char show; //mouse 光标显示/不显示 char left; //mouse左键 char right //mouse右键 char middle; //mouse中键 int x; //mouseX坐标 int y; //mouseY坐标 unsigned char color; //mouse光标颜色 }mouse; int mouse_page; /*-------------------------------------------------------------------------------*/ void set_mouse_xy(int x_min,int x_max,int y_min,int y_max) { REGS regs; if (x_min<0) x_min=0; if (x_max>SCR_H) x_max=SCR_H; if (y_min<0) y_min=0; if (y_max>SCR_V) y_max=SCR_V; //Define H min-max regs.w.ax=0x07; regs.w.cx=x_min; regs.w.dx=x_max; int386(0x33,&regs,&regs); //Define V min-max regs.w.ax=0x08; regs.w.cx=y_min; regs.w.dx=y_max; int386(0x33,&regs,&regs); //POSITION MOUSE CURSOR regs.w.ax=0x04; regs.w.cx=(x_max-x_min)>>1; regs.w.dx=(y_max-y_min)>>1; int386(0x33,&regs,&regs); } /*-------------------------------------------------------------------------------*/ void init_mouse(void) { REGS regs; mouse.x=SCR_H/2; mouse.y=SCR_V/2; mouse.left=0; mouse.right=0; mouse.middle=0; mouse.color=255; mouse.show=0; //mouse reset regs.w.ax=0x00; int386(0x33,&regs,&regs); //old mouse hidden regs.w.ax=0x01; int386(0x33,&regs,&regs); set_mouse_xy(0,SCR_H,0,SCR_V); //Define Mic/Piexl regs.w.ax=0x0F; regs.w.cx=4; regs.w.dx=4; int386(0x33,&regs,&regs); } /*-------------------------------------------------------------------------------*/ void hard_disp_mouse(void) { int i,j,x,y; long addr,addr1,page; unsigned short temp; unsigned char color; unsigned int b[]={1,2,4,8,16,32,64,128,256,512,1024,2048,4096,8192,16384,32768}; unsigned char *video=(unsigned char *)0xA0000; color=mouse.color; y=mouse.y-1; addr=count_offest(SCR_H,mouse.x,mouse.y); mouse_page=addr>>16; set_page(mouse_page); for(i=0;i<16;i++) { x=mouse.x-1; temp=cursor[i]; addr=count_offest(SCR_H,x,y); for(j=16;j>=0;j--) { if((b[j]&temp)&&(x>0)&&(x<SCR_H)&&(y>0)&&(y<SCR_V)) { page=addr>>16; addr1=addr-(page<<16); if (mouse_page!=page) { mouse_page=page; set_page(mouse_page); } *(video+addr1)=color; *(buffer+addr)=color; } x++; addr++; } y++; } } /*-------------------------------------------------------------------------------*/ void read_mouse(void) { REGS in,out; mouse.color=255; in.w.ax=0x03; int386(0x33,&in,&out); mouse.left=(out.w.bx&0x01); mouse.right=(out.w.bx&0x02); mouse.middle=(out.w.bx&0x04); mouse.x=out.w.cx; if (0>mouse.x) mouse.x=0; if (SCR_H<mouse.x) mouse.x=SCR_H; mouse.y=out.w.dx; if (0>mouse.y) mouse.y=0; if (SCR_V<mouse.y) mouse.y=SCR_V; if (mouse.show) hard_disp_mouse(); } /*-------------------------------------------------------------------------------*/ 19、键盘操作要拦截键盘中断,可不能用系统给的函数,游戏程序员戒条:凡是系统给的,必然是不合用的。下面是例子。使用时,在你的循环中直接监测key_ascii就行了。 /*-------------------------------------------------------------------------------*/ #define SCAN_ALT 56 #define SCAN_CTRL 29 #define SCAN_caps 58 #define SCAN_LEFTSHIFT 42 #define SCAN_RIGHTSHIFT 54 #define SCAN_SHIFT (keyflag[SCAN_RIGHTSHIFT]||keyflag[SCAN_LEFTSHIFT]) #define KEY_END 255 #define KEY_LEFT 254 #define KEY_RIGHT 253 #define KEY_PAGEUP 252 #define KEY_UP 251 #define KEY_DOWN 250 #define KEY_HOME 249 #define KEY_CTRLBREAK 248 #define KEY_F1 247 #define KEY_F2 246 #define KEY_F3 245 #define KEY_F4 244 #define KEY_F5 243 #define KEY_F6 242 #define KEY_F7 241 #define KEY_F8 240 #define KEY_F9 239 #define KEY_F10 238 #define KEY_PAGEDOWN 237 #define KEY_INSERT 236 #define KEY_DELETE 235 #define KEY_LEFTALT 234 #define KEY_RIGHTALT 233 #define KEY_RIGHTCTRL 232 #define KEY_LEFTCTRL 231 #define KEY_caps 230 #define KEY_F11 229 #define KEY_F12 228 #define KEY_PRINTSCREEN 228 #define KEY_NUMLOCK 227 #define KEY_SCROLLLOCK 226 #define KEY_LEFTSHIFT 225 #define KEY_RIGHTSHIFT 224 #define KEY_WINDOWS 223 /*-------------------------------------------------------------------------------*/ static unsigned char asciinames[]={ 0,27,'1','2','3','4','5','6','7','8','9','0','-','=',8,9, 'q','w','e','r','t','y','u','i','o','p','[',']',13,KEY_LEFTCTRL,'a','s', 'd','f','g','h','j','k','l',';',39,'`',0,92,'z','x','c','v', 'b','n','m',',','.','/',0,'*',KEY_LEFTALT,' ',KEY_caps,KEY_F1,KEY_F2,KEY_F3,KEY_F4,KEY_F5, KEY_F6,KEY_F7,KEY_F8,KEY_F9,KEY_F10,KEY_NUMLOCK,KEY_SCROLLLOCK,'7','8','9','-','4','5','6','+','1', '2','3','0',127,0,0,'\\',KEY_F11,KEY_F12,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 }; /*-------------------------------------------------------------------------------*/ static unsigned char shiftnames[]={ 0,27,'!','@','#','$','%','^','&','*','(',')','_','+',8,9, 'Q','W','E','R','T','Y','U','I','O','P','{','}',13,1,'A','S', 'D','F','G','H','J','K','L',':',34,'~',KEY_LEFTSHIFT,'|','Z','X','C','V', 'B','N','M','<','>','?',KEY_RIGHTSHIFT,'*',1,' ',0,0,0,0,0,0, 0,0,0,0,0,0,0,'7','8','9','-','4','5','6','+','1', '2','3','0',127,0,0,'|',0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0 }; /*-------------------------------------------------------------------------------*/ static char specialnames[]={ 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,13,KEY_RIGHTCTRL,0,0, 0,0,0,0,0,0,0,0,0,0,KEY_WINDOWS,0,0,0,0,0, 0,0,0,0,0,'/',0,KEY_PRINTSCREEN,KEY_RIGHTALT,0,0,0,0,0,0,0, 0,0,0,0,0,0,KEY_CTRLBREAK,KEY_HOME,KEY_UP,KEY_PAGEUP,0,KEY_LEFT,0,KEY_RIGHT,0,KEY_END, KEY_DOWN,KEY_PAGEDOWN,KEY_INSERT,KEY_DELETE,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 }; /*-------------------------------------------------------------------------------*/ unsigned char pause_key; unsigned char key_ascii; unsigned char key_scan; unsigned char keyflag[128]; static unsigned char caps; static unsigned char cur_code,key_code; static void (_interrupt far *_old_key_interrupt)(void); /*-------------------------------------------------------------------------------*/ void setkeyspeed() { REGS regs; regs.w.bx=0x0; regs.w.ax=0x0A05; int386 (0x16,&regs,&regs); } /*-------------------------------------------------------------------------------*/ void clearkey() { int i; key_scan=0; key_ascii=0; memset(keyflag,0,sizeof(keyflag)); } /*-------------------------------------------------------------------------------*/ static void interrupt key_interrupt(void) { static unsigned char specialflag; unsigned char k,c,temp; int i; k=inp(0x60); outp(0x61,(temp=inp(0x61))|0x80); outp(0x61,temp); if(k==0xe0)specialflag=1; else if(k==0xe1)pause_key=1; else { if(k&0x80) { k&=0x7f; keyflag[k]=0; } else { key_code=cur_code; cur_code=key_scan=k; keyflag[k]=1; if(specialflag)c=specialnames[k]; else { if(k==SCAN_caps) { caps=(~caps)&1; } if(SCAN_SHIFT) { c=shiftnames[k]; if((c>='A')&&(c<='Z')&&caps) c+='a'-'A'; } else { c=asciinames[k]; if((c>='a')&&(c<='z')&&caps) c-='a'-'A'; } } if(c)key_ascii=c; } specialflag=0; } outp(0x20,0x20); } /*-------------------------------------------------------------------------------*/ static void initkey(void) { clearkey(); setkeyspeed(); _old_key_interrupt=_dos_getvect(9); _dos_setvect(9,key_interrupt); } /*-------------------------------------------------------------------------------*/ static void closekey(void) { _dos_setvect(9,_old_key_interrupt); } /*-------------------------------------------------------------------------------*/ unsigned char getscan(void) { unsigned char result; while((result=key_scan)!=0); key_scan=0; return(result); } /*-------------------------------------------------------------------------------*/ unsigned char getkey(void) { unsigned char result; while((result=key_ascii)==0); key_ascii=0; return(result); } /*-------------------------------------------------------------------------------*/ 20、任何情况下要注意调试,WATCOM C/C++程序员可以用以下几条函数调试: 调用: debug("test.dbg","i=%d\n",i); debug_print(); int debug_count=0; struct debug_type { char *fname[255]; char *fcoment[255]; int value[255]; }debug_i; //除错程序 void debug(char *file_name,char *coment,int debug_v) { if (debug_count<255) { debug_i.fname[debug_count]=file_name; debug_i.fcoment[debug_count]=coment; debug_i.value[debug_count]=debug_v; debug_count++; } } //除错写盘程序 void debug_print(void) { FILE *debug_file; int i; if (debug_count!=0) { for (i=0;i<=debug_count;i++) { debug_file=fopen(debug_i.fname[i],"a"); fprintf(debug_file,"debug[%3d] ",i); fprintf(debug_file,debug_i.fcoment[i],debug_i.value[i]); fclose(debug_file); } } init_debug(); } 21、最后一条,写不下去了,就不要写了,游戏开发是一个漫长的过程,没有一天能写成的游戏,至少我没见过,写得太累了,就玩去吧,千万不要把自己的热情消耗没了,那样的话,再简单的游戏也写不出来。   �⊙标点工作室 (2001-2002)∷

本文地址:http://com.8s8s.com/it/it25878.htm