Press n or j to go to the next uncovered block, b, p or k for the previous block.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 | /** * PhaserGame.tsx * * This component wraps the Phaser game instance within a React component. * It initializes the Phaser game on mount, handles cleanup, and passes the reference * to both the parent component and the scene once it becomes available. */ import { forwardRef, useEffect, useLayoutEffect, useRef } from 'react'; import StartGame from './game/main'; import { EventBus } from './game/EventBus'; import { AAC_DATA } from './Foods'; /** * Interface to expose the Phaser game and current scene instance to parent components via ref. */ export interface IRefPhaserGame { game: Phaser.Game | null; scene: Phaser.Scene | null; } /** * Props for the PhaserGame component. * * @property currentActiveScene - Callback to receive the active Phaser scene instance. */ interface IProps { currentActiveScene?: (scene_instance: Phaser.Scene) => void } /** * PhaserGame is a React component that instantiates the Phaser game engine, * and notifies the parent when the current scene becomes available. * * It mounts the game in a container div and properly cleans up on unmount. */ export const PhaserGame = forwardRef<IRefPhaserGame, IProps>(function PhaserGame({ currentActiveScene }, ref) { const game = useRef<Phaser.Game | null>(null!); /** * useLayoutEffect creates the Phaser.Game instance when the component mounts, * and ensures cleanup when the component unmounts. */ useLayoutEffect(() => { if (game.current === null) { game.current = StartGame("game-container"); if (typeof ref === 'function') { ref({ game: game.current, scene: null }); } else if (ref) { ref.current = { game: game.current, scene: null }; } } return () => { if (game.current) { game.current.destroy(true); if (game.current !== null) { game.current = null; } } } }, [ref]); /** * useEffect listens for the Phaser event `current-scene-ready`, * then injects AAC food keys into the scene and shares the scene reference via ref. */ useEffect(() => { // Define the handler outside, so the reference is stable const handler = (scene_instance: Phaser.Scene) => { const foodKeys = AAC_DATA.categories.flatMap(cat => cat.foods.map(f => f.id)); if ('setFoodKeys' in scene_instance && typeof scene_instance['setFoodKeys'] === 'function') { scene_instance['setFoodKeys'](foodKeys); } if (currentActiveScene && typeof currentActiveScene === 'function') { currentActiveScene(scene_instance); } if (typeof ref === 'function') { ref({ game: game.current, scene: scene_instance }); } else if (ref) { ref.current = { game: game.current, scene: scene_instance }; } }; EventBus.on('current-scene-ready', handler); return () => { EventBus.off('current-scene-ready', handler); }; }, [currentActiveScene, ref]); // Prevent iPad/iPhone swipe navigation during gameplay, but allow edge gestures for system nav useEffect(() => { const container = document.getElementById('game-container'); if (!container || !('ontouchstart' in window)) return; function preventEdgeSwipe(e: TouchEvent) { const touch = e.touches[0]; if (!touch) return; // Allow system gesture if <10px from left/right edge const atLeftEdge = touch.clientX < 10; const atRightEdge = touch.clientX > window.innerWidth - 10; if (!atLeftEdge && !atRightEdge) { e.preventDefault(); } } container.addEventListener('touchstart', preventEdgeSwipe, { passive: false }); container.addEventListener('touchmove', preventEdgeSwipe, { passive: false }); return () => { container.removeEventListener('touchstart', preventEdgeSwipe); container.removeEventListener('touchmove', preventEdgeSwipe); }; }, []); /** * Renders the HTML container that will hold the Phaser canvas. */ return ( <div id="game-container"></div> ); }); |