live2d web笔记之一:官方SDK尝试

最近没太多事情,想要给群里建一个网站。想来不知道放些什么内容进去,建好站之后姑且先放一个live2d的看板娘。因此产生了这个live2d网页开发的尝试。

第一次尝试,从live2d的官方原生的SDK入手。在我之前已经有很多大佬做成了,这里先放我的成果,再放本人从0开始实现的过程。

一、成品

~浼钤茶会~群内共享站~ (meiqianhime.com)http://www.meiqianhime.com/

前端新手,学了没多久,所以做的网页很烂,轻喷orz

二、过程

开发环境:

Live2D Cubism 4.1 #来自官方,live2d模型的编辑器和查看器。

vscode #官方指定的代码编辑器

node.js TypeScript Webpack #必须环境

python+Flask 本人使用的网页应用框架,可以换其他

1.下载SDK,准备模型

登录官网,下载webSDK。官网需要翻墙,不然会比较慢。

Live2D Cubismhttps://www.live2d.com/

选择【各种下载】-【live2D SDK下载与功能介绍】,

在弹出的页面中找【Live2D Cubism SDK for Web】并下载。

接下来是准备模型。官方提供了几种现成的模型可供下载。本人葱厨,所以下载了初音未来的模型。之后会尝试用自己的模型来做,可能会放在以后的笔记中。

2.运行SDK示例项目

安装vscode、nodejs和TypeScript,并做好相关环境配置。

将下载好的SDK压缩包解压并用vscode打开,在README.md文件中可以看到项目结构的说明。

在终端中cd到Samples/TypeScript/Demo,这是官方的示例项目。

运行指令npm install,根据文件package.json中信息安装项目所需插件。

运行指令npm run build进行项目打包,

完成后会在Demo文件夹下生成dist文件夹,

里面有打包好的bundle.js文件,用于之后的移植开发。

运行指令npm run serve运行服务,打开终端给出的本地链接进入示例页面。 

终端中按ctrl-c退出服务。

3.加入自己准备的模型

3.1 模型方面的准备

打开资源文件夹Samples/Recourses,放入准备好的模型。

修改文件夹的名字与里面的cmo3文件相同,双击打开。

 这里想要做一个点击交互,点一下miku就会有动作的那种。

点击【建模】--【图形网格】--【创建触碰检测用途的图形网格】,

把红框框套在想要触发点击交互的地方,

然后把左边检查器中的名称改成Body。(不改也行)

然后点上面的【编辑纹理集 】

弹出的窗口中找到【用于触碰检测的图像】,右键添加到纹理集。

 

完成后ctrl-s保存,然后选择【文件】--【导出运作档】--【导出为moc3文件】,将模型导出。

接下来打开.can3文件进行动画处理。

miku的模型有附带做好的动画,如果是自定义模型,可以看相关教程做一两个简单的动画。

打开后选择【文件】--【导出运作档】--【导出动态文件】,导出所有的动画文件到指定文件夹。

这里创建了新文件夹anime用于存储动画文件。

接下来打开官方的模型查看器CubismViewer4,将模型的moc3文件拖入。

点开左边的【hitAreas】,将定义好的点击框命名为Body(首字母大写) 

然后把刚刚的动画文件拖入,选择想要触发的动画,将组名改为TapBody。

然后ctrl-s保存文件,把生成的model3.json文件覆盖原有文件。

处理完之后,模型文件夹应该有以下结构。

3.2 向Demo中加入自己的模型

vscode打开Samples/TypeScript/Demo/src/lappdefine.ts,找到存放模型名的列表。

在列表最末端添加自己的模型的文件夹名。

如果不想显示其他模型,可以只留自己的模型名字。

// モデル定義---------------------------------------------
// モデルを配置したディレクトリ名の配列
// ディレクトリ名とmodel3.jsonの名前を一致させておくこと
//在这里修改,添加'miku'
export const ModelDir: string[] = ['Haru', 'Hiyori', 'Mark', 'Natori', 'Rice', 'miku'];
export const ModelDirSize: number = ModelDir.length;

// 外部定義ファイル(json)と合わせる
export const MotionGroupIdle = 'Idle'; // アイドリング
export const MotionGroupTapBody = 'TapBody'; // 体をタップしたとき

 保存之后,使用指令npm run build重新打包,

然后使用指令npm run serve 运行服务。

点击齿轮更换模型,就可以看到自己的模型显示在网页上了= =

到这里大概完成了三分之一,可以稍微喘口气了= =

4. 源码修改

我们最终想要一个id为live2d的canvas画布,当我们在html中创建它时就会显示live2d的内容。

基于此对demo中的源码进行修改。

这里主要参考这几位大佬的方法进行修改。第一篇的大佬有比较详细的讲代码,这里不多说。

(10条消息) 笔记:live2d4.0 sdk 博客园网页动画_weixin_44128558的博客-CSDN博客_live2d生成https://blog.csdn.net/weixin_44128558/article/details/104792345(10条消息) live2d(Web SDK 4.x)Web看板娘进阶_仰望星空的sun的博客-CSDN博客_live2d sdkhttps://blog.csdn.net/qq_37735413/article/details/119413744

4.1 lappdefine.ts

将变量ResourcesPath、BackImageName、ModelDir、ModelDirSize的变量声明从const改为let。

// 相対パス
// 相对路径 打完包移植的时候要修改它
export let ResourcesPath = '../../Resources/';

// モデルの後ろにある背景の画像ファイル
// 背景图片 不是很想要,就把它赋了空值。
export let BackImageName = '';

// 歯車
// 画面右上角的齿轮的图片
export let GearImageName = 'icon_gear.png';

// 終了ボタン
// 结束按钮 没见过这东西?
export let PowerImageName = 'CloseNormal.png';

// モデル定義---------------------------------------------
// モデルを配置したディレクトリ名の配列
// ディレクトリ名とmodel3.jsonの名前を一致させておくこと
// 模型定义
// 配置模型的目录名的列表
// 模型的目录名应该与model3.json的文件名一致
// 这里只想要一个模型,所以只留下了miku的模型。
export let ModelDir: string[] = ['miku'];
export let ModelDirSize: number = ModelDir.length;

 然后在文件末尾添加代码

export const win: any = window

win.initDefine=function(resourcesPath: string, backImageName: string, modelDir: string[]){
    ResourcesPath = resourcesPath;
    BackImageName = backImageName;
    ModelDir = modelDir;
    ModelDirSize = modelDir.length;
}

 4.2 main.ts

注释掉window.onresize,否则live2d模型会随着窗口改变大小。

/**
 * Process when changing screen size.
 */
// window.onresize = () => {
//   if (LAppDefine.CanvasSize === 'auto') {
//     LAppDelegate.getInstance().onResize();
//   }
// };

 4.3 lappdelegate.ts

首先找到getInstance.initialize()进行修改。

public initialize(): boolean {
    // キャンバスの作成
    // 生成画布
    // canvas = document.createElement('canvas');
    // if (LAppDefine.CanvasSize === 'auto') {
    //   this._resizeCanvas();
    // } else {
    //   canvas.width = LAppDefine.CanvasSize.width;
    //   canvas.height = LAppDefine.CanvasSize.height;
    // }
    canvas = <HTMLCanvasElement>document.getElementById("live2d"); // index.html中的id为live2d的画布
    canvas.width = canvas.width;
    canvas.height = canvas.height;
    canvas.toDataURL("image/png");


    // glコンテキストを初期化
    // 初始化gl上下文
    // @ts-ignore
    gl = canvas.getContext('webgl') || canvas.getContext('experimental-webgl');

    if (!gl) {
      alert('不能初始化 WebGL. 浏览器不支持.');
      gl = null;

      document.body.innerHTML =
        '浏览器不支持 <code>&lt;canvas&gt;</code> 元素.';

      // gl初期化失敗
      return false;
    }

    // キャンバスを DOM に追加
    // 向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 canvas;

    if (supportTouch) {
      // タッチ関連コールバック関数登録
      // 触摸模式下的回调函数
      canvas.ontouchstart = onTouchBegan;
      // canvas.ontouchmove = onTouchMoved;
      window.ontouchmove = onTouchMoved;
      canvas.ontouchend = onTouchEnded;
      canvas.ontouchcancel = onTouchCancel;
    } else {
      // マウス関連コールバック関数登録
      // 鼠标点击模式下的回调函数
      canvas.onmousedown = onClickBegan;
      // canvas.onmousemove = onMouseMoved;
      window.onmousemove = onMouseMoved;
      canvas.onmouseup = onClickEnded;
    }

    // AppViewの初期化
    // 初始化AppView
    this._view.initialize();

    // Cubism SDKの初期化
    // 初始化SDK
    this.initializeCubism();

    return true;
  }

 然后找到onMouseMoved函数进行修改。

/**
 * マウスポインタが動いたら呼ばれる。
 * 鼠标移动时触发
 */
function onMouseMoved(e: MouseEvent): void {
  // if (!LAppDelegate.getInstance()._captured) {  //判断鼠标是否单击
  //   return;
  // }

  if (!LAppDelegate.getInstance()._view) {
    LAppPal.printMessage('view notfound');
    return;
  }

  const rect = (e.target as Element).getBoundingClientRect();
  // const posX: number = e.clientX - rect.left;
  // const posY: number = e.clientY - rect.top;
  //这里是模型身体随着鼠标摆动的数值
  //使用canvas.getBoundingClientRect()读取画布实时位置,加入身体位置的计算,
  // 方便移动画布之后模型能依然根据和鼠标的相对位置摆动
  let posX: number = e.clientX - canvas.getBoundingClientRect().left;
  let posY: number = e.clientY - window.innerHeight + canvas.height + canvas.getBoundingClientRect().top;

  // 图像在网页的坐下角,简单处理坐标将超过画布边界坐标就等与边界坐标
  posX = (posX > canvas.width) ? canvas.width : posX;
  posY = (posY < 0) ? 0 : posY;

  LAppDelegate.getInstance()._view.onTouchesMoved(posX, posY);
}

 然后找到run()函数,将gl.clear注释掉。

      // カラーバッファや深度バッファをクリアする
      // 清除彩色缓冲区和深度缓冲区
      // gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);

4.4 lappview.ts

找到initializeSprite()函数进行修改。

  /**
   * 画像の初期化を行う。
   */
  public initializeSprite(): void {
    const width: number = canvas.width;
    const height: number = canvas.height;

    const textureManager = LAppDelegate.getInstance().getTextureManager();
    const resourcesPath = LAppDefine.ResourcesPath;

    let imageName = '';

    // 背景画像初期化
    imageName = LAppDefine.BackImageName;

    if(imageName != "" && imageName != null){ //如果指定了背景图片,就加载
    // 非同期なのでコールバック関数を作成
      const initBackGroundTexture = (textureInfo: TextureInfo): void => {
        const x: number = width * 0.5;
        const y: number = height * 0.5;

        const fwidth = textureInfo.width * 2.0;
        const fheight = height * 0.95;
        this._back = new LAppSprite(x, y, fwidth, fheight, textureInfo.id);
    };

    textureManager.createTextureFromPngFile(
      resourcesPath + imageName,
      false,
      initBackGroundTexture
    );
    
    }

    // 歯車画像初期化
    // imageName = LAppDefine.GearImageName;
    // const initGearTexture = (textureInfo: TextureInfo): void => {
    //   const x = width - textureInfo.width * 0.5;
    //   const y = height - textureInfo.height * 0.5;
    //   const fwidth = textureInfo.width;
    //   const fheight = textureInfo.height;
    //   this._gear = new LAppSprite(x, y, fwidth, fheight, textureInfo.id);
    // };

    // textureManager.createTextureFromPngFile(
    //   resourcesPath + imageName,
    //   false,
    //   initGearTexture
    // );

    // シェーダーを作成
    if (this._programId == null) {
      this._programId = LAppDelegate.getInstance().createShader();
    }
  }

 4.5 index.html

在body里插入一个id为live2d的canvas。

<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=1900">
  <title>TypeScript HTML App</title>
  <style>
    html, body {
        margin: 0;
        overflow: hidden;
    }
  </style>
  <!-- Pollyfill script -->
  <script src="https://unpkg.com/core-js-bundle@3.6.1/minified.js"></script>
  <!-- Live2DCubismCore script -->
  <script src = "../../../Core/live2dcubismcore.js"></script>
  <!-- Build script -->
  <script src = "./dist/bundle.js"></script>
</head>
<body>
  <canvas id="live2d" width="600" height="600" class="live2d"></canvas>
</body>
</html>

全部处理好之后,重新打包并运行。

npm run build

npm run serve

 5.打包完成后的移植

完成上述步骤后,开始尝试在普通的网页中加入live2d。

移植只移动模型文件夹和几个js文档,故对移植环境要求不高。

wamp或者flask什么的应该都可以。

这里使用flask的开发环境来移植live2d到原有项目。

需要的东西有:

1.模型文件夹

2.live2dcubismcore.js和live2dcubismcore.js.map,在core文件夹里。

3.bundle.js,在dist文件夹里。

网页名:浼钤茶会

static                    存放静态资源文件
  css                     存放css文件
    meiqiancss1.css
  src                     存放js文件
    meiqianjs1.js
  resource                存放模型和图片文件
templates                 存放网页文件
  index.html              登入界面
app.py                    用于启动flask网页应用

把模型文件夹扔进resource文件夹,其余三个文件扔进src文件夹。

打开bundle.js,ctrl-f搜索ResourcesPath,找到资源路径并修改为现在的resource。

exports.ResourcesPath = '../static/Resources/';
exports.BackImageName = '';
exports.GearImageName = 'icon_gear.png';
exports.PowerImageName = 'CloseNormal.png';
exports.ModelDir = ['miku'];

然后打开index.html,在head中加入三个js文件的引用。

    <!-- Pollyfill script -->
    <script src="https://unpkg.com/core-js-bundle@3.6.1/minified.js"></script>
    <!-- Live2DCubismCore script -->
    <script src = "../static/src/live2dcubismcore.js"></script>
    <!-- Build script -->
    <script src = "../static/src/bundle.js"></script>

运行app.py,完成。

以下是各种源码。

app.py

from flask import Flask, render_template, request, Response, send_from_directory

app = Flask(__name__)


@app.route('/', methods=['GET', 'POST'])
def main():
    return render_template('index.html')


if __name__ == '__main__':
    app.run()

index.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <title>~浼钤茶会~群内共享站~</title>
    <link rel="icon" href="../static/resources/favicon.ico">
    <link rel="stylesheet" href="../static/css/meiqiancss1.css">
</head>

<body>
    <div id="live2d-main">
        <canvas id="live2d" height=600 class="live2d"></canvas>
    </div>

    <div class="header" style="background-image: url('../static/resources/bg2.JPG')">
        <h1>浼钤茶会</h1>
        <p>尚在建设中的茶会大厅~</p>
    </div>

    <div class="navbar">
        <a href="#" onclick="building()">主页</a>
        <a href="#" onclick="building()">花园</a>
        <a href="index.html">大厅</a>
        <a href="#" onclick="building()">图书馆</a>
        <a href="#" onclick="building()">call吧</a>
        <a href="#" onclick="building()">浼钤姬的房间</a>
        <a href="#" onclick="building()">留言板</a>
        <a href="#" class="right" onclick="building()">个人房间</a>
    </div>

    <div class="row">
        <div class="side">
        <h1>建设中。。。</h1>
        </div>
        <div class="main">
        <h1>建设中。。。</h1>
        </div>
    </div>

    <div class="footer">
        <p>made by 某电饭锅司机</p>
    </div>

    <!-- Pollyfill script -->
    <script src="https://unpkg.com/core-js-bundle@3.6.1/minified.js"></script>
    <!-- Live2DCubismCore script -->
    <script src = "../static/src/live2dcubismcore.js"></script>
    <!-- Build script -->
    <script src = "../static/src/bundle.js"></script>
    <script src="../static/src/meiqianjs1.js"></script>
</body>
</html>

meiqiancss1.css

* {
    box-sizing: border-box;
}

/* body 样式 */
body {
    font-family: Arial;
    margin: 0;
}

/* 标题 */
.header {
    padding: 80px;
    text-align: center;
    background: #1abc9c;
    color: white;
}

/* 标题字体加大 */
.header h1 {
    font-size: 40px;
}

/* 导航 */
.navbar {
    overflow: hidden;
    background-color: #333;
}

/* 导航栏样式 */
.navbar a {
    float: left;
    display: block;
    color: white;
    text-align: center;
    padding: 14px 20px;
    text-decoration: none;
}

/* 右侧链接*/
.navbar a.right {
    float: right;
}

/* 鼠标移动到链接的颜色 */
.navbar a:hover {
    background-color: #ddd;
    color: black;
}

/* 列容器 */
.row {
    display: -ms-flexbox; /* IE10 */
    display: flex;
    -ms-flex-wrap: wrap; /* IE10 */
    flex-wrap: wrap;
}

.row div{
    min-height: 500px;
    max-height: 2000px;
}

/* 创建两个列 */
/* 边栏 */
.side {
    -ms-flex: 30%; /* IE10 */
    flex: 30%;
    background-color: #f1f1f1;
    padding: 20px;
}

/* 主要的内容区域 */
.main {
    -ms-flex: 70%; /* IE10 */
    flex: 70%;
    background-color: white;
    padding: 20px;
}

/* 测试图片 */
.fakeimg {
    background-color: #aaa;
    width: 100%;
    padding: 20px;
}

/* 底部 */
.footer {
    padding: 20px;
    text-align: center;
    background: #ddd;
}

.footer p{
    font-family: '黑体', serif;
}

#live2d-main {
    position : absolute;
    padding: 10px;
    bottom: 5px;
    left:5px;
}

#live2d {
    /* position : absolute; */

}

/* 响应式布局 - 在屏幕设备宽度尺寸小于 700px 时, 让两栏上下堆叠显示 */
@media screen and (max-width: 700px) {
    .row {
        flex-direction: column;
    }
}

/* 响应式布局 - 在屏幕设备宽度尺寸小于 400px 时, 让导航栏目上下堆叠显示 */
@media screen and (max-width: 400px) {
    .navbar a {
        float: none;
        width: 100%;
    }
}

meiqianjs1.js

function building() {
    alert("正在建设中。。");
}

//Make the DIV element draggagle:
dragElement(document.getElementById("live2d-main"));

function dragElement(elmnt) {
  var pos1 = 0, pos2 = 0, pos3 = 0, pos4 = 0;
  if (document.getElementById(elmnt.id )) {
    /* if present, the header is where you move the DIV from:*/
    document.getElementById(elmnt.id ).onmousedown = dragMouseDown;
  } else {
    /* otherwise, move the DIV from anywhere inside the DIV:*/
    elmnt.onmousedown = dragMouseDown;
  }

  function dragMouseDown(e) {
    e = e || window.event;
    // get the mouse cursor position at startup:
    pos3 = e.clientX;
    pos4 = e.clientY;
    document.onmouseup = closeDragElement;
    // call a function whenever the cursor moves:
    document.onmousemove = elementDrag;
  }

  function elementDrag(e) {
    e = e || window.event;
    // calculate the new cursor position:
    pos1 = pos3 - e.clientX;
    pos2 = pos4 - e.clientY;
    pos3 = e.clientX;
    pos4 = e.clientY;
    // set the element's new position:
    elmnt.style.top = (elmnt.offsetTop - pos2) + "px";
    elmnt.style.left = (elmnt.offsetLeft - pos1) + "px";
  }

  function closeDragElement() {
    /* stop moving when mouse button is released:*/
    document.onmouseup = null;
    document.onmousemove = null;
  }
}

项目包:https://pan.baidu.com/s/1IYMhwrQxuCObrPimwyn1KA 
提取码:6xqb 
--来自百度网盘超级会员V3的分享

 三、一些问题

1.首先在运行的时候,发现了这个报错

去提示的位置查看 发现是已经被注释掉的齿轮的相关代码。

将其注释掉,重新打包bundles.js装入,问题解决 

2.编写js代码实现了画布的移动,但是当画布离开原来的位置时,点击触发的交互会变得不敏感。

暂时没什么精力处理这个问题,以后再看。

3.使用官方的SDK进行开发,在点击交互的问题上好像只看到了一个Body点击框的情况,在示例中没有看到同时有多个点击框判定不同事件的模型。在实际尝试中,也没能做出想要的结果。之后如果还不行的话,就换个非官方的SDK试试吧。。

  • 7
    点赞
  • 32
    收藏
    觉得还不错? 一键收藏
  • 9
    评论
首先,我们需要在Canvas上创建一个可拖动的区域。我们可以使用HTML5的Drag事件来实现这个功能。 在HTML中添加一个canvas元素,并为其添加一个id属性: ```html <canvas id="live2d-canvas"></canvas> ``` 接下来,在JavaScript中,我们可以使用以下代码来初始化canvas并添加拖动事件: ```javascript // 获取canvas元素 var canvas = document.getElementById('live2d-canvas'); // 初始化Live2D模型 var live2d = new LAppLive2DModel(); // 添加拖动事件 canvas.addEventListener('mousedown', startDragging); canvas.addEventListener('mousemove', dragModel); canvas.addEventListener('mouseup', stopDragging); // 定义拖动状态 var isDragging = false; var dragStartX, dragStartY; // 开始拖动 function startDragging(event) { isDragging = true; dragStartX = event.clientX; dragStartY = event.clientY; } // 拖动模型 function dragModel(event) { if (isDragging) { var deltaX = event.clientX - dragStartX; var deltaY = event.clientY - dragStartY; live2d.addX(deltaX * 0.5); live2d.addY(deltaY * 0.5); dragStartX = event.clientX; dragStartY = event.clientY; } } // 停止拖动 function stopDragging(event) { isDragging = false; } ``` 以上代码中,我们定义了一个`startDragging`函数,用于在鼠标按下时设置拖动状态,并记录鼠标的起始位置。接着,我们使用`mousemove`事件来调用`dragModel`函数,在拖动期间更新模型的X和Y坐标。最后,我们定义了一个`stopDragging`函数,用于在鼠标释放时停止拖动。 注意:以上代码仅为示例代码,实际应用时需要根据Live2D模型的大小和Canvas的大小进行调整。
评论 9
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值