Bulletproof 重构的工作进展不错,主要是花了时间基于 WebGL 写了 GLantern,将基础的绘制剥离了,以此为基础写弹幕相关的就轻松了。
九月份的测试是概念的验证,架构极其混乱。当时 TypeScript 版本还在 1.4,import
语法限制很多(只有 import x = require("file");
),而且 TS 和 C# 那一点点微妙的差距我还没很好地体会出来,最后就变大文件乱引用加一堆hack的混合物了。当时也没什么大的目标,能做出一个能执行代码的高级弹幕的原型就可以了,因为自己都不知道能不能做成。——是,有先例,但是看着这么多已有的代码就开始仰望巨大的工程了。
既然验证通过了,就要开始做架构了。(地基打好了,就开始规划柱梁,再装修。)考虑到应用场合,姑且做了一个。
新的源码在通过一些测试用例后会推上来。莽然融合 WebGL 的测试的各种东西放在了 native-webgl-obsolete
分支(看名字就知道是废弃了)。
详情请展开。
先扯一点远的。我觉得 Win32 啊 COM 啊 COFF 啊这些架构/格式都是神级的设计,经历了多少的时间和实践检验哪……完毕。
第一次运行测试的测试,直接读入之前的 kanpai-madoka.js。MotionGroup 之类的就省了,按照所有值默认显示出来,第一个静帧。
设计还算合理。怎么说呢?在规划之后(停留在想法上,没写下来——这是个缺陷,因为我经常忘东西 = =),动手实施,很快就写出来了。运行测试,改了几个 NotImplementedError
(我会在所有未实现的函数里引发 NotImplementedError
而不是指定默认值,这是学习 C# 的抽象函数实现模板中引发 System.NotImplementedException
的方式)之后就能正常工作了。(这种方式只能证明,当前的设计是一个有效解,而不能证明是最优解……)
然后我开始讲目前用到的 Bulletproof 新架构。(以下是 SVG 图形,如果你看得别扭的话……其实 Chrome 一旦对于没指定字体的文字应用系统默认字体,也就是宋体,就开始乱套了。)
首先是整体(包视角)的设计。
对应的文件结构是这样的:
D:\SOURCE\JAVASCRIPT\BULLETPROOF\SRC
│ Bulletproof.ts
│
├─bilibili
│ │ BiliBiliDanmakuApiContainer.ts
│ │ IBiliBiliDanmakuApiContract.ts
│ │
│ └─danmaku_api
│ │ BiliBiliDamakuApiObject.ts
│ │ Display.ts
│ │ Global.ts
│ │
│ └─data_types
│ ICommentBitmapCreateParams.ts
│ ICommentButtonCreateParams.ts
│ IGeneralCreateParams.ts
│ IMotion.ts
│ IMotionPropertyAnimation.ts
│
└─danmaku
│ DanmakuBase.ts
│ DanmakuCoordinator.ts
│ DanmakuKind.ts
│ DanmakuLayoutManagerBase.ts
│ DanmakuProviderBase.ts
│
├─code
│ │ CodeDanmaku.ts
│ │ CodeDanmakuLayoutManager.ts
│ │ CodeDanmakuProvider.ts
│ │
│ └─dco
│ DCOHelper.ts
│ DCShape.ts
│ IDanmakuCreatedObject.ts
│ IDCExtraCreateParams.ts
│
├─mode7
└─simple
首先,在一个全局包 Bulletproof
中有一个全局(对于每个实例而言)类 Bulletproof
。每个 Bulletproof
对象会创建一个弹幕的综合管理类 DanmakuCoordinator
,用于控制弹幕的产生(兼顾视觉效果与渲染效率)。每个 DanmakuCoordinator
会创建 *DanmakuProvider
对象,提供不同类型的弹幕控制(管理)服务。理论上,可以通过反向注入的方式添加遵循新逻辑的弹幕类型,不过目前还是使用着固定索引的方式,和 BiliBili 的当前值对应(准备先实现普通弹幕、定位弹幕、代码弹幕)。一个 *DanmakuProvider
拥有一个 *DanmakuLayoutManager
实例,该实例负责根据情况计算所属弹幕种类中的某个弹幕的应在位置。每个弹幕种类有自己的实现类 *Danmaku
,负责更新与绘制所属的弹幕内容。
接下来是显示核心 *Danmaku
的设计。我目前实现了大部分的 CodeDanmaku
,就以此为例子。
CodeDanmaku
继承自 DisplayObjectContainer
,为其创建的代码弹幕提供一个统一的视觉元素根节点。同时,每个 CodeDanmaku
有独立的 BiliBiliDanmakuApiContainer
实例,作为高级弹幕代码与 Bulletproof
(以及底层的 GLantern
)的通信接口。例如,以下的代码会被分配到一个具体的 CodeDanmaku
(假设名字为 cd1
)中执行:
var s = $.createShape();
执行后,s
的逻辑和视觉父亲自动是 cd1
。这段代码是代入了 cd1.apiContainer
的各个对象(对 BiliBili 高级弹幕 API 的模拟),因此保证不同的代码弹幕的创建能自动与对应的 CodeDanmaku
挂钩,执行是分离的。但是目前的实现问题(用 Function
),如果突破了简易沙箱,还是会相互影响的。例如,在另一个高级弹幕中输入内容:
s.alpha = 0.4;
就会发现,前面创建的图形透明度发生了变化——此时 s
是一个全局变量。这一点以后考虑采用jabbany的 WebWorker 方案,更安全而且不会造成污染——毕竟浏览器的沙箱帮挡着,除了伪造通讯之外是没有能破坏主线程的方法的。
这些弹幕都被作为 DisplayObject
(之后的普通弹幕和定位弹幕也会采用此策略)加入到了根 Stage
的孩子集合中,因而更新和绘制是随着元素树进行的。
最后再回顾一下 DanmakuCoordinator
-*CodeDanmakuProvider
的设计。这种预先创建并索引的关系和 ShaderManager
-ShaderBase
是一样的。这一点模仿了 Pixi。当初 GLantern 起稿的时候,shader管理是按照最后一个 WebGL 测试的形式写的,每个 WebGLRenderTarget
带着专属的 ShaderBase
,如果要实现不同功能,还得实时销毁并创建绑定新的 ShaderBase
。后来咬着牙动了大手术,将整个蹩脚的设计改成了现在的 ShaderManager
。感谢这个 ShaderManager
,后来就一直没有担心过shader资源分配的问题,而且添加新shader非常方便。