首先我们需要一个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()