接上篇文章
欢迎大家阅读本篇教程
该篇教程为进阶版的看板娘设置教程,阅读完后可以极大程度的自定义你的看板娘项目。
教程内容:
- 去掉canvas中的背景图案和切换模型按钮,并将背景设置为透明
- 新增配置类,对需要自定义的属性进行修改,同时在模型中新建mapper
- 设置模型放大倍率
- 修改鼠标交互方式
- 适配模型的控制属性,自定义模型加载的位置
- 模块化该项目
1.修改canvas绘制的内容
打开【Sample\TypeScript\Demo\src\lappview.ts】文件,定位至以下位置,将我们不需要的功能注释掉。
定位到initializeSprite()函数
/**
* 执行图像初始化
*/
public initializeSprite(): void
{
let width: number = canvas.width;
let height: number = canvas.height;
let textureManager = LAppDelegate.getInstance().getTextureManager();
const resourcesPath = LAppDefine.ResourcesPath;
let imageName: string = "";
//注释掉所有图片加载方法
/**
// 背景画像初期化
imageName = LAppDefine.BackImageName;
// 创建回调函数,因为它是异步的
let initBackGroundTexture = (textureInfo: TextureInfo): void =>
{
let x: number = width * 0.5;
let y: number = height * 0.5;
let fwidth = textureInfo.width * 2.0;
let fheight = height * 0.95;
this._back = new LAppSprite(x, y, fwidth, fheight, textureInfo.id);
};
//将背景图片的注释掉
textureManager.createTextureFromPngFile(resourcesPath + imageName, false, initBackGroundTexture);
// 歯車画像初期化
imageName = LAppDefine.GearImageName;
let initGearTexture = (textureInfo: TextureInfo): void =>
{
let x = width - textureInfo.width * 0.5;
let y = height - textureInfo.height * 0.5;
let fwidth = textureInfo.width;
let fheight = textureInfo.height;
this._gear = new LAppSprite(x, y, fwidth, fheight, textureInfo.id);
};
textureManager.createTextureFromPngFile(resourcesPath + imageName, false, initGearTexture);
*/
-------
省略其他代码
-------
}
onTouchesEnded函数
public onTouchesEnded(pointX: number, pointY: number): void
{
// 触摸结束
let live2DManager: LAppLive2DManager = LAppLive2DManager.getInstance();
live2DManager.onDrag(0.0, 0.0);
{
let x: number = this._deviceToScreen.transformX(this._touchManager.getX()); // 論理座標変換した座標を取得。
let y: number = this._deviceToScreen.transformY(this._touchManager.getY()); // 論理座標変化した座標を取得。
if(LAppDefine.DebugTouchLogEnable)
{
LAppPal.printLog("[APP]touchesEnded x: {0} y: {1}", x, y);
}
live2DManager.onTap(x, y);
/*
// 点击了齿轮,调用live2D管理器切换人物模型
if(this._gear.isHit(pointX, pointY))
{
live2DManager.nextScene();
}
*/
}
}
完成后我们找到Sample\TypeScript\Demo\src\lappdelegate.ts的run函数
/**
* 実行処理。
*/
public run(): void
{
// メインループ
let loop = () =>
{
// インスタンスの有無の確認
if(s_instance == null)
{ return;}
// 時間更新
LAppPal.updateTime();
// 画面の初期化
//这里时初始化背景的,四个参数分别是 r g b a
//原本的参数是0,0,0,1
//我们把它改成0,0,0,0
gl.clearColor(0.0, 0.0, 0.0, 0.0);
----------------------
省略.....
----------------------
};
loop();
}
修改完背景后,我们修改模型在页面中的布局。这里我是参考的别人的布局方式:是帐篷啊
完成以上步骤后我们可以运行一下看看效果
2.了解自己准备使用的模型
为了使项目兼容更多的模型,我们需要先了解不同模型直接的差异
- 首先是版本问题,该项目只支持模型入口文件为 *.moc3.json的模型
- 其次是模型的控制参数,这个是我们最需要关注的,因为这关系到你能不能控制人物的交互行为
控制参数我们可以使用官方的Cubism Viewer打开文件,之后点击moc文件,来查看。这里我们主要关注和控制身体、面部、眼球相关的参数(官方标准值为:ParamAngleX、Y、Z,ParamBodyAngleX和ParamEyeBallX、Y)
有了以上的了解,我们可以进行下一步,接下来我们要做的就是将当前模型的非标准值与标准值进行映射
首先,我们打开进入到你准备的模型的根目录(moc3.json文件同级),在这里新建mapper.json文件,内容如下:
{
"parameter": [
// 标准值:模型中所对应的值,如果你的模型用的就是标准值,也需要这样操作
{"ParamAngleX": "PARAM_ANGLE_X"},
{"ParamAngleY": "PARAM_ANGLE_Y"},
{"ParamAngleZ": "PARAM_ANGLE_Z"},
{"ParamEyeBallX": "PARAM_EYE_BALL_X"},
{"ParamBodyAngleX": "PARAM_BODY_ANGLE_X"},
{"ParamEyeBallY": "PARAM_EYE_BALL_Y"}
],
//这个暂时不用,后续更新的话有可能会使用到
"url": [],
//如果你想调整模型的位置,修改这个值,分别对应X轴和Y轴
"center":[2,1]
}
该文件是根据我自己在源码中修改的加载逻辑而额外加的,方便来回切换模型时使用,你根据你自己的情况来决定要不要进行这一步。
第一步和第二步准备工作做好后,我们开始修改源码中与模型有关的内容
3. 修改资源路径等文件
- 新建MocMapper.ts文件
/**
* 将Moc文件中的id值和资源文件名称及对应的url映射到程序中
*/
import {resourcesConfig } from './lappdefine'
export class MocMapper {
private parameterIdMAp: Map<string, string> = new Map();
private resourcesPathToUrlMap = new Map();
private jsonResources = null;
public static mapper: MocMapper = null;
/**
* 根据url地址获取资源路径和url的映射对象
* @param url 保存映射关系的json的url地址
*/
public static getInstance() {
if (this.mapper == null) {
this.mapper = new MocMapper();
}
return this.mapper;
}
/**
* 将当前模型的id值保存到键值对中
* @param defaultParameter 官方默认的id值
* @param currentModParameter 当前模型中与官方默认值效果相同的id值
* @note 一般只需要设置 ParamEyeBall[X,Y] ParamAngle[X,Y,Z]
*/
public setParameter(defaultParameter: string, currentModParameter: string) {
this.parameterIdMAp.set(defaultParameter,currentModParameter)
}
/**
*
* @param defaultParameter 根据默认id值获取当前模型对应的id值
* @returns
*/
public getParemeter(defaultParameter: string): string {
return this.parameterIdMAp.get(defaultParameter)
}
/**
*
* @param resourcesPath moc3.json中的资源路径
* @param url 服务器中该路径对应文件的访问地址
*/
public setPathToUrl(resourcesPath:string, url:string) {
this.resourcesPathToUrlMap.set(resourcesPath, url);
}
/**
* 根据资源路径获取url
* @param resourcesPath 资源路径
* @returns
*/
public getUrl(resourcesPath: string): string {
return this.resourcesPathToUrlMap.get(resourcesPath)
}
public getJsonConfig() {
return this.jsonResources;
}
/**
* 从指定url读取模型目录中的mapper.json【自定义的变量映射关系文件】文件
* @param url mapper文件的url
*/
public async setMapperJson(url: string) {
this.jsonResources = await fetch(url).then(async function (response) {
const json = await response.json();
return json;
})
let arrayOfparameters = this.getJsonConfig().parameter;
let arrayOfUrl = this.getJsonConfig().url;
//将id值存入map中
for (let i = 0; i < arrayOfparameters.length; i++) {
let item = arrayOfparameters[i]
for (let key in item) {
this.setParameter(key,item[key])
}
}
//将资源路径和url映射关系存入map中
for (let i = 0; i < arrayOfUrl.length;i++) {
let item = arrayOfUrl[i]
for (let key in item) {
this.setParameter(key,item[key])
}
}
//设置模型的中心位置
let centerPointScal = this.getJsonConfig().center;
resourcesConfig.setXscal(centerPointScal[0]);
resourcesConfig.setYscal(centerPointScal[1]);
}
}
- 打开【Samples\TypeScript\Demo\src\lappdefine.ts】文件
修改源文件内容如下:
import { LogLevel } from '@framework/live2dcubismframework';
// Canvas width and height pixel values, or dynamic screen size ('auto').
export const CanvasSize: { width: number; height: number } | 'auto' = 'auto';
// 画面,默认放大倍率
export const ViewScale = 2;
export const ViewMaxScale = 4.0;
export const ViewMinScale = 0.4;
export const ViewLogicalLeft = -1.0;
export const ViewLogicalRight = 1.0;
export const ViewLogicalBottom = -1.0;
export const ViewLogicalTop = 1.0;
export const ViewLogicalMaxLeft = -2.0;
export const ViewLogicalMaxRight = 2.0;
export const ViewLogicalMaxBottom = -2.0;
export const ViewLogicalMaxTop = 2.0;
//新建ResouConfig对象
class ResourceConfig {
public resourcesPath: string;
public modelNames: string[];
public modelSize: number;
public canvasId: string = 'live2d';
public x_scal: number = 2;
public y_scal:number = 1
constructor() {
this.resourcesPath = '../../Resources/';
this.modelNames = ['Haru', 'Hiyori', 'Mark', 'Natori', 'Rice'];
this.modelSize = this.modelNames.length;
}
public setResourcesPath(path:string) {
this.resourcesPath = path;
}
public setCanvasId(canvasId:string) {
this.canvasId = canvasId;
}
public setModelNames(models:string[]) {
this.modelNames = models;
this.setModelSize();
}
public setModelSize() {
this.modelSize = this.modelNames.length;
}
public getResourcesPath() {
return this.resourcesPath;
}
public getModelNames() {
return this.modelNames;
}
public getModelSize() { return this.modelSize;}
public getCanvasId() {
return this.canvasId
}
public setXscal(scal:number) { this.x_scal = scal }
public setYscal(scal:number) { this.y_scal = scal }
public getXscal() { return this.x_scal }
public getYscal() { return this.y_scal}
}
//将该对象导出,方便在其他文件中使用
export const resourcesConfig = new ResourceConfig();
// 相対パス
//export const ResourcesPath = '../../Resources/';
// モデルの後ろにある背景の画像ファイル
export const BackImageName = '';
// 歯車
export const GearImageName = '';
// 終了ボタン
export const PowerImageName = '';
// モデル定義---------------------------------------------
// モデルを配置したディレクトリ名の配列
// ディレクトリ名とmodel3.jsonの名前を一致させておくこと
//export const ModelDir: string[] = ['Haru', 'Hiyori', 'Mark', 'Natori', 'Rice'];
//export const ModelDirSize: number = ModelDir.length;
//下方省略.................
- 打开**【Samples\TypeScript\Demo\src\lappdelegate.ts】,导入lappdefine.ts中的resourceConfig对象,定位到initialize()**修改内容如下:
import * as LAppDefine from './lappdefine';
/**
省略其他方法
*/
public initialize(): boolean {
// 修改成我们自己配置的canvas
canvas = <HTMLCanvasElement>document.getElementById(LAppDefine.resourcesConfig.getCanvasId());
// glコンテキストを初期化
// @ts-ignore
gl = canvas.getContext('webgl') || canvas.getContext('experimental-webgl');
if (!gl) {
alert('Cannot initialize WebGL. This browser does not support.');
gl = null;
document.body.innerHTML =
'This browser does not support the <code><canvas></code> element.';
// gl初期化失敗
return false;
}
// キャンバスを DOM に追加
// document.body.appendChild(canvas);
if (!frameBuffer) {
frameBuffer = gl.getParameter(gl.FRAMEBUFFER_BINDING);
}
// 透過設定
gl.enable(gl.BLEND);
gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA);
// 注释掉原有的点击和触摸事件
/*
const supportTouch: boolean = 'ontouchend' in document;
if (supportTouch) {
// タッチ関連コールバック関数登録
document.ontouchstart = onTouchBegan;
document.ontouchmove = onTouchMoved;
document.ontouchend = onTouchEnded;
//document.ontouchend = onTouchEnded;
//canvas.ontouchcancel = onTouchCancel;
} else {
*/
// マウス関連コールバック関数登録
//只能在canvas的事件上调用on*Ended()方法,不然会影响人物的点击效果
//onmousemove方法只能在onmousedown和onmouseup之间调用,不然没有效果
//document.onmousemove = onMouseMoved;
canvas.onmouseup = onClickEnded;
document.addEventListener("mousemove", function (e) {
if (!LAppDelegate.getInstance()._view) {
LAppPal.printMessage("view notfound");
return;
}
//之前我是使用documents对象来获取canvas,其实已经有全局变量了,这里也可以直接用
let rect = canvas.getBoundingClientRect();
let posX: number = e.clientX - rect.left;
let posY: number = e.clientY - rect.top;
LAppDelegate.getInstance()._view.onTouchesMoved(posX, posY);
}, false);
//在这里加上鼠标离开浏览器后,一切归位
document.addEventListener("mouseout", function (e) {
//鼠标离开document后,将其位置置为(0,0)
let live2DManager: LAppLive2DManager = LAppLive2DManager.getInstance();
live2DManager.onDrag(0.0, 0.0);
}, false);
// AppViewの初期化
this._view.initialize();
// Cubism SDKの初期化
this.initializeCubism();
return true;
}
- 打开【Samples\TypeScript\Demo\src\lapplive2dmanager.ts】文件,添加或修改以下方法
//开头导入相关文件
import { MocMapper}from './MocMapper'
import * as LAppDefine from './lappdefine';
-----------------------------下边是对象内容----------------------------------------------
public nextScene(): void {
const no: number = (this._sceneIndex + 1) % LAppDefine.resourcesConfig.getModelSize();
this.changeScene(no);
}
/**
* 随机加载模型
*/
public randomScene(): void {
//设置随机的模型选项
let randomNum= Math.random()*(LAppDefine.resourcesConfig.getModelSize()+1)//取值范围[0,size+1)包含小数
//转为整数后再次取余防止出现数组越界问题
let indexOfModel = Math.floor(randomNum)%LAppDefine.resourcesConfig.getModelSize()
this.changeScene(indexOfModel);
}
public async changeScene(index: number): Promise<void> {
this._sceneIndex = index;
if (LAppDefine.DebugLogEnable) {
LAppPal.printMessage(`[APP]model index: ${this._sceneIndex}`);
}
// ModelDir[]に保持したディレクトリ名から
// model3.jsonのパスを決定する。
// ディレクトリ名とmodel3.jsonの名前を一致させておくこと。
const model: string = LAppDefine.resourcesConfig.getModelNames()[index];
const modelPath: string = LAppDefine.resourcesConfig.getResourcesPath() + model + '/';
let modelJsonName: string = LAppDefine.resourcesConfig.getModelNames()[index];
modelJsonName += '.model3.json';
let mapperJsonOfModel = modelPath + 'mapper.json';
let mapper = MocMapper.getInstance();
await mapper.setMapperJson(mapperJsonOfModel)
this.releaseAllModel();
this._models.pushBack(new LAppModel());
this._models.at(0).loadAssets(modelPath, modelJsonName);
}
- 打开【Sample\TypeScript\Demo\src\lappview.ts】文件,initializeSprite()函数
//修改
const resourcesPath = LAppDefine.resourcesConfig.getResourcesPath();
- 打开【Framework\src\math\cubismmodelmatrix.ts】,导入resourcesConfig,然后找到构造方法
//在开头加上
import { resourcesConfig } from '../../../Samples/TypeScript/Demo/src/lappdefine';
---------------------------------下面是构造方法内容----------------------------------------------
constructor(w?: number, h?: number) {
super();
this._width = w !== undefined ? w : 0.0;
this._height = h !== undefined ? h : 0.0;
//这个是官方随便给的一个数,调整后会影响模型的放大倍率
this.setHeight(2.0);
//设置模型中心位置
this.setCenterPosition(w*resourcesConfig.getXscal()/2, h*resourcesConfig.getYscal()/2);
}
- 修改【Sample\TypeScript\Demo\src\main.ts】
import { LAppDelegate } from './lappdelegate';
import { resourcesConfig} from './lappdefine';
import { LAppLive2DManager} from './lapplive2dmanager'
function start() {
// create the application instance
if (LAppDelegate.getInstance().initialize() == false) {
return;
}
LAppDelegate.getInstance().run();
}
function stop() {
LAppDelegate.releaseInstance();
}
/**
* 根据index来控制切换模型,只能填整数
* @param index 模型的次序
* 从1开始。 小于0:随机加载
* >0加载数组下标为index-1的模型
* ==0 加载下一个模型
*/
function changeScene(index: number) {
let manager = LAppLive2DManager.getInstance();
if (index < 0) {
manager.randomScene();
} else if (index > 0 ) {
manager.changeScene(index-1)
} else if (index ==0) {
manager.nextScene();
}
}
module.exports = { start , stop , changeScene , resourcesConfig}
- 打开【Samples\TypeScript\Demo\src\lappmodel.ts】,导入MocMapper,定位到构造方法
public constructor() {
super();
this._modelSetting = null;
this._modelHomeDir = null;
this._userTimeSeconds = 0.0;
this._eyeBlinkIds = new csmVector<CubismIdHandle>();
this._lipSyncIds = new csmVector<CubismIdHandle>();
this._motions = new csmMap<string, ACubismMotion>();
this._expressions = new csmMap<string, ACubismMotion>();
this._hitArea = new csmVector<csmRect>();
this._userArea = new csmVector<csmRect>();
let mapper = MocMapper.mapper;
if (mapper != null) {
this._idParamAngleX = CubismFramework.getIdManager().getId(
mapper.getParemeter("ParamAngleX")
);
this._idParamAngleY = CubismFramework.getIdManager().getId(
mapper.getParemeter("ParamAngleY")
);
this._idParamAngleZ = CubismFramework.getIdManager().getId(
mapper.getParemeter("ParamAngleZ")
);
this._idParamEyeBallX = CubismFramework.getIdManager().getId(
mapper.getParemeter("ParamEyeBallX")
);
this._idParamEyeBallY = CubismFramework.getIdManager().getId(
mapper.getParemeter("ParamEyeBallY")
);
this._idParamBodyAngleX = CubismFramework.getIdManager().getId(
mapper.getParemeter("ParamBodyAngleX")
);
} else {
this._idParamAngleX = CubismFramework.getIdManager().getId(
CubismDefaultParameterId.ParamAngleX
);
this._idParamAngleY = CubismFramework.getIdManager().getId(
CubismDefaultParameterId.ParamAngleY
);
this._idParamAngleZ = CubismFramework.getIdManager().getId(
CubismDefaultParameterId.ParamAngleZ
);
this._idParamEyeBallX = CubismFramework.getIdManager().getId(
CubismDefaultParameterId.ParamEyeBallX
);
this._idParamEyeBallY = CubismFramework.getIdManager().getId(
CubismDefaultParameterId.ParamEyeBallY
);
this._idParamBodyAngleX = CubismFramework.getIdManager().getId(
CubismDefaultParameterId.ParamBodyAngleX
);
}
this._state = LoadStep.LoadAssets;
this._expressionCount = 0;
this._textureCount = 0;
this._motionCount = 0;
this._allMotionCount = 0;
this._wavFileHandler = new LAppWavFileHandler();
}
- 打开【Samples\TypeScript\Demo\webpack.config.js】,修改打包输出方式
module.exports = {
mode: 'production',
target: ['web', 'es5'],
entry: './src/main.ts',
output: {
path: path.resolve(__dirname, 'dist'),
filename: 'bundle.js',
publicPath: '/dist/',
library: 'live2dLoader',
libraryTarget:'umd'
},
resolve: {
extensions: ['.ts', '.js'],
//省略。。。。。。。。。。。
- 打开【index.html】,添加js代码
<script type="text/javascript">
var loader = live2dLoader
//这两个是必须设置的
loader.resourcesConfig.setResourcesPath("./live2d/models/")
loader.resourcesConfig.setModelNames(['a','b'])
loader.start();
console.log(loader)
</script>
以上修改完成后,打包运行即可。
结尾
修改完成后的项目我上传到GitHub:https://github.com/cqc233/live2dDemo
如果对于某个细节不是很明白可以参考我的旧版文章: