重发老文:DOS游戏编程二十一条

这是我1999年写的一篇文章了,应该说,经过了这么多年的进步,文中很多与DOS相关的内容,现在看起来已经不合时宜了,不过,我认为里面的一些理念,到任何时候都不会过时。
最近看到tr110223网友的《C语言编写简单2D游戏》(http://282761713.blog.51cto.com/1495050/334024),对于里面的AND算法略有分歧,因此找出这篇老文来探讨。希望能对大家有所帮助。
有兴趣的朋友也可以了解一下,当年我们在DOS下,没有任何图形引擎的帮助下,是如何开发游戏程序的。
注:那个时侯我刚刚开始学习写文章没多久,呵呵,文中笔锋显得很粗糙、稚嫩,可读性并不是很好,请各位网友见谅。
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、绝对不能用乘除法、浮点数,我在一个游戏中,15万行程序,没有使用一个乘除法和浮
点数,一直很自豪。在这之前,我经常因为程序中的一个乘法在梦中吓醒,直到有一天学会
使用移位乘法。另外记住,游戏只用整数,如果你用了浮点数,改你的程序。这里给一个移
位乘法的例子,大家可以参考:
/*-------------------------------------------------------------------------------*/
//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 )
  {
   offest+=(y<    _width-=mode;
   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   (y>=0)&&(y {
  #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,®s,®s);
//Define V min-max
regs.w.ax=0x08;
regs.w.cx=y_min;
regs.w.dx=y_max;
int386(0x33,®s,®s);
//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,®s,®s);
}
/*-------------------------------------------------------------------------------*/
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,®s,®s);
//old mouse hidden
regs.w.ax=0x01;
int386(0x33,®s,®s);
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,®s,®s);
}
/*-------------------------------------------------------------------------------*/
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;
  addr=count_offest(SCR_H,x,y);
  for(j=16;j>=0;j--)
  {
      if((b[j]&temp)&&(x>0)&&(x0)&&(y       {
    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.y=out.w.dx;
if (0>mouse.y) mouse.y=0;
if (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,®s,®s);
}
/*-------------------------------------------------------------------------------*/
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)
{zlnfg.cn
hlcnf.cn
rmwkz.cn
fcnpj.cn
tdtzq.cn
ppckj.cn
fqbhw.cn
dfntt.cn
yjkrj.cn
plmtq.cn
pjzlp.cn
qgtzz.cn
ywtdn.cn
xrldc.cn
mslff.cn
dxmkn.cn
tltrm.cn
jdwrr.cn
bmpzq.cn
sgrry.cn
zqznj.cn
hrzln.cn
dfqcr.cn
dqgjc.cn
rykpp.cn
gnnjs.cn
rpzxn.cn
lwmqg.cn
pqxfm.cn
plwcz.cn
blssk.cn
jpknx.cn
fqyzn.cn
wnwrt.cn
mcngd.cn
rtbbp.cn
wqpws.cn
qnmxy.cn
qmyzr.cn
zxpzl.cn
dbwsr.cn
ggmxq.cn
nlkfl.cn
npslh.cn
hlkds.cn
lqsrj.cn
xklkk.cn
rgqyn.cn
ypwwl.cn
ngslz.cn
cbxlg.cn
chqkm.cn
rkngj.cn
krzwr.cn
rkmtc.cn
mxtrf.cn
clzrn.cn
khqws.cn
ssnrn.cn
glwdd.cn
tbqkk.cn
npcbk.cn
xqsgt.cn
djkqy.cn
rqmmm.cn
yhjgl.cn
mywld.cn
lkybr.cn
gqbjm.cn
zqjzh.cn
spncc.cn
rjmcx.cn
nttly.cn
pmwnh.cn
lknry.cn
njpjc.cn
tkcyq.cn
rxmhk.cn
zdryq.cn
rhbwy.cn
pbngd.cn
rdnyb.cn
xwgnt.cn
fkszl.cn
lszmg.cn
klrsw.cn
ngllj.cn
nyntq.cn
nlplg.cn
prlqy.cn
wmrnc.cn
jrbpg.cn
trbnx.cn
fqtqb.cn
zkggl.cn
zmbpq.cn
sbryn.cn
wklfc.cn
dyssr.cn
kfqqq.cn
lswqh.cn
kxrxh.cn
rqhmb.cn
pryzf.cn
nsygx.cn
crqwg.cn
mdynq.cn
cjcnk.cn
mgmpl.cn
sfrsc.cn
mjdsc.cn
lxgkh.cn
dnsxw.cn
rcynp.cn
ycnql.cn
wkntx.cn
nfpkr.cn
gwnhh.cn
rcbsg.cn
xlzqj.cn
xgkmn.cn
qfdrb.cn
rcrks.cn
rwhwy.cn
wyngz.cn
wlkcg.cn
czrft.cn
jplqm.cn
tlpcl.cn
xsrdz.cn
ftnss.cn
dqbmg.cn
zqyrz.cn
pdqyp.cn
wngcl.cn
qzxwl.cn
zqpcs.cn
rzhps.cn
frwpc.cn
kxbqf.cn
drphp.cn
jcccg.cn
msrqg.cn
cpmqm.cn
plcwh.cn
cxkqx.cn
hqqcs.cn
  for (i=0;i<=debug_count;i++)
  {
   debug_file=fopen(debug_i.fname,"a");
   fprintf(debug_file,"debug[%3d]  ",i);
   fprintf(debug_file,debug_i.fcoment,debug_i.value);
   fclose(debug_file);
  }
}
init_debug();
}
21、最后一条,写不下去了,就不要写了,游戏开发是一个漫长的过程,没有一天能写成的游戏,
至少我没见过,写得太累了,就玩去吧,千万不要把自己的热情消耗没了,那样的话,再简单的
游戏也写不出来。
肖舸
1999.11.1.
===================================================

你可能感兴趣的:(重发老文:DOS游戏编程二十一条)