如何编制RPG游戏(上)   
游戏玩得多了,可能每个人都曾会想过一个问题:要是自己也能制作出一套游戏那多好。究竟编游戏的吸引力在哪里呢?在很多地方,颇具微妙--你就真正控制了故事中剧情的发展、主角的命运,也许还有用自己主观感情去感染别人。这就好比写作,虽然并非人人都会去做,却人人想做,极富魅力。    
   
  那么,在业余条件下能否完成游戏的编制呢?答案是完全可以。    
   
  一套游戏,其中包含的内容很多,涉及了编程技巧的方方面面。本文力求从简便的角度出发介绍RPG类游戏的制作,只要你有一点C语言的基础,就可以按部就班编出一个游戏来,若对C没有一点概念,那么正好趁此机会学习一下C,体会体会当代最实用的编程语言。考虑到现在C及C++各种版本的普及情况,在此拟采用Turbo   C   2.0为例,逐一介绍这方面的具体知识。    
   
   
  一 浅说:    
   
  在正式进入游戏编程前,我们先来谈谈有关屏幕管理的几个有趣的问题。计算机显示器的工作方式有两种:正文方式和图形方式,在每种显示方式下还可分为不同的显示模式。对于我们的GAME来说,她的处理对象主要是图形,所以我们需要采用后一种显示方式。    
   
  在Turbo   C中initgraph是一个初始化图形系统的有用函数,它从磁盘装入一个图形驱动程序,且把系统置成图形方式。除了这种动态装入机制外,也可以将一个或几个图形驱动程序文件直接同可执行程序文件连接起来。游戏中通常采用的是640×480或320×200的分辨率。让我们来看看一个子程序:    
   
  setupgraph()    /*   图形初始化   */    
  {    
  int   driver=VGA,mode=VGAHI;    
  /*   VGA卡的VGAHI图形模式,640*480同显16色   */    
  initgraph(&driver,&mode,"");    
  /*   使用空字符串   ""   表示设备驱动程序在当前目录中   */    
  }    
   
  这个程序装入的图形驱动程序文件为EGAVGA.BGI,在例程中我们也将采用这种640×480的模式进行编程。若想选用其他分辩率,只需改变driver和mode的值便可。    
   
  下面我们来谈谈有关调色板的应用。    
   
  对于VGA显示器,图形模式12H(640×480,16色)共设置了16个颜色寄存器,所以显示器上能同时最多显示16种颜色。系统初始化时,也将颜色寄存器予以初始化,若这种默认颜色值不满足要求的话,只需修改相应颜色寄存器的数据,就可实现从64种颜色中任意选取,使显示的图形更加美观。    
   
  setpalette(调色板号,颜色号)函数可将指定的颜色填入指定的调色板中,从而改变调色板上的默认颜色。例如:    
   
  setpalette   (1,4);    
   
  用该函数重新设置后,以后使用1号调色板时,则显示4号颜色红色。    
   
  【注】有效的颜色依赖于当前的图形驱动程序和当前的图形模式,该函数不可用于IBM-8514图形驱动程序,应使用setrgbpalette函数。    
   
  VGA的13H模式是320×200点阵的彩色图形模式,同显256色。  
   
  在图形初始化完成后,我们就可以进入富有挑战的游戏编程了。    
   
  二 制作精灵:  
   
  在RPG游戏中,有很多能根据不同规则和目的而在背景场地跑来跑去的小目标:象城镇里的居民,野地上的小动物,或者再加上几株会动的小树,当然了,还有最重要的主角--游戏中的英雄或淘气鬼。这些小角色我们通常称之为“精灵”。    
   
  这些精灵要在各自的场地上迅速移动,当它们在草地上跑动时,经过的地方要遮住背景,而当它们经过后,背景的草地又要迅速恢复回来,若是遇到房屋、小河、岩石,就要停下来。那么,这些是如何实现的呢?    
  在程序中实现精灵的移动,我们通常采用这样的方法:在屏幕上擦除精灵当前的位置,然后在下一个位置进行重显……依次循环,你就会感到精灵跑动起来。这个原理是很简单的,但重要的是:擦掉并重显的速度必须足够快,才能有活生生的效果。    
   
  构成图形的最基本要素是点,无论背景也好,精灵也好,都是由许许多多的点组成的,所以精灵的重显问题其实也就是擦点画点的问题。在屏幕上画点有两种方法,一种是使用ROM   BIOS调用,另一种是直接写显示RAM。这两种方法比较而言,前一种是相当慢的,而后一种极快。正因为如此,我们不能通过ROM   BIOS去画点,而必须采用直接写显示RAM的办法画点。  
   
  精灵在背景画面上保持动态最好的办法是通过XOR(“逻辑异或”)运算,借助XOR操作,动态精灵在同一显示位置上连续进行两次XOR运算,使画面出现和消失,而不会导致背景画面的任何损坏,我们来看例程:  
   
  void   putpoint   (int   x,int   y,int   color)    
  {    
  union   REGS   r;    
  color=color|128;    
  /*   参数color   和   128相逻辑或,置位最高位   */    
  r.h.bh=0;    
  r.h.ah=12;    
  r.h.al=color;    
  r.x.dx=y;    
  r.x.cx=x;    
  int86(0x10,&r,&r);    
  }    
   
  这个画点程序采取直接写显示RAM的办法,在画每一个点时,   先让彩色值与值128进行逻辑“或”,使彩色值的位7为1,(这就告诉了函数不是以新彩色值替换原彩色值,而是与原彩色值进行逻辑“异或”)。所以这个点总是可见的.   因为它总是与周围其它点颜色不一样,而连续两次XOR运算又很容易使周围曾经被它占据的点恢复成被占据前的颜色。    
   
  在解决了画点问题后,只要把组成精灵的点归入一个集合,使这些点同时进行运动,那么,精灵的问题也就解决了。    
   
  精灵的造型设计是丰富多彩的,   你尽可以凭你的想象创造出要在游戏中出现的形态各异的人和其它各种角色。我们可以借助Animator   pro来完成角色设计,如果精灵不是很复杂的话,我们甚至可以直接“点”出她们:打开Spt或Windows的画笔,我们在“逐点修改”的方格阵上点绘出精灵的造型,反复修改至满意,给精灵定个外框,在四角任取一个点作为参照坐标(X,Y),以(X,Y)为参数组成精灵集合man(int   x,int   y):    
   
  void   man(int   x,int   y)    
  {    
  putpoint(x+dx1,y+dy1,color);    
  ......    
  putpoint(x+dxn,y+dyn,color);    
  }    
  一般来说,精灵应比较小,以便加快重显速度,使移动看起来比较平滑。若精灵太大,移动起来就比较慢。在例程中,我们用一个9×12的框代替精灵:    
  void   man(int   x,int   y,int   color)    
  {    
  int   i;    
  for   (i=0;i<9;i++)    
  {putpoint(x+i,y,color);    
  putpoint(x+i,y+12,color);}    
  for   (i=0;i<12;i++)    
  {putpoint(x,y+i,color);    
  putpoint(x+9,y+i,color);}    
  }    
  [返回页首]    
   
  三 键盘控制:    
   
  在游戏中,主角的移动一般是由上、下、左、右键或HOME、END、PGUP、PGDN四键控制的,此处还有几个问题:若键入空格、回车等,计算机如何识别?在游戏的中文菜单中如何用上下键移动选择光条?这些都是在游戏编程中会碰到的典型问题。    
   
  在Turbo   C中,我们用bioskey   (int   cs)函数来获取键盘字符。这个函数使用BIOS中断0x14完成各种键盘操作,参数cs确定实际的操作:cs=0,返回从键盘输入的下一键码;cs=1,测试输入键是否有效;cs=2,请求当前移位键(SHIFT)的状态。    
   
  bioskey(0)获得所击键的扩展的扫描码。扩展的扫描码分两部分,即扫描码和ASCⅡ码。当按键是一般ASCⅡ码时,如回车、空格、ESC键,从扩展扫描码的低字节中可以得到它的ASCⅡ码值;当按键是特殊键时,如上、下、END、HOME键,其ASCⅡ码部分为0,所以要将组成扩展扫描码的两部分分开:    
   
  ……    
  do    
  {key.i=bioskey(0);    
  if(key.ch[0])    
  {switch(tolower(key.ch[0]))    
  {case   ‘\‘:    
  程序一   /*   空格键   */    
  case   ‘r‘:    
  程序二   /*   回车键   */    
  ……    
  }}    
  else    
  {switch(key.ch[1])    
  {case   72:    
  程序三   /*   上键   */    
  case   80:    
  程序四   /*   下键   */    
  case   75:    
  程序五   /*   左键   */    
  case   77:    
  程序六   /*   右键   */    
  ……    
  }}    
  }while(tolower(key.ch[0]!=27);   /*   ESC键   */    
  ……    
  key.ch[0]中为ASCⅡ码部分,key.ch[1]为扫描码部分。ESC键为ASCⅡ字符键,键值为27,所以上面程序最后一行表示当按键不是ESC时,就一直循环。而由于上、下、左、右键为特殊键,所以判别是否按了这些键用switch(key.ch[1])。    


五 背景处理: 

现在,我们的精灵已能自由运动,且能识别障碍了,它活动的动画环境可以取自一些图形数据文件。这些文件是事先用各种编辑软件编辑好,之后保存于硬盘上,以便随时调入。在VGA卡上可设多个有效图形页,程序可直接将图形输出到一个关闭屏幕页,然后通过调用setvisualpage改变为可见页,可快速显示关闭屏幕图形。

但如果你的背景画面不是很复杂的话,一个更简便的方法便是即时画图。 

在RPG游戏中,有很多东西是重复的,象树、山脉、房子、草地,这些我们可以称为图形单元,将每个图形单元编制出来放在一个函数中,那么绘一幅背景场地无非是反复调用这些函数。例如树木单元可以是由椭圆和长条组成: 

void tree (int x,int y) 

setcolor(2); 
setfillstyle(1,10); 
fillellipse(x,y,10,15); /* 椭圆 */ 
setfillstyle(1,6); 
bar(x-2,y+10,x+2,y+18); /* 长条 */ 


比较而言,房屋单元就稍微复杂些,整幅背景图形可用一个二维数组标识,存放图形序号和特征点(如门、宝箱)地址。在主程序调用这些背景函数时一定要小心谨慎,这里往往是游戏BUG产生的地方(象《侠客英雄传》中有时就会碰到进入特征点(门)后背景混乱的现象),要注意特征点和序号,序号和序号之间的对应。 

六.中文环境: 

在编制中文RPG及其它类型的中文游戏时,无可避免地会碰到与中文环境有关的一些问题。例如:在游戏编程中能否直接用现成的中文系统(UCDOS、中国龙等)支撑? 

如果你的游戏是建立在文本模式下的话,这个问题不大,象现在有些商业化的应用软件的确是自己不带中文环境的。但这种情况对我们的图形模式游戏来说是不适合的。 

使用外挂的中文支撑系统,占用内存很大,而且会与图形模式的部分功能冲突(如屏幕色彩与西文状态下的色彩  不一致),使得游戏程序根本无法运行或运行中经常死机。我们通常采用的是西文下显示汉字的技术。 
一种通用的方法是为开发的软件编制小字库。若要更简单的话,直接调用现成字库也是可以的。例:UCDOS或213汉字系统中16×16点阵的字库文件是HZK16;24×24点阵字库分别为HZK24S(宋体)、HZK24K(楷体)、HZK24H(黑体)。在24点阵字库中我向大家推荐楷体,这种字形非常美观,接近商业化的要求。 

有关西文下显示汉字的例子鉴于各类书刊上极多,这里不再重复,只想对其作出一些勘误: 

16×16点阵: 
…… 
c1=(i-0xal)&0x07f;  

c2=(i-0xal)&0x07f; 

rec=(c1-23)*94+c2-23; 
…… 
24×24点阵: 
…… 
c1=(i-0xa0)&0x07f; 

c2=(i-0xa0)&0x07f; 

rec=(c1-16)*94+c2-1; 
…… 

七.游戏菜单: 

游戏中中文菜单是必不可少的,简单一点的有对话框、信息框,稍复杂的有弹出式指令菜单及其子菜单(物品栏、状态栏等)。 

这些菜单分别可以由第三节所介绍的热键激活。例如用空格键激活对话框,用回车键激活指令菜单,上下键选择子菜单,ESC退回游戏。 

指令菜单往往被设计成一个弹出并显示的窗口,选择项在窗口中垂直排列,而被选中的项为高亮,反显中文,后开的窗口常要覆盖屏幕上原来的部分内容,窗口消失后,又要将覆盖的内容再现出来。要实现这一点,就必须在覆盖之前将要覆盖区域的内容保护起来,称为保存屏幕;窗口消失后,再将保护的那块屏幕拿出来补上,称为恢复屏幕。在TURBO C中要用到这样一组函数: 

void far getimage (int left,int top,int right,int bottom,void far *bitmap); 
unsigned far imagesize (int left,int top,int right, int bottor, voidfar *bitmap); 
void far putimage (int left,int top,void far *bitmap,int op); 
imagesize 决定getimage用于保存指定矩形所需的字节数,它返回的图象大小包括用于记录矩形宽和高的空间。

getimage将屏幕上一个矩形区域的位图像存到内存中,(left,top)和(right,bottom) 四个参数用于定义屏幕上的矩形,bitmap指向内存中存放位图像的区域。

putimage将以前用getimage保存的位图像重新送回屏幕,图像的左上角位于(left,right),bitmap 指向保存源图像的内存区域,参数op指明了一个组合:COPY_PUT拷贝,XOR_PUT异或, OR_PUT或,AND_PUT与,NOT_PUT取反拷贝。 

了解以上函数后,我们用一个详细的例程来说明如何实现西文状态下菜单系统的编制: 

…… 
/* 状态栏 */ 
setcolor(14); /* 设置当前绘图色为黄颜色 */ 
setlinestyle(0,0xff00,3); /* 设置线型为粗实线 */ 
rectangle(10,125,100,255); 
/* 画一个左上角为(10,125)右下角为(100,255)的矩形 */ 
setlinestyle(0,0xff00,1); /* 设置线型为细实线 */ 
rectangle(14,129,96,251); 
setfillstyle(8,2); /* 设置绿色斜网格的填充模式 */ 
bar(17,132,93,249); 
/* 画一个左上角为(17,132)右下角为(93,249) 的矩形并填充 */ 
p24(25,137,0,14,"状态"); 
/* 在点(25,137)处输出楷体汉字,字间距为0,颜色为白色 */ 
p24(25,163,0,14,"法力"); 
p24(25,189,0,14,"物品"); 
p24(25,215,0,14,"系统"); 
size2=imagesize(17,137,93,163); 
/* 保存屏幕上矩形(17,137)(93,163)所需的字节数 */ 
buf2=malloc(size2); 
getimage(17,137,93,163,buf2); 
/* 将所定义的矩形区域(17,137)(93,163)的位图像保存至内存 */ 
putimage(17,137,buf2,NOT_PUT); 
/* 将getimage保存的位图像反显送回屏幕,达到当前选项高亮效果 */ 
cdy=137;ky1=0; 
while(ky1!=27) /* 检查是否按下ESC键 */ 

while(bioskey(1)==0); /* 等待操作者击键 */ 
ky1=ky2=bioskey(0); 
ky1=ky1&0xff; 
ky2=ky2&0xff?0:ky2>>8; /* 将组成扩展扫描码的两部分分开 */ 
if(ky2==72||ky2==80) /* 判断是否为上下键 */ 

putimage(17,cdy,buf2,COPY_PUT); 
/* 将getimage保存的位图像反显送回屏幕,取消当前选项高亮效果 */ 
if(ky2==72) cdy=cdy==137?215:cdy-26; 
/* 键入上键,若cdy=137则取215,反之cdy值减26,26为选项所占高度 */ 
if(ky2==80) cdy=cdy==215?137:cdy+26; 
/* 键入下键,若cdy=215则取137,反之cdy值加26 */ 
getimage(17,cdy,93,cdy+26,buf2); 
/* 将所定义的矩形区域(17,cdy)(93,cdy+26)的位图像保存至内存 */ 
putimage(17,cdy,buf2,NOT_PUT); 
/* 将getimage保存的位图像反显送回屏幕,达到当前选项高亮效果 */ 

test1=(cdy-137)/26+1; /* test1 菜单行号代码 */ 
if(ky1==13) /* 检查是否按下回车键 */ 

switch(test1) 

case 1: /* 此处插入状态栏子程序 */ 
break; 
case 2: 
break; 
case 3: 
break; 
case 4: 
break; 



free (buf2); /* 释放buf2所指向的内存空间 */ 

至于对话框、信息框等因为是单一菜单,就相对简单些了,运用图形函数可设计出具有专业效果的窗口,具体见所附例程。 

八.精灵动画: 

也许大家已注意到了我们在上面所说的动画程序产生的精灵本身形状是不变的,还达不到动画片的效果。因为一个精灵无论外形多么逼真,但如果在跑动时既不提手也不抬腿,那么效果便会大打折扣。解决这个问题最好的办法是再建立一个或更多的精灵组,让它们在精灵运动时轮流显示,由于人的视觉暂留现象,就给人一种连续运动的感觉。 

由于精灵在四个方向上移动形状各不相同,所以至少要建立8个精灵组。用一个二维数组bj[i][j]标识,i表示方向序号,j表示运动序号,显示时对j进行循环累加,产生精灵的动画效果。 

九.战斗系统: 

RPG游戏中战斗系统的地位至关重要,它的好坏直接影响游戏的可操作性和可玩性。现在市面上的游戏,有些战斗系统极其复杂,特别象一些S-RPG类游戏,有些则很简单,象日式传统RPG。战斗系统是游戏的灵魂,它留给设计者的自由度是很大的,但它的好与坏并不取决于简与繁,而取决于另外许多东西:象参数设计合理性、键盘鼠标操作的易上手性、战斗手段多样化等等,甚至包括敌人出现的频繁程度。至于简繁问题,繁是专门为发烧级玩友准备,简则留予入门级朋友消谴,这是由设计者的出发点决定。 

从传统RPG的角度来说,主角通常由手无缚鸡之力的0级成长为顶级英雄。在各个活动区域注意敌人的强弱分布,不妨将强弱档次拉开,增加游戏的难度及在同一区域活动的适应性。 

在攻防方面,为了避免出现敌人打不死的情况,注意要设置较低的敌人防御值。敌人的攻击力应跟着自己的防御值调整,使主角等级提升到一定程度不惧怕一类敌人的进攻,增加玩者的成就感。损血可以这样计算: 

Hp损=K(己方攻击力-对方攻击力) 

K值根据设计者采用的Hp总值来定,通常所说的会心一击或致命一击即将K值调为三至五倍。在损血计算之前,还应加入闪避、常态及致命一击的随机频率计算。 

十.DEMO及过场动画: 

很多朋友可能都被PC游戏中优秀的片头动画震惊过。其实,这些动画几乎都是由各种各样的应用软件包制作、调整,再联接到主程序,而并非由编程直接完成。同样我们也可以借3DS、ANIMATOR、CORELDRAW的威力制作出满意的、甚至超越市面中很多游戏水准的动画、图片来融入我们自己编制的游戏之中。 

我们有幸拥有这样一个函数system(),system可启动MS-DOS COMMAND.COM文件去执行 command字符串中给出的命令,即使命令输入在DOS提示行。如system("C: \ GAME\ TITLE.EXE"); 表示调用C盘GAME目录下的TITLE.EXE片头动画可执行文件。 在上例引号中也可是一个批命令或带参数命令。又如system ("C:\GAME\PLAYMVY TITLE.FLI"); 即一个常见的调用FLI、FLC格式动画文件的方法。 

【注】在引用system命令之前,一定要先关闭不相容的图形模式,以免冲突死机。 

十一.SLG类游戏引申及总体制作: 

以上我们介绍了一个RPG类中文游戏简便的制作方法,对引擎作稍许修改,我们也可以制作中文战棋SLG类游戏。在战棋类GAME中主角不是RPG中的一个(RPG中三四个其实也仅相当于一个),而是变成敌我双方的若干个。每一个主角都会在各自的时间段中独立思考运动。运动时除了受阻障碍外,还有影响障碍,即地表性质影响速度。行动原则(敌方自由运动的主角):2)魔法师检查攻击范围内有无敌人,若有使用魔法;僧侣检查医疗范围内有无己方损血人员,若有进行治疗;战士检查攻击范围内有无敌兵,若有转入攻击等级最低者或Hp值最小者。2)各自攻击范围内无敌人,检查搜索范围内有无敌人,若有,向等级最低或Hp值最小方向移动。3)检查地面,改变运动步长(攻击时检查地面,改变攻防参数)。将主角编入数组循环,可制作出《魔法世纪》式的战棋游戏。 

纵观目前众多的游戏,大家不难看出一个成功的游戏不光要有优秀的画面和流畅的界面,更要有一个引人入胜的剧情,九五年大宇的《仙剑奇侠传》便是一个典型的例子。挑选一个合适的主题,编写理想的情节在游戏制作中无疑极其重要。自从声卡面市以来,音乐音效所占的份量也日趋受到瞩目(限于篇幅,有关声卡、声霸卡编程方面的问题我们以后有机会再谈),而在RPG类游戏中,尤以音乐为重。正因为一个完整游戏涉及的面很多且很杂,所以游戏BUG是在所难免的(笔者在新出的《新蜀山剑侠传》中同样发现了若干个臭虫),制作公司对成功的游戏一般都会接着推出除BUG版。 

那么我们按以上方法制作出的RPG游戏效果如何呢?我可以告诉大家,比《勇者斗恶龙》强,和《侠客英雄传》差不多。我们何不动手试一试,编出你的第一个中文RPG游戏!
posted on 2009-04-29 23:04  蓝色侵略  阅读(341)  评论(0编辑  收藏  举报