feat: add collision detection between falling letters and ship

- Detect collisions when letters reach ship's Y position
- Both letter and ship explode with particle animations on collision
- Ship explosion displays expanding particles in yellow, orange, and red
- Letters explode with their existing animation system
- After collision, letters clear and new letter spawns automatically
- Game continues after collision (no game-over condition yet)
- Collision threshold tuned for reliable detection (30px from ship center)
- Added shipState tracking to GameStatusManager
- Ship position continuously synced during game loop
This commit is contained in:
2025-11-03 22:57:41 +00:00
parent d69a9a6cc9
commit 08c2d40d23
4 changed files with 186 additions and 95 deletions
+13 -5
View File
@@ -1,5 +1,5 @@
import { useState, useEffect } from "react";
import type { GameManager, GameOptions, KeyLogEntry } from "../types";
import type { GameManager, GameOptions, KeyLogEntry, FallingLetter } from "../types";
import { useBoard } from "../useBoard";
import { useCursor } from "../useCursor";
import { useGameStatus } from "../useGameStatus";
@@ -13,14 +13,18 @@ export function useGame(
const boardManager = useBoard();
const cursorManager = useCursor();
const gameStatusManager = useGameStatus();
// Wrap renderBoard to include player position
const renderBoardWrapped = (letters: any = []) => {
boardManager.renderBoard(cursorManager.position(), letters);
boardManager.renderBoard(cursorManager.position(), letters, gameStatusManager.shipState, gameStatusManager.shipExplosionTime);
};
// Game loop tick handler
const handleGameLoopTick = (deltaTime: number, letters: any) => {
const handleGameLoopTick = (deltaTime: number, letters: FallingLetter[]) => {
const playerPos = cursorManager.position();
gameStatusManager.updatePlayerX(playerPos.x);
if (letters.length > 0) {
const letter = letters[0];
cursorManager.setTargetX(letter.x);
@@ -30,14 +34,18 @@ export function useGame(
renderBoardWrapped(letters);
};
const gameStatusManager = useGameStatus(handleGameLoopTick);
const scoreManager = useScore(gameStatusManager);
// Set the game loop callback
useEffect(() => {
gameStatusManager.setGameLoopCallback(handleGameLoopTick);
}, [gameStatusManager, cursorManager, boardManager]);
// Initial render on mount
useEffect(() => {
renderBoardWrapped();
}, []);
const scoreManager = useScore(gameStatusManager);
const gameManager: GameManager = {
containerRef: boardManager.containerRef,
renderBoard: renderBoardWrapped,