作为成熟的操作系统,Unix在重要的行业中有很广泛的应用。而在实际应用中,却存在游戏软件少、游戏开发难度大等缺点。现在虽然推出了Unix下的 X窗口,但因其应用的不广泛加上介绍功能的书籍少,使新手也很难入手开发自己的游戏程序。下面以自己开发的一个小游戏来介绍在Unix中游戏开发的一些基本知识。
游戏名称:《Unix下的俄罗斯方块》
开发环境:SCO Unix System V/386 Release 3.2 后经少量改动在SCO OpenServer 5.0及多种Linux环境下编译通过
开发语言:Microsoft@#s C compiler version 6
开发方式:Unix字符方式下用Unix自带CURSES.H图形库开发
一、开发思路
1.单人游戏时:随机产生一个方块,用户使用键盘功能键操作方块,以合适的方式放入方块队列中。当某一行方块满时就消去这一行,显示用户所消行数及得分。机器中方块下落的速度会随得分的不同而改变。
2.联机对战:基本游戏思路同单人游戏,增加了对战功能。当多人进行对战时,如某玩家一次所消的方块行数大于二行,则把所消行数随机去掉一列后传给其他用户。在其他用户的界面上可以看到从游戏界面底部一下子增加了数行。
二、游戏的界面
在Unix下,游戏的界面是一个令人头痛的问题。在字符方式下,无法完成各种精美的图形。好在我们所开发的游戏对界面要求不太高。因此,对于新手来说,我们可以用Unix本身所带的CURSES.H库中几个简单的函数来完成界面设计。
1.下面介绍几种常用的CURSES.H函数:
initscr() 初始化屏幕(必须有)
clear()、wclear() 清屏
newwin() 开一个新窗口
box() 在窗口边画一个框
mvprintw(int x,int y,char *,...)类似于printf,不同之处是在窗口的 (x,y)位置显示信息
mvscanw(int,int,char *,...)在指定位置读信息
mvwaddstr() 在指定窗口的指定位置显示字符串
wstandout() 反相显示开始
wstandend() 反相显示结束
cbreak() 立即显示所接收到的字符
nocbreak() 接收到一个换行符后才显示所接收到的字符
echo() 显示所输入的字符
noecho() 不显示所输入的字符
delwin() 删除一个窗口
touchwin() 击活一个窗口
wrefresh() 刷新一个窗口
beep() 响铃
keypad() 功能键开关
wgetch() 在窗口中读一个字符输入
wmove() 在窗口中移动光标
endwin() 结束图形方式(必须有)
2.图形方式光标移动键定义如下:
KEY_DOWN 0402向下键
KEY_UP 0403 向上键
KEY_LEFT 0404向左键
KEY_RIGHT 0405向右键
三、随机方块的产生
在俄罗斯方块中,共有19种不同的方块类型,要随机产生不同的方块可以用以下两个函数来实现:
1.定义随机数发生器:根据时间函数的返回值不同而产生不同的随机数。
#define randomize() srand((unsigned)time(NULL))
2.定义随机数:该函数能产生介于0到num-1之间的所有数。
#define random(num) (rand()%num)
定义上面两函数后便可用下面的函数rand_block()产生一个随机数:
/* 程序功能:随机产生一个介于0-19之间的数;入口参数:无
返回值:flag */
int rand_block()
{
int flag;
randomize();
flag=rand(19);
return(flag);
}
四、游戏的控制-
---1.终端控制
Unix中终端的输入输出控制用的是ioctl()系统调用。原型如下:
#include
int ioctl(int fd,int request,struct termio *argu)
fd为文件描述字,与一台终端相对应;request 是一个命令,我们用到的命令有TCGETA和TCSETA两个,作用分别是将fd所对应的终端信息存入termio结构和将fd所对应的终端设定为termio所描述的形式。 argu是一个指向 termio 类型的指针。
程序中,对终端的控制主要是对中断键的处理和对输入模式的不同切换。对中断键的处理主要是用到termio指针中c_clearcase/" target="_blank" >cc数组中的控制。下面的一段程序便可保存起当前终端信息,忽略中断键(置其值为255),然后重设当前终端。
struct termio *old_tbuf,tbuf;
ioctl(0,TCGETA,&old_tbuf); /* 取当前终端信息 */
tbuf=old_tbuf; /* 保存终端信息 */
tbuf.c_cc[VINTR]=255; /* DEL */
tbuf.c_cc[VQUIT]=255; /* CTRL-\ */
tbuf.c_cc[VERASE]=255; /* CTRL_h */
tbuf.c_cc[VKILL]=255; /* CTRL-u */
tbuf.c_cc[VEOL]=255; /* CTRL-d */
tbuf.c_cc[VSWTCH]=255; /* CTRL-z */
tbuf.c_cc[VSTOP]=255; /* CTRL-s */
tbuf.c_cc[VSTART]=255; /* CTRL-q */
ioctl(0,TCSETA,&tbuf);/* 设置当前终端 */
2.下落动画控制
在Unix系统中,有两种输入模式可供选择:一种是标准模式,一种是原始模式。在标准模式下,输入字符存储在输入队列的缓冲区中,直到读入换行符或是EOT(^-d)字符后才将所有数据一次输出;在原始模式下,字符一键入就立即输出,没有缓冲区。系统默认的是标准模式,但在游戏中,我们要不断在两种模式间切换,完成切换要用到tbuf.c_lflag[ICANON]来完成,如果设为ON,则默认为标准输入模式,否则为原始模式。
在游戏中,我们要求每一个方块在产生后,便能自动地下落。实现这个动画效果,要用到原始模式下的两个重要标志:VMIN和 VTIME,它们分别表示read系统调用返回前所需读取的最少字数和最长时间。要控制方块自动下落,可用下面两句:
tbuf.c_cc[VMIN]=0;
tbuf.c_cc[VTIME]=1;
ioctl(0,TCSETA,&tbuf);
功能:read在收到一个字符或未收到字符但定时一秒时返回
3.速度控制
在俄罗斯方块中,游戏的可玩性还在于速度在不断的变化中。在这里,我们就要用到间隔化技术。所谓间隔化技术,就是在指定了开始位置和结束位置的情况下,确定游戏间隔步骤过程的技术。我们可以用一个空循环来实现方块因得分不同而使每一次下落的速度不同。定义速度常数 SPEED为十万(可根据机器速度不同自行定义),然后根据不同的行分而改变循环次数,如下边所写(score为用户当前得分):
if(score<10000) for(i=0;i else for(i=0;i五、游戏操作的实现 在游戏中,对方块的操作主要是由几个光标功能键来控制,功能定义如下:
1、↑ 翻转
2、↓ 速降
3、← 左移
4、→ 右移 5、Esc结束 6、c 刷屏
7、p 暂停
由于在图形方式下不能完成对方块的各种操作,这就使我们要找到合适的函数来定义各功能键。在Unix中,↑↓ ← →各键的引导字符是27,对游戏操作的实现也就是改变方块显示位置及显示不同方块类型的转换。当然还可以定义一些秘键:减速、增速、使对手增行、清空自己所有行等。
六、游戏的记录
1.记录结构的定义:
struct user_data
{
char name[8]; /* 用户姓名 */
int score; /* 用户最高得分 */
int line; /* 最高分时所消除方块行数 */
}
2.记录文件的几种操作:
在程序中要用到记录文件来存放用户记录,本游戏中记录文件名为user.dat,以下是几种文件的常用操作。在操作前要先定义一个FILE类型的指针 (FILE *fp):
fopen() 打开文件
fscanf() 从文件中读数据
access() 检测文件存在否及文件属性
creat() 建立新文件
fseek() 文件定位
fprintf() 写数据到文件中
fclose() 关闭文件
用下面的一段程序便可测试用户记录文件存在否。如不存在,则建立新的文件,并写一个空的入门成绩到用户记录文件中。游戏中你可用这几个函数来往记录文件中追加新的记录或改写记录。
if(access(“user.dat",00)) /* 检测有无用户记录文件 */
{
creat(“user.dat",0644); /* 如无文件则建立新的文件 */
fp=fopen(“user.dat",“r+"); /* 写入一个空记录 */
fprintf(fp,“入门成绩 3000 20\n");fclose(fp);
七、联机对战的实现
联机对战的主要思路是在各个不同用户间传递信息。在Unix 下,我们可以用消息队列来进行数据的传递。消息队列能将格式化的数据送往任意的进程,所用到的有关系统调用如下。
1.建构一个新的消息队列或取得一个现存的消息队列的标识符:
#include “sys/types.h"
#include “sys/ipc.h"
#include “sys/msg.h"
int msgget(key_t key,int flags)
key是指消息队列的名字,它的值若是IPC_PRIVATE(值为零),指该消息队列为该进程专用,否则表示为一个以上的进程所共用。
flags用于表示消息队列的存取权限。
2.取得或修改已存在的消息队列的属性:
int msgctl(int msgid,int cmd,struct msqid_ds *mbuf)
msgid是消息队列的标识符。
cmd为符号常数: IPC_STAT(拷贝消息队列到MBUF所指向的地址)、IPC_SET(设定消息队列的存取权限)、IPC_RMID(删除消息队列)。
3.发送和接收消息:
int msgsnd(int msgid,const void *msgp,size_t msgsz,int msgflg)
int msgrcv(int msgid,void *msgp,size_t msgsz,long msgtyp,int msgflg)
msgid:消息队列的标识符。
msgp:消息缓冲区,暂时存储发送与接收的消息。
msgsz:消息的大小。
msgtyp:消息的类型。
msgflg:用来指明核心程序在队列没有数据的状况下,所应采取的行动。
八、游戏中的各种标志位
最重要的标志位是位置方块标志:定义一个二维数组,对应于游戏中方块的区域大小。如play[25][80],然后根据该标志为0或1来判断该位置有无方块,其他标志都是根据这个标志来工作的。所用到的标志还有游戏左右界标志、消行标志、增行标志(联机对战中)、能否移动标志、游戏结束标志等。
九、其他功能
游戏主体内容建立后,还有一些功能需要用户自己来完成。如清除上次显示方块、显示下一方块提示信息、无敌作弊法、记录排序、游戏速度的精确计算、增删行函数、速降函数、方块变换等,还需要进一步完善才成为完整的程序。
附例程:从标准输入读字符,如为光标移动键返回该键值,否则返回该字符值。
int mygetch()
{
int i;
char c;
struct termio *old_tbuf,tbuf;
ioctl(0,TCGETA,&old_tbuf);/* 取当前终端信息 */
tbuf=old_tbuf;
tbuf.c_lflag&=~ICANON; /* 设为原始模式 */
tbuf.c_cc[VNUM]=1;/* 设最少输入一个字符 */
tbuf.c_cc[VTIME]=0;/* 等待时间为0 */
ioctl(0,TCSETA,&tbuf);
i=read(0,&c,1);
if(c==27){/* 如为27则为光标键前导码 */
read(0,&c,1);read(0,&c,1);
switch(c){
case ‘A@#:return(56);break;/* UP */
case ‘B@#:return(50);break;/* DOWN */
case ‘C@#:return(54);break;/* RIGHT */
case ‘D@#:return(52);break;/* LEFT */
case 27:return(27);break;/* ESC */
}
ioctl(0,TCSETA,&old_tbuf);
return(c);
}