fix(webgl): share single Pixi context between KiraAvatar and Live2DCat
Eliminates WebGL bindBuffer/bindTexture spam from dual Application contexts. Cat model now loads onto KiraAvatar's shared stage via onAppReady callback.
This commit is contained in:
@@ -1,58 +1,42 @@
|
||||
import { useEffect, useRef } from 'react';
|
||||
|
||||
interface Props {
|
||||
app: any;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
export default function Live2DCat({ className }: Props) {
|
||||
const canvasRef = useRef<HTMLCanvasElement>(null);
|
||||
export default function Live2DCat({ app, className }: Props) {
|
||||
const modelRef = useRef<any>(null);
|
||||
|
||||
useEffect(() => {
|
||||
if (!app) return;
|
||||
let mounted = true;
|
||||
const canvas = canvasRef.current;
|
||||
if (!canvas) return;
|
||||
|
||||
let app: any = null;
|
||||
|
||||
const init = async () => {
|
||||
try {
|
||||
await loadScript('/live2d/cubism/live2dcubismcore.min.js');
|
||||
const { Application, Ticker } = await import('pixi.js');
|
||||
const { Live2DModel } = await import('pixi-live2d-display/cubism4');
|
||||
(Live2DModel as any).registerTicker(Ticker as any);
|
||||
|
||||
const w = canvas.clientWidth || 120;
|
||||
const h = canvas.clientHeight || 120;
|
||||
|
||||
app = new Application({
|
||||
view: canvas,
|
||||
width: w,
|
||||
height: h,
|
||||
antialias: true,
|
||||
resolution: Math.min(window.devicePixelRatio || 1, 2),
|
||||
backgroundAlpha: 0,
|
||||
autoDensity: true,
|
||||
});
|
||||
if (!mounted) { app.destroy(true); return; }
|
||||
|
||||
const model = await Live2DModel.from('/live2d/models/little-cat/LittleCat.model3.json', {
|
||||
autoInteract: false,
|
||||
});
|
||||
if (!mounted) { app.destroy(true); return; }
|
||||
if (!mounted) return;
|
||||
|
||||
// Scale to fit the small canvas
|
||||
// Scale to fit ~120x120 area on the shared stage
|
||||
const sw = app.screen.width;
|
||||
const sh = app.screen.height;
|
||||
const s = Math.min((sw * 0.85) / model.width, (sh * 0.85) / model.height);
|
||||
const targetW = 120;
|
||||
const targetH = 120;
|
||||
const s = Math.min(targetW / model.width, targetH / model.height);
|
||||
model.scale.set(s);
|
||||
model.anchor.set(0.5, 0.5);
|
||||
model.position.set(sw / 2, sh / 2);
|
||||
// Position near top-left corner with some padding
|
||||
model.position.set(80, 80);
|
||||
|
||||
app.stage.addChild(model as any);
|
||||
(model as any).isInteractive = () => false;
|
||||
modelRef.current = model;
|
||||
|
||||
try { model.motion('Idle'); } catch { /* */ }
|
||||
|
||||
} catch (e) {
|
||||
console.warn('[Live2DCat]', e);
|
||||
}
|
||||
@@ -62,26 +46,14 @@ export default function Live2DCat({ className }: Props) {
|
||||
|
||||
return () => {
|
||||
mounted = false;
|
||||
if (app) { app.destroy(true, { children: true }); }
|
||||
if (modelRef.current) {
|
||||
app.stage.removeChild(modelRef.current);
|
||||
modelRef.current.destroy?.();
|
||||
modelRef.current = null;
|
||||
}
|
||||
};
|
||||
}, []);
|
||||
}, [app]);
|
||||
|
||||
return (
|
||||
<canvas
|
||||
ref={canvasRef}
|
||||
className={className}
|
||||
style={{ display: 'block' }}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
function loadScript(src: string): Promise<void> {
|
||||
return new Promise((resolve, reject) => {
|
||||
if (document.querySelector('script[src="' + src + '"]')) { resolve(); return; }
|
||||
const s = document.createElement('script');
|
||||
s.src = src;
|
||||
s.onload = () => resolve();
|
||||
s.onerror = () => reject(new Error('Failed ' + src));
|
||||
document.head.appendChild(s);
|
||||
});
|
||||
// Live2DCat no longer renders its own canvas — it shares the KiraAvatar canvas
|
||||
return null;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user