Spaces:
Sleeping
Sleeping
| // Collision detection system | |
| import { PlayerState, Block, Enemy, Position } from './types'; | |
| import { GAME_CONFIG } from '../constants'; | |
| const { TILE_SIZE } = GAME_CONFIG; | |
| export interface CollisionResult { | |
| collided: boolean; | |
| side?: 'top' | 'bottom' | 'left' | 'right'; | |
| block?: Block; | |
| } | |
| export class Collision { | |
| // AABB collision check between two rectangles | |
| checkAABB( | |
| a: { x: number; y: number; width: number; height: number }, | |
| b: { x: number; y: number; width: number; height: number } | |
| ): boolean { | |
| return ( | |
| a.x < b.x + b.width && | |
| a.x + a.width > b.x && | |
| a.y < b.y + b.height && | |
| a.y + a.height > b.y | |
| ); | |
| } | |
| // Determine collision side | |
| getCollisionSide( | |
| player: PlayerState, | |
| block: Block, | |
| prevY: number | |
| ): 'top' | 'bottom' | 'left' | 'right' { | |
| const playerBottom = player.y + player.height; | |
| const playerTop = player.y; | |
| const blockBottom = block.y + block.height; | |
| const blockTop = block.y; | |
| // Check if landing on top | |
| if (prevY + player.height <= block.y + 2 && player.vy >= 0) { | |
| return 'top'; | |
| } | |
| // Check if hitting from below | |
| if (prevY >= blockBottom - 2 && player.vy < 0) { | |
| return 'bottom'; | |
| } | |
| // Determine left/right based on position | |
| const playerCenterX = player.x + player.width / 2; | |
| const blockCenterX = block.x + block.width / 2; | |
| return playerCenterX < blockCenterX ? 'right' : 'left'; | |
| } | |
| // Check player collision with blocks | |
| checkPlayerBlockCollision( | |
| player: PlayerState, | |
| blocks: Block[], | |
| prevY: number | |
| ): { grounded: boolean; hitBlock?: Block } { | |
| let grounded = false; | |
| let hitBlock: Block | undefined; | |
| for (const block of blocks) { | |
| // Skip coins - they have separate handling | |
| if (block.type === 'coin') continue; | |
| if (this.checkAABB(player, block)) { | |
| const side = this.getCollisionSide(player, block, prevY); | |
| switch (side) { | |
| case 'top': | |
| player.y = block.y - player.height; | |
| player.vy = 0; | |
| player.isJumping = false; | |
| player.isFalling = false; | |
| grounded = true; | |
| break; | |
| case 'bottom': | |
| player.y = block.y + block.height; | |
| player.vy = 1; // Small downward velocity | |
| if (block.type === 'question' && !block.isHit) { | |
| hitBlock = block; | |
| } | |
| break; | |
| case 'left': | |
| player.x = block.x + block.width; | |
| player.vx = 0; | |
| break; | |
| case 'right': | |
| player.x = block.x - player.width; | |
| player.vx = 0; | |
| break; | |
| } | |
| } | |
| } | |
| return { grounded, hitBlock }; | |
| } | |
| // Check enemy collision with blocks (ground only) | |
| checkEnemyBlockCollision(enemy: Enemy, blocks: Block[]): void { | |
| for (const block of blocks) { | |
| if (block.type === 'coin') continue; | |
| if (this.checkAABB(enemy, block)) { | |
| // Land on top | |
| if (enemy.vy > 0) { | |
| enemy.y = block.y - enemy.height; | |
| enemy.vy = 0; | |
| } | |
| // Side collision - reverse direction | |
| if ( | |
| enemy.x + enemy.width > block.x && | |
| enemy.x < block.x + block.width | |
| ) { | |
| if (enemy.y + enemy.height > block.y + 4) { | |
| enemy.direction *= -1; | |
| } | |
| } | |
| } | |
| } | |
| } | |
| // Check player collision with enemy | |
| checkPlayerEnemyCollision( | |
| player: PlayerState, | |
| enemy: Enemy | |
| ): { killed: boolean; playerHit: boolean } { | |
| if (!enemy.isAlive || !player.isAlive) { | |
| return { killed: false, playerHit: false }; | |
| } | |
| if (this.checkAABB(player, enemy)) { | |
| // Check if player is landing on enemy (stomping) | |
| if (player.vy > 0 && player.y + player.height < enemy.y + enemy.height / 2) { | |
| return { killed: true, playerHit: false }; | |
| } else { | |
| return { killed: false, playerHit: true }; | |
| } | |
| } | |
| return { killed: false, playerHit: false }; | |
| } | |
| // Check player collision with coin | |
| checkCoinCollision(player: PlayerState, blocks: Block[]): Block[] { | |
| const collectedCoins: Block[] = []; | |
| blocks.forEach((block, index) => { | |
| if (block.type === 'coin' && this.checkAABB(player, block)) { | |
| collectedCoins.push(block); | |
| } | |
| }); | |
| return collectedCoins; | |
| } | |
| // Check if player reached the flag | |
| checkFlagCollision(player: PlayerState, flagPosition: Position): boolean { | |
| return ( | |
| player.x + player.width >= flagPosition.x && | |
| player.x <= flagPosition.x + 50 && | |
| player.y <= flagPosition.y + 100 | |
| ); | |
| } | |
| // Check if player fell off the level | |
| checkFallDeath(player: PlayerState, levelHeight: number): boolean { | |
| return player.y > levelHeight + 100; | |
| } | |
| } | |