@vmz/plugin-live2d 0.0.20 → 0.0.22
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.
- package/components/Live2dHost.vmz +128 -41
- package/package.json +2 -2
|
@@ -1,20 +1,40 @@
|
|
|
1
1
|
<template>
|
|
2
2
|
<div class="live2d-host" style={hostStyle}>
|
|
3
3
|
<div class="live2d-host__mount" id={hostId}></div>
|
|
4
|
-
<div
|
|
4
|
+
<div
|
|
5
|
+
if={showProgress && loading}
|
|
6
|
+
class="live2d-host__progress"
|
|
7
|
+
role="progressbar"
|
|
8
|
+
aria-valuenow={progressPercent}
|
|
9
|
+
aria-valuemin="0"
|
|
10
|
+
aria-valuemax="100"
|
|
11
|
+
>
|
|
5
12
|
<span>{progressLabel} · {progressPercent}%</span>
|
|
6
13
|
</div>
|
|
7
14
|
</div>
|
|
8
15
|
</template>
|
|
9
16
|
|
|
10
17
|
<style>
|
|
11
|
-
.live2d-host {
|
|
12
|
-
|
|
13
|
-
|
|
18
|
+
.live2d-host {
|
|
19
|
+
position: relative;
|
|
20
|
+
display: inline-block;
|
|
21
|
+
}
|
|
22
|
+
.live2d-host__mount {
|
|
23
|
+
width: 100%;
|
|
24
|
+
height: 100%;
|
|
25
|
+
}
|
|
26
|
+
.live2d-host__mount canvas {
|
|
27
|
+
display: block;
|
|
28
|
+
width: 100%;
|
|
29
|
+
height: 100%;
|
|
30
|
+
}
|
|
14
31
|
.live2d-host__progress {
|
|
15
|
-
position: absolute;
|
|
32
|
+
position: absolute;
|
|
33
|
+
inset: auto 0 0;
|
|
34
|
+
padding: 0.35rem 0.5rem;
|
|
16
35
|
background: rgba(255, 255, 255, 0.92);
|
|
17
|
-
font: 600 0.75rem/1.2 ui-monospace, monospace;
|
|
36
|
+
font: 600 0.75rem/1.2 ui-monospace, monospace;
|
|
37
|
+
color: #377ba8;
|
|
18
38
|
}
|
|
19
39
|
</style>
|
|
20
40
|
|
|
@@ -33,6 +53,14 @@ export default class Live2dHost {
|
|
|
33
53
|
public showProgress = true;
|
|
34
54
|
public exposeCaptureApi = false;
|
|
35
55
|
public capturePresetId = '';
|
|
56
|
+
/** Called with Live2DRuntime after loadModel succeeds. */
|
|
57
|
+
public onReady = null;
|
|
58
|
+
/** Optional frame profile listener (caller should throttle UI). */
|
|
59
|
+
public onProfile = null;
|
|
60
|
+
/** Load / mount failure. */
|
|
61
|
+
public onError = null;
|
|
62
|
+
/** Pointer hit area string (often `drawable:N`). */
|
|
63
|
+
public onHit = null;
|
|
36
64
|
|
|
37
65
|
hostId = `live2d-host-${++hostSeq}`;
|
|
38
66
|
hostStyle = '';
|
|
@@ -40,8 +68,10 @@ export default class Live2dHost {
|
|
|
40
68
|
progressPercent = 0;
|
|
41
69
|
progressLabel = 'Loading…';
|
|
42
70
|
runtime = null;
|
|
43
|
-
|
|
44
|
-
|
|
71
|
+
_unsubProfile = null;
|
|
72
|
+
_canvas = null;
|
|
73
|
+
_boundPointer = null;
|
|
74
|
+
_bootGeneration = 0;
|
|
45
75
|
|
|
46
76
|
onMount() {
|
|
47
77
|
this.hostStyle = `width:${this.width}px;height:${this.height}px`;
|
|
@@ -57,23 +87,48 @@ export default class Live2dHost {
|
|
|
57
87
|
}
|
|
58
88
|
|
|
59
89
|
onDestroy() {
|
|
60
|
-
this.
|
|
61
|
-
|
|
90
|
+
this.teardownRuntime();
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
teardownRuntime() {
|
|
94
|
+
try {
|
|
95
|
+
this.runtime?.stage?.stop?.();
|
|
96
|
+
} catch {
|
|
97
|
+
/* updateMode may be manual on older callers */
|
|
98
|
+
}
|
|
99
|
+
this.detachPointer();
|
|
100
|
+
if (typeof this._unsubProfile === 'function') {
|
|
101
|
+
this._unsubProfile();
|
|
102
|
+
this._unsubProfile = null;
|
|
103
|
+
}
|
|
104
|
+
this.runtime?.destroy?.();
|
|
62
105
|
this.runtime = null;
|
|
106
|
+
this._canvas = null;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
detachPointer() {
|
|
110
|
+
if (this._canvas && this._boundPointer) {
|
|
111
|
+
this._canvas.removeEventListener('pointerdown', this._boundPointer);
|
|
112
|
+
}
|
|
113
|
+
this._boundPointer = null;
|
|
63
114
|
}
|
|
64
115
|
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
this.
|
|
68
|
-
this.
|
|
116
|
+
attachPointer(canvas) {
|
|
117
|
+
this.detachPointer();
|
|
118
|
+
this._boundPointer = (event) => this.onPointerDown(event);
|
|
119
|
+
canvas.addEventListener('pointerdown', this._boundPointer);
|
|
69
120
|
}
|
|
70
121
|
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
const
|
|
74
|
-
this.
|
|
75
|
-
|
|
76
|
-
|
|
122
|
+
onPointerDown(event) {
|
|
123
|
+
const runtime = this.runtime;
|
|
124
|
+
const canvas = this._canvas;
|
|
125
|
+
if (!runtime || !canvas || typeof this.onHit !== 'function') return;
|
|
126
|
+
const rect = canvas.getBoundingClientRect();
|
|
127
|
+
if (rect.width <= 0 || rect.height <= 0) return;
|
|
128
|
+
const nx = ((event.clientX - rect.left) / rect.width) * 2 - 1;
|
|
129
|
+
const ny = 1 - ((event.clientY - rect.top) / rect.height) * 2;
|
|
130
|
+
const area = runtime.hitTest?.(nx, ny) ?? null;
|
|
131
|
+
this.onHit(area);
|
|
77
132
|
}
|
|
78
133
|
|
|
79
134
|
ensureCanvas() {
|
|
@@ -87,53 +142,85 @@ export default class Live2dHost {
|
|
|
87
142
|
canvas.style.width = `${this.width}px`;
|
|
88
143
|
canvas.style.height = `${this.height}px`;
|
|
89
144
|
host.appendChild(canvas);
|
|
145
|
+
this._canvas = canvas;
|
|
90
146
|
return canvas;
|
|
91
147
|
}
|
|
92
148
|
|
|
149
|
+
getRuntime() {
|
|
150
|
+
return this.runtime;
|
|
151
|
+
}
|
|
152
|
+
|
|
93
153
|
async capturePng(opts = {}) {
|
|
94
154
|
if (!this.runtime) throw new Error('Live2dHost: runtime not ready');
|
|
95
155
|
return this.runtime.capturePng(opts);
|
|
96
156
|
}
|
|
97
157
|
|
|
158
|
+
reportError(err) {
|
|
159
|
+
this.loading = false;
|
|
160
|
+
const message = String(err?.message || err || 'load error');
|
|
161
|
+
if (this.exposeCaptureApi && typeof window !== 'undefined') {
|
|
162
|
+
const api = window.__LIVE2D_CAPTURE__;
|
|
163
|
+
if (api) api.error = message;
|
|
164
|
+
}
|
|
165
|
+
if (typeof this.onError === 'function') this.onError(message);
|
|
166
|
+
}
|
|
167
|
+
|
|
98
168
|
async boot() {
|
|
99
169
|
if (!this.model) return;
|
|
170
|
+
const generation = ++this._bootGeneration;
|
|
100
171
|
this.loading = true;
|
|
172
|
+
this.progressPercent = 0;
|
|
173
|
+
this.progressLabel = 'loading';
|
|
101
174
|
try {
|
|
102
|
-
this.
|
|
103
|
-
|
|
175
|
+
this.teardownRuntime();
|
|
176
|
+
if (generation !== this._bootGeneration) return;
|
|
104
177
|
const canvas = this.ensureCanvas();
|
|
105
178
|
if (!canvas) return;
|
|
106
|
-
const
|
|
107
|
-
|
|
179
|
+
const prefer = Array.isArray(this.prefer)
|
|
180
|
+
? this.prefer
|
|
181
|
+
: ['canvas2d', 'webgl2', 'webgpu'];
|
|
182
|
+
const runtime = createLive2D({ prefer, updateMode: 'auto' });
|
|
108
183
|
runtime.events.on('ready', () => {
|
|
184
|
+
if (generation !== this._bootGeneration) return;
|
|
109
185
|
this.loading = false;
|
|
186
|
+
this.progressPercent = 100;
|
|
187
|
+
this.progressLabel = 'ready';
|
|
110
188
|
if (this.autoplay) {
|
|
111
|
-
|
|
112
|
-
|
|
189
|
+
try {
|
|
190
|
+
runtime.stage.start();
|
|
191
|
+
} catch {
|
|
192
|
+
/* already running */
|
|
193
|
+
}
|
|
113
194
|
}
|
|
195
|
+
this.attachPointer(canvas);
|
|
114
196
|
if (this.exposeCaptureApi) void this.publishCapture();
|
|
197
|
+
if (typeof this.onReady === 'function') this.onReady(runtime);
|
|
115
198
|
});
|
|
116
199
|
runtime.events.on('error', (p) => {
|
|
117
|
-
this.
|
|
118
|
-
|
|
119
|
-
const api = window.__LIVE2D_CAPTURE__;
|
|
120
|
-
if (api) api.error = String(p?.error || 'load error');
|
|
121
|
-
}
|
|
200
|
+
if (generation !== this._bootGeneration) return;
|
|
201
|
+
this.reportError(p?.error || 'load error');
|
|
122
202
|
});
|
|
123
|
-
|
|
124
|
-
|
|
203
|
+
if (typeof this.onProfile === 'function') {
|
|
204
|
+
this._unsubProfile = runtime.events.on('profile', (profile) => {
|
|
205
|
+
if (generation !== this._bootGeneration) return;
|
|
206
|
+
this.onProfile(profile);
|
|
207
|
+
});
|
|
208
|
+
}
|
|
209
|
+
runtime.mount(canvas);
|
|
210
|
+
this.progressPercent = 35;
|
|
211
|
+
await runtime.loadModel(this.model);
|
|
212
|
+
if (generation !== this._bootGeneration) {
|
|
213
|
+
runtime.destroy?.();
|
|
214
|
+
return;
|
|
215
|
+
}
|
|
125
216
|
this.runtime = runtime;
|
|
126
|
-
|
|
127
|
-
|
|
217
|
+
this.progressPercent = 90;
|
|
218
|
+
} catch (err) {
|
|
219
|
+
if (generation !== this._bootGeneration) return;
|
|
220
|
+
this.reportError(err);
|
|
128
221
|
}
|
|
129
222
|
}
|
|
130
223
|
|
|
131
|
-
onProgress(p) {
|
|
132
|
-
const progress = p?.progress ?? 0;
|
|
133
|
-
this.progressLabel = p?.stage || 'loading';
|
|
134
|
-
this.progressPercent = Math.round(Math.min(1, Math.max(0, progress)) * 100);
|
|
135
|
-
}
|
|
136
|
-
|
|
137
224
|
async publishCapture() {
|
|
138
225
|
const api = typeof window !== 'undefined' ? window.__LIVE2D_CAPTURE__ : null;
|
|
139
226
|
if (!api) return;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vmz/plugin-live2d",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.22",
|
|
4
4
|
"description": "VMZ Live2D adapter — <Live2dHost> for @doki-land/live2d in VMZ applications",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -36,7 +36,7 @@
|
|
|
36
36
|
"test": "vitest run --passWithNoTests"
|
|
37
37
|
},
|
|
38
38
|
"dependencies": {
|
|
39
|
-
"@doki-land/live2d": "0.0.
|
|
39
|
+
"@doki-land/live2d": "0.0.22",
|
|
40
40
|
"@vmz/plugin": "^0.1.8"
|
|
41
41
|
}
|
|
42
42
|
}
|