【万吨巨轮,史前天坑】h5pal是怎样练成的 - 开篇

h5pal AKA 仙剑奇侠传Web版是我造过最大的轮子,也是开过最大的坑。曾经装过的逼,终究是要还,不然我脸被打得啪啪响那还了得。虽然早就没更新(呃,准确的说,是自从开源以后就再也没更新),但对我自己而言心里其实还是想把这个坑给填了——至少是以另一种方式——也就是现在这个新坑——h5pal是怎样练成的。

其实一开始我是想做一个PPT完事儿,但最后发现信息量放进一个PPT里实在是有点太大了,还是老老实实的写文章吧。

挖坑如山倒,填坑如抽丝,今天是第0篇,开坑之作自然也没什么干货(逃,不过重点是先帮助我自己梳理一下这艘万吨巨轮要从何说起。

所以开篇其实只是一个提纲性质,我的打算是按照模块又底层到表层来慢慢介绍h5pal是如何从船坞到下水,当然我在写的过程中也毫无疑问肯定会被自己当年写的代码丑到吐,看我都吐了的份上,你们就别跟着吐槽了是吧……

架构

h5pal的绝大部分代码来自于对SDLPAL的从C到JS的人肉翻译,因此大致也沿用了SDLPAL的架构。在之前的文章中我列了一个完成度的表,这里先照搬一下

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

在h5pal里这些模块和SDLPAL里的作用不一定是完全一样的,比方说SDLPAL的资源模块除了负责加载还负责卸载这样可以节省内存,但在JS中缺乏手工控制内存的方式,而且像我这样不负责任的男人怎么可能节省内存口亨。再比如SDLPAL的输入模块除了支持键盘以外还支持Symbian手机。再比如SDLPAL当中有很多支持仙剑的分支版本(比如什么什么梦幻版)的代码,还用条件编译分开了95版和98版,这些我都大幅度简化了。

这里对h5pal中每个模块做一个概述性的介绍:

  • 资源:通过二进制Ajax加载仙剑的资源文件和存档文件,使用一种牛人前辈们逆向工程出来的算法解压缩。
  • 读档:仙剑的存档文件是一个巨大的结构体,在C里可以通过结构体指针转换做到极其无脑的快速读写,但在JS里折腾了一圈。
  • 存档:理论上讲完整形态应该是以二进制方式写入存档文件,不过偷懒的我只是为了开发调试,实现了一个写入localStorage的存档功能。
  • Surface:在设备(SDLPAL是屏幕,h5pal是canvas)上抽象出一个画布,它能够提供像素级的操作、调色盘设置、整块或区域的暂存和恢复等等功能。
  • 位图:仙剑中有大量的位图(没有透明底的图),位图模块就是从资源文件里读取位图并且画到Surface上。
  • Sprite:仙剑中也有大量的带透明底的图,其中一些是单帧的图片,一些是多帧的Sprite,这个模块提供了对这类图片的解析、绘制等功能。
  • 地图:和诸多2D游戏一样,仙剑的地图是基于Tile的,地图模块提供了地图的绘制和卷动功能。
  • 场景:场景是一个综合概念,它包含了地图、地图上的元件、角色等,除了绘制这些东西,它也有碰撞检测、切屏效果这些功能。
  • 调色盘:仙剑是一个256色的游戏,但是它所做到的事情远远大于256色,因为调色盘的存在,不同的场景应用不同的调色盘,色彩更加丰富,但计算量和存储量并没有增大,一些视觉效果比如黑屏、红屏也是通过调色盘实现的。
  • 文本:游戏引擎中的文本绘制是一个大话题,仙剑使用了一个点阵字库资源文件来输出文本。
  • 脚本:如今而言,一个正经的游戏引擎基本上不可能不标配一个脚本系统,但你可能很难想象20年前的游戏竟然也内置了一个脚本系统,整个游戏的故事流程都是通过脚本来编写的,场景中的可互动元素也是通过脚本系统来串接的。
  • UI:仙剑分战斗UI和平常UI,UI模块除了负责绘制界面以外还要负责处理交互。
  • 战斗:战斗系统相当复杂,首先它自己是一个场景系统,其次它要管理玩家角色、敌人角色,还要管理整个战斗的状态、流程和规则,还要负责绘制角色执行动作的绘制,法术效果的绘制等等。
  • 播片:游戏内其实是有一些播片的,比如酒剑仙山神庙教李逍遥剑法的动画。
  • 结局:结局反而不是通过播片实现的,所以有一段相当不可复用的脚本来控制位图播放,看起来很像播片的样子。
  • 音乐、音效:这块很坑,但其实一直以来是我的遗憾。仙剑的音乐部分是超级经典的,但h5pal却没有声音。事实上后来我通过抄一个日本人的项目已经实现了通过WebAudioAPI实现MIDI播放,理论上讲应该是可以播放仙剑的MIDI了(事实上,用单独提取出来的mid文件扔进去的确是能播了的),但后来已经没有动力再去填坑了,而且仙剑也有RIX版本的音轨,还不知道怎么在网页里播,反正,这个我不打算写进这个系列里了。

除了这些游戏系统本身的模块以外,还有一些基础模块

  • 输入:这个不用说了吧
  • 主循环:这个,呃,也不用说了吧
  • 二进制:提供了一个以结构体方式访问字节数组的抽象,这在C里只需要通过指针强制转换就能解决的问题,在JS里费了九牛二虎之力(为了把尽可能多的二进制操作都重构到这种方式,我大约重写了6000行代码。
  • 基础库:更不用说了吧

计划

那么这么多模块我当然不会都说,只会讲一些有含金量的或者有意思的,比如二进制、地图场景、战斗等,二对于脚本这样又臭又长的系统我就不打算写了,可能在写其他模块的时候顺带提几句吧。

那么,坑是挖好了,你看么?嗯,就算你看我也未必真的能填坑,因为我现在心里真的是好虚啊……