本帖最后由 weyl 于 2012-12-14 18:18 编辑
注意:本文完成于2008年6月,应该有两张插图,但是已经散失了,如果有兴趣,可以将游戏的色深设为8位,以及将源码中字串最前的空格去掉查看效果。
代码的构成
基本上代码仅由两部分构成:
kys_xxx.dpr: 工程主文件.
kysfunctions.pas: 功能实现文件.
大部分内容都在第二个文件里面, 使用了较多的全局变量, 基本上没有使用较复杂的数据结构(甚至仅在极少的地方才使用指针操作数据)和单元组织方式. 如果希望最后的exe文件有一个图标, 需要添加kys_xxx.res文件。
主要封装的部分如下:
1. 绘图. 一个基本绘图子程, 其余所有绘图子程均是对此子程的调用. 同时包括绘制基本元素(例如背景矩形)的部分。
2. 文字显示. 主要由文字显示的主子程和几个用于转码的API构成. Font使用的是全局变量.
3. 音频. 对Mixer中的函数进行了封装, 可以用简单的方式调用. 主要的播放子程均有数次重载, 以处理文件名是字符串或数字的情况.
这些是最基本的部分, 其余的部分均是对这几部分的调用时机的掌握.
除此之外, 公用数据的部分使用全局变量保存, 在开始的时候多数从文件读入. 事件系统和主要的计算方式基本是对这些全局变量的操作.
对系统事件的处理手段是贯穿始终的, 似乎没有封装的好办法.
起因
铁血丹心论坛上一个id为fenghou的网友发布了大地图查看器,在查看的时候颇有Windows版的意味。这就是我想重新制作一个版本的起因。
以前的重制版有两个:
swimmingfish使用VB和DDraw制作的版本,特点是使用640*480的分辨率。缺点是字体难看,部分武功会导致错误,无法支持复杂修改版,战斗速度太慢等。
tianua使用SDL制作的版本,特点是添加了很多新的功能。基本上bug很少,但也是不能使用修改档,而且速度过快了。
在此之前,Guest曾经问过我有关音乐文件格式的问题。游戏自己的XMI格式基本没有资源,虽然可以从mid转换但仍是有很大限制。mid本身的格式决定了它不可能容纳复杂的音乐。而且游戏使用的音效文件似乎也限制在特定的格式。
尽管那时对事件、战斗指令的研究已经颇具规模,但是我仍旧觉得原来的引擎实在有太多限制了。
Pascal
最早我想使用fenghou制作的版本继续扩展,但是我发现C++的代码已经看不懂了,后来就使用了用得较多的Object Pascal制作。
其实在此之前我的设想是通过对Dosbox源码的优化来实现Windows版,但是后来我放弃了这个设想。原因之一也是C++的代码几乎看不懂了。
Pascal是一种高度结构化的语言,正因为此它比C少了很多灵活性。其实通常C++和Pascal的代码放在一起的时候,Pascal的代码会显得稍为清晰一些。而且Borland在扩展Pascal的时候,选择的关键字很多比较合理,看起来一针见血。
花括弧和begin..end都是标志程序段开始和结束的标记,本质上并没有不同。但是C++和C#在微软的环境中是会自动对花括弧的位置重排的,而且缩进的量比较多。Delphi里面缩进的量比较少,太多的begin..end确实也会造成区分上的不便。
用多了的时候也只是习惯问题。
SDL
尽管fenghou是使用的DDraw,但我后来还是选择了SDL。SDL学起来很快,而且上Windows下面的SDL就是对DirectX的封装。即使我学懂了DirectX,我肯定还要自己封装。
尽管RMXP,GM都是非常出色的引擎,但它们实在太“高级”了,能够自主的空间十分有限。而在底层API中,最适合的当然就是DirectX。
开始
在一开始对游戏制作什么也不懂,对编写Console程序也缺少经验,而且有关SDL+Pascal方面的资料并不太多。很多站点上标着Delphi资料里面却是C++的代码(谴责之)。
http://www.cppblog.com/shaoyun/archive/2007/07/28/28876.html
上面的网站是几乎唯一使用SDL+Delphi,并且适于初学者的例子。尽管也有不足,但是十分难得。
对于一个初学的,但是又有程序设计的经验的人来说,一个这样的例子胜过详细的帮助文件。虽然那些帮助文档的水平肯定要高得多,但是一上来就是似懂非懂的函数介绍,谁受得了?
- procedure draw_text(surface:PSDL_Surface; words:PChar; x_pos:integer; y_pos:integer );
- var
- text : PSDL_Surface;
- font : PTTF_Font;
- dest: TSDL_Rect;
- textColor : TSDL_Color;
- begin
- textcolor.r:=$00;
- textcolor.g:=$FF;
- textcolor.b:=$00;
- textcolor.unused:=0;
- font:= TTF_OpenFont( ' simhei.ttf ' ,20);
- if font=nil then
- begin
- MessageBox(0, PChar(Format( ' Error:%s! ' #9, [SDL_GetError])), ' Error ' , MB_OK or MB_ICONHAND);
- exit;
- end;
- text:= TTF_RenderText_Blended(font,words, textColor);
- dest.x:=x_pos;
- dest.y:=y_pos;
- SDL_BlitSurface ( text, nil, surface, @Dest );
- SDL_Flip(screen);
- SDL_FreeSurface ( text );
- TTF_CloseFont( font );
- end;
复制代码
上面的代码就是在那个网站上摘下来的显示文字的代码。而实际上在我后来完成的源码中,实现同样功能的部分仍旧保留了这个程序段的风格,只是部分变量改成了全局变量。
绘图
决定了设计使用的环境之后,第一件事要解决的就是绘图。可以接受游戏没声音,但是没图像的游戏似乎是没人玩(文字mud不算了)。
在一开始绘图的时候,画出的图总是颜色偏暗,而且图形不能完整拼合。就像下图的效果一样:
【插图1:8位色深的效果】
原因就是把色深设成了8位,在一开始我甚至费了很长时间去调整贴图的位置。
引用建筑贴图的时候,正确的引用方式应是先y坐标再x坐标(即主地图文件数据的存贮顺序),但是这与我当时的思维正相反,这就是后来人物坐标与场景实际坐标是相反的原因。
主地图基本是实时画出来的,在判断画图的顺序的时候使用了一些小伎俩,基本上是依照贴图的中心点,但是实际上仍有部分贴图错位。
场景地图和战场地图则是因为实时的效率太低,是先初始化一个不含人物的映像,将适合的部分画到窗口,再把人物画上去,在画人物的时候还必须更新人物附近建筑和其他物品的遮挡。
最早的仅包含场景和主地图的版本是由KG和Guest简单测试过的。因为反复编译的关系,这些版本已经不再存在了。
在复刻引擎制作的过程中,KG和Guest是在测试上出力最多的。而且KG为了这个可能还学了一点Pascal。在我后来成为版主之后,我非常想在现有的版主中找一个有程序设计基础的,将这份代码继续维护下去。尽管维护的工作也许比较繁琐,但是添加新功能就简单得多。
文字
文字的显示是很重要的部分,至少要让人讲话,但是刚开始的时候并不顺利。
开始的时候已经知道要使用WideChar和Unicode方式才能正确显示文字,这个倒是没有浪费时间。
难的地方首先是TTF的使用,要在适合的时候构造和析构。而且在一开始你很难判断究竟是TTF还是WideChar的问题。我甚至考虑过干脆添加一个隐藏的Label控件,先在上面显示文字,再按点阵绘制到图形中,但是这个也没有成功(后来知道了是Form方式应用程序的问题)。在我几乎绝望的时候终于在某一次调试的时候看到了汉字,这是那时最大的曙光。但之后又发现文字不能对齐。
【插图2:文字不对齐】
就像上图,有些文字偏移了一段,但是似乎偏移的量又不同,谁说得清楚?所以后来我把每个字串前都加了一个空格。
原本的资源大部份是Big5码,转码在一开始也耗费了很长时间,似乎有组织做了各种编码的转化函数,可资料大部分是C的。找到了适合的API之后,又发现转码过程中程序崩溃,原因是字符串长度没初始化。在编写这个引擎的过程中,我也在积攒程序设计的经验。
后来添加了一个专用来输出英文的子程,是因为发现标楷体在使用Unicode方式时输出的数字是全角的,而且并不美观。
事件
事件系统由70个左右的函数构成,包括主事件调用,全部指令,扩展指令和一些辅助函数。其中只有扩展指令中的一个函数用了特殊的返回值以进一步处理。
所有扩展指令是用同一个函数处理的,里面有一个很长的case语句。这些函数如果做成不同的反而代码会很麻烦。至于扩展指令的功能倒是都很容易做,因为扩展指令多数很简单,屏幕输出的地方也很少。
对话框的效果设计了很久,后来干脆添加了一个画不带外框矩形的函数。最初甚至想放弃背景框。
有些指令我添加了细节和改掉了我认为不合理的地方。比如获取物品的2号指令,如果物品数量为负,显示的是“失去物品”,但测试中这个情况根本没有出现过。
有少数指令没有做,有的是使用次数太少,有的是太复杂。
此外,播放动画的指令尽管基本能正常播放大部分动画,但其实仍旧有很大缺陷。主要是根本搞不清楚原版是怎么判断是否要需要画主角的。还有更新的范围有时并不合适。
显示
这里指的是显示状态等。均是耗费时间比较多的。难度都比较低,大部分时间花在了调整上。
特别地,物品选单的显示方式与其他选单是明显不同的,因此至今物品选单的绘制效率还是比较低。
当使用物品能够增加的属性超过11项的时候,会分两列显示,但这个机会非常小。
在后来的不断更新中,这部分也是修正最频繁的。至今为止最后一个添加的显示相关子程是横排的选单。
音频
我最开始并没有做音频部分,因为我根本就没打算要用那些XMI文件。KG说MP3最好,其实我早就有相同的想法(他是想添加主题歌进去吧)。添加音频的过程尽管略有不顺利,但还是在很短时间就做好了。
在放出测试版的时候,我曾在一个版本中添加了mid格式的支持,但最终还是去掉了。老实说,那些音乐我实在是听得厌烦了。
战斗
战斗是最复杂的一部分,大约是使用一个周末编写的。在那一周里,我放出过一个预览版,利用其它日子的业余时间修正错误,完善事件系统,而决定在周末集中精力编写战斗系统。
战斗所使用的函数在50个左右,仍然感觉分得不够细。而且判断两点连通的算法并未被包括进去,这使自动战斗时多数的障碍形同虚设。
此外战斗中所使用的公式也是最多的,很多跟原版并不一致。因为原版的非常麻烦,真怀疑原来他们是怎么想出来的。
最初绘制战场的方式是实时绘制。但后来发现在某些建筑物密集的战场里面效率较低,于是就采用了类似场景内绘制的方式。这个弊端就是在某些时候可能出现人物的遮挡错误。
数据结构
在最开始仅使用二维数组记录R文件中的数据,后来定义了几种较大的record类型,这样单访问某一个属性会方便很多,代码也因此有了很大变动。改了很久才改好。实际上record是变体,可以用两种方式访问。这个应该是C++做不到的,同样的功能只能用指针。
除此之外,大部分的数据结构都非常简单。最复杂的也不过是多维数组。
动态效果
包括场景的动态效果和大地图的动态效果。
本来想使用多线程处理,但发现这样会造成场景闪烁和CPU占用率变大。后来还是用了PollEvent循环中检查时间点的办法。
大地图的云效果是可以添加的,但并没有完成。主要是比较懒。
《苍龙逐日》版和其他MOD
我制作的目的并不是复刻原版。因为已经有了两个Windows版,何必再多出一个?我的目的是MOD再现和新的制作,这也是现在的两个版本没有完成的。所以我复刻了原版之后,就把这个最有名的MOD重制了。
最早放出来的确实是原版,到现在仍有部分功能不完善,其实我也无意将它们完善了。
在完成度较高的测试版放出之后,我是很希望能收到bug报告的,可是并不多。是我编的好没什么bug,还是玩的人少?我并不相信是第一种。
新引擎对原版的支持甚至不及对《苍龙逐日》的支持完美。因为原版中实际有太多指令作用很小。比如“是否放完十四天书”这个指令,多半是连续检查14个贴图,这实在是太麻烦了,不如偷点懒把精力放在其他地方上去,所以到目前,结局相关的几个指令还是未完成的。而《苍龙逐日》中并没有使用这个指令,是可以正常结束游戏的。
至于其他著名的MOD,PTT版我是测试过的,但并未完整完成剧情。PTT版在结局部分和原版基本一致,所以对它的支持应该不会比《苍龙逐日》更好,大致在结局的时候要求会松一些。
另一个著名的改版《再战江湖》我只测试了其中几个特殊事件:自创武功,炼药,炼武器,随机事件,保镖等,基本上都可以通过。
还有另外两个MOD《再破菠萝》和《笑梦游记》都因为使用了太多的特殊技术,所以难以复刻。最主要的区别是:
读取和保存内存数据这两个指令(包括我扩展的“调用任意子程指令”)无疑是不能使用的;
读取的键值与原来完全不同;
计算显示文字位置的方式不同;
转换场景指令难以重制。
而上述的两个MOD使用了太多这方面相关的技术,所以除非是这些特殊事件的作者KG亲自来处理,很多我已经无能为力。其实《笑梦游记》基本上还是能够支持大部分的,至少新的对话样式可以显示出来,只是文字会错位。
总之复刻的过程是十分艰难的。虽然时间上仅用了2周,但中间耗费的精力却是极大的。
写在后面的随想
至今我也不清楚把这个版本称为“复刻版”是否合适,KG曾提议叫做“群侠再现”(他当时误解了我的意思了,我是在考虑如何制作输入姓名的部分,他理解成为游戏起一个名字),我最后用了这个看起来有点低调的名字。
后来我在几个金庸游戏论坛闲逛的时候,我想到,在这些论坛置顶的游泳的鱼做的Windows版终于可以拿下来了,这其实也有些敝帚自珍的意味。无论如何,在《金庸群侠传》MOD发展的最初阶段,wind和游泳的鱼在解读代码上做出了最大的贡献。在后来的铁血丹心时期,游泳的鱼的50指令集开创了MOD制作的新时代,我们今天的很多人只是站在他的肩膀上而已。许多人离开了,又有许多新的人加入,希望技术上的人才不要出现断档,铁血丹心的MOD制作就会再有新的发展。
我看到在百花谷里面,许多人提出很多剧情和设计上的想法,有些帖子还得了高分,加了精华。剧情的想法在实现上多数是体力活,但是如果没有制作组肯采用,就算是加了几百分的剧情,何时能够变成现实呢。制作事件的技术门槛很低很容易学,但有多少人愿意去干那些体力活呢?许多MOD早就在策划和制作中,但是却频频跳票,就像KG说过的,制作人理念不同,时间都用在了扯皮上。很多新的设计,如今在物品事件和战斗事件的辅助之下,都能成为现实,幻想与实践的距离越来越短,但当年以钻研技术创新为荣的论坛气氛,却淡得多了。
我自己也曾说过不擅长事件编写,但实际上这也不过是一个托辞。有了程序设计的经验和思想,编制出严格的算法并非难事。但更多的时候,我倾向于动脑较少的工作,比如我帮小猪导入过上百个贴图,当过名副其实的苦力。其实我也希望看见自己研究出来的技术能够被广泛应用的一天。
Fishedit是一个好的修改器,但并不是一个好的应用程序,无数的bug和频繁的崩溃都是很让人烦心的事。游泳的鱼曾经说要放出它的源码,其实我是很期待的。老实说,fishedit的大部分功能在这里的很多人都很清楚,但是想完全重制,耗费的精力实在不可估计,计算下来,也就忍了那些bug了。
我在论坛里最早发布的一个修改是关于“辟邪剑谱”效果的研究。后来发布了修改物品选单和状态选单的方法,这两个似乎被飞虫王加入了精华(在我成为版主之后将它们取消了)。之后研究了很久游戏中的地址调用问题,在看雪论坛和游泳的鱼留下资料的蛛丝马迹中找到了重定位表的资料,最后才找到是重定位表的问题。我相信以后如果有人再想研究系统修改,这个问题一定会困扰他们很久,这也是我后来整理了很多资料的原因。
在放飞心情上找以前的资料,当年的chaoliu,wind,游泳的鱼都有在胡斐家做客一周末的记录。我能够理解他们的艰辛和身在其中的快乐,因为我也有过类似的经历。那些二进制的代码,都是他们最早一一解读的,原版的1000多个事件,是令狐心情一个个试出来的。做这些事的时候,热情和毅力是最重要的。
写了这么多。 |