@doki-land/live2d 0.0.2 → 0.0.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1 @@
1
+ .live2d-icon-panel{position:fixed;right:10px;bottom:150px;z-index:10000;display:flex;flex-direction:column;gap:10px;opacity:0;transition:opacity .3s ease}.live2d-icon-panel.visible{opacity:1}.live2d-icon{width:40px;height:40px;background-color:#fffc;border-radius:50%;display:flex;justify-content:center;align-items:center;cursor:pointer;box-shadow:0 2px 5px #0003;transition:transform .2s ease}.live2d-icon:hover{transform:scale(1.1)}.live2d-icon svg{width:24px;height:24px;fill:#666}
package/lib/index.d.ts CHANGED
@@ -1,7 +1,7 @@
1
- declare module "cubism2" {
2
- export = Live2D;
3
- }
4
-
5
- declare module "cubism5" {
6
- export = Live2DCubismCore;
7
- }
1
+ declare module "cubism2" {
2
+ export = Live2D;
3
+ }
4
+
5
+ declare module "cubism5" {
6
+ export = Live2DCubismCore;
7
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@doki-land/live2d",
3
- "version": "0.0.2",
3
+ "version": "0.0.4",
4
4
  "keywords": [
5
5
  "live2d",
6
6
  "pixi",
@@ -15,9 +15,13 @@
15
15
  "types": "src/index.ts",
16
16
  "exports": {
17
17
  ".": {
18
+ "types": "./src/index.ts",
18
19
  "require": "./dist/l2d.umd.js",
19
- "import": "./dist/l2d.esm.js",
20
- "types": "./src/index.ts"
20
+ "import": "./dist/l2d.esm.js"
21
+ },
22
+ "./fs": {
23
+ "types": "./src/fs/index.ts",
24
+ "import": "./src/fs/index.ts"
21
25
  }
22
26
  },
23
27
  "files": [
@@ -32,13 +36,14 @@
32
36
  "scripts": {
33
37
  "dev": "vite",
34
38
  "build": "vite build",
35
- "preview": "vite preview"
39
+ "preview": "vite preview",
40
+ "prepublishOnly": "pnpm build"
36
41
  },
37
42
  "dependencies": {
38
43
  "@pixi/core": "^7.4.3",
39
44
  "@pixi/display": "^7.4.3",
40
- "@pixi/interaction": "^6.5.10",
41
45
  "@pixi/ticker": "^7.4.3",
46
+ "minimatch": "^10.0.1",
42
47
  "pixi-live2d-display-lipsyncpatch": "0.5.0-ls-7",
43
48
  "pixi.js": "^7.4.3"
44
49
  }
@@ -0,0 +1,88 @@
1
+ import { minimatch } from 'minimatch';
2
+ import * as fs from 'node:fs';
3
+ import * as path from 'node:path';
4
+ import { ModelOptions } from '@/types';
5
+
6
+ /**
7
+ * 判断根据当前路径和指定的包含/排除规则,是否允许显示 Live2D 功能。
8
+ *
9
+ * @param currentPath 当前页面路径。
10
+ * @param includePaths 包含 Live2D 功能的路径列表。
11
+ * @param excludePaths 排除 Live2D 功能的路径列表。
12
+ * @returns 如果满足显示条件返回 true,否则返回 false。
13
+ *
14
+ * @description
15
+ *
16
+ * 排除的优先级高于包括
17
+ */
18
+ export function allowShowLive2D(currentPath: string, includePaths: string[], excludePaths: string[]): boolean {
19
+
20
+ // 如果当前路径匹配任何排除路径规则,不显示 Live2D
21
+ if (excludePaths.some(pattern => minimatch(currentPath, pattern))) {
22
+ return false;
23
+ }
24
+
25
+ // 如果包含路径列表不为空,则仅当当前路径匹配任意包含路径规则时才显示 Live2D
26
+ if (includePaths.length > 0) {
27
+ return includePaths.some(pattern => minimatch(currentPath, pattern));
28
+ }
29
+
30
+ // 默认情况下,允许显示 Live2D
31
+ return true;
32
+ }
33
+
34
+
35
+ /**
36
+ * 找到文件夹里的所有模型的相对路径
37
+ * @param folder
38
+ * @param depth
39
+ *
40
+ * @returns 模型路径列表
41
+ *
42
+ * @description
43
+ *
44
+ * 模型需要以 `*.model.json` 或 `*.model3.json` 结尾
45
+ */
46
+ export function findAllModels(folder: string, depth: number = 99): string[] {
47
+ const models: string[] = [];
48
+ for (const file of fs.readdirSync(folder)) {
49
+ const filePath = path.join(folder, file);
50
+ const stat = fs.statSync(filePath);
51
+ if (stat.isDirectory()) {
52
+ if (depth > 0) {
53
+ models.push(...findAllModels(filePath, depth - 1));
54
+ }
55
+ } else if (file.endsWith('.model.json') || file.endsWith('.model3.json')) {
56
+ models.push(filePath);
57
+ }
58
+ }
59
+ return models;
60
+ }
61
+
62
+ /**
63
+ * 合并模型函数
64
+ * 该函数的目的是将一系列本地模型与远程模型选项合并,以生成一个新的模型列表
65
+ * 它接受一个基础URL字符串,一个本地模型名称数组和一个远程模型选项数组作为输入
66
+ *
67
+ * @param base 基础URL字符串,用于解析相对URL
68
+ * @param local 本地模型名称数组,这些模型将被合并到最终的模型列表中
69
+ * @param remote 远程模型选项数组
70
+ * @returns 返回一个新的模型 url 列表
71
+ *
72
+ * @description
73
+ *
74
+ * 本地模型的优先级更高, 本地模型根据自然序(Natural Sort)排列
75
+ */
76
+ export function mergeModels(base: string, local: string[], remote: ModelOptions[]): string[] {
77
+ const models: string[] = [];
78
+ const url = new URL(base);
79
+ for (const model of local) {
80
+ models.push(new URL(model, url).href);
81
+ }
82
+ for (const model of remote) {
83
+ models.push(model.model_url);
84
+ }
85
+ return models
86
+ }
87
+
88
+
@@ -0,0 +1,39 @@
1
+ // 图标定义
2
+ export const icons = [
3
+ {
4
+ id: 'message',
5
+ svg: `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M20 2H4c-1.1 0-2 .9-2 2v18l4-4h14c1.1 0 2-.9 2-2V4c0-1.1-.9-2-2-2zm0 14H5.17L4 17.17V4h16v12z"/></svg>`,
6
+ title: '发送消息',
7
+ action: 'message'
8
+ },
9
+ {
10
+ id: 'telegram',
11
+ svg: `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M9.78 18.65l.28-4.23 7.68-6.92c.34-.31-.07-.46-.52-.19L7.74 13.3 3.64 12c-.88-.25-.89-.86.2-1.3l15.97-6.16c.73-.33 1.43.18 1.15 1.3l-2.72 12.81c-.19.91-.74 1.13-1.5.71L12.6 16.3l-1.99 1.93c-.23.23-.42.42-.83.42z"/></svg>`,
12
+ title: '联系我们',
13
+ action: 'telegram'
14
+ },
15
+ {
16
+ id: 'user',
17
+ svg: `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M12 12c2.21 0 4-1.79 4-4s-1.79-4-4-4-4 1.79-4 4 1.79 4 4 4zm0 2c-2.67 0-8 1.34-8 4v2h16v-2c0-2.66-5.33-4-8-4z"/></svg>`,
18
+ title: '用户信息',
19
+ action: 'user'
20
+ },
21
+ {
22
+ id: 'info',
23
+ svg: `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm1 15h-2v-6h2v6zm0-8h-2V7h2v2z"/></svg>`,
24
+ title: '关于',
25
+ action: 'info'
26
+ },
27
+ {
28
+ id: 'photo',
29
+ svg: `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M12 12c1.1 0 2-.9 2-2s-.9-2-2-2-2 .9-2 2 .9 2 2 2zm0 1c-1.63 0-3.06.79-3.98 2H8c-1.1 0-2-.9-2-2V6c0-1.1.9-2 2-2h8c1.1 0 2 .9 2 2v7c0 1.1-.9 2-2 2h-.02c-.92-1.21-2.35-2-3.98-2zm7-8h-4.18C14.4 4.84 13.3 4 12 4c-1.3 0-2.4.84-2.82 2H5c-.14 0-.27.01-.4.04-.39.08-.74.28-1.01.55-.18.18-.33.4-.43.64-.1.23-.16.49-.16.77v14c0 .27.06.54.16.78s.25.45.43.64c.27.27.62.47 1.01.55.13.02.26.03.4.03h14c1.1 0 2-.9 2-2V7c0-1.1-.9-2-2-2zm-7 0c.55 0 1 .45 1 1s-.45 1-1 1-1-.45-1-1 .45-1 1-1zm5 13c0 .55-.45 1-1 1H6c-.55 0-1-.45-1-1V8c0-.55.45-1 1-1h2.18c.42 1.16 1.52 2 2.82 2 1.3 0 2.4-.84 2.82-2H16c.55 0 1 .45 1 1v10z"/></svg>`,
30
+ title: '拍照',
31
+ action: 'photo'
32
+ },
33
+ {
34
+ id: 'close',
35
+ svg: `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z"/></svg>`,
36
+ title: '关闭',
37
+ action: 'close'
38
+ }
39
+ ];
@@ -0,0 +1,38 @@
1
+ .live2d-icon-panel {
2
+ position: fixed;
3
+ right: 10px;
4
+ bottom: 150px;
5
+ z-index: 10000;
6
+ display: flex;
7
+ flex-direction: column;
8
+ gap: 10px;
9
+ opacity: 0;
10
+ transition: opacity 0.3s ease;
11
+ }
12
+
13
+ .live2d-icon-panel.visible {
14
+ opacity: 1;
15
+ }
16
+
17
+ .live2d-icon {
18
+ width: 40px;
19
+ height: 40px;
20
+ background-color: rgba(255, 255, 255, 0.8);
21
+ border-radius: 50%;
22
+ display: flex;
23
+ justify-content: center;
24
+ align-items: center;
25
+ cursor: pointer;
26
+ box-shadow: 0 2px 5px rgba(0, 0, 0, 0.2);
27
+ transition: transform 0.2s ease;
28
+ }
29
+
30
+ .live2d-icon:hover {
31
+ transform: scale(1.1);
32
+ }
33
+
34
+ .live2d-icon svg {
35
+ width: 24px;
36
+ height: 24px;
37
+ fill: #666;
38
+ }
@@ -0,0 +1 @@
1
+ <svg t="1743275099251" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="2636" width="200" height="200"><path d="M759.04 334.976a247.936 247.936 0 1 0-393.024 201.216l-0.896 0.384a372.608 372.608 0 0 0-119.296 80.64c-34.56 34.496-61.504 74.688-80.448 119.488a373.632 373.632 0 0 0-29.376 138.112 8 8 0 0 0 8 8.192h59.904a8.064 8.064 0 0 0 8-7.808 298.368 298.368 0 0 1 87.616-204.288 296.384 296.384 0 0 1 211.456-87.936 247.936 247.936 0 0 0 248-248zM510.912 507.008a171.968 171.968 0 1 1 0-344 171.968 171.968 0 0 1 0 344z m105.024 220.992h264a8 8 0 0 0 8-8v-56a8 8 0 0 0-8-8h-176.512l47.232-60.16a8 8 0 0 0-6.272-12.928l-72.64 0.064a16.32 16.32 0 0 0-12.608 6.144l-68.48 87.04a32.064 32.064 0 0 0 25.28 51.84z m240 64H592a8 8 0 0 0-8 8v56c0 4.416 3.584 8 8 8h176.512l-47.232 60.16a8 8 0 0 0 6.272 12.928l72.64-0.064a16.32 16.32 0 0 0 12.608-6.144l68.48-87.04a32.064 32.064 0 0 0-25.28-51.84z" p-id="2637"></path></svg>
@@ -0,0 +1 @@
1
+ <svg t="1743275341493" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="24590" width="200" height="200"><path d="M512 0c279.637333 0 506.88 224.170667 511.914667 502.727111l0.085333 9.386667-0.085333 9.500444C1018.794667 800.312889 791.210667 1024 512 1024 232.362667 1024 5.12 799.829333 0.085333 521.272889L0 511.886222l0.085333-9.500444C5.205333 223.687111 232.789333 0 512 0z m0 28.728889c-260.352 0-473.173333 206.222222-482.759111 464.896l-0.284445 9.159111-0.056888 9.216c0 266.894222 216.291556 483.271111 483.100444 483.271111 260.352 0 473.173333-206.222222 482.759111-464.896l0.284445-9.159111 0.056888-9.216C995.100444 245.105778 778.808889 28.728889 512 28.728889z m-16.896 161.194667A84.280889 84.280889 0 0 1 598.471111 248.888889a83.797333 83.797333 0 0 1-59.249778 102.883555v47.502223c21.276444 0.853333 42.467556 3.214222 63.431111 7.111111l0.625778 0.113778 0.682667 0.113777c12.515556 2.56 23.324444 10.325333 29.781333 21.333334l1.166222 2.360889 1.223112 2.332444a126.976 126.976 0 0 0 59.335111 55.552l4.835555 2.076444 4.949334 1.877334a488.106667 488.106667 0 0 1 180.622222 130.503111l3.413333 3.982222 3.328 3.982222 2.190222 3.185778a67.242667 67.242667 0 0 1-20.679111 93.127111 67.953778 67.953778 0 0 1-93.582222-20.565333H254.862222a67.982222 67.982222 0 0 1-97.052444 18.147555 67.214222 67.214222 0 0 1-14.734222-94.236444l3.100444-3.669333 3.128889-3.640889a488.049778 488.049778 0 0 1 175.843555-128.597334l4.636445-1.848888 4.522667-1.991112a126.976 126.976 0 0 0 58.168888-54.954666l1.251556-2.417778 1.223111-2.446222c5.774222-9.841778 15.104-17.123556 26.083556-20.366222l2.844444-0.654223 2.844445-0.625777c22.556444-4.892444 45.454222-7.964444 68.494222-9.301334v-67.697778l0.085333-0.796444a21.902222 21.902222 0 0 1 21.105778-20.138667h1.649778a40.248889 40.248889 0 0 0 37.432889-53.475555 40.533333 40.533333 0 0 0-51.541334-24.888889 40.248889 40.248889 0 0 0-24.974222 51.256889l0.682667 1.792a21.333333 21.333333 0 0 1 1.109333 6.826666l-0.256 3.214223a21.959111 21.959111 0 0 1-41.898667 5.233777l-0.056889-0.056888v-0.056889l-0.426666-1.137778a83.797333 83.797333 0 0 1 57.002666-109.909333z m22.613333 348.615111a288.312889 288.312889 0 0 0-237.568 124.529777h475.136a288.312889 288.312889 0 0 0-237.568-124.529777z" fill="#666666" p-id="24591"></path></svg>
package/src/index.ts CHANGED
@@ -3,50 +3,69 @@ import '../lib/cubism5.min.js';
3
3
  import * as PIXI from 'pixi.js';
4
4
  import { Live2dOptions } from './types';
5
5
  import { Ticker, TickerPlugin } from '@pixi/ticker';
6
- import { InteractionManager } from '@pixi/interaction';
7
- import { Live2DModel, MotionManager, SoundManager } from 'pixi-live2d-display-lipsyncpatch';
6
+ import {
7
+ Live2DFactory,
8
+ Live2DModel,
9
+ MotionManager,
10
+ SoundManager
11
+ } from 'pixi-live2d-display-lipsyncpatch';
12
+ import { Application, } from 'pixi.js';
13
+ import { icons } from './icons/icons';
14
+ import './icons/style.css';
8
15
 
9
- export { MotionManager, SoundManager, Live2DModel, PIXI, type Live2dOptions };
10
16
 
17
+ export { MotionManager, SoundManager, Live2DModel, PIXI, type Live2dOptions };
11
18
 
12
19
  /**
13
20
  * 创建Live2D模型
14
21
  * @param options 模型配置选项
15
22
  * @returns Promise<Live2DModel> 加载完成的Live2D模型实例
16
23
  */
17
- export async function createLive2dModel(options: Live2dOptions): Promise<Live2DModel> {
24
+ export async function createLive2D(options: Live2dOptions): Promise<Live2DModel> {
18
25
  const {
26
+ element_id = 'live2d-canvas',
19
27
  models,
20
- element_id,
28
+ width = 300,
29
+ height = 300,
21
30
  auto_fit = true,
22
31
  auto_motion = true,
23
32
  mouse_tracking = true
24
33
  } = options;
25
-
34
+ // 创建画布
35
+ let element = findOrCreateCanvas(element_id, options)
26
36
  // 初始化PIXI应用
27
- const app = new PIXI.Application({
28
- view: document.getElementById(element_id) as HTMLCanvasElement,
29
- width: 300,
30
- height: 300,
37
+ const app = new Application({
38
+ view: element,
39
+ width: width,
40
+ height: height,
41
+ // resolution: 1,
31
42
  backgroundAlpha: 0,
32
43
  autoDensity: true,
33
- antialias: true
44
+ antialias: true,
45
+ eventMode: 'static',
46
+ eventFeatures: {
47
+ move: true,
48
+ globalMove: true,
49
+ click: true,
50
+ wheel: true
51
+ }
34
52
  });
35
53
 
36
54
  // 加载模型
37
- const model = await Live2DModel.from(models[0]);
55
+ const model = await Live2DModel.from(models[0].model_url);
38
56
 
39
57
  // 添加模型到舞台
40
58
  app.stage.addChild(model);
41
59
 
42
60
  // 自动适应大小
43
61
  if (auto_fit) {
44
- const scale = Math.min(300 / model.width, 300 / model.height);
62
+ // 修复auto_fit功能,确保模型不被截断且保持正确比例
63
+ const scale = Math.min(width / model.width, height / model.height);
45
64
  model.scale.set(scale);
46
65
 
47
- // 居中显示
48
- model.x = (300 - model.width * scale) / 2;
49
- model.y = (300 - model.height * scale) / 2;
66
+ // 居中显示,确保模型完全可见
67
+ model.x = (width - model.width * scale) / 2;
68
+ model.y = (height - model.height * scale) / 2;
50
69
  }
51
70
 
52
71
  // 启用鼠标跟踪
@@ -58,16 +77,38 @@ export async function createLive2dModel(options: Live2dOptions): Promise<Live2DM
58
77
  if (auto_motion) {
59
78
  await startIdleAnimation(model);
60
79
  }
80
+
81
+ // 创建右侧图标栏
82
+ createIconPanel(model, element);
61
83
 
62
84
  return model;
63
85
  }
64
86
 
87
+ function findOrCreateCanvas(id: string, options: Live2dOptions): HTMLCanvasElement {
88
+ let canvas = document.getElementById(id) as HTMLCanvasElement;
89
+ if (!canvas) {
90
+ canvas = document.createElement('canvas');
91
+ canvas.id = id;
92
+ document.body.appendChild(canvas);
93
+ canvas.style.display = 'none';
94
+ canvas.style.position = 'fixed';
95
+ // 设置画布样式
96
+ canvas.style.display = 'block';
97
+ canvas.style.position = 'absolute';
98
+ canvas.style.bottom = `${options.spacing_y || 0}px`;
99
+ canvas.style[options.position === 'left' ? 'left' : 'right'] = `${options.spacing_x || 0}px`;
100
+ canvas.style.zIndex = '9999';
101
+ }
102
+ return canvas;
103
+ }
104
+
105
+
65
106
  /**
66
107
  * 启用鼠标跟踪功能
67
108
  * @param model Live2D模型实例
68
109
  * @param canvas 画布元素
69
110
  */
70
- function enableMouseTracking(model: Live2DModel, canvas: HTMLCanvasElement) {
111
+ function enableMouseTracking(model: Live2DModel, canvas: any) {
71
112
  // 跟踪鼠标移动
72
113
  document.addEventListener('mousemove', (event) => {
73
114
  const rect = canvas.getBoundingClientRect();
@@ -76,13 +117,37 @@ function enableMouseTracking(model: Live2DModel, canvas: HTMLCanvasElement) {
76
117
 
77
118
  // 计算鼠标位置相对于画布中心的偏移
78
119
  const mouseX = (event.clientX - centerX) / rect.width;
79
- const mouseY = (event.clientY - centerY) / rect.height;
120
+ // 修正Y轴方向,使用负值来反转方向
121
+ const mouseY = -(event.clientY - centerY) / rect.height;
80
122
 
81
123
  // 更新模型的视线方向
82
124
  if (model.internalModel) {
83
125
  model.internalModel.focusController?.focus(mouseX, mouseY);
84
126
  }
85
127
  });
128
+
129
+ // 添加点击模型触发动作的交互
130
+ canvas.addEventListener('click', () => {
131
+ // 尝试播放tap_body动作,如果没有则尝试其他可用动作
132
+ try {
133
+ const motions = model.internalModel?.motionManager.definitions;
134
+ if (motions) {
135
+ if (motions.tap_body) {
136
+ model.motion('tap_body');
137
+ } else if (motions.tap) {
138
+ model.motion('tap');
139
+ } else {
140
+ // 使用第一个可用的动作组
141
+ const firstMotionGroup = Object.keys(motions)[0];
142
+ if (firstMotionGroup) {
143
+ model.motion(firstMotionGroup);
144
+ }
145
+ }
146
+ }
147
+ } catch (error) {
148
+ console.warn('Failed to play tap animation:', error);
149
+ }
150
+ });
86
151
  }
87
152
 
88
153
  /**
@@ -100,7 +165,7 @@ async function startIdleAnimation(model: Live2DModel) {
100
165
  if (motions.idle) {
101
166
  await model.motion('idle');
102
167
  } else if (motions.tap_body) {
103
- // 如果没有idle动作,尝试使用tap_body动作
168
+ // 如果没有idle动作,尝试使用 tap_body 动作
104
169
  await model.motion('tap_body');
105
170
  } else {
106
171
  // 使用第一个可用的动作组
@@ -137,5 +202,5 @@ export async function initializeLive2D(cubism2?: any, cubism5?: any) {
137
202
  // 为 Application 注册 Ticker
138
203
  PIXI.extensions.add(TickerPlugin);
139
204
  // 注册 InteractionManager 以支持 Live2D 模型的自动交互
140
- PIXI.extensions.add(InteractionManager);
205
+ // PIXI.extensions.add(InteractionManager);
141
206
  }
@@ -9,9 +9,35 @@ export interface Live2dOptions {
9
9
  */
10
10
  models: ModelOptions[];
11
11
  /**
12
- * 要渲染到的HTML元素ID
12
+ * 画布的ID
13
+ * @default 'live2d-canvas'
13
14
  */
14
- element_id?: string;
15
+ element_id?: string,
16
+ /**
17
+ * 画布的宽度
18
+ * @default 300
19
+ */
20
+ width?: number;
21
+ /**
22
+ * 画布的高度
23
+ * @default 300
24
+ */
25
+ height?: number;
26
+ /**
27
+ * 模型位置,可选 'left' 或 'right'
28
+ * @default 'right'
29
+ */
30
+ position?: 'left' | 'right';
31
+ /**
32
+ * 模型与左右边框的距离(像素)
33
+ * @default 20
34
+ */
35
+ spacing_x?: number;
36
+ /**
37
+ * 模型与底部边框的距离(像素)
38
+ * @default 20
39
+ */
40
+ spacing_y?: number;
15
41
  /**
16
42
  * 是否自动适应模型大小
17
43
  * @default true