游戏编程入门:电脑游戏编程入门 (DOS)



这里以电脑VGA 13H视频模式为例系统介绍制作电脑游戏基本思路方法VGA INT 13H模式是种工作在图形方式下模式它具有320X200屏幕分辩率同屏上可以显示256种颜色(超级任天堂和世嘉5代游戏机也只有256种颜色)早期许多游戏都是在这图形方式下开发(如大家非常熟悉仙剑奇侠传、红色警戒、DOOM等)在这种模式下开发游戏具有编程简单、运行速度快、颜色丰富等特点虽然以今天标准来说320X200分辩率已经不算什么但是它仍然是个学习电脑游戏编程很好入门环境我们可以通过开发VGA 13H模式下游戏逐步地由简单到复杂地学习游戏设计基本思路方法只要掌握了游戏设计基本思路方法和理论再利用其它软件Software工具(象WATCOM C、DJGPP、VC、DIRECT X等32位C编译器等)来设计游戏将变得非常容易用TURBO C 2.0编译也可以稍作修改用其它C编译器来编译运行我们先来了解下电脑显示器工作原理和显示卡结构

.显示器工作原理

目前在个人计算机上广泛使用是采用阴极射线管(CRT)光栅扫描显示器我们在屏幕上所看到颜色是由电子枪发出电子束打在CRT屏幕背面荧光层上点形成通过控制点亮度可以产生区别颜色电子束不断地从左到右、从上到下扫描整个屏幕使屏幕显示出图案电子束以大约每秒70次速率在屏幕上重画这图案这个过程称为显示刷新或屏幕刷新具体扫描频率依赖于所用显示适配器(又称为显示卡)电子束从屏幕左上角开始向右扫描到达屏幕右边缘后电子束被关闭(水平断开)接着它又迅速地返回到屏幕左边缘(水平回扫)开始进行下行水平方向扫描在完成全部水平方向扫描后电子束在屏幕右下角结束此时电子束被关闭(垂直断开)接着又迅速地返回到屏幕左上角(垂直回扫)开始下屏扫描电子束就是这样周而复始地扫描整个屏幕显示器在两种方式下工作:文本方式和图形方式电脑游戏般在图形方式下进行

2.显示器坐标系统

计算机屏幕上坐标和我们通常使用直角坐标系区别坐标原点(00)在屏幕左上角向右是水平方向坐标向下是垂直方向坐标且坐标没有负值

3.显示卡结构

显示器上显示卡负责将图形显示在屏幕上显示存储器中存放着在屏幕上显示图像数据显示卡硬件不停地将显存中内容显示在屏幕上显示存储器实际上是安装在显示卡上块或几块大规模集成电路其容量有1M、2M、4M、8M等在DOS下我们可以访问内存只有1MB空间(这就是DOS局限性所在)地址从00000H到FFFFFH这段内存根据用途又分为区别系统分配给图形缓冲区(显示存储器)地址在A0000H到BFFFFH的间大小为128KB其中VGA占用了A0000H到AFFFFH段共64KB这段地址是内存映射地址供我们访问显示存储器用在VGA 13H图形模式下显示内存使用A0000H到AF9FFH段线性内存空间每个字节表示个点对应屏幕上个像点320*200屏幕分辨率共需要64000个字节刚好64KB个字节可以表示最大整数值为256所以每个像点就可以表示256种颜色

4.设置视频模式

画图以前必须使屏幕工作在图形方式这就要设置屏幕视频模式设置视频模式有许多种思路方法其中视频BIOS功能是最简单通过BIOS中断0x10服务可以很方便地设置屏幕模式思路方法是将值0放入ah寄存器显示模式放入al寄存器中然后86

设置视频模式:

void SetVideoMode( mode)
{
union REGS r;
r.h.ah=0;
r.h.al=mode;
86(0x10,&r,&r);
}

其中mode是视频模式

5.在屏幕上画点

由于显示存储器是线性排列每个像点用个字节来表示所以对像点寻址非常容易像点在显示内存中偏移地址可由这个公式确定:y*320+x其中y是像点在垂直方向坐标x是水平方向坐标320是屏幕宽度有了像点偏移地址然后加上显示内存首地址即可得到像点在显示内存中绝对地址只要将表示点颜色值放到这个地址处就可以在屏幕上画点了首先建立个指针VideoBufferPtr使它指向显示内存首地址:

char far *VideoBufferPtr=( char far *)0xa0000000;

将这个指针加上像点偏移地址像点最终地址就确定了它等于:

VideoBufferPtr+y*320+x;

把颜色值color写到这个地址:

*(VideoBufferPtr+y*320+x)=color;

画点:

void DrawPo( x, y,unsigned char color)
{
*(VideoBufferPtr+y*320+x)=color;
}

6.显示

游戏经常在屏幕上打印出显示游戏状态文本使游戏者了解当前游戏状态这是游戏和游戏者进行交互必不可少然而在图形模式下显示文本和在文本模式下有很大区别在图形模式下文本必须使用基于画位图思路方法来显示这里介绍种简便思路方法:利用计算机只读存储器中ASCII字体数据在计算机只读存储器(ROM)中固化有ASCII字体数据这可在基址F000:FA6E上找到我们只需了解数据是如何存储然后再得出存取该数据算法就可以用任意颜色在屏幕上用画位图思路方法把画出来在ROM中按照ASCII编码顺序放置由于每个为8*8点阵所以每个占据8个字节存储空间要找出某个我们只需将这个ASCII码和8相乘然后将结果加到基址F000:FA6E上由于用画位图思路方法来显示因此我们可以随意给它们指定颜色(前景或背景颜色)在图形模式下我们只需要两个就能够打印文本:个用于显示单个个用来显示



定义个远指针指向ROM开始位置:

char far *RomCharPtr=(char far *)0xf000fa6e;

下面是在屏幕上打印:

1.打印

void PrChar( cx, cy,char c,unsigned char Fcolor,unsigned char Bcolor, flag)
{
off,x,y;
char far *TempPtr;
unsigned char bit_mask;
TempPtr=RomCharPtr+(c<<3);
off=(cy<<8)+(cy<<6)+cx;
for(y=0;y<8;y)
{
bit_mask=0x80;
for(x=0;x<8;x)
{
((*TempPtr&bit_mask))
*(VideoBufferPtr+off+x)=Fcolor;
(flag1)
*(VideoBufferPtr+off+x)=Bcolor;
bit_mask=(bit_mask>>1);
}
off320;
TempPtr;
}
}

介绍说明:

cx,cy 是在屏幕上坐标

c ASCII码

Fcolor,Bcolor 分别是前景和背景颜色

flag 打印标志当flag=1时显示背景色否则打印具有透明效果

2.打印

void PrString( x, y,char *,unsigned char Fcolor,unsigned char Bcolor, flag)
{
index;
for(index=0;[index]!=0;index)
PrChar(x+(index<<3),y,[index],Fcolor,Bcolor,flag);
}

介绍说明:

1.x,y 是串在屏幕上坐标

2.* 串指针

3.Fcolor,Bcolor 分别是前景和背景颜色

4.flag 打印标志当flag=1时显示背景色否则打印串具有透明效果

7.设置颜色寄存器

我们知道VGA显示卡具有显示256种颜色能力每种颜色能够用个0-255的间数值来表示那么这些数值和我们在屏幕上实际见到颜色的间有什么关系呢?其实这些数值只是VGA显示卡上颜色寄存器索引值颜色寄存器里才保存了屏幕上颜色真实值VGA显示卡上有个包含256个单元颜色寄存器(又称为调色板)每个单元由 3部份组成这 3部份分别代表颜色中红、绿、蓝 3种成份(显示器就是用这 3种成份来组成任何我们所看到颜色)用 3个字节表示颜色寄存器共有768个字节(3*256=768)当我们要在屏幕上显示某种颜色时显示卡硬件就根据颜色索引值在颜色寄存器中查找找到后再从相应单元中取出颜色值显示在屏幕上这个过程和画家使用调色板相似颜色寄存器相当于调色板颜色寄存器中单元相当于调色板上色格在色格中装有预先调好颜色当画家需要用某种颜色作画时就从装有那种颜色色格中把颜色取出来例如我们要显示颜色索引值为30颜色显示卡硬件就去查找颜色寄存器第30单元30单元位于距颜色寄存器首址3*30=90处(每个单元有 3个字节)然后取出90处记录有红、绿、蓝 3种成份 3个字节作为在屏幕上显示色彩信号但是实际上每个字节只用了 6位来表示颜色其它两位没用这 6位表示值域为0-63所以每种颜色(红、绿、蓝)成份具有64种亮度表现能力 3种颜色成份组合共可以产生64*64*64=262,144种颜色(VGA 13H模式从这262,144种颜色中取出256种在同屏幕上显示)我们可以通过事先设置颜色寄存器值来使用我们自己颜色
设置颜色寄存器有多种思路方法BIOS功能但是这种思路方法速度比较慢游戏设计中通常采用直接访问VGA显示卡I/O端口思路方法来快速设置颜色寄存器我们只需访问 4个I/O端口就可以完成设置颜色寄存器工作这 4个端口分别是: 0x3c6、0x3c7、0x3c8和0x3c9
端口0x3c6称为调色板屏蔽寄存器用来屏蔽所要求调色板寄存器如果你在这个寄存器中放入0xff你就可以通过调色板索引寄存器0x3c7和0x3c8(个用于读个用于写)访问任何你希望访问颜色寄存器端口0x3c9称为调色板数据寄存器红、绿、蓝 3种成份就是通过它进行读写(颜色值要读或写 3次)

我们定义个结构来方便处理颜色寄存器:

typedef struct RGB_COLOR
{
unsigned char red;
unsigned char green;
unsigned char blue;
}RGBColor,*RGBColorPtr;

结构中red、green和blue变量用来保存颜色红、绿、蓝 3种成份

设置颜色寄存器值:

void SetPaletteRegister( index,RGBColorPtr color)
{
outportb(0x3c6,0xff);
outportb(0x3c8,index);
outportb(0x3c9,color->red);
outportb(0x3c9,color->green);
outportb(0x3c9,color->blue);
}

获取颜色寄存器值:

void GetPaletteRegister( index,RGBColorPtr color)
{
outportb(0x3c6,0xff);
outportb(0x3c7,index);
color->red=inportb(0x3c9);
color->green=inportb(0x3c9);
color->blue=inportb(0x3c9);
}

8.在屏幕上画位图

计算机绘制图像通常采用种称为位映射图(BITMAP)图形处理思路方法进行位映射图是个矩形点阵结构( 2维矩阵)显示在屏幕上时对应屏幕上个矩形区域组成位图数据储存在内存中段连续区间我们比较常见位图文件有:BMP、PCX、GIF、JPG等位图通常存储在外部文件中使用以前必须将其从磁盘文件调入内存下面介绍将个256色PCX图形文件读入内存思路方法:



1.定义PCX文件头结构:

typedef struct PCX_HEADER
{
char menufactrue; /* 厂家标识编号 0x0a */
char version; /* 文件版本编号 */
char packing_type; /* 压缩模式 */
char bits_per_pixel; /* 每点占用位数 */
minx; /* 最小X坐标值 */
miny; /* 最小Y坐标值 */
maxx; /* 最大X坐标值 */
maxy; /* 最大Y坐标值 */
hres; /* 水平分辨率 */
vres; /* 垂直分辨率 */
char palette[48]; /* 颜色调色板 */
char unused; /* 未使用 */
char bit_plance; /* 位平面个数 */
s; /* 单水平线占用字节数 */
palette_type; /* 调色板类型 */
char unused2[58]; /* 未使用 */
}PCXHeader,*PCXHeaderPtr;

2.定义用来存放PCX图像数据结构:

typedef struct PCX_PICTURE
{
width;
height;
char far *buffer;
RGBColor palette[256];
}PCXPicture,*PCXPicturePtr;

3.化图像数据:

InitPCX(PCXPicturePtr image, w, h)
{
unsigned size=w*h;
image->width=w;
image->height=h;
image->buffer=(char far *)farmalloc(size);
(image->bufferNULL) 0;
1;
}

4.从外部文件读入数据:

LoadPCX(char *filename,PCXPicturePtr image, flag)
{
FILE *fp;
unsigned num_s,count,size;
index;
unsigned char data;
PCXHeader PcxHeader;
size=image->width*image->height;
((fp=fopen(filename,\"rb\"))NULL)
0;
fread(&PcxHeader,(PCXHeader),1,fp);
count=0;
while(count<=size)
{
data=fgetc(fp);
(data>=192&&data<=255)
{
num_s=data-192;
data=fgetc(fp);
while(num_s-->0)
{
*(image->buffer+count)=data;
count;
}
}

{
*(image->buffer+count)=data;
count;
}
}
fseek(fp,-768L,SEEK_END);
for(index=0;index<256;index)
{
image->palette[index].red=((fgetc(fp))>>2);
image->palette[index].green=((fgetc(fp))>>2);
image->palette[index].blue=((fgetc(fp))>>2);
}
fclose(fp);
(flag1)
for(index=0;index<256;index)
SetPaletteRegister(index,(RGBColorPtr)&image->palette[index]);
1;
}

其中参数flag用来指明调入文件同时是否设置颜色寄存器(flag=1设置)

5.画位图:

void DrawImage( x, y, width, height,char far *image)
{
i,j;
for(i=0;i<height;i)
for(j=0;j<width;j)
{
(*image!=0&&(x+j)>=0&&(x+j)<320&&(y+i)>=0&&(y+i)<200)
DrawPo(x+j,y+i,*image);
image;
}
}

x,y是图像在屏幕上左上角坐标width,height是图像宽度和高度image是指向内存中图像指针

我们对(*image!=0&&(x+j)>=0&&(x+j)<320&&(y+i)>=0&&(y+i)<200)语句进行下分析:

*image!=0用来检查所画颜色值是否是透明色如果是则不画出来这样我们就可以画出有透明效果图象即透过图象可以看到背景透明色通常取值0也可以用其他颜色值表示
(x+j)>=0&&(x+j)<320&&(y+i)>=0&&(y+i)<200语句用来判断所画点坐标是否超出屏幕显示范围这样可以画出具有裁剪效果图象如图象部份在屏幕外

这个并不是最快它要执行width X height次判断更快请看VGA13H库中绘图

9.控制游戏速度

初学游戏编程人常常会遇到这样问题设计出来游戏在台机子上速度运行正常为什么把游戏在另台运算速度比较快机子上运行时游戏速度也变得很快呢?是为什么呢?
问题就在于游戏中没有个时间基准值在控制它
那么如何控制游戏速度呢?这就得用电脑时钟计数来控制游戏首先我们来了解下PC机时钟是如何工作PC机采用块8253定时器芯片计算系统时钟脉冲若干个系统时钟周期转换成个脉冲这些脉冲序列可以用以计时也可以送入计算机扬声器产生特定频率声音8253定时器芯片独立于CPU运行它可以象实时时钟那样CPU工作状态对它没有任何影响
8253芯片有 3个独立通道每个通道功能各不相同 3个通道功能如下:通道0:为系统时钟所用在启动时由BIOS置入初值每秒钟约发出18.2个脉冲脉冲计数值存放在BIOS数据区0040:006c存储单元中(注意这个单元内容对我们非常有用!)通道0输出脉冲作为申请定时器中断请求信号还用于磁盘某些定时操作如果改变了通道0计数值必须确保在CPU每次访问磁盘以前恢复原来读数否则将使磁盘读写产生通道1:用于控制计算机动态RAM刷新速率般情况下不要去改变它通道2:连接计算机扬声器产生单方波信号控制扬声器发声8253定时器芯片个通道含有3个寄存器CPU通过访问3个端口(通道0为40h通道1为41h通道2为42h)来访问各个端口3个寄存器8253每个端口有6种工作模式当通道0用于定时或通道2用于定时或发声时般用模式3在模式3下计数值被置入锁存器后立即复制到计数器计数器在每次系统时钟到来时减1减至0后方面马上从锁存器中重新读取计数值方面向CPU发出个中断请求(INT 1CH中断很有用)如此循环在输出线上高低电平时间各占计数时间从而产生方波输出
对8253定时器芯片编程是通过命令端口寄存器43h来实现它决定选用通道、工作模式、送入锁存器计数值是字节还是两字节、是 2进制码还是BCD码等工作参数端口43h各位组合形式如下:



位0____若为1则采用 2进制表示否则用BCD码表示计数值

位3-1____工作模式号其值(0-5)对应6种模式

位5-4____操作类型:00:把计数值送入锁存器;

01:读写高字节;

10:读写低字节;

11:先读写高字节再读写低字节;

位7-6____决定选用通道号其值为0-2

对8253芯片编程 3个步骤:

1.设置命令端口43h

2.向端口发送个工作状态字节

3.确定定时器工作方式;

若是通道2给端口61h第0位和第1位置数启动时钟信号当第1位置1时通道2驱动扬声器置0时用于定时操作;将个字计数值按先低字节后高字节顺序送入通道I/O端口寄存器(通道0为40h通道1为41h通道2为42h)当第3个步骤完成后被编程通道马上在新状态下开始工作由于8253 3个通道都独立于CPU运行所以在结束以前要恢复各通道正常状态值
在游戏中我们只使用通道0怎样对其进行编程呢?首先得确定放入锁存器16位计数值:

16位计数值=1.19318MB/希望频率

其中1.19318MB是系统振荡器频率

由上式可知计数器所能产生值是18.2Hz-1.19318MHz这已经足够了可以满足我们游戏要求

以下是对8253定时器芯片通道0编程:

# T60HZ 0x4dae
# T50HZ 0x5d37
# T40HZ 0x7468
# T30HZ 0x965c
# T20HZ 0xe90b
# T18HZ 0xffff
# LOW_BYTE(n) (n&0x00ff)
# HI_BYTE(n) ((n>>8)&0x00ff)
far *clk=( far *)0x0000046c;


1.改变时间定时器值:

void ChangTime(unsigned cnt)
{
outportb(0x43,0x3c);
outportb(0x40,LOW_BYTE(cnt));
outportb(0x40,HI_BYTE(cnt));
}

2.延时:

void Delay( d)
{
tm=*clk;
while(*clk-tm<d);
}

上面定义指针*clk指向BIOS数据区0040:006c存储单元该单元中存放着定时器计数值我们可以根据该单元内容计算差值来达到延时
需要注意由于改变定时器计数值操作会和保护模式下代码发生冲突所以不能在WINDOWSDOS仿真环境下改变定时器必须在纯DOS环境里使用改变了定时器值以后结束以前必须恢复原来如果不恢复原来18.2次计数值在读写磁盘操作时将引起读写甚至死机

我们将时钟设置成每秒60HZ这样:ChangTime(T60HZ);

在游戏中延时定时间这样:Delay(10);

十.在后台运行

所谓后台运行就是在游戏运行当中“同时“运行其它如在游戏中演奏背景音乐、游戏计时等这些工作看起来好象和游戏过程“同时“在进行这里“同时“其实就是利用中断思路方法来实现即在游戏中每隔定时间产生次中断去做其他工作完成后再回到游戏中
在BIOS中包含了个特殊伪中断----INT 1CH中断中断在BIOS化时没有任何作用中断处理只有条中断返回语句----IRET因此该中断立即返回INT 1CH中断是在BIOS中断INT 8H修正日历计数后由该中断和INT 8H中断起以定时器中断频率(正常情况下为每秒钟18.2次)不停地执行而和CPU无关我们可以改变这中断使它指向我们中断处理当我们改变了时间定时器值以后INT 1CH将按改变后频率被
重置INT 1CH中断向量前必须先保存好原来INT 1CH中断向量以便在我们运行结束时再恢复它否则将引起系统崩溃
另外中断处理处理时间不能超过定时器确定时间否则将死机所以在中断处理里不能运行太多代码也不能和相关C如sound,delay等

编写新INT 1CH中断思路方法如下:

1.设置个中断类型指针用来保存原先INT 1CH中断向量:

void errupt far (*OldInt1chHandler);

2.编写新INT 1CH中断:

void far errupt NewInt1ch(void)

{

/*我们中断*/

}

3.保存原来INT 1CH中断向量:

OldInt1chHandler=getvect(0x1c);

4.设置新INT 1CH中断向量:

vect(0x1c,NewInt1ch);

5.恢复旧INT 1CH中断:

vect(0x1c, OldInt1chHandler);



.如何处理按键

PC键盘是个智能化键盘它相当于部完整计算机键盘内有片Intel 8048(或8049)单片机(处理器)对整个键盘上键、功能键、控制键和组合键进行管理当在键盘上按下个键时键盘上处理器首先向计算机主机发出硬件中断请求然后将该键扫描码以串行方式传送给计算机主机计算机主机在硬件中断作用下INT 09H硬件中断把键盘送来扫描码读入并转换为ASCII码存入键盘缓冲区中按下个键送出个闭合码键被释放时送出个断开码键盘处理中断从键盘I/O端口(端口地址为60H)读取个字节数据如果读取数据第7位为1时表示按键已放开(送出断开码)如第7位为0表示键按下(送出闭合码)数据第0-6位则为按键扫描码键盘上个键都对应个扫描码根据扫描码就能唯确定个键键盘缓冲区位于0040:001EH-4000:003EH的间BIOS数据区长度为34个字节个先进后出循环队列使用PC机原有键盘处理可以很方便地处理键盘但是它是BIOS所以反应比较慢另外当我们要同时处理几个按键时(例如同时按下Up箭头键和Left箭头键沿对角线运动)原有键盘中断就不能满足要求这时就需要编写个适合我们要求键盘中断

编写新键盘中断要做以下几项工作:

1.进入键盘中断

2.从键盘I/O端口60H读取个字节按键码并将它存入个全局变量中供处理或者将按键码存入个数据表中

3.读取控制寄存器61H并用82h完成个OR操作

4.将结果写回控制寄存器端口61H

5.在控制寄存器上用7fh完成个AND操作以便复位键盘触发器告诉硬件个按键已被处理可以读下个键了

6.复位中断控制器8259向端口20h写个20h

7.退出键盘中断

我们先定义组宏常量记录键值它包括128个键盘扫描码:

# KEY_A 0x1E

# KEY_B 0x30

# KEY_C 0x2e

# KEY_D 0x20

# KEY_E 0x12

# KEY_F 0x21

# KEY_G 0x22

# KEY_H 0x23

# KEY_I 0x17

# KEY_J 0x24

# KEY_K 0x25

# KEY_L 0x26

# KEY_M 0x32

# KEY_N 0x31

# KEY_O 0x18

# KEY_P 0x19

# KEY_Q 0x10

# KEY_R 0x13

# KEY_S 0x1f

# KEY_T 0x14

# KEY_U 0x16

# KEY_V 0x2f

# KEY_W 0x11

# KEY_X 0x2d

# KEY_Y 0x15

# KEY_Z 0x2c

# KEY_1 0x02

# KEY_2 0x03

# KEY_3 0x04

# KEY_4 0x05

# KEY_5 0x06

# KEY_6 0x07

# KEY_7 0x08

# KEY_8 0x09

# KEY_9 0x0a

# KEY_0 0x0b

# KEY_DASH 0x0c /* _- */

# KEY_EQUAL 0x0d /* */

# KEY_LBRACKET 0x1a /* {[ */

# KEY_RBRACKET 0x1b /* }] */

# KEY_SEMICOLON 0x27 /* :; */

# KEY_RQUOTE 0x28 /* \"\' */

# KEY_LQUOTE 0x29 /* ~` */

# KEY_PERIOD 0x33 /* >. */

# KEY_COMMA 0x34 /* <, */

# KEY_SLASH 0x35 /* ?/ */

# KEY_BACKSLASH 0x2b /* |\\ */

# KEY_F1 0x3b

# KEY_F2 0x3c

# KEY_F3 0x3d

# KEY_F4 0x3e



# KEY_F5 0x3f

# KEY_F6 0x40

# KEY_F7 0x41

# KEY_F8 0x42

# KEY_F9 0x43

# KEY_F10 0x44

# KEY_ESC 0x01

# KEY_BACKSPACE 0x0e

# KEY_TAB 0x0f

# KEY_ENTER 0x1c

# KEY_CONTROL 0x1d

# KEY_LSHIFT 0x2a

# KEY_RSHIFT 0x36

# KEY_PRTSC 0x37

# KEY_ALT 0x38

# KEY_SPACE 0x39

# KEY_CAPSLOCK 0x3a

# KEY_NUMLOCK 0x45

# KEY_SCROLLLOCK 0x46

# KEY_HOME 0x47

# KEY_UP 0x48

# KEY_PGUP 0x49

# KEY_MINUS 0x4a

# KEY_LEFT 0x4b

# KEY_CENTER 0x4c

# KEY_RIGHT 0x4d

# KEY_PLUS 0x4e

# KEY_END 0x4f

# KEY_DOWN 0x50

# KEY_PGDOWN 0x51

# KEY_INS 0x52

# KEY_DEL 0x53

然后定义两个来保存键盘状态:

char key_state[128],key_pressed[128];

其中key_state[128]用来表示键当前状态key_pressed[128]里保存值表示哪些键被按下值1表示按下0表示放开

在挂上新键盘中断以前将原来键盘中断地址保存好以便在运行结束后恢复它我们定义个中断指针来存放原来地址:

void errupt far (*OldInt9Handler);

1.安装新键盘中断:

void InstallKeyboard(void)
{
i;
for(i=0;i<128;i)
key_state[i]=key_pressed[i]=0;
OldInt9Handler=getvect(9);
vect(9,NewInt9);
}

2.恢复旧键盘中断:

void ShutDownKeyboard(void)
{
vect(9,OldInt9Handler);
}

3.新键盘中断:

void far errupt NewInt9(void)
{
unsigned char ScanCode,temp;
ScanCode=inportb(0x60);
temp=inportb(0x61);
outportb(0x61,temp | 0x80);
outportb(0x61,temp & 0x7f);
(ScanCode&0x80)
{
ScanCode&=0x7f;
key_state[ScanCode]=0;
}

{
key_state[ScanCode]=1;
key_pressed[ScanCode]=1;
}
outportb(0x20,0x20);
}

4.读取按键状态(游戏中来确定按了哪些键):

GetKey( ScanCode)
{
res;
res=key_state[ScanCode]|key_pressed[ScanCode];
key_pressed[ScanCode]=0;
res;
}

例如:

(GetKey(KEY_UP))

{

....

}

用来判断是否按下UP键

十 2.消除屏幕闪烁

当我们直接在显示缓冲区内绘图时屏幕上往往会产生闪烁或类似裁剪现象这是为什么呢?这是眼睛有惰性当人眼看东西时看过东西影像会在人眼睛里停留段很短时间大约为1/24秒当图像在个时间间隔中被更新时如果更新时间长于1/24秒看到图像就会产生闪烁1/24秒是个人能够看出视觉变化最少时间量产生屏幕闪烁还有另外个原因就是我们绘制图像时没有和视频显示完全同步VGA视频硬件以大约每秒70次速率刷新屏幕绘制图像过程如果不在个刷新周期内完成屏幕也会产生闪烁现象可能会出现幅图像还没有绘制完就被显示出来情况
如何避免屏幕出现闪烁呢?般有 2种思路方法:是使图像绘制过程在屏幕个刷新周期内进行 2是采用双缓冲思路方法
在垂直回扫周期内视频缓冲区不会被视频硬件访问如果我们改变了视频缓冲区也要等到下帧才能看到结果更新视频缓冲最佳时机是它没有被访问时候----垂直回扫周期内为此我们必须找到检测垂直回扫开始思路方法并且用检测结果使视频缓冲更新和屏幕刷新同步在VGA显示卡上有个寄存器可以被我们用来监视垂直回扫这个寄存器我们称为VGA输入状态字寄存器它在端口0x3da上是个 8位寄存器我们只对其中第 4位感兴趣当该位为1时屏幕正在进行回扫该位为0时没有回扫



等待垂直回扫:

void WaitForVsync(void)
{
while(!inportb(0x3da)&0x08);
}

从端口0x3da取出个字节然后将这个字节和值0x08进行和运算运算结果如果为1则回扫过程开始

对垂直回扫计数也可用来给定时

那么什么是双缓冲?所谓双缓冲就是在内存中开辟段和视频缓冲区大小内存空间把图形先画在这段内存中然后快速地把内存内容复制到视频缓冲区由于复制速度非常快人眼感觉不到绘图过程也避免了屏幕出现闪烁现象
在创建双缓冲区前先建立个指针指向双缓冲区开始位置:

char far *DoubleBufferPtr;

有时我们需要访问视频缓冲区所以还要建立个指针用来记录当前活动缓冲区:

char far *ActiveBufferPtr;

以下是和双缓冲有关:

1.创建双缓冲:

CreateDoubleBuffer(void)
{
DoubleBufferPtr=(char far *)farmalloc(64000L);
(DoubleBufferPtrNULL) 0;
1;
}

2.撤消双缓冲区:

void DeleteDoubleBuffer(void)
{
(DoubleBufferPtr!=NULL)
farfree(DoubleBufferPtr);
}

3.显示双缓冲区:

void ShowDoubleBuffer(void)
{
memcpy(VideoBufferPtr,DoubleBufferPtr,64000L);
}

4.设置当前活动缓冲区:

void SetActiveBuffer( n)
{
switch(n)
{
VIDEO_BUFFER:
ActiveBufferPtr=VideoBufferPtr;
;
DOUBLE_BUFFER:
ActiveBufferPtr=DoubleBufferPtr;
}
}

十 3.游戏动画对象

游戏中活动体都可以看成为个动画对象动画对象由定数量静态图像(子画面)组成时间间隔把这些图像在屏幕上显示出来给人以运动感觉游戏中各种活动人物、精灵怪物、活动背景等都可以当成动画对象来处理为了方便我们处理动画对象我们定义个动画对象结构用来存放有关信息

动画对象结构:

typedef struct OBJECT
{
x,y,h,v,x_old,y_old,width,height;
anim_clock,anim_speed;
motion_clock,motion_speed;
char far *frame;
cur_frame;
state;
char far *mask;
}Object,*ObjectPtr;

其中: x,y 是对象在屏幕上坐标(左上角坐标);

h,v 对象在水平和垂直方向移动速率;

x_old,y_old 对象移动以前屏幕位置;

width,height 对象宽度和高度;

anim_clock 对象子画面更新计数值;

anim_speed 对象子画面更新计数阀值当anim_clock累加值等于anim_speed时显示对象下个子画面anim_clock同时复位为值;

motion_clock 对象位置更新计数值;

motion_speed 对象位置更新计数阀值当motion_clock值累加等于motion_speed值时对象变换位置otion_clock值复位为值;

*frame 指向当前显示对象子画面指针;

cur_frame 当前显示对象子画面号;

state 对象当前状态(是否活动是否存活等);

*mask 对象图形掩模指针掩模大小和对象相同用来保存屏幕上即将被对象覆盖掉位置上图形当对象位置移动后再用来恢复原来位置图形以避免对象破坏屏幕



Tags:  dos入门 电脑编程入门c语言 电脑编程入门 游戏编程入门

延伸阅读

最新评论

发表评论