diff --git a/packages/client/src/assets/images/sprites/wall.svg b/packages/client/src/assets/images/sprites/wall.svg index 0413d77..380f0dd 100644 --- a/packages/client/src/assets/images/sprites/wall.svg +++ b/packages/client/src/assets/images/sprites/wall.svg @@ -1,9 +1,9 @@ - - + + - + diff --git a/packages/client/src/components/Game/Game.tsx b/packages/client/src/components/Game/Game.tsx index 6adfdcf..ddef2c4 100644 --- a/packages/client/src/components/Game/Game.tsx +++ b/packages/client/src/components/Game/Game.tsx @@ -1,11 +1,17 @@ import React, { useCallback, useEffect, useRef, useState } from 'react' import './Game.scss' -import { initializeEnemies } from '@/components/Game/enemy' +import { + initializeCampanyEnemies, + initializeRandomEnemies, +} from '@/components/Game/enemy' import { PLAYER_DEFAULT_PARAMS } from '@/components/Game/player' import { gameLoop } from '@/components/Game/gameLoop' import { handleKeyDown, handleKeyUp } from '@/components/Game/controls' import { AbstractEntity, Obstacle } from '@/components/Game/gameTypes' -import { initializeObstacle } from '@/components/Game/obstacle' +import { + initializeCompanyMapObstacle, + initializeRandomObstacle, +} from '@/components/Game/obstacle' import { Modal } from '../common/Modal/Modal' const livesUse = 3 @@ -13,9 +19,9 @@ const livesUse = 3 export const Game: React.FC = () => { const canvasRef = useRef(null) const playerRef = useRef(PLAYER_DEFAULT_PARAMS) - const enemiesRef = useRef(initializeEnemies(5)) + const enemiesRef = useRef(initializeRandomEnemies(5)) const bulletsRef = useRef([]) - const obstaclesRef = useRef(initializeObstacle()) + const obstaclesRef = useRef(initializeRandomObstacle(20)) const livesRef = useRef(livesUse) const [gameStarted, setGameStarted] = useState(false) const [isPaused, setIsPaused] = useState(false) @@ -78,8 +84,20 @@ export const Game: React.FC = () => { isPausedRef.current = false setIsGameOver(false) livesRef.current = livesUse - playerRef.current = PLAYER_DEFAULT_PARAMS - enemiesRef.current = initializeEnemies(5) + playerRef.current = { ...PLAYER_DEFAULT_PARAMS } + enemiesRef.current = initializeRandomEnemies(5) + obstaclesRef.current = initializeRandomObstacle(20) + } + + const startCompany = () => { + setGameStarted(true) + setIsPaused(false) + isPausedRef.current = false + setIsGameOver(false) + livesRef.current = livesUse + playerRef.current = { ...PLAYER_DEFAULT_PARAMS } + enemiesRef.current = initializeCampanyEnemies() + obstaclesRef.current = initializeCompanyMapObstacle() } return ( @@ -88,7 +106,10 @@ export const Game: React.FC = () => { {!gameStarted ? ( - + <> + + + ) : (

Игра окончена

- + + ) diff --git a/packages/client/src/components/Game/enemy.tsx b/packages/client/src/components/Game/enemy.tsx index 032c7c1..d966e0c 100644 --- a/packages/client/src/components/Game/enemy.tsx +++ b/packages/client/src/components/Game/enemy.tsx @@ -3,25 +3,37 @@ import { AbstractEntity, Enemy, Obstacle } from '@/components/Game/gameTypes' import { createBullet } from '@/components/Game/bullet' import { detectCollision } from '@/components/Game/collision' -export const initializeEnemies = (numberOfEnemies: number) => { +const enemyParams = { + width: 70, + height: 70, + speed: 1, + direction: { x: 0, y: 0 }, +} + +export const initializeRandomEnemies = (numberOfEnemies: number) => { const initialEnemies: Enemy[] = [] for (let i = 0; i < numberOfEnemies; i++) { // количество врагов const { x, y } = getRandomEdgePosition(800, 600) const enemy: Enemy = { + ...enemyParams, id: i, x, y, - width: 70, - height: 70, - speed: 1, - direction: { x: 0, y: 0 }, } initialEnemies.push(enemy) } return initialEnemies as Enemy[] } +export const initializeCampanyEnemies = (): Enemy[] => { + return [ + { ...enemyParams, id: 0, x: 50, y: 55 }, + { ...enemyParams, id: 1, x: 320, y: 250 }, + { ...enemyParams, id: 2, x: 715, y: 60 }, + ] +} + export const updateEnemyPositions = ( player: AbstractEntity, enemiesRef: React.MutableRefObject, @@ -97,8 +109,8 @@ const isPositionOccupied = ( } const respawnEnemy = ( - enemy: AbstractEntity, - enemies: AbstractEntity[], + enemy: Enemy, + enemies: Enemy[], canvasWidth: number, canvasHeight: number ) => { @@ -115,7 +127,7 @@ const respawnEnemy = ( // Пример использования respawnEnemy в вашем коде export const respawnEnemies = ( - enemiesRef: React.MutableRefObject, + enemiesRef: React.MutableRefObject, canvasRef: React.MutableRefObject ) => { if (!canvasRef.current) { diff --git a/packages/client/src/components/Game/gameLoop.tsx b/packages/client/src/components/Game/gameLoop.tsx index efc6562..5061a44 100644 --- a/packages/client/src/components/Game/gameLoop.tsx +++ b/packages/client/src/components/Game/gameLoop.tsx @@ -1,10 +1,5 @@ -import { HandlePlayerHit, resetPlayerPosition } from './player' -import { - updateEnemyPositions, - respawnEnemies, - killEnemy, - handleEnemyShooting, -} from './enemy' +import { HandlePlayerHit } from './player' +import { updateEnemyPositions, killEnemy, handleEnemyShooting } from './enemy' import { clearCanvas, drawPlayer, @@ -104,16 +99,11 @@ export const gameLoop = ( } }) - // Проверка на столкновения между игроком и врагами - enemiesRef.current.forEach(enemy => { - if (detectEnemyCollision(playerRef.current, enemy)) { - // Обработка столкновения: уменьшаем жизни - HandlePlayerHit( - livesRef, - handleGameOver, - () => resetPlayerPosition(playerRef), - () => respawnEnemies(enemiesRef, canvasRef) - ) - } - }) + const collidedEnemy = enemiesRef.current.find(enemy => + detectEnemyCollision(playerRef.current, enemy) + ) + + if (collidedEnemy) { + HandlePlayerHit(livesRef, playerRef, enemiesRef, canvasRef, handleGameOver) + } } diff --git a/packages/client/src/components/Game/obstacle.tsx b/packages/client/src/components/Game/obstacle.tsx index dba6b8f..e1e9d7e 100644 --- a/packages/client/src/components/Game/obstacle.tsx +++ b/packages/client/src/components/Game/obstacle.tsx @@ -5,10 +5,42 @@ import { detectObstacleCollision, } from '@/components/Game/collision' -export const initializeObstacle = (): Obstacle[] => { +export const initializeCompanyMapObstacle = (): Obstacle[] => { + return [ + { x: 0, y: 0, width: 120, height: 50 }, + { x: 200, y: 0, width: 70, height: 50 }, + { x: 350, y: 0, width: 70, height: 50 }, + { x: 500, y: 0, width: 120, height: 50 }, + { x: 700, y: 0, width: 100, height: 50 }, + + { x: 0, y: 130, width: 50, height: 120 }, + { x: 130, y: 130, width: 50, height: 70 }, + { x: 260, y: 130, width: 50, height: 200 }, + { x: 390, y: 130, width: 50, height: 70 }, + { x: 520, y: 130, width: 50, height: 120 }, + { x: 650, y: 130, width: 50, height: 125 }, + + { x: 0, y: 330, width: 120, height: 50 }, + { x: 200, y: 330, width: 150, height: 50 }, + { x: 350, y: 330, width: 70, height: 50 }, + { x: 500, y: 330, width: 120, height: 50 }, + { x: 700, y: 330, width: 100, height: 100 }, + + { x: 0, y: 460, width: 50, height: 140 }, + { x: 130, y: 530, width: 50, height: 70 }, + { x: 260, y: 480, width: 50, height: 120 }, + { x: 390, y: 460, width: 50, height: 70 }, + { x: 520, y: 490, width: 50, height: 120 }, + { x: 650, y: 480, width: 50, height: 120 }, + ] +} + +export const initializeRandomObstacle = ( + numberOfObstacles: number +): Obstacle[] => { const obstacles: Obstacle[] = [] - while (obstacles.length < 20) { + while (obstacles.length < numberOfObstacles) { const { x, y } = getRandomPosition(800, 600) const obstacle: Obstacle = { x, y, width: 50, height: 50 } diff --git a/packages/client/src/components/Game/player.tsx b/packages/client/src/components/Game/player.tsx index 614f983..e1f1f72 100644 --- a/packages/client/src/components/Game/player.tsx +++ b/packages/client/src/components/Game/player.tsx @@ -1,8 +1,9 @@ -import { AbstractEntity } from '@/components/Game/gameTypes' +import { AbstractEntity, Enemy } from '@/components/Game/gameTypes' +import { respawnEnemies } from '@/components/Game/enemy' export const PLAYER_DEFAULT_PARAMS = { x: 400, - y: 300, + y: 560, width: 70, height: 70, speed: 3, @@ -21,23 +22,24 @@ export const resetPlayerPosition = ( /** * Функция для обработки столкновения игрока с врагом. * @param livesRef - Ссылка на текущее количество жизней игрока. + * @param playerRef - Ссылка на игрока. + * @param enemiesRef - Ссылка на массив врагов. + * @param canvasRef - Ссылка на HTML-элемент canvas. * @param handleGameOver - Обработчик события окончания игры. - * @param resetPlayerPosition - Функция для сброса позиции игрока. - * @param respawnEnemies - Функция для респауна врагов. */ export const HandlePlayerHit = ( livesRef: React.MutableRefObject, - handleGameOver: () => void, - resetPlayerPosition: () => void, - respawnEnemies: () => void + playerRef: React.MutableRefObject, + enemiesRef: React.MutableRefObject, + canvasRef: React.MutableRefObject, + handleGameOver: () => void ) => { - const newLives = livesRef.current - 1 + livesRef.current -= 1 - if (newLives <= 0) { + if (livesRef.current <= 0) { handleGameOver() } else { - livesRef.current = newLives - resetPlayerPosition() // Сбрасываем позицию игрока - respawnEnemies() // Респавн врагов + resetPlayerPosition(playerRef) + respawnEnemies(enemiesRef, canvasRef) } } diff --git a/packages/client/src/components/Game/utils.tsx b/packages/client/src/components/Game/utils.tsx index aaacbc7..d83bca5 100644 --- a/packages/client/src/components/Game/utils.tsx +++ b/packages/client/src/components/Game/utils.tsx @@ -122,8 +122,23 @@ export const drawObstacles = ( context: CanvasRenderingContext2D, obstacles: Obstacle[] ) => { + const SPRITE_SIZE = 50 + obstacles.forEach(obstacle => { - context.drawImage(wallSprite, obstacle.x, obstacle.y) + const horizontalCount = Math.ceil(obstacle.width / SPRITE_SIZE) + const verticalCount = Math.ceil(obstacle.height / SPRITE_SIZE) + + Array.from({ length: horizontalCount }).forEach((_, i) => { + Array.from({ length: verticalCount }).forEach((_, j) => { + const x = obstacle.x + i * SPRITE_SIZE + const y = obstacle.y + j * SPRITE_SIZE + + const width = Math.min(SPRITE_SIZE, obstacle.width - i * SPRITE_SIZE) + const height = Math.min(SPRITE_SIZE, obstacle.height - j * SPRITE_SIZE) + + context.drawImage(wallSprite, 0, 0, width, height, x, y, width, height) + }) + }) }) }