如何在前端网页实现live2d的动态效果

React如何在前端网页实现live2d的动态效果

业务需求:

因为公司需要做机器人相关的业务,主要是聊天形式的内容,所以需要一个虚拟的卡通形象。而且为了更直观的展示用户和机器人对话的状态,该live2d动画的嘴型需要根据播放的内容来动。

相关技术原理:

1. 网页上使用live2d的相关技术原理

Live2D是一种先进的技术,它允许艺术家和开发者创建具有高度表现力的二维角色动画。这些角色可以根据用户的交互进行动态响应,从而提供一种非常生动和吸引人的用户体验。Live2D广泛应用于游戏、虚拟偶像、广告和移动应用程序中。

Live2D由日本Cybernoids公司开发,旨在创造出能够进行平滑动作和表情变化的二维角色,而不需要传统的帧动画。这是通过对二维图像进行切割和变形来实现的,使得角色能够以看似三维的方式移动和表达情感。

  1. WebGL和Canvas:Live2D在网页上的实现依赖于WebGL(一个JavaScript API,用于在任何兼容的网页浏览器中渲染高性能的交互式3D和2D图形,而不需要使用插件)和Canvas元素(提供了一个用于绘制图形的2D环境)。通过这些技术,开发者可以在用户的浏览器中直接渲染Live2D模型。
  2. Live2D Cubism SDK:为了在网页上使用Live2D,开发者需要利用Live2D Cubism SDK for Web。这个SDK提供了一系列的API,允许开发者加载、渲染和控制Live2D模型。它处理模型的动画、变形和用户交互。
  3. 模型和动画数据:Live2D模型通常存储在特定格式(如.moc或.moc3)的文件中,这些文件包含了角色的图形和骨架信息。动画数据(如表情和动作)通常以JSON或其他格式存储,可以通过SDK的API来播放。
  4. 交互性:通过JavaScript和SDK的API,开发者可以实现模型对鼠标移动、点击等用户交互的响应。例如,让角色的眼睛跟随鼠标移动,或者在点击时播放特定的动画。
  5. 性能优化:为了确保流畅的用户体验,特别是在性能有限的设备上,Live2D在网页上的实现需要进行性能优化。这包括合理控制动画的帧率、减少不必要的资源加载和使用WebGL的高效特性。

2. 文字转语音 TTS

本项目使用的是讯飞的TTS在线文字转语音功能。使用Websocket实现客户端和讯飞服务器之间的通信。总体逻辑就是客户端把需要播报的文字发送给讯飞的服务端,服务端经过处理后,把文字转成了二进制的音频流返回给客户端,然后客户端使用适当的方法播放解析后的音频数据,完成从文字到语音的整个过程。

不过这项功能不是本次的内容的重点,另外详解。

3. 语音转文字 IAT

不是本次的内容的重点,另外详解。

项目实现

1. 引入live2d相关库

Cubism Web是一个基于Web技术的软件开发工具包,用于创建Live2D。Cubism Web提供了一组工具和库,使开发人员可以轻松地将Live2D模型集成到Web应用程序中。它支持多种平台和框架,包括HTML5、JavaScript和CSS,因此可以在各种设备和浏览器上运行。

官网如下:

https://www.live2d.com/zh-CHS/sdk/download/web/

下载Cubism SDK文件:

解压后的目录如下,其中Core核心库和Framework框架库是我们所需要的。

  1. Core

将上述展示的Core文件夹复制到项目的public文件下。

同时,在public文件夹下面的index.html中加上如下代码:

html 复制代码
<script src = "%PUBLIC_URL%/Core/live2dcubismcore.js"></script>
  1. framework

同理,将上述展示的Framework移动到项目的src文件夹下。注意不是pubilc文件夹下了。直接放到src文件夹即可,或者其他目录也行,引用的时候知道地址就行。

  1. Resources

Resources文件夹下放的是live2d的模型,具体模型的内容可以去官网下载简单的Model,也可以根据需要找设计师去设计你需要的Modal。笔者这边只是展示了项目里面一个Modal里面的内容。

官方提供的Modal地址:

https://www.live2d.com/zh-CHS/sdk/download/web/

  1. Render

以上文件准备好后,可以做页面渲染相关的工作了。根据官方给的Demo,如下图所示:

根据Demo中main.ts中的样例可以推断出,这段代码用于实现Live2D模型的初始化、渲染和响应浏览器事件。

  1. 导入必要的模块:首先,代码通过import语句导入了LAppDelegate、LAppDefine和LAppGlManager等模块,这些模块包含了Live2D模型渲染所需的各种功能和配置。
  2. 浏览器加载完成后的处理:通过监听load事件,当浏览器完成加载后,执行初始化WebGL和创建应用实例的操作。这里涉及到两个关键的步骤:
  • 检查LAppGlManager的实例是否存在,以及调用LAppDelegate的initialize方法进行初始化。这些操作确保了WebGL环境的正确设置和应用的初始化。
  • 调用LAppDelegate.getInstance().run()启动Live2D模型的渲染循环。
  1. 浏览器关闭前的处理:监听beforeunload事件,当浏览器准备关闭前,释放LAppDelegate的实例,以确保资源被正确清理。
  2. 浏览器窗口尺寸变化的处理:监听resize事件,当浏览器窗口尺寸发生变化时,根据LAppDefine.CanvasSize的配置决定是否调用LAppDelegate.getInstance().onResize()方法来调整Live2D模型的尺寸。这保证了模型在不同尺寸的屏幕上都能正确显示。
typescript 复制代码
/**
 * Copyright(c) Live2D Inc. All rights reserved.
 *
 * Use of this source code is governed by the Live2D Open Software license
 * that can be found at https://www.live2d.com/eula/live2d-open-software-license-agreement_en.html.
 */

import { LAppDelegate } from './lappdelegate';
import * as LAppDefine from './lappdefine';
import { LAppGlManager } from './lappglmanager';

/**
 * ブラウザロード後の処理
 */
window.addEventListener(
  'load',
  (): void => {
    // Initialize WebGL and create the application instance
    if (
      !LAppGlManager.getInstance() ||
      !LAppDelegate.getInstance().initialize()
    ) {
      return;
    }

    LAppDelegate.getInstance().run();
  },
  { passive: true }
);

/**
 * 終了時の処理
 */
window.addEventListener(
  'beforeunload',
  (): void => LAppDelegate.releaseInstance(),
  { passive: true }
);

/**
 * Process when changing screen size.
 */
window.addEventListener(
  'resize',
  () => {
    if (LAppDefine.CanvasSize === 'auto') {
      LAppDelegate.getInstance().onResize();
    }
  },
  { passive: true }
);

而我们需要做的,则是把Demo中除了main.ts的其他文件Copy到项目中在自有的项目中,Render的相关目录如图所示:

2. 用React转写main.ts

然后main.ts实现的逻辑,用React的方式进行重构,在index.js中。本文只展示核定代码,因为实际情况会根据需求,增加很多其他的业务实现代码,比如笔者最初的需求是增加live2d的收听、播放等动画效果,所以需要额外传入sourceBuffer等数据。

  1. 导入依赖:首先,代码导入了React相关的hooks(如useEffect, useRef, useMemo等),以及Live2D相关的配置和管理模块(LAppDelegate, LAppLive2DManager, LAppDefine等)。
  2. 组件结构:ReactLive2d组件通过return语句返回一个包含元素的结构,这个元素被指定了id="live2d",以及动态计算的width和height属性。这个就是用来渲染Live2D动画的画布。
  3. 初始化和配置
  • 在组件挂载时(useEffect),根据传入的props(如live2dModelId)动态设置Live2D模型的目录。
  • 如果满足条件(非移动设备或允许在移动设备上显示),则初始化Live2D应用(LAppDelegate.getInstance().initialize())并运行(LAppDelegate.getInstance().run())。
  1. 音频处理:组件还处理了音频输入(通过audioContext和source),并将音频数据通过rmsRef(一个自定义的音频处理引用)连接到Live2D动画,可能用于根据音频数据动态调整Live2D动画的表情或动作。
  2. 清理资源:在组件卸载时,清理资源,如销毁rmsRef引用和释放Live2D应用实例。
jsx 复制代码
import React, { useEffect, useRef, useMemo, useCallback } from 'react';
import AudioRMS from '@/utils/AudioProcess';
import { Button } from 'antd-mobile';

import { LAppDelegate } from './live2dConfig/lappdelegate';
import { LAppLive2DManager } from './live2dConfig/lapplive2dmanager';
import * as LAppDefine from './live2dConfig/lappdefine';
import './index.css';


function ReactLive2d(props, ref) {
  const { width, height, audioContext, source, live2dModelId, isTTSPlaying } = props;
  const rmsRef = useRef(null);

  // setModelDir()用于根据外部传的Model Id动态的引入模型,如果没有这个需求可以就写死Model的逻辑即可
  useEffect(() => {
    // 根据外部传入的modelId,动态设置model
    LAppDefine.lappdefineSet.setModelDir([live2dModelId]);
    if (!navigator.userAgent.match(/mobile/i)) {
      if (LAppDelegate.getInstance().initialize()) {
        LAppDelegate.getInstance().run();
      }
    }
    return () => {
      rmsRef.current && rmsRef.current.destroy && rmsRef.current.destroy();
      LAppDelegate.releaseInstance();
    }
  }, []);

  const configRmsWithAudio = () => {
    if (source) {
      source.connect(rmsRef.current.input)
    }
  }
  useEffect(() => {
    configRmsWithAudio();
  }, [audioContext, source])

  const onData = useCallback((data) => {
    LAppLive2DManager.getInstance().setRmsToValue(data[0])
  }, []);

  useEffect(() => {
    if (audioContext) {
      if (!rmsRef.current) {
        rmsRef.current = AudioRMS(audioContext, 'sqr')
      }
      if (isTTSPlaying) {
        rmsRef.current.on('data', onData)
      } else {
        rmsRef.current.off('data', onData)
      }
    }
  }, [audioContext, isTTSPlaying, onData])

  const canvasWidth = useMemo(() => {
    if (!window.matchMedia("(orientation: landscape)").matches) {
      return document.body.clientWidth;
    }
    return document.body.clientWidth * 0.3;
  }, [])

  const canvasHeight = useMemo(() => {
    if (!window.matchMedia("(orientation: landscape)").matches) {
      return document.body.clientHeight * 0.3;
    }
    return document.body.clientHeight * 0.6;
  }, [])


  return (
    <div className='live2d-container' >
      <div className='live2d-canvas'>
        <canvas
          id="live2d"
          width={width ? width : canvasWidth}
          height={height ? height : canvasHeight}
          className="live2d"
          color="#f5f5f9"
        />
      </div>
    </div>
  )
}

export default ReactLive2d;

在上述代码通过React组件设置了一个元素,并通过Live2D的JavaScript API初始化和控制Live2D模型的加载、显示和动作,实现了在Web页面上展示Live2D动画形象的功能。

但是其中有些代码是根据实际业务增加的,不属于原本demo的逻辑,这边做一些简述,防止读者混淆。

jsx 复制代码
// 
const onData = useCallback((data) => {
    LAppLive2DManager.getInstance().setRmsToValue(data[0])
  }, []);
// AudioRMS()用于处理音频的技术,实现音频信号的实时分析和处理
// 这段代码的目的是在确认音频源 source 存在的情况下,将它连接到通过 rmsRef 引用的目标的 input 上。
// 这样的操作在音频处理、音频可视化等场景中非常常见,比如连接一个音频源到一个用于计算实时音频信号强度的节点上。
  useEffect(() => {
    if (audioContext) {
      if (!rmsRef.current) {
        rmsRef.current = AudioRMS(audioContext, 'sqr')
      }
      if (isTTSPlaying) {
        rmsRef.current.on('data', onData)
      } else {
        rmsRef.current.off('data', onData)
      }
    }
  }, [audioContext, isTTSPlaying, onData])

// 。它调用source的connect方法,将音频源连接到rmsRef.current.input。
// 这里,rmsRef是一个引用(通过useRef创建),指向RMS处理器的实例,而rmsRef.current.input是RMS处理器的输入端。
  const configRmsWithAudio = () => {
    if (source) {
      source.connect(rmsRef.current.input)
    }
  }
// 这段代码的目的是确保每当音频上下文(audioContext)或音频源(source)发生变化时,都会更新音频源与RMS处理器之间的连接。
  useEffect(() => {
    configRmsWithAudio();
  }, [audioContext, source])

3. 引用

上述工作做完后,引用则变得很简单了。

jsx 复制代码
import ReactLive2d from '...'

...

// 以下参数根据实际情况自行填写即可
  <ReactLive2d
    source={bufferSource}
    live2dModelId={'LH1'}
    isTTSPlaying={iatStatus === 'ttsPlaying'}
    audioContext={ttsRecorder.audioContext}
  />
  }, [audioContext, source])

3. 引用

上述工作做完后,引用则变得很简单了。

jsx 复制代码
import ReactLive2d from '...'

...

// 以下参数根据实际情况自行填写即可
  <ReactLive2d
    source={bufferSource}
    live2dModelId={'LH1'}
    isTTSPlaying={iatStatus === 'ttsPlaying'}
    audioContext={ttsRecorder.audioContext}
  />

然后运行引用<ReactLive2d/>的界面上,便可以看到live2d的效果。

相关推荐
Apifox7 分钟前
如何在 Apifox 中通过 Runner 运行包含云端数据库连接配置的测试场景
前端·后端·ci/cd
树上有只程序猿35 分钟前
后端思维之高并发处理方案
前端
庸俗今天不摸鱼1 小时前
【万字总结】前端全方位性能优化指南(十)——自适应优化系统、遗传算法调参、Service Worker智能降级方案
前端·性能优化·webassembly
黄毛火烧雪下1 小时前
React Context API 用于在组件树中共享全局状态
前端·javascript·react.js
Apifox1 小时前
如何在 Apifox 中通过 CLI 运行包含云端数据库连接配置的测试场景
前端·后端·程序员
一张假钞2 小时前
Firefox默认在新标签页打开收藏栏链接
前端·firefox
高达可以过山车不行2 小时前
Firefox账号同步书签不一致(火狐浏览器书签同步不一致)
前端·firefox
m0_593758102 小时前
firefox 136.0.4版本离线安装MarkDown插件
前端·firefox
掘金一周2 小时前
金石焕新程 >> 瓜分万元现金大奖征文活动即将回归 | 掘金一周 4.3
前端·人工智能·后端
三翼鸟数字化技术团队2 小时前
Vue自定义指令最佳实践教程
前端·vue.js