在博客养一只猫 -- Live2D SDK 4.x 探索

提前声明,这不能当教程,只是一份笔记,还有吐槽。

搞一只看版猫到博客居然比搞一只看板娘还要麻烦……囧

虽说最后猫是放出来了,但八成还是哪里没用对吧,编译出来动辄四五百kb的js文件(虽然确实是没有特别狠的压缩,uglify全开之后运行会出错……),还有官方原版Demo直接编译出的1.6MB js+sourcemap(不含模型与图片),怎么看怎么别扭(捂脸)…… 这个只有短短5页的官方文档也是没说多少东西,也可能是因为大部分用户都是开发手机原生应用,而不是网页的吧。

要找教程的话,往这边走吧~

其他人努力研究的v4 SDK教程:

还不清楚是哪个版本,但是文档链接很多:

应该是旧版本SDK的应用:

Live2D

就是目前的很多手机游戏里2D 动态立绘背后的那个技术,或者说,一套专门应对活动幅度较小的类生物的2D 动画生成技术。我的直观印象是一套可以灵活控制的形变系统 + 物理 + 模型和动作管理。

Live2D Editor & Sample Data (hijiki)

Live2D 官网大多的模型样例(除了有三方版权的)大多是对一般使用者和小型公司免费的。这只猫就是tororo&hijiki包里的其中一只。

使用

使用Live2D需要有:

  1. 模型: 包括texture、部件、参数(上面绑有不同部件的关键帧)、动作(部件、组件参数变化)
  2. SDK

模型

Runtime里的model3.json长这个样子:

其中.2048文件夹里是材质png图片,

motion文件夹是每个动作的json(每个动作,各部件网格顶点位置变化等),

cdi3是参数的描述的样子(但也并不是到default parameter的mapper...),

moc3是模型文件,里面包含部件和参数的设定,

model3是对模型的描述和配置,

physics3是一些物理(如头发摆动?)和动画的参数,

pose3似乎是同一部位的多个图片(如无法靠单一图片形变做到的,同一部件的不同状态)

杂谈

说到动画,就想跟死掉的Flash比较一下。小学的时候,Flash在我心目中的一大黑科技就是补间动画。给出形状A和形状B,计算从A变成B之间的过程并自动填补关键帧。但除非图形规则,这个生成的补间对用户来说基本是不可控的。中间过程可能生成得很自然,也可能先从A扭曲成奇怪的图案,再变成正常的B。当然,这种情况,大家一般会删了补间,在中间加几个缓冲的关键帧(我们预期的中间状态),让帧与帧之间的差异更小,变化更规则,再创建补间。Live2D 这边,通过在图形上创建由多个三角形组成的多边形网格(一堆三角形可以组合出各种多边形),拖拽网格上节点造成形变生成关键帧。然后因为这些形变都被这些节点的移动描述了,补间的生成就会很规则。

印象中与Flash几乎同期的,还有一个基于骨骼生成补间的Moho。查了下它还好好活着……是在图片上创建并绑定骨架,通过拖拽骨架顶点对图片进行形变来创建关键帧,然后创建补间。感觉很像是Flash和Live2D的中间状态。

Live2D SDK for Web

模型自定义参数名的问题

比较快速但一点也不正规的操作:改Framework/src/cubismdefaultparameterid.ts

附camel case与snake case可以放进浏览器console转换:

/*
目标参数名 json示例 (猫的模型tororo和hijiki使用的参数命名)
{
    "ParamAngleX": "PARAM_ANGLE_X",
    "ParamAngleY": "PARAM_ANGLE_Y",
    "ParamAngleZ": "PARAM_ANGLE_Z",
}


默认参数名json示例
{
    "ParamAngleX": "ParamAngleX",
    "ParamAngleY": "ParamAngleY",
    "ParamAngleZ": "ParamAngleZ",
}
*/

//主要是需要这个,因为默认是camel case,但有些模型是snake case
function camel_to_snake(name) {
  return name.replace(/([A-Z])/g,"_$1").toLowerCase().substring(1);
}

for(k in json){
	json[k] = camel_to_snake(json[k]).toUpperCase();
}


function snake_to_camel(name) {
	return name.split("_")
        .map(x => x.slice(0,1).toUpperCase()+
             x.slice(1).toLowerCase()).join('');
}

当然了,如果压根就不在所谓“标准参数”列表里,那也就没什么简便办法了。

事件监听与动作的问题

总之,在官方Github的例子里,事件监听在lappdelegate.ts,如下部分。自行按需改动,比如把canvas.ontouchmove改成document.ontouchmove之类的。

if (supportTouch) {
      // タッチ関連コールバック関数登録
      canvas.ontouchstart = onTouchBegan;
      canvas.ontouchmove = onTouchMoved;
      canvas.ontouchend = onTouchEnded;
      canvas.ontouchcancel = onTouchCancel;
} else {
	...
}

动作在lapplive2dmanager.ts里(这个onTap这里):

public onTap(x: number, y: number): void {
    if (LAppDefine.DebugLogEnable) {
      LAppPal.printMessage(
        `[APP]tap point: {x: ${x.toFixed(2)} y: ${y.toFixed(2)}}`
      );
    }

    for (let i = 0; i < this._models.getSize(); i++) {
      if (this._models.at(i).hitTest(LAppDefine.HitAreaNameHead, x, y)) {
        if (LAppDefine.DebugLogEnable) {
          LAppPal.printMessage(
            `[APP]hit area: [${LAppDefine.HitAreaNameHead}]`
          );
        }
        this._models.at(i).setRandomExpression();
      } else if (this._models.at(i).hitTest(LAppDefine.HitAreaNameBody, x, y)) {
        if (LAppDefine.DebugLogEnable) {
          LAppPal.printMessage(
            `[APP]hit area: [${LAppDefine.HitAreaNameBody}]`
          );
        }
        this._models
          .at(i)
          .startRandomMotion(
            LAppDefine.MotionGroupTapBody,
            LAppDefine.PriorityNormal,
            this._finishedMotion
          );
      } else {
        if (LAppDefine.DebugLogEnable) {
          LAppPal.printMessage(
            `[APP]hit area: None`
          );
        }
        this._models
          .at(i)
          .startRandomMotion(
            LAppDefine.MotionGroupTap,
            LAppDefine.PriorityNormal,
            this._finishedMotion
          );
      }
    }
  }

也可以在监听器里直接这样触发动作的更新

let instance = LAppLive2DManager.getInstance();
instance._models.at(0).startRandomMotion(
	LAppDefine.MotionGroupTap, 
    //其实就是model3里的动作, 比如"Tap", "Flick"
	
    LAppDefine.PriorityNormal, 
    //更换动作的优先级
	
    instance._finishedMotion 
    //回调,其实就在console打印了个 Motion Finished
);

应用于Ghost CMS的问题

由于目前Ghost CMS不支持图片、js、css以外的静态资源,模型是不可以直接打包在Theme里的,不然读取json和moc文件会直接301然后404。

解决方案

需求:让脚本能够成功的加载模型(静态资源,.json, .moc, .png等等),最好还不要跨域。

情况:Ghost官方估计不会这么做的,它们都声明过连媒体库都不做。

暴力的办法:反正本来网站也是nginx反代的,在相同域名下直接多配置一个location在根之前进行匹配,用于加载模型。

location /live2d/ {
    root /somepath/somepath;
    autoindex on;
}

如上,把模型放在/somepath/somepath/live2d目录下,然后在themes的js里从/live2d/加载模型就行了。