@doki-land/live2d 0.0.13 โ 0.0.15
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/README.md +180 -1
- package/dist/index.d.ts +72 -7
- package/dist/index.js +407 -208
- package/package.json +9 -7
- package/src/index.ts +4 -0
- package/src/stage/actor-model-slot.ts +63 -167
- package/src/stage/actor.ts +42 -7
- package/src/stage/model-asset-key.ts +19 -0
- package/src/stage/model-asset-registry.ts +337 -0
- package/src/stage/single-facade.ts +5 -5
- package/src/stage/stage.ts +27 -1
package/README.md
CHANGED
|
@@ -1,3 +1,182 @@
|
|
|
1
1
|
# @doki-land/live2d
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
The public runtime facade for browser-native Live2D rendering.
|
|
4
|
+
|
|
5
|
+
Use this package in browser games, game-engine integrations, interactive content, model tools, and custom webpage experiences. It composes the default loader, MOC runtimes, and rendering backends behind one small API.
|
|
6
|
+
|
|
7
|
+
## โจ Features
|
|
8
|
+
|
|
9
|
+
- One dependency for the standard runtime pipeline.
|
|
10
|
+
- Pure TypeScript model execution.
|
|
11
|
+
- WebGPU, WebGL2, and Canvas2D backend selection.
|
|
12
|
+
- Explicit game-loop integration through `update(deltaTime)`.
|
|
13
|
+
- Local URL, remote URL, and `npm:` model sources.
|
|
14
|
+
- Parameter inspection and mutation.
|
|
15
|
+
- Frame capture for diagnostics.
|
|
16
|
+
- Hit testing in normalized model coordinates.
|
|
17
|
+
- Progress, ready, error, phase, and frame-profile events.
|
|
18
|
+
|
|
19
|
+
## ๐ฆ Installation
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
pnpm add @doki-land/live2d
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
The implementation packages are installed transitively. Most applications should not depend on them directly.
|
|
26
|
+
|
|
27
|
+
## ๐ Quick Start
|
|
28
|
+
|
|
29
|
+
```ts
|
|
30
|
+
import { createLive2D } from "@doki-land/live2d";
|
|
31
|
+
|
|
32
|
+
const canvas = document.querySelector<HTMLCanvasElement>("#live2d");
|
|
33
|
+
|
|
34
|
+
if (!canvas) {
|
|
35
|
+
throw new Error("Missing Live2D canvas");
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const runtime = createLive2D({
|
|
39
|
+
prefer: ["webgpu", "webgl2", "canvas2d"],
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
runtime.mount(canvas);
|
|
43
|
+
await runtime.loadModel("/models/character.model3.json");
|
|
44
|
+
|
|
45
|
+
let previous = performance.now();
|
|
46
|
+
|
|
47
|
+
function frame(now: number) {
|
|
48
|
+
runtime.update((now - previous) / 1000);
|
|
49
|
+
previous = now;
|
|
50
|
+
requestAnimationFrame(frame);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
requestAnimationFrame(frame);
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
## ๐ฎ Game Loop
|
|
57
|
+
|
|
58
|
+
The runtime does not require ownership of `requestAnimationFrame`. Drive it from an existing engine scheduler:
|
|
59
|
+
|
|
60
|
+
```ts
|
|
61
|
+
engine.onUpdate((deltaTime) => {
|
|
62
|
+
runtime.update(deltaTime);
|
|
63
|
+
});
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
The delta is expressed in seconds. Keep the value bounded after tab suspension or long pauses to avoid unstable animation and physics once those systems are enabled.
|
|
67
|
+
|
|
68
|
+
## ๐ฅ Loading Models
|
|
69
|
+
|
|
70
|
+
```ts
|
|
71
|
+
await runtime.loadModel("/models/actor.model3.json");
|
|
72
|
+
|
|
73
|
+
await runtime.loadModel(
|
|
74
|
+
"https://cdn.example.com/models/actor.model3.json",
|
|
75
|
+
);
|
|
76
|
+
|
|
77
|
+
await runtime.loadModel(
|
|
78
|
+
"npm:live2d-widget-model-hijiki@1.0.5/assets/hijiki.model.json",
|
|
79
|
+
);
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
Listen for progress when presenting a loading interface:
|
|
83
|
+
|
|
84
|
+
```ts
|
|
85
|
+
runtime.events.on("progress", ({ stage, progress, detail }) => {
|
|
86
|
+
console.log(stage, Math.round(progress * 100), detail);
|
|
87
|
+
});
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
Remote servers must allow cross-origin access to settings, model binaries, and textures.
|
|
91
|
+
|
|
92
|
+
## ๐๏ธ Parameters
|
|
93
|
+
|
|
94
|
+
```ts
|
|
95
|
+
runtime.setParameter("PARAM_ANGLE_X", 15);
|
|
96
|
+
|
|
97
|
+
const angleX = runtime
|
|
98
|
+
.listParameters()
|
|
99
|
+
.find((parameter) => parameter.id === "PARAM_ANGLE_X");
|
|
100
|
+
|
|
101
|
+
console.log(angleX);
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
Parameter availability and ranges belong to the loaded model. Do not assume every model implements the same IDs.
|
|
105
|
+
|
|
106
|
+
## ๐ฑ๏ธ Hit Testing
|
|
107
|
+
|
|
108
|
+
`hitTest(x, y)` expects normalized model coordinates where both axes are approximately in the `-1..1` range:
|
|
109
|
+
|
|
110
|
+
```ts
|
|
111
|
+
const area = runtime.hitTest(modelX, modelY);
|
|
112
|
+
|
|
113
|
+
if (area) {
|
|
114
|
+
console.log("Hit", area);
|
|
115
|
+
}
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
Current fallback hit testing can identify visible drawables. Semantic names such as `Head` or `Body` require corresponding model metadata and runtime support.
|
|
119
|
+
|
|
120
|
+
## ๐ Frame Profiling
|
|
121
|
+
|
|
122
|
+
```ts
|
|
123
|
+
runtime.events.on("profile", (profile) => {
|
|
124
|
+
console.log({
|
|
125
|
+
fps: profile.fpsSmooth,
|
|
126
|
+
frameMs: profile.frameMs,
|
|
127
|
+
evaluateMs: profile.evaluateMs,
|
|
128
|
+
drawMs: profile.drawMs,
|
|
129
|
+
drawables: profile.drawableCount,
|
|
130
|
+
});
|
|
131
|
+
});
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
These timings are runtime-side measurements. Use browser GPU profiling when diagnosing shader, mask, upload, or device scheduling costs.
|
|
135
|
+
|
|
136
|
+
## ๐งฉ Custom Pipeline
|
|
137
|
+
|
|
138
|
+
Advanced applications can provide a renderer or model backends:
|
|
139
|
+
|
|
140
|
+
```ts
|
|
141
|
+
const runtime = createLive2D({
|
|
142
|
+
renderer: customRenderer,
|
|
143
|
+
backends: [customBackend],
|
|
144
|
+
});
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
Custom implementations must preserve the contracts exported by the renderer package. Avoid moving format or renderer logic into application adapters.
|
|
148
|
+
|
|
149
|
+
## ๐งน Lifecycle
|
|
150
|
+
|
|
151
|
+
```ts
|
|
152
|
+
runtime.destroy();
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
Destroy the runtime when its canvas or host scene is permanently removed. This releases model state, textures, draw passes, event listeners owned by the runtime, and graphics resources owned by the selected renderer.
|
|
156
|
+
|
|
157
|
+
## โก Performance Notes
|
|
158
|
+
|
|
159
|
+
- Reuse one runtime for repeated updates instead of recreating it per frame.
|
|
160
|
+
- Keep canvas backing dimensions intentional; CSS size alone does not limit GPU pixel work.
|
|
161
|
+
- Prefer an engine-owned loop when integrating with a game.
|
|
162
|
+
- Avoid repeatedly enumerating parameters in a hot loop.
|
|
163
|
+
- Measure the complete frame path before attributing a bottleneck to TypeScript, WebAssembly, or a specific graphics API.
|
|
164
|
+
|
|
165
|
+
## ๐งช Development
|
|
166
|
+
|
|
167
|
+
From the workspace root:
|
|
168
|
+
|
|
169
|
+
```bash
|
|
170
|
+
pnpm typecheck
|
|
171
|
+
pnpm --filter @doki-land/live2d build
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
Changes to the facade should include tests for state transitions, cancellation, events, and resource cleanup where applicable.
|
|
175
|
+
|
|
176
|
+
## ๐ค Contributing
|
|
177
|
+
|
|
178
|
+
Keep the facade small. Model-format behavior belongs in `@doki-land/live2d-renderer`, source resolution belongs in `@doki-land/live2d-loader`, and webpage chrome belongs in `@doki-land/live2d-widget`.
|
|
179
|
+
|
|
180
|
+
## ๐ License
|
|
181
|
+
|
|
182
|
+
See the repository license. Model and artwork licenses are separate from the runtime license.
|
package/dist/index.d.ts
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import * as _doki_land_live2d_core from '@doki-land/live2d-core';
|
|
2
|
-
import {
|
|
3
|
-
export { ActorHit, ActorTransform, AssetResolver, CreateActorOptions, CreateLive2dStageOptions, DEFAULT_ACTOR_TRANSFORM, EventEmitter, FrameProfile, FrameSnapshot, InternalModel, Live2DSession, Live2dActor, Live2dStage, LoadProgress, LoadProgressStage, ModelFormat, ModelInstance, ModelProgram, ModelSettings, ModelSource, PointerTrackingMode, PointerTrackingPolicy, SessionPhase, SessionState, StagePointerEvent, StageUpdateMode } from '@doki-land/live2d-core';
|
|
2
|
+
import { Live2dStageAssets, ModelSource, AssetResolver, ModelAsset, LoadProgress, ModelSettings, InternalModel, Live2dActor, CreateActorOptions, ActorTransform, PlayMotionActorOptions, ActorHit, CreateLive2dStageOptions, Live2dStage, PointerTrackingPolicy, StagePointerEvent, Live2DSession } from '@doki-land/live2d-core';
|
|
3
|
+
export { ActorHit, ActorInstance, ActorTransform, AssetResolver, CreateActorOptions, CreateLive2dStageOptions, DEFAULT_ACTOR_TRANSFORM, EventEmitter, FrameProfile, FrameSnapshot, InternalModel, Live2DSession, Live2dActor, Live2dStage, Live2dStageAssets, LoadProgress, LoadProgressStage, ModelAsset, ModelFormat, ModelInstance, ModelProgram, ModelSettings, ModelSource, PlayMotionActorOptions, PointerTrackingMode, PointerTrackingPolicy, SessionPhase, SessionState, StagePointerEvent, StageUpdateMode } from '@doki-land/live2d-core';
|
|
4
4
|
export { DEFAULT_NPM_CDN, resolveModelSourceUrl, resolveNpmSpecifier } from '@doki-land/live2d-loader';
|
|
5
|
-
import
|
|
5
|
+
import * as _doki_land_live2d_renderer from '@doki-land/live2d-renderer';
|
|
6
|
+
import { ModelBackend, compileSharedModelCompile, TextureData, Renderer, ParameterBinding, DrawableMesh, RendererKind } from '@doki-land/live2d-renderer';
|
|
6
7
|
export { ModelBackend, ParameterBinding, Renderer, RendererKind, createCanvas2DRenderer, createMoc2Backend, createMoc3Backend, createQuadProgram, createRenderer, createWebGl2Renderer, createWebGpuRenderer, decodeMoc3, evaluateFrame, fingerprintSnapshot, parseCpuProgram, serializeCpuProgram } from '@doki-land/live2d-renderer';
|
|
7
8
|
|
|
8
9
|
/** Cubism motion3 segment kinds (spec). */
|
|
@@ -158,8 +159,53 @@ declare function blendMotionLayers(layers: ReadonlyArray<{
|
|
|
158
159
|
*/
|
|
159
160
|
declare function parseMotion3(json: unknown): Motion3Clip;
|
|
160
161
|
|
|
161
|
-
|
|
162
|
+
/** @internal */
|
|
163
|
+
declare class ModelAssetHandle implements ModelAsset {
|
|
164
|
+
#private;
|
|
165
|
+
constructor(entry: SharedModelAssetEntry);
|
|
166
|
+
get key(): string;
|
|
167
|
+
get settings(): ModelSettings;
|
|
168
|
+
/** @internal */
|
|
169
|
+
get entry(): SharedModelAssetEntry;
|
|
170
|
+
}
|
|
171
|
+
/** @internal */
|
|
172
|
+
interface SharedModelAssetEntry {
|
|
173
|
+
readonly key: string;
|
|
174
|
+
readonly settings: ModelSettings;
|
|
175
|
+
readonly backend: ModelBackend;
|
|
176
|
+
readonly resolver: AssetResolver;
|
|
177
|
+
readonly sharedCompile: ReturnType<typeof compileSharedModelCompile>;
|
|
178
|
+
readonly textures: TextureData[];
|
|
179
|
+
readonly motionCache: Map<string, Motion3Clip>;
|
|
180
|
+
refCount: number;
|
|
181
|
+
}
|
|
182
|
+
/** Lease held by one actor slot while a model is attached. */
|
|
183
|
+
interface ModelAssetLease {
|
|
184
|
+
readonly asset: ModelAssetHandle;
|
|
185
|
+
readonly motionCache: Map<string, Motion3Clip>;
|
|
186
|
+
readonly resolver: AssetResolver;
|
|
187
|
+
readonly textures: readonly TextureData[];
|
|
188
|
+
createInstance(renderer: Renderer): Promise<{
|
|
189
|
+
model: InternalModel;
|
|
190
|
+
backend: ModelBackend;
|
|
191
|
+
}>;
|
|
192
|
+
release(): void;
|
|
193
|
+
}
|
|
194
|
+
interface ModelAssetRegistryOptions {
|
|
162
195
|
backends: readonly ModelBackend[];
|
|
196
|
+
}
|
|
197
|
+
declare class ModelAssetRegistry implements Live2dStageAssets {
|
|
198
|
+
#private;
|
|
199
|
+
constructor(options: ModelAssetRegistryOptions);
|
|
200
|
+
load(source: ModelSource, resolver?: AssetResolver): Promise<ModelAsset>;
|
|
201
|
+
acquire(source: ModelSource, resolver: AssetResolver | undefined, onProgress?: (payload: LoadProgress) => void): Promise<ModelAssetLease>;
|
|
202
|
+
acquireExisting(asset: ModelAsset): ModelAssetLease;
|
|
203
|
+
destroy(): void;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
type ModelDrawPass = ReturnType<Renderer["createModelDrawPass"]>;
|
|
207
|
+
interface ActorModelSlotOptions {
|
|
208
|
+
assets: ModelAssetRegistry;
|
|
163
209
|
renderer: Renderer;
|
|
164
210
|
onProgress?: (payload: LoadProgress) => void;
|
|
165
211
|
onMotionStart?: (payload: {
|
|
@@ -178,9 +224,10 @@ declare class ActorModelSlot {
|
|
|
178
224
|
#private;
|
|
179
225
|
constructor(options: ActorModelSlotOptions);
|
|
180
226
|
get model(): InternalModel | null;
|
|
181
|
-
get drawPass():
|
|
182
|
-
ensureDrawPass():
|
|
227
|
+
get drawPass(): ModelDrawPass | null;
|
|
228
|
+
ensureDrawPass(): ModelDrawPass;
|
|
183
229
|
load(source: ModelSource, resolver?: AssetResolver): Promise<InternalModel>;
|
|
230
|
+
loadAsset(asset: ModelAsset): Promise<InternalModel>;
|
|
184
231
|
setParameter(id: string, value: number): void;
|
|
185
232
|
listParameters(): readonly ParameterBinding[];
|
|
186
233
|
listMotionGroups(): Record<string, readonly _doki_land_live2d_core.MotionDefinition[]>;
|
|
@@ -204,7 +251,7 @@ declare class ActorModelSlot {
|
|
|
204
251
|
interface Live2dActorImplOptions {
|
|
205
252
|
id: string;
|
|
206
253
|
creationIndex: number;
|
|
207
|
-
|
|
254
|
+
assets: ModelAssetRegistry;
|
|
208
255
|
renderer: Renderer;
|
|
209
256
|
}
|
|
210
257
|
declare class Live2dActorImpl implements Live2dActor {
|
|
@@ -224,7 +271,22 @@ declare class Live2dActorImpl implements Live2dActor {
|
|
|
224
271
|
getTransform(): ActorTransform;
|
|
225
272
|
setTransform(patch: Partial<ActorTransform>): void;
|
|
226
273
|
load(source: Parameters<Live2dActor["load"]>[0], resolver?: Parameters<Live2dActor["load"]>[1]): Promise<InternalModel>;
|
|
274
|
+
loadAsset(asset: ModelAsset): Promise<InternalModel>;
|
|
227
275
|
setParameter(id: string, value: number): void;
|
|
276
|
+
listParameters(): readonly _doki_land_live2d_renderer.ParameterBinding[];
|
|
277
|
+
listMotionGroups(): Record<string, readonly _doki_land_live2d_core.MotionDefinition[]>;
|
|
278
|
+
playMotion(group: string, index?: number, options?: PlayMotionActorOptions): Promise<boolean>;
|
|
279
|
+
stopMotion(opts?: {
|
|
280
|
+
fade?: boolean;
|
|
281
|
+
slot?: string;
|
|
282
|
+
}): void;
|
|
283
|
+
listPlayingMotions(): readonly {
|
|
284
|
+
slot: string;
|
|
285
|
+
group: string;
|
|
286
|
+
index: number;
|
|
287
|
+
time: number;
|
|
288
|
+
priority: number;
|
|
289
|
+
}[];
|
|
228
290
|
lookAt(stageX: number, stageY: number): void;
|
|
229
291
|
/** Internal: evaluate motion/physics and cache drawables for render. */
|
|
230
292
|
update(deltaTimeSeconds: number): DrawableMesh[] | null;
|
|
@@ -243,12 +305,15 @@ type PointerListener = (event: StagePointerEvent) => void;
|
|
|
243
305
|
declare class Live2dStageImpl implements Live2dStage {
|
|
244
306
|
#private;
|
|
245
307
|
constructor(options?: CreateLive2dStageFullOptions);
|
|
308
|
+
get assets(): ModelAssetRegistry;
|
|
246
309
|
get actors(): readonly Live2dActor[];
|
|
247
310
|
get pointerTracking(): PointerTrackingPolicy;
|
|
248
311
|
set pointerTracking(policy: PointerTrackingPolicy);
|
|
249
312
|
get renderer(): Renderer;
|
|
250
313
|
mount(canvas: HTMLCanvasElement): Promise<void>;
|
|
251
314
|
createActor(options?: CreateActorOptions): Live2dActor;
|
|
315
|
+
getActor(id: string): Live2dActor | null;
|
|
316
|
+
resize(width?: number, height?: number): void;
|
|
252
317
|
removeActor(actorOrId: Live2dActor | string): void;
|
|
253
318
|
defineLayers(layers: readonly string[]): void;
|
|
254
319
|
update(deltaTimeSeconds: number): void;
|