十一长假之前悄悄发布一个情怀之作-h5pal(无底天坑)

趁着大家都赶着休假去了,悄悄发布,免得被喷,23333。

h5pal是《仙剑奇侠传》的web移植。

0. 前言

这是一个坑了太久太久的项目,久到我已经不记得挖这个坑是什么时候了。大概是13年的夏天吧,我挖了这个坑,然后信心满满的在当年十一长假宅了N天(我还比较清楚的记得那时候正是WOW开荒围攻奥格瑞玛副本的阶段),写下了整个框架,以及最核心的一部分代码,然后,就没有然后了。

大概一年后,我又翻出来了这个坑,重构了大量的代码,但是进度几乎没有实质性的进步,甚至因为重构而有所倒退- -“,不过因为读了《游戏引擎架构》这本书,我对这个坑又有了新的认识,对于这个程序到底要怎么写心里有谱多了。

本来计划是在今年夏天搞出来,这样可以赶上仙剑20周年(1995年7月)发布,不过不用想也知道毫无疑问是继续坑了。

磕磕绊绊到如今,总算是把游戏的总体完成度拉到了一个比较能见人的程度,于是我觉得还是赶紧发布的好,免得又变有生之年了。

1. 无图言屌

优酷视频——有视频有JB!

2. 自问自答的FAQ

2.1. 能玩吗?

。但在GitHub repo里并不会包含游戏的资源文件,于是需要自己去找(嘿嘿mq2x)。由于不分发游戏资源文件,且考虑到体积,我也不会提供一个在线游玩的版本。所以基本上只有开发者或者动手能力强的同学才能玩上它了(如果你真的想玩……)

不考虑遇到BUG(无数个)造成游戏直接罢工的情况下(当然身为作者的我是可以驾轻就熟地避过这些BUG的233333),已经可以从新开游戏一直玩到大结局了,而且我已经通关两三遍了XD

2.2. 这是什么程度的移植?

原汁原味移植。h5pal从SDLPAL里平移(就是抄啦)了大量的代码。SDLPAL是一个基于SDL的跨平台版仙剑,它已经能顺利的运行在Windows、Linux、OS X、Symbian、PSP、Android等很多种平台上面。

h5pal与SDLPAL有着相同的出发点,就是实现仙剑的主程序,你只需要有仙剑的资源文件就可以运行整个游戏。

2.3. 为什么需要仙剑的原版资源文件

出于上面所说的只实现主程序的出发点,并且出于技(xīn)术(lǐ)洁(biàn)癖(tài),我选择不对资源文件进行任何预处理。如果按照现代游戏引擎的方式,先把资源文件里的位图、Sprite、数据等资料都解开成更适合HTML5/JS所需要的结构化数据,整个开发也许会变得容易很多。

但那样就不好玩了

因此最终我选择了保留SDLPAL的味道,不对资源文件进行任何的预处理,而是直接读取原始资源文件。当然因为完成度和工作量的原因我只能支持一个固定版本的资源文件,而SDLPAL则有更强的兼容性(甚至支持民间MOD仙剑梦幻版)。并且SDLPAL实现了半即时制战斗的创新,我个人不太喜欢,也没有迁移这个。

2.4. 使用了什么游戏引擎/框架/库/技术

从思路上看的话,可以说使用了The-Best-JS-Game-Framework

最主要的,这个程序主要使用了co,使用co/yield/generator来改善异步开发的体验,让整个庞大的程序实现成为了可能——前言中说的去年的一次大重构就是干这个——这是一个非常重要的重构,过去的话一个异步的update/render loop就可以让人抓狂,以至于我现在根本不想再写异步的JS了T_T,也许有机会我会再写一篇文章来介绍JS“同步”编程以及js-csp这个非常好玩的东西。但你知道co其实是一个非常非常简单的库,所以即使没有co的话,自己造一个堪堪一用的轮子也非常容易,所以想解除这个依赖是很简单的。

在这个坑之初,原生Promise还没普及,所以引入了q,但其实在整个项目中贯彻了co之后,很少用得着Promise,并且也可以很容易的向原生Promise迁移,当然因为懒我是没这么干的。

其他方面可以说几乎没有依赖第三方的库了,可能还有jQuery啊这类的东西,只是用了一丁丁点,非常容易解除依赖。

仙剑是一个很古老的游戏,使用现代游戏引擎重新实现仙剑的主程序并没有太直接的帮助。现代的2D游戏引擎围绕Sprite和场景管理为主,虽然在SDLPAL和h5pal中也有Sprite和场景模块,但具体到技术层面和现代游戏引擎里的还是差别比较大。再加上技(xīn)术(lǐ)洁(biàn)癖(tài)的原因,我没有用任何现代的游戏引擎,不过等到轮子造得差不多的时候,发现游戏引擎的思想果然是几十年没有太大变化……

由于音乐和音效系统彻底坑了(原因见后文),所以WebAudio暂时不涉及。图形方面只涉及到canvas 2D,并且因为仙剑本身的资源都是像素级的,所以图形这一层也基本上都是在getImageData/putImageData的层次直接操作像素,并没有使用任何canvas的绘图API。因此如果后续把绘图层迁移到WebGL也会很简单,不过目前看来完全没有这个必要。

h5pal使用GPLv3发布,我对开源协议几乎不懂,只知道GPL是比较严格的一种协议,而且SDLPAL是用GPLv3的,考虑到我抄了他很多代码,于是用了这个至少不比他宽松的协议,并且再次向SDLPAL表示敬意。

2.5. 为什么没实现音乐/音效部分,不是有Audio和WebAudio了吗?

音效部分仙剑用的是voc格式,这个格式太古老了以至于Audio和WebAudio都不可能直接支持它。为了不对资源文件做预处理的原则,在这里就让它坑了。

音乐部分仙剑用的是MIDI,目前在Web里有MIDI.js可以处理(P.S.这个项目相当之屌!)。但是懂MIDI的人都知道,MIDI格式本身并不复杂,难的在于实现音色库。这样一来会引入很大一堆东西,甚至上百MB的音色库,这非常不现实,所以我选择先(forever)把它坑了。

2.6. 为什么没有实现存档?

其实是实现了(隐藏功能哦),但因为存档到资源文件的话,需要向服务端POST,这样需要CGI支持了,麻烦……然后我为了方便自己玩就用了很猥琐的办法实现(其实还是堪堪一用的)。

2.7. 现在看起来都是dev状态,什么时候会成为成品游戏?

也许永远不会,因为没动力再把各种BUG还有音频部分的坑填了……

如果有生之年真的能填,那么也许可以用node-webkit这类的东西打包成成品游戏,不过……有意思么……

2.8. 有可能在手机上运行吗

目前不可以,性能最好的iOS Safari尚未支持yield/generator,而Android Chrome我目前没有关注。

性能方面没有明确的评价,在MacbookPro上CPU占用率并不高,但是内存很高(因为惨无人道的用内存,毫无优化之心),所以我觉得还是挺堪忧的。

2.9. 所以总的完成度?

直接搬GitHub上给(胡邹)的吧:

模块 进度
资源 90%
读档 99%
存档 40%
Surface 90%
位图 99%
Sprite 99%
地图 90%
场景 90%
调色盘 90%
文本 99%
脚本(天坑) 70%
平常UI 90%
战斗UI 90%
战斗(天坑) 70%
播片 90%
结局 95%
音乐 0%
音效 0%

3. 后记

(呃,这个真的是流水账了,可能就长了)

其实一开始让我发布h5pal的时候,我是拒绝的。因为我只想把它当做一个情怀的玩具,烂在自己的硬盘里面算了。而且心理洁癖造成我觉得没完成的东西就不要发布了吧。后来在@licstar的鞭策之下一点点推进,断断续续改了很多没头绪的BUG。突然有一天似乎流程能走通了(那时候还没实现战斗),而他竟然磕磕绊绊的就玩到通关了,我特么真是惊了,瞬间有种拨云见日的感觉。

我知道即使发布了也估计没有人会用这个版本来玩,不过如标题所说,情怀之作。今年的仙剑6让很多玩家非常失望,而身为老仙剑迷的我其实从4代过后就已经弃坑了。尽管如此,我一直都认为如果想做一名合格的RPG玩家,从游戏评论的角度出发的话,仙剑1一定是必玩之作,因为在那个时候它是中文RPG游戏当中能和同期日系RPG有一战的一作,代表了当年RPG的最高水平,可以称为游戏发展史上的一个标志。选择仙剑很大一部分原因当然是有SDLPAL这个现成的对象可以抄,不过情怀满分这一点也是其他游戏不可取代的。

我是一名游戏爱好者,也一直想着能做游戏,并且是想做出版级的“大”游戏。不过因为各种原因,似乎离这个目标越来越远了。其实游戏是一个非常大也非常复杂的软件工程,甚至有人说游戏是软件工程当中最难的一个分支。我一直非常佩服各种3A大厂,能够集结上千人,几千万美元的资金做出一部部牛逼的作品(每打通一个游戏我都要把制作群字幕看完),也非常佩服各路独立游戏神人,能在那么有限的资源下做出精彩的作品。虽然仙剑不是新IP,我想我也不太有可能做新IP,甚至说没有SDLPAL和PalResearch的基础的话也不可能做出h5pal,不过这也已经在很大程度上满足了我做游戏的梦想吧,能做到现在这个程度我还是很开心的。

至于为什么是用HTML5/JS来实现呢?首先我本职是做前端的,对JS是非常熟悉,也可以当练手用呗(虽然整个h5pal的JS代码几乎没有任何技术难度可言吧……)其次就是因为SDLPAL本身已经做到跨很多很多平台了,惟独web这个炙手可热的平台还是个空缺。我在网上也没有找到仙剑1的完整web移植。另一方面,因为有别的一些老游戏的web移植中有很多(比如Diablo、星际)只是伪移植,也就是用原版游戏资源解包以后在web上做一个demo,根本没法玩的,这一点坚定了我做完整移植和资源文件不进行预处理的目标。

最大的遗憾也是留下了音频这个无底天坑,因为仙剑1的经典的配乐很得人心,没有音乐的伴随,即使体验剧情也会觉得少了太多味道,可惜可惜。

h5pal里面实现了一个用来读取C结构体指针的库,C里面通过指针转换,从文件里读取一段字节直接“铺开内存”就能转成一个结构体,这一点非常好用。这个JS库能把ArrayBuffer直接转成JS对象,利用getter/setter可以把对字段的操作落在ArrayBuffer(JS里的字节数组)上,这样一来还可以让不同对象共享内存(比如实现一个union什么的),在h5pal里是一个很核心的库了(重构的时候也是血虐啊)。我觉得还挺方便的,也许用在nodejs里的话实现一些native互访以及网络协议的时候会用得着吧。以后有时间的话可能会考虑把它重构一下,API弄弄更易用了单独发布一个库吧(有生之年

最后感谢@licstar的鞭策(催)和积极的帮忙测试,如果不是这么催的话估计早就烂硬盘里了。

最后的最后,我才发现仙剑里的女生都很积极主动啊,有的地方甚至还挺毁三观的……