制作手札——RPG是怎样做成的

题目: 《制作手札——RPG是怎样做成的》
 备注: 作者:汪疆
成都金点工作室的首席程序员,你可以到他们的主页了解更多的情况。
文章以程序员的标准角度分析了一个RPG游戏所包含的各个部分。作为一名设计师,你可以不懂得如何去分析,但一定要能够读懂程序员的思路,这是每一个游戏开发者应具备的基本功。

制作手札—RPG是怎样做成的
作者:汪疆

  自从我们的第一个正式电脑游戏《冲击》完成之后,已经有很长一段时间没有碰这方面的东西了,不过在我心中好象一直在期盼着什么东西……目睹着当今五彩缤纷的游戏世界和国产游戏的尴尬境地,我忽然有一种莫名的冲动,做游戏的冲动。也许是以前有过这方面的经验或者说是教训吧!我知道应该先让自己冷静下来,仔细的想一想我到底应该做什么类型的游戏?怎样做?我的目的又是什么呢?或许是对RPG的偏爱,或许是因为RPG实现起来相对简单而且容易发挥吧(一个好朋友的建议)!所以,我最终还是选择了它。
  好,现在就让我先来说说我的计划吧:
  1、 经过反复的思量之后,发现以我现有的条件和实力,想作出一款超越《仙剑》的RPG无疑是个天方夜谭^_^,首先就没有美工,更加谈不上剧本和音乐了!所以,何不退而求其次,把它当作是一场实战演习吧!并且记录下整个游戏的制作过程,和大家一起分享,岂不更妙!
  2、 开发平台我选择Microsoft的VC++5和DirectX SDK,这是当今的主流游戏开发平台,而且预计在相当长一段时间内都不会被淘汰。但是,在这里我不打算过多的讲解具体的程序代码,那样会浪费太多的时间,更会失去大部分的读者!还好,我们身处在一个资讯相当发达的社会中,Internet连接着你和我,想看游戏或是源程序的朋友可以来我们的主页下载(http://www.gpgame.com/)随时欢迎您的光临!(注:这里可以加上杂志的配套盘)
  3、 为了让这篇文章不至于枯燥的如同一本C++技术手册,我会尽量从游戏设计的整体规划入手,把叙述的重点放在设计思想的建立和一些技巧的应用上面,而并不局限于某种具体的编程语言。所以即使你不懂编程也一样可以轻松的读懂它,当然,如果你已经是一位程序高手的话,那就更妙了!
  4、 由于写这篇文章是和游戏的制作同步进行的,所以看上去可能会有一种读日记的感觉,不过也许只有这样的感觉,才是最真实的!
  好了,就让我们一步步的去揭开RPG的神秘面纱吧。

三月二十六日 星期五:策划并完成剧本创作

  故事是RPG的灵魂,每个成功的RPG游戏都是一个动人的故事,当我第一次打爆《仙剑》后,脑海中映像最深的也是那一段凄美的故事。经常想:如果当时李逍遥要是如此……如此……,那么就会这般……这般……(停!我怎么越扯越远呢?真是不好意思^_^)
  不过对我们来说,既然是一次练习,游戏的故事情节自然应该是简单而清晰的,这样有助于大家更好的理解游戏设计的思路,而不会被错综复杂的故事弄昏了头。经过一天的冥思苦想,终于弄出了这样一个’美丽’的故事:
  英雄救美(暂命名)〔即:《圣剑英雄传之英雄救美》〕
  "在很久很久以前,白云城的居民过着富足而安定的生活。这一天,我们故事的主人翁’小飞刀’正在城中闲逛(游手好闲?),突然传来了一个惊人的消息,十年前被剑圣击败的大魔王又再度出现了,并且还掳走了美丽的公主以此来要挟国王让出王位。这个坏消息迅速传开,全城为之哗然!
  相传大魔王生性残忍而且法力高强,当年剑圣也是凭借一把’圣剑’的威力才将其击败,剑圣死后’圣剑’也遗失在了城外的森林中,一直没有被人找到。所以大魔王才又变得如此的肆无忌惮!
  得知了这个消息后,我们的小英雄当然不会坐视不管,于是马上挺身而出,孤身一人深入虎穴找寻’圣剑’,最后来到恶魔城击败大魔王救出了美丽的公主!挽救了白云城!结局是’小飞刀’当上了驸马,和公主过着幸福的生活……(是不是很老套啊!呵呵……^_^)。"

  故事情节敲定之后,下面要做的工作就是如何把它变成可以用程序实现的游戏剧本了。经过简单的分析,可知这个游戏一共分为了三个场景:白云城、森林 和 恶魔城,故事发展是非常简单的单线式拓补结构(可见 图1)。在游戏中一共需要三张地图来表示各个场景,具体的实现方法在后面讨论。
  游戏中出现的人物共有’小飞刀’、白云城居民、森林中的强盗、恶魔城的妖怪、大魔王和公主六类,于是我用下面的这张表就可以清楚的表示出他们之间的强弱关系:

生命值 攻击 防御
小飞刀 50 10 10
白云城居民 — – —
强盗 50 15 5
妖怪 100 20 20
大魔王 500 50 50
公主 — – —

  其中小飞刀的各项能力可通过战斗不断提升,而其他人保持不变。由此可见,要击败大魔王并不是一件容易的事,必须通过不断的战斗以提高小飞刀的能力才行。
  因为是单线式RPG,就只有两种结局。一是打败大魔王救出公主(破关结局);二是小飞刀被敌人杀死(失败告终)。
  到此为止,整个游戏的框架是不是已经比较清楚的展现在了你的脑海中,而不再是刚开始时的混乱和无从下手?如果是的话,我们今天的任务就顺利完成了,大伙累了一天可要好好休息一下了,让我们明天继续吧。(zzzZZZ………)

三月二十七日 星期六:系统分析

  "系统分析"如此专业的词汇可能会立刻吓退一大片人,我当初也是如此!不过只要你认真读完下面的文字,也许你会说一声:’系统分析’也不过如此嘛!
  顾名思义’系统分析’就是对整个游戏系统的规划,自顶向下的将它细化成一个个可由程序实现的模块;然后再把各个模块细化成一条条的语句。如此一来,写程序时就可以有条不紊,不会再像以前那样想到哪儿写到哪儿了。
  相信大家对RPG都是非常熟悉的,通常的RPG按功能可分为:消息处理系统、场景显示及行走系统、打斗系统、升级系统、对话系统和事件处理系统六大部分。其中又以消息处理系统走为核心模块,其余五部分紧紧围绕它运行。我的系统分析报告如下:

《英雄救美》系统分析记录
名称:英雄救美
类型:中文RPG
硬件要求:PENTIUM 100以上CPU、16M内存、SVGA、键盘、声卡
     (可选)。
软件平台:Windows 95/98/NT、DirextX 5.0以上
开发环境:VC++5.0、DirectX SDK
剧本:略(参见上文)
模块分析:
  一、 消息处理(核心)
  二、 场景显示及行走
  三、 打斗
  四、 升级
  五、 对话
  六、 事件处理
流程图:

(注:各个模块的具体实现略)

  在这里并没有把每个模块的具体实现方法写出来,因为我认为把大量细节上的东西一股脑的堆在面前,实在是太枯燥了。为了不让你失去耐性,我决定还是把这些东西留到具体实现的时候来讲,也许这样会较生动而利于理解一些。
  利用今天剩下的时间,我建立了两个非常重要的数据结构:’角色’和’地图’的属性类结构。

//*********角色属性结构**********
typedef struct{
char Name[32]; //角色名称
int Width,Hight; //角色大小
int x,y; //当前坐标
int old_x,old_y; //旧的坐标
int Way; //方向
int Stats; //状态
int Level; //等级
int Exp; //经验
int Lift; //生命
int MaxLift; //生命最大值
int Attack; //攻击
int Defend; //防御
int Speed; //速度
char Goods[10]; //物品(最多可带10件)
}Role_Struct;

//*********地图结构**********
typedef struct{
char Name[32]; //名字
int Width; //宽
int Hight; //高
char Ground; //地面材料
char res[7]; //保留
unsigned short *Data; //数据
}Map_Struct;

  将同一事物的属性归在一起,使程序清晰易读,有利于我们方便的管理数据、把握大局;更是顺应了当今面向对象的程序设计思路。

三月二十八日 星期天:消息处理系统

   今天的任务非常明确,就是搭起游戏的筐架(即建立游戏的消息处理系统)。
  我们的这个消息处理系统和Windows的消息处理机制其实非常相似,都是先等待消息,然后根据收到的消息转到相应的函数进行处理。所不同的是Windows 接收的消息大多是系统或是用户输入产生的,而我们的消息都是自定义的,如:小飞刀在森林中行走时碰到了强盗,那么我们就让程序产生’打斗消息’(即:Message=打斗),消息处理系统收到这个消息后就会马上转到打斗模块中去。
  不要把消息处理想的太复杂,打个简单的比方:假如你是一位接线员,在你前面的桌上有很多部电话,那么你要做的是:有电话响时就接电话,没有电话响时就什么都不做。是不是很简单!其实消息处理也就是这个道理。
  消息处理的大体框架如下:

//首先定义一些消息:
#define 行走 1 //行走消息定义为1
#define 打斗 2 //行走消息定义为2
#define 对话 3 //行走消息定义为3
#define 升级 4 //行走消息定义为4
#define 事件 5 //行走消息定义为5
#define 结束 6 //行走消息定义为6
……
//定义程序中要用到的变量
DWORD Message;//消息变量
Role_Struct Hero;//定义小飞刀的属性结构
Map_Struct Map;//定义地图的属性结构
……
WinMain() //进入程序
{
  初始化主窗口;
  初始化DirectDraw环境,并调入程序需要的图形、地图数据;

  while( 1 ) //消息循环
  {
    switch( Message )
    {
      case 行走: 行走模块();
      case 打斗: 打斗模块();
      case 对话: 对话模块();
      case 事件: 事件模块();
      case 退出: 退出游戏();
    }
  }
}

  OK!我们的程序框架已经出来了,这时你心中也一定有了一个相当清晰的轮廓吧!好了,到目前为止该游戏的整体规划也就可以告一段落了,今后几天的工作重心将转到具体模块的实现方法了。可要加油哦^_^

三月二十九日 至 四月一日:场景显示及行走系统

   如果说’消息处理’是整个程序的核心!那么’场景显示及行走系统’就是整个游戏的核心。因为作为RPG游戏,其所有事件的发生几乎都是和场景有关,例如:不同的地方会碰到不同的敌人、与不同的人对话得知不同的事情、在特定的地点才能找到宝物等等,所有的这一切都说明’场景’在一个RPG游戏中的重要地位。鉴于这部分的重要性,我们可再将它划分为:背景显示、行走 和 事件发生 三个子模块,分别处理各自的功能。要注意这可是一个RPG游戏制作的难点哦!下面进行具体分析。

  (一)背景显示
  通常RPG游戏的场景是采用拼图方式生成的(如《仙剑奇侠传》、《金庸群侠传》等),拼图的好处是节省内存消耗、运行速度快和容易实现滚屏操作等。同时也减轻了美工的工作量,这点对我尤其重要。
  我们将计算机屏幕当成拼板,先把屏幕分辨率设为640×480x256色,然后用32×32大小的方格来划分它,这样整个屏幕就分成了15行乘20列的小格子(如图3所示),这就是我们用来表现游戏的舞台了。

  舞台搭建好之后,就可以在上面放我们需要的’道具’,如:树木、石头、房子等等,将它们的大小都统一定为32×32,这样一个格子正好可以放下一个’道具’;不过你一定会问:难道所有的道具都一样大吗?当然不一样,如果那样就太不真实了,我们把32×32作为最小的道具单位,比较大的道具可以占多个格子。如:一座山的大小为96×64,那么用3×2的格子来放置它就可以了。要注意的是所有道具的长和宽都必须是32的整数倍,这些需要你事先将它们处理好。

  请记住每个道具都要有它唯一的编号(如:树木=100、石头=101、房子=102、大山的编号是200~205共6个,因为它占6个格子)。这样当场景布置好后,我们的地图数据也就自然而然的产生了,如图4:
  以上就是场景编辑器的原理了(我在游戏制作前先写了一个场景编辑器,用来生成游戏中要用到的场景。场景编辑器附带在程序之中:MapEdit.exe),用场景编辑器设计好场景后先存在磁盘中,以便在游戏中调用。注意:道具的图片应事先制作好并存于另外的图形文件中。这里需要存储的仅仅是对应的数据,而不是真正的图片。
  搞清楚原理之后,要在游戏中生成场景就非常简单了,其实就是场景编辑的逆过程,一个是根据场景生成数据,而另一个是根据数据生成场景。我的算法如下:

MapData[20][15]; //事先用场景编辑器生成的地图数据
Picture[nums]; //道具的图片nums表示道具的总数
void MakeBackGround() //生成场景函数
{
 int n;
 for( int i=0; i<15; i++)
  for( int j=0; j<20; j++)
  {
   n=MapData[ i ][ j ]; //取得该位置的道具编号
   Blt( j*32, i*32, Picture[n]); //在此位置(j*32,i*32)画道具
  }
}

  一切就是这么简单,看着像图4一般的游戏场景,你的心情是不是非常激动?我想你一定和我一样吧!不过可别先忙着高兴,下面的内容会更精彩的。

  (二)行走
  舞台搭建好了,终于轮到我们的主角出场了(注意了!灯光、奏乐……),要让小飞刀在场景中自由自在的行走可不是一件容易的事,算算一共有上、下、左、右四个行走方向,每个方向3幅图(站立、迈左腿、迈右腿),所以要事先做好12幅图,如下:

  游戏中一定要将图片的背景设为透明,这样在画人物的时候就不会覆盖上背景色了(这一技术在DirectDraw中非常容易实现,只要将背景色定为ColorKey就行了)。
  其实让角色在场景上移动并不困难,只须改变他的屏幕坐标(x,y)就可以了,但是,一般的地图都比屏幕大的多,如果仅仅改变屏幕坐标的话,小飞刀不是就会走到屏幕外面去了吗?所以我们要让主角位置不动,而使场景移动。你是否注意到了《仙剑》中李逍遥一直就在屏幕的正中间?他行走时场景向着相反的方向移动。对,这就是滚屏技术的效果。
  地图是一张很大的画,而屏幕就是我们用来观察的窗口。利用我们先前讲的拼图方法就可以很容易的实现滚屏操作。只须引入一个相对坐标(SX,SY),表示当前屏幕左上角相对于整张地图左上角的位置。那么,生成场景时加入这个相对坐标就可以了,看看改进后的算法:

//场景生成算法
int W=100,H=80; //假如地图的大小为100*80个格子
MapData[W][H]; //事先用场景编辑器生成的地图数据
Picture[nums]; //道具的图片nums表示道具的总数

void MakeBackGround() //生成场景函数
{
 int n;
 for( int i=SY; i<SY+15; i++) //共15行
  for( int j=SX; j<SX+20; j++) //共20列
  {
   n=MapData[ i ][ j ]; //取得该位置的道具编号
   Blt( j*32, i*32, Picture[n]); //在此位置(j*32,i*32)画道具
  }
}

  怎么样?变化不大但是却很有效,这也正是游戏编程的魅力!这时要让角色移动就只须往相反的方向重绘背景就可以了,而角色一直保持在屏幕的正中间,需要做的工作只是根据行走方向和步伐不停变换图片而已。
  还要注意的是行走时的障碍物判断,因为有一些道具是不可跨越的,比如树木、房屋等。那么我们在行走时,应该先计算出下一步是否会碰到障碍,如果有障碍就取消移动。在这个程序中就是判断下一个格子中数据是否为0,因为0代表地面。
  好了,到现在为止,一套完整的行走系统就基本上出来了。运行一下,看看自己做的RPG游戏,感觉也不比别人的差嘛!看来只要是用了心,就会有好的心情!

  (三)事件发生
  不要忘了,这部分还有一个非常重要的功能,那就是事件发生了。其实原理非常简单,就是把相应事件的编号放在地图的某些格子中,那么,当主角一踏入这个格子就会触发对应事件。例如:
  我们设定游戏一开始时,小飞刀是在他的房间里。那么他要是想出去的话,就需要执行场景切换这个处理函数,但是应该在什么时候执行呢?这就需要触发一定的事件了。我们假定该事件的编号为1000,那么在地图上把门口处的格子值设为1000。这样无论小飞刀在房间里怎么走动都没有关系,而一旦他走到门口时,编号为1000的场景切换函数就会被触发,于是小飞刀便从屋内来到了屋外。
  类似的事件还有很多,踩到陷阱、碰到敌人、发现宝物等等……都可以用同样的方法进行处理,所不同的只是调用的函数。

  OK!最艰苦的时段终于熬过去了,现在就让我们以无比轻松的心情来回顾一下这几天的工作成果吧!
  首先,我们用拼图的方式建立了游戏的场景显示系统。
  然后,以滚屏技术实现了角色在场景上的移动。
  最后,了解了事件发生的一般原理。
  到目前为止,我们已经拥有了一个可以实实在在运行、真真切切感受的RPG了。不过现在的她才刚刚学会走路,还需要你用大量的营养来浇灌。这样就可以使她迅速成长,变的更加美丽动人!

四月二日:对话系统

  经常听见有人说"RPG游戏不就是一个小人在屏幕上走来走去,碰到了人就说两句话吗!"。虽然这种评价有点过于偏激,但是,也从一个侧面反映出了对话在RPG游戏的作用和影响。
  客观的讲,人物对话是表现一个游戏故事情节的最直接、最有效方法!而其他的任何手段都无法替代它。例如:过场动画也可以表现游戏的情节和发展,但是,过场动画最适合的是烘托整个游戏的气氛,交代游戏发生的背景或是作为两段情节间的衔接。而在表现游戏中的生活细节、情节线索或是人物心态时,它的作用就远远不如对话了。
  说到这里,我突然想说说近来关于游戏的一些感受。玩游戏也有几年的时间了,而在这几年玩过的游戏中,真正能让我难忘的经典之作好象并不多。而国内制作的更是寥寥无几(除《剑侠情缘》外基本上没有什么映像了),为什么这么多的游戏都不成功呢?我的最直接的感受就是–粗糙!不是讲画面的粗糙,而是游戏细节的不合理。
  举个例子:许多RPG中你可以随意进入别人家中翻箱倒柜而主人却毫不介意;敌人老窝里到处放有宝箱给玩家提供道具和强力装备……这都是不和理的地方。但如果说这些地方玩家们还可以接受的话。那么,当角色突然说出跟自己身份毫不相干的不伦不类的俏皮话时,我想这足以把你玩游戏的情绪破坏的干干净净。
  所以想对国内游戏制作公司说一句:当你们在宣传新游戏的投入如何巨大、技术如何先进、规模如何宏伟……的同时,请不要忘了把最基本的地方做好,多体贴一下玩家吧!
  好了,废话也说了不少,现在就言归正传。
  一般来说,游戏中的对话数据是和执行文件分开的,用了一个单独的文件来存储游戏中的所有对话数据,这样的好处是:修改对话数据时不必重新编译源程序!哪数据的组织结构是怎样的呢?应该说还没有一个统一的规范,各个游戏有都自己的方法,但不论采用哪种方法,只要能够使其简单清晰就可以了。请看下面的例子:

  例:这是一段主角和兵器店老板的对话摘抄:(摘自文件Talk.txt)
—————————————————————–  =======兵器店======
  [59999]; //老板
  {是小飞刀啊,今天到我这里来想买点什么呢?}我随便看看!@;
  {我就知道你舍不得花钱,警告你不要打坏主意哦!}真是的!把我看成什么人了!@;
  {拿好了,欢迎下次再来!}还有下次,你就是请我来我都不来了!@;
—————————————————————–
  第1行是注释,表明地点。
  第2行的[59999]是兵器店老板的编号,在游戏中用该号码唯一标示兵器店老板。
  第3行到第5行是三段对话,其中以 { 开头的话是老板说的,以 } 开头的话是主角小飞刀说的,每段对话用 @; 做结束标志。分析一下上述三段对话,我们不难看出:第1段话是小飞刀在买兵器之前和老板的对话;第2段是小飞刀没有买东西就要走,这时和老板的对话;而 第3段是在小飞刀买了东西后,他们说的话。
  具体在程序中的过程是这样的:当小飞刀走到兵器店老板面前时,我们按下对话按钮,这时程序得知对话对象的编号是[59999](即是兵器店老板),然后在对话数据文件Talk.txt中查找[59999]这个编号,寻找的结果就是上面我们摘抄的内容。执行第1段对话,然后就转入挑选兵器的界面,退出时程序会自动根据小飞刀有没有买东西而执行第2段对话(没买)或是第3段对话(买了)。很简单吧?短短的三段对话就表现出了一次买兵器的过程,而且通过这些对话,还刻画出了对话者的性格特点,可谓是一举多得了!
  这就是游戏中的对话过程,当然,我省略了其中的一些细节步骤,比如:怎样显示汉字、在什么位置显示等等。那是因为这些东西都很简单,自己翻翻书就可以了。

图6 小飞刀与兵器店老板的对话场面

四月六日:打斗和升级系统

  虽然"RPG"的字面意义"角色扮演游戏"和战斗似乎无关,但实际上,绝大多数的RPG都是有战斗存在的,因此,打斗系统就成为RPG系统中很重要的一环。通常在RPG中打斗和升级都是联系在一起的,所以就把它们放在一起介绍吧。先总结一下,现有的打斗方式不外乎有以下几种:
  一、回合制:这是最古老也是最经典的一种,自RPG诞生它就出现了,而且一直到今天这种打斗方式还经常被采用。它的特点是:简单、轻松,给了玩家充分的时间来思考。而且凭借它的回合制特点可以做出非常华丽的画面效果,给人一种轻松、休闲的享受。不足之处是:缺乏紧张刺激的感觉、真实性不强。代表作有:《仙剑奇侠传》、《侠客英雄传》和《剑侠情缘》等。
  二、战棋制:这种方式其实就是在回合制的基础上加入了类似棋盘的格子,人物可以走动,这种扩展带来的好处是:打斗时角色不是站在原地不动,然后你一拳、我一剑的拼血,而是可以玩出一些类似诱敌深入、个个击破的战术来。代表作是:《金庸群侠转》。
  三、即时制;现在最流行的一种方式,最大的特点是打斗时不用切换画面,直接在主地图上进行,给人的感觉是真实、刺激。不过实现起来难度要大得多,国外的大型游戏公司比较喜欢采用。其中的《暗黑破坏神》是最成功的一个例子。
  还应注意现在出现的一种所谓半即时的打斗方式,这其实也只是回合制的一种扩展,不应单独归为一类,代表作是《阿猫阿狗》。
  我列举这些打斗方式的目的不是想说明它们孰优孰劣,而是想告诉大家当你在设计RPG的打斗系统时,应该根据自己游戏的特点来进行选择,不要盲目的追求一种所谓流行的方式,要知道今天的流行并不代表在明天也会受欢迎,只有不断创新的东西才有旺盛的生命力。
  和打斗紧密相关的是升级,通常在一场战斗结束后,主角的经验值都会增加。而当经验值到达一定程度时,角色就升级了。这在程序实现上应该是非常简单的部分,但是很多游戏就是因为这部分处理的不好而大大降低了可玩性。为什么这么说呢?要知道我们玩游戏最希望碰到的就是和自己实力相当的对手,太强或太弱的对手都会使我们的兴致降低。所以在RPG中升级的快慢和幅度是一个相当难把握的尺度。必须通过大量的测试来调整,总之,一个原则就是不要让主角碰到的敌人和他相差太大。
  以上就是打斗系统和升级系统的介绍,希望大家能够在现有的基础上不断创新,让我们RPG游戏更加好玩。

结束语:
  好了,到次为止我们的RPG制作之旅也就告一段落了。如果你还有什么不清楚的地方或是有什么问题,就请通过电子邮件告诉我([email protected]),或在我的主页面上留言(http://www.gpgame.com/),我一定会认真回复的。
再见!

http://yikenbbs.wigame.org/txt6/13.htm

你可能感兴趣的:(制作手札——RPG是怎样做成的)