diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index 8a4b51f..1960f1c 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -33,7 +33,6 @@ export default function App() { const [currentOutfit, setCurrentOutfit] = useState('cozy-hoodie'); const [currentAcc, setCurrentAcc] = useState(null); const [textInput, setTextInput] = useState(''); - const [pixiApp, setPixiApp] = useState(null); useEffect(() => { if (preferences.scene) setCurrentSceneId(preferences.scene); @@ -148,7 +147,6 @@ export default function App() { outfit={currentOutfit} accessory={currentAcc} onTalkToggle={handleTalkToggle} - onAppReady={setPixiApp} /> @@ -157,7 +155,7 @@ export default function App() { - + diff --git a/frontend/src/components/KiraAvatar.tsx b/frontend/src/components/KiraAvatar.tsx index 840d335..e91d522 100644 --- a/frontend/src/components/KiraAvatar.tsx +++ b/frontend/src/components/KiraAvatar.tsx @@ -7,7 +7,6 @@ interface Props { outfit: string; accessory: string | null; onTalkToggle: () => void; - onAppReady?: (app: any) => void; } type ExpressionName = 'Normal' | 'Smile' | 'Sad' | 'Angry' | 'Surprised' | 'Blushing'; @@ -83,7 +82,6 @@ export default function KiraAvatar(props: Props) { app.stage.addChild(model as any); (model as any).isInteractive = () => false; if (!mounted) return; - if (props.onAppReady) props.onAppReady(app); try { textureRef.current = { diff --git a/frontend/src/components/Live2DCat.tsx b/frontend/src/components/Live2DCat.tsx index 65fd736..ab4c50d 100644 --- a/frontend/src/components/Live2DCat.tsx +++ b/frontend/src/components/Live2DCat.tsx @@ -1,42 +1,56 @@ import { useEffect, useRef } from 'react'; interface Props { - app: any; // shared Pixi Application from KiraAvatar + className?: string; } -export default function Live2DCat({ app }: Props) { - const modelRef = useRef(null); +export default function Live2DCat({ className }: Props) { + const canvasRef = useRef(null); useEffect(() => { - if (!app) return; let mounted = true; + const canvas = canvasRef.current; + if (!canvas) return; + + let app: any = null; const init = async () => { try { + const pixi = await import('pixi.js'); const { Live2DModel } = await import('pixi-live2d-display/cubism4'); - const { Ticker } = await import('pixi.js'); - (Live2DModel as any).registerTicker(Ticker as any); + (Live2DModel as any).registerTicker(pixi.Ticker as any); + + const w = canvas.clientWidth || 120; + const h = canvas.clientHeight || 120; + + // Use explicit CanvasRenderer to avoid WebGL context conflict with KiraAvatar + app = new pixi.Application({ + view: canvas, + width: w, + height: h, + antialias: true, + resolution: Math.min(window.devicePixelRatio || 1, 2), + backgroundAlpha: 0, + autoDensity: true, + // Use WebGL1 context (more compatible with separate contexts) + preferWebGLVersion: 1, + } as any); + if (!mounted) { app.destroy(true); return; } const model = await Live2DModel.from('/live2d/models/little-cat/LittleCat.model3.json', { autoInteract: false, }); - if (!mounted) { model.destroy(); return; } + if (!mounted) { app.destroy(true); return; } - // Scale cat to ~15% of avatar canvas height - const targetH = app.screen.height * 0.18; - const s = targetH / model.height; + const sw = app.screen.width; + const sh = app.screen.height; + const s = Math.min((sw * 0.85) / model.width, (sh * 0.85) / model.height); model.scale.set(s); model.anchor.set(0.5, 0.5); - - // Bottom-right corner of Kira's canvas - model.position.set( - app.screen.width - (model.width * s) / 2 - 10, - app.screen.height - (model.height * s) / 2 - 10 - ); + model.position.set(sw / 2, sh / 2); app.stage.addChild(model as any); (model as any).isInteractive = () => false; - modelRef.current = model; try { model.motion('Idle'); } catch { /* */ } } catch (e) { @@ -48,13 +62,15 @@ export default function Live2DCat({ app }: Props) { return () => { mounted = false; - if (modelRef.current && app) { - app.stage.removeChild(modelRef.current); - modelRef.current.destroy(); - modelRef.current = null; - } + if (app) { app.destroy(true, { children: true }); } }; - }, [app]); + }, []); - return null; // renders on KiraAvatar's shared canvas + return ( + + ); } diff --git a/frontend/src/components/PetZone.tsx b/frontend/src/components/PetZone.tsx index debfe14..d530a8f 100644 --- a/frontend/src/components/PetZone.tsx +++ b/frontend/src/components/PetZone.tsx @@ -1,23 +1,15 @@ import Live2DCat from './Live2DCat'; -interface Props { - app?: any; -} - -export default function PetZone({ app }: Props) { +export default function PetZone() { return ( -
+

🐱 Pet Zone

- {app ? ( -
- - Mochi -
- ) : ( -

loading...

- )} +
+ + Mochi +
); }