首先我们需要一个Live2D的库用来帮助我们在Python环境下完成显示,这样就不用通过网页的形式显示了

pip install live2d-py

接下来我们就可以导入库了

from live2d.v3 import live2d

准备工作

我们需要创建一个Live2DModel类用于管理模型

class Live2DModel:
    def __init__(self):
        self.model = None
        self.last_update_time = time.time()

        # 模型变换参数
        self.offset = (0.0, 0.0)
        self.scale = 1.0
        self.rotation = 0.0

        # 初始化Live2D系统
        live2d.init()

同时再构建一个自定义控件QOpenGLWidget

class Live2DWidget(QOpenGLWidget):
    def __init__(self, parent=None):
        super().__init__(parent)
        self.live2d_model = Live2DModel()

然后通过定时器设定刷新率

self.timer = QTimer(self)
self.timer.timeout.connect(self._update_model)
self.timer.start(16)

规定参数

self.transform_params = {
    'offset': (0.0, 0.0),
    'scale': 1.0,
    'rotation': 0.0
}

接下来准备工作就全部做完了,我们构建了两个类,一个是创建qt控件的,另一个是加载Live2D模型的

Live2DModel

模型加载

设置一个方法用于加载模型

def load_model(self, model_path):
    """加载Live2D模型"""
    self.model = live2d.Model()
    self.model.LoadModelJson(model_path)
    self.model.CreateRenderer(2)

其中LoadModelJson是加载模型的方法,而model_path需要填入模型的地址,例如hibiki.model3.json这样的文件,后缀是model3.json结尾的

模型更新

def update(self, width, height):
    """更新模型状态"""
    if not self.model:
        return

    current_time = time.time()
    delta = max(0.0001, current_time - self.last_update_time)
    self.last_update_time = current_time

    # 执行模型更新管线
    self.model.LoadParameters()
    motion_updated = self._update_motions(delta)
    self._update_common(delta, motion_updated)
    self.model.SaveParameters()

这个方法主要是驱动模型更新的,计算时间delta(最小0.0001避免除零)。LoadParameters()是用来加载加载模型参数。SaveParameters():保存参数变更

更新模型动作

def _update_motions(self, delta):
    """处理动作更新"""
    if not self.model.IsMotionFinished():
        return self.model.UpdateMotion(delta)
    return False
  • 若当前动作未完成!IsMotionFinished()),调UpdateMotion(delta)推进动作进度。

  • 返回布尔motion_updated表示是否正在播放动作。

更新模型通用状态

def _update_common(self, delta, motion_updated):
    """通用状态更新"""
    if not motion_updated:
        self.model.UpdateBlink(delta)
    self.model.UpdateExpression(delta)
    self.model.UpdateDrag(delta)
    self.model.UpdateBreath(delta)
    self.model.UpdatePhysics(delta)
    self.model.UpdatePose(delta)
  • 若未播放动作!motion_updated),更新眨眼动UpdateBlink

  • 依次更新表情、拖拽、呼吸、物理模拟、姿势。

  • 确保各状态按合理顺序更新(如物理在姿势之后)。

渲染模型

def draw(self, width, height):
    """执行绘制命令"""
    if self.model:
        self.model.Resize(width, height)
        self.model.Draw()
  • Resize(width, height)调整模型渲染尺寸(通常与视口一致)。

  • Draw()执行实际绘制命令,需在渲染循环中调用。

拖拽事件

def handle_drag(self, x, y):
    """处理拖拽操作"""
    self.model.Drag(x, y)
  • 将屏幕坐(x,y)传递给模型,触发内部拖拽逻辑(如头部跟随)。

  • 需注意坐标系转换(如屏幕坐标到模型局部坐标)。

设置模型的Transform

  • offset:模型在画布中的偏移量((0.5, 0.5)居中)。

  • scale:缩放比例(1.0为原始大小)。

  • rotation:旋转角度(弧度制)。

整体代码

# live2d_model.py - Live2D纯逻辑封装
import time
import live2d.v3 as live2d


class Live2DModel:
    def __init__(self):
        self.model = None
        self.last_update_time = time.time()

        # 模型变换参数
        self.offset = (0.0, 0.0)
        self.scale = 1.0
        self.rotation = 0.0

        # 初始化Live2D系统
        live2d.init()

    def load_model(self, model_path):
        """加载Live2D模型"""
        self.model = live2d.Model()
        self.model.LoadModelJson(model_path)
        self.model.CreateRenderer(1000)

    def update(self, width, height):
        """更新模型状态"""
        if not self.model:
            return

        current_time = time.time()
        delta = max(0.0001, current_time - self.last_update_time)
        self.last_update_time = current_time

        # 执行模型更新管线
        self.model.LoadParameters()
        motion_updated = self._update_motions(delta)
        self._update_common(delta, motion_updated)
        self.model.SaveParameters()

    def _update_motions(self, delta):
        """处理动作更新"""
        if not self.model.IsMotionFinished():
            return self.model.UpdateMotion(delta)
        return False

    def _update_common(self, delta, motion_updated):
        """通用状态更新"""
        if not motion_updated:
            self.model.UpdateBlink(delta)
        self.model.UpdateExpression(delta)
        self.model.UpdateDrag(delta)
        self.model.UpdateBreath(delta)
        self.model.UpdatePhysics(delta)
        self.model.UpdatePose(delta)

    def draw(self, width, height):
        """执行绘制命令"""
        if self.model:
            self.model.Resize(width, height)
            self.model.Draw()

    def handle_drag(self, x, y):
        """处理拖拽操作"""
        self.model.Drag(x, y)

    def start_random_motion(self):
        """触发随机动作"""
        self.model.StartRandomMotion(
            onStart=lambda g, n: print(f"Motion {g}-{n} started"),
            onFinish=lambda g, n: print(f"Motion {g}-{n} finished"))

    def set_transform(self, offset, scale, rotation):
        """设置模型变换参数"""
        self.offset = offset
        self.scale = scale
        self.rotation = rotation
        self.model.SetOffset(*offset)
        self.model.SetScale(scale)
        self.model.Rotate(rotation)

    @staticmethod
    def dispose():
        live2d.dispose()

Live2DWidget

OpenGL初始化

def initializeGL(self):
    """OpenGL初始化"""
    live2d.glInit()
    self.live2d_model.load_model("live2d_assets/hibiki/runtime/hibiki.model3.json")
  • live2d.glInit()初始化OpenGL上下文

  • 加载指定路径的Live2D模型文件(示例路径hibiki.model3.json

执行每帧渲染

def paintGL(self):
    """执行绘制"""
    live2d.clearBuffer()
    self.live2d_model.draw(self.width(), self.height())
  • 清空OpenGL颜色缓冲区

  • 调用模型draw()方法,传入当前部件尺寸

模型状态更新

def _update_model(self):
    """更新模型状态"""
    self.live2d_model.update(self.width(), self.height())
    self.update()

由定时器周期性触发,调用模型update()推进动画/物理等状态,请求界面重绘self.update()

整体代码


from PyQt5.QtCore import QTimer, Qt
from PyQt5.QtWidgets import QOpenGLWidget
from live2d.v3 import live2d

from Live2DUnits import Live2DModel


class Live2DWidget(QOpenGLWidget):
    def __init__(self, parent=None):
        super().__init__(parent)
        self.live2d_model = Live2DModel()

        # 设置定时器
        self.timer = QTimer(self)
        self.timer.timeout.connect(self._update_model)
        self.timer.start(16)

        # 初始化变换参数
        self.transform_params = {
            'offset': (0.0, 0.0),
            'scale': 1.0,
            'rotation': 0.0
        }

    def initializeGL(self):
        """OpenGL初始化"""
        live2d.glInit()
        self.live2d_model.load_model("live2d_assets/hibiki/runtime/hibiki.model3.json")

    def paintGL(self):
        """执行绘制"""
        live2d.clearBuffer()
        self.live2d_model.draw(self.width(), self.height())

    def resizeGL(self, width, height):
        """处理窗口尺寸变化"""
        self.live2d_model.model.Resize(width, height)

    def _update_model(self):
        """更新模型状态"""
        self.live2d_model.update(self.width(), self.height())
        self.update()

    def click_live2d_event(self):
        self.live2d_model.start_random_motion()

    def push_mouse_move_info(self,pos):
        self.live2d_model.handle_drag(pos.x(), pos.y())


    def wheelEvent(self, event):
        """滚轮事件"""
        pos = event.pos()
        y = self.height() - pos.y()


    def keyPressEvent(self, event):
        """键盘控制"""
        key = event.key()
        offset = list(self.transform_params['offset'])
        scale = self.transform_params['scale']
        rotation = self.transform_params['rotation']

        # 处理方向键
        if key == Qt.Key_Up:
            offset[1] += 0.1
        elif key == Qt.Key_Down:
            offset[1] -= 0.1
        elif key == Qt.Key_Left:
            offset[0] -= 0.1
        elif key == Qt.Key_Right:
            offset[0] += 0.1

        # 处理缩放
        elif key == Qt.Key_U:
            scale -= 0.1
        elif key == Qt.Key_I:
            scale += 0.1

        # 处理旋转
        elif key == Qt.Key_BracketRight:
            rotation -= 5
        elif key == Qt.Key_BracketLeft:
            rotation += 5

        self.transform_params.update({
            'offset': tuple(offset),
            'scale': scale,
            'rotation': rotation
        })
        self.live2d_model.set_transform(**self.transform_params)

    def __del__(self):
        """资源清理"""
        self.live2d_model.dispose()