给博客添加Live2D看板娘
突然心血来潮想给自己的 Hexo 博客也整一个看板娘,刚刚好手上又有一个比较喜欢的角色模型,于是就开始动手,很快就找到了 hexo-helper-live2d
[1]这个插件,配置也非常简单,三下五除二就搞定了,效果还不错,甚至觉得有点无聊,因为没有一点儿挑战性😃
正当我准备收手的时候,我突然发现有点儿不对劲😕:模型除了会根据鼠标位置转头之外就没了,非常单调,这让准完美主义(指强迫症)的我非常难受。由于我手上的模型已经有打包好的多种动作表情,因此我打算先去看看有没有什么办法可以直接调用模型的这些动作。Cubism Editor 就是制作 Live2D 模型的工具,我想大概也可以拿来修改模型从而加载各种动作吧,于是就下载了最新版(4.0)。然而杯具的是无论是 Cubism Editor 还是附带安装的 Viewer 都没法打开我的模型。
百度了相关文章,不出预料果然就是软件版本的问题,现在已有的模型,基本都是用 Cubism Editor 2 开发的,新版本并没有向下兼容,因此是没法用官方的最新软件来编辑的。好在同时发现一个 Live2D Viewer[2] 的软件可以直接打开旧版本模型,只需要安装 Adobe Air 就可以使用。
通过查找软件使用文档(官方是日语,有中文版的镜像站但是完成度不高),基本摸清了这个软件的使用方法和 Live2D 模型的组成结构:
1 |
|
分别点击表情和动作文件,可以在旁边的模型预览效果,需要新的效果可以自己调整下方的参数。不过毕竟不是来搞模型设计的,贴图、物理效果、姿势这些不用管了,也不需要自己制作表情和动作,想办法把已有的资源利用起来就好了,那么关键就是 xxx.mode.json
这个文件了。以我这个模型为例:
1 |
|
参数非常多,但其实需要注意的就这几个:
hit_areas
:设置模型的可触发区域,一般的模型都有“head”和“body”两个区域,后面的id
是模型制作时设置的,可以在 Live2D Viewer 里面查看;expressions
:设置模型的表情;motions
:设置模型的动作;idle
:模型闲置时动作;tap_body
:点击hit_areas
中对应的部分时触发的动作,这里对应了“body”;flick_head
:同上,这里对应了“head”。
其他参数要么意义不言而喻,要么就完全一无所知,关于这些参数,可以参考 Live2DViewerEX
文档中[3] SDK 配置这部分内容(Live2DViewerEX 是 Steam 上的一个收费软件,功能类似 Wallpaper Engine,不过集成了 Live2D 的 SDK)。当然最开始我的配置并没有这么完善,因此模型的动作才十分单调,新的配置中,我给模型增加了4个闲置时的动作,可以随机触发,一个点击头部的动作和一个点击身体的动作,这下终于完美了!
这么想的话就太天真了,模型现在确实动作多了起来,随之而来的 B!U!G!也来了。主要有两个问题,一个我设置了4个闲置动作,想的是可以随机触发4个不同的动作,然而实际运行时,每次加载出模型后会随机选择一个动作,然后会反复重复这个动作;另一个问题是我点击头部不会触发 flick_head
这个动作,而是随机切换表情。准完美主义(指强迫症)的我怎么可能允许这种问题存在!于是我又开始了新一轮的学(zuo)习(si)。
通过 hexo-helper-live2d
插件的文档和张鑫旭大佬的博客[4]文章,我了解到 hexo-helper-live2d
插件使用的是 Live2D_SDK_WebGL
官方版的魔改版本 live2d-widget
[5]。也就是说可以确定问题来自 live2d-widget
这个插件,作为对比,我下载了官方 SDK,并运行了里面的例子,结果发现官方的示例中,尽管不存在第一个问题,但是单击头部依然是切换表情而非触发动作,此时骑虎难下,只好开始研究示例的代码。
折腾了一整天,终于大概搞懂了 SDK 的运作方法,同时找了下 SDK 的 API 参考[6],不完整,但是也可以了解一下。首先以官方示例(注:这个示例是我稍微魔改过的,修改了显示样式和代码格式,不影响原有功能)介绍一下 SDK 的组成,有兴趣的可以下载这个例子[7]来玩一下:
1 |
|
然后是文档中描述的关于 Live2D 渲染模型的整个过程:
- 初始化 canvas
- 创建 webgl 上下文
- 初始化 live2d
- 从moc文件读取Live2D模型对象
- 读取贴图,对 live2DModel 设置贴图、WebGL对象、矩阵
- 模型的更新和绘制
通过对 SDK 整体的了解,再来找问题,就轻松多了(个屁),关于第二个问题,点击头部只切换表情不触发动作的,那么只需要找到触发事件的函数,再一个一个跟踪下去就能找到问题。最后找到是在 LAppLive2DManager
的 LAppLive2DManager.prototype.tapEvent
这里:
1 |
|
可以看到,输出日志 “Tap face” 的下面调用的 setRandomExpression()
方法,字面理解就是设置随机表情,这就是为什么点击头部只会切换表情了,只需要改为下面 “Tap body” 的部分一样,应该就可以触发动作了,因此把这里改成:
1 |
|
这样一来,第二个问题就完美解决了,这时候再来看第一个问题。由于已经确定第一个问题是 live2d-widget
的原因,那么这里只需要把它和官方的代码对比一下,很容易就可以找到问题了(个屁)。通过查看网页的请求,可以发现官方代码中,每次随机选择一个闲置动作时,会发出一个 ajax
请求,而 live2d-widget
只请求一次,因此只要找到请求闲置动作的部分,大概率就可以找到问题。(这里每次随机都请求实际是官方代码的BUG,感谢官方的BUG,如果不是官网的BUG我也发现不了这个问题,具体什么BUG下面有讲到)
通过查找调用栈,最后终于找到相关代码,在 LAppModel.js
中的 LAppModel.prototype.startMotion
里面,有一段这样的代码:
1 |
|
这里面最开始判断使用的“name”应该是指动作的名字,所有动作都保存在 this.motions
中,不过根据代码的上下文来看,这里传入的名字是动作的组名“idle”(详见上面的 config.json
,组名就是一组同类型动作的名称),具体的动作名称应该是“motionName” (上述代码前面定义的一个变量,保存的是当前加载的动作的文件名)。根据现在这段代码的意思,当 this.motions
里面没有名为“idle”的动作时,就加载一个随机动作并执行,否则的话就直接执行这个动作 。然而从 config.json
也看到,四个动作没有一个名字叫 ”idle“ 的,因此每次都会随机加载一个动作而不是直接执行这个动作。
再看 live2d-widget
中同样部分(cModel.js
)的代码,跟官方的代码不同的地方在于 loadMotion
这个函数:
1 |
|
live2d-widget
的 cModel.js
里这里的第一个参数是“name”而官方的代码中是“null”,我顺着找了下这个函数的实现:
1 |
|
可以看到,如果参数指定的是一个“name”的话,加载完后就会把这个动作添加到 this.motions
中,然后回到 startMotion
时就会执行 else
后面的内容,即每次都会运行这个 motion,但是上面也说了,这里的“name”实际上是动作的组名 ”idle“,也就是说它会把加载的第一个动作保存为 this.motions.idle
,之后无论随机到哪个动作,都只会运行 this.motions.idle
这个动作,因此出现了第一个问题。现在找到问题所在就很简单了,把 live2d-widget
中对应的地方的 name
改成 null
就可以了。
表面上看这样就解决问题了,实际上仔细想一下会发现官方的代码是有问题的,也就是上面说到的官方的BUG。如果每次都传入“idle”作为动作的名称,那么 else
之后的代码永远都不会运行了,这违反了这段代码的原意,仔细观察之后就会发现这段代码应该是这样的:
1 |
|
这样一来既能保证每次随机到不同的动作,又不需要每次重新请求动作文件,节省了 http 请求的时间。
目前对 Live2D 的了解就到这里了,哪怕是研究了一整天,也只是一点皮毛罢了,真正核心的图形编程我都还没开始入门,因此之后学习了相关知识后,再来写写文章吧。