From c3c40977bbee92cf6890d58ad296b3c2e125fce7 Mon Sep 17 00:00:00 2001 From: Daniil <77277774+shamemask@users.noreply.github.com> Date: Thu, 26 Sep 2024 13:26:24 +0300 Subject: [PATCH 01/19] [SOK-26] +game prototype --- packages/client/src/pages/Game/Game.scss | 42 +++++++ packages/client/src/pages/Game/Game.tsx | 142 ++++++++++++++++++++++- 2 files changed, 182 insertions(+), 2 deletions(-) create mode 100644 packages/client/src/pages/Game/Game.scss diff --git a/packages/client/src/pages/Game/Game.scss b/packages/client/src/pages/Game/Game.scss new file mode 100644 index 0000000..a1842d2 --- /dev/null +++ b/packages/client/src/pages/Game/Game.scss @@ -0,0 +1,42 @@ +@import '../../scss/vars'; + +.game-container { + display: flex; + justify-content: center; + align-items: center; + width: 100vw; + height: 100vh; + background-color: $c_default-background; + font-family: $f_default-font-family; + overflow: hidden; +} + +canvas { + border-radius: $border-radius--default; + box-shadow: 0 4px 8px rgba($c_black, 0.5); + background-image: $img_default-background; + background-size: cover; +} + +.player { + position: absolute; + background-color: $c_button; + border: 2px solid $c_button-top; + border-radius: 4px; + transition: transform 0.1s ease; +} + +.obstacle { + position: absolute; + background-color: $c_button-bottom; + border: 2px solid $c_black; + border-radius: 4px; +} + +.game-text { + color: $c_font-default; + font-size: 16px; + position: absolute; + top: 10px; + left: 10px; +} diff --git a/packages/client/src/pages/Game/Game.tsx b/packages/client/src/pages/Game/Game.tsx index 908d885..5b4a644 100644 --- a/packages/client/src/pages/Game/Game.tsx +++ b/packages/client/src/pages/Game/Game.tsx @@ -1,3 +1,141 @@ -export const Game = () => { - return <>Тут будет игра +import React, { useEffect, useRef, useState } from 'react' +import './Game.scss' + +interface Player { + x: number + y: number + width: number + height: number + speed: number + direction: { x: number; y: number } +} + +interface Obstacle { + x: number + y: number + width: number + height: number +} + +export const Game: React.FC = () => { + const canvasRef = useRef(null) + const [player, setPlayer] = useState({ + x: 50, + y: 50, + width: 30, + height: 30, + speed: 5, + direction: { x: 0, y: 0 }, + }) + + const obstacles: Obstacle[] = [ + { x: 200, y: 200, width: 50, height: 50 }, + { x: 400, y: 100, width: 50, height: 50 }, + ] + + useEffect(() => { + const canvas = canvasRef.current + if (!canvas) return + + const context = canvas.getContext('2d') + if (!context) return + + const handleKeyDown = (e: KeyboardEvent) => { + switch (e.key) { + case 'ArrowUp': + setPlayer(prev => ({ ...prev, direction: { x: 0, y: -1 } })) + break + case 'ArrowDown': + setPlayer(prev => ({ ...prev, direction: { x: 0, y: 1 } })) + break + case 'ArrowLeft': + setPlayer(prev => ({ ...prev, direction: { x: -1, y: 0 } })) + break + case 'ArrowRight': + setPlayer(prev => ({ ...prev, direction: { x: 1, y: 0 } })) + break + default: + break + } + } + + const handleKeyUp = () => { + setPlayer(prev => ({ ...prev, direction: { x: 0, y: 0 } })) + } + + const detectCollision = (player: Player, obstacle: Obstacle): boolean => { + return ( + player.x < obstacle.x + obstacle.width && + player.x + player.width > obstacle.x && + player.y < obstacle.y + obstacle.height && + player.y + player.height > obstacle.y + ) + } + + const gameLoop = (timestamp: number) => { + const deltaTime = timestamp - lastTime + if (deltaTime > 1000 / 60) { + updatePlayerPosition() + clearCanvas() + drawPlayer() + drawObstacles(context) + lastTime = timestamp + } + requestAnimationFrame(gameLoop) + } + + let lastTime = 0 + const updatePlayerPosition = () => { + const newX = player.x + player.direction.x * player.speed + const newY = player.y + player.direction.y * player.speed + + let collisionDetected = false + obstacles.forEach(obstacle => { + if (detectCollision({ ...player, x: newX, y: newY }, obstacle)) { + collisionDetected = true + } + }) + + if (!collisionDetected) { + setPlayer(prev => ({ + ...prev, + x: newX, + y: newY, + })) + } + } + + const clearCanvas = () => { + context.clearRect(0, 0, canvas.width, canvas.height) + } + + const drawPlayer = () => { + context.fillStyle = 'gray' + context.fillRect(player.x, player.y, player.width, player.height) + } + + const drawObstacles = (context: CanvasRenderingContext2D) => { + context.fillStyle = 'black' + obstacles.forEach(obstacle => { + context.fillRect( + obstacle.x, + obstacle.y, + obstacle.width, + obstacle.height + ) + }) + } + + window.addEventListener('keydown', handleKeyDown) + window.addEventListener('keyup', handleKeyUp) + + gameLoop(0) + + return () => { + window.removeEventListener('keydown', handleKeyDown) + window.removeEventListener('keyup', handleKeyUp) + } + }, [player, obstacles]) + + return } From ab6292a0ce91ace230faa7b4238b6a4a7d31c813 Mon Sep 17 00:00:00 2001 From: Daniil <77277774+shamemask@users.noreply.github.com> Date: Thu, 26 Sep 2024 21:14:58 +0300 Subject: [PATCH 02/19] [SOK-27] +enemys +obstacles +start +pause --- packages/client/src/app/App.tsx | 4 +- .../src/{pages => components}/Game/Game.scss | 47 +++++- packages/client/src/components/Game/Game.tsx | 133 ++++++++++++++++ .../client/src/components/Game/collision.tsx | 0 .../client/src/components/Game/controls.tsx | 46 ++++++ packages/client/src/components/Game/enemy.tsx | 61 +++++++ .../client/src/components/Game/gameLoop.tsx | 90 +++++++++++ .../client/src/components/Game/gameTypes.tsx | 28 ++++ .../client/src/components/Game/obstacle.tsx | 12 ++ .../client/src/components/Game/player.tsx | 80 ++++++++++ packages/client/src/components/Game/utils.tsx | 52 ++++++ packages/client/src/pages/Game/Game.tsx | 150 ++---------------- packages/client/src/pages/Game/GamePage.scss | 7 + 13 files changed, 565 insertions(+), 145 deletions(-) rename packages/client/src/{pages => components}/Game/Game.scss (50%) create mode 100644 packages/client/src/components/Game/Game.tsx create mode 100644 packages/client/src/components/Game/collision.tsx create mode 100644 packages/client/src/components/Game/controls.tsx create mode 100644 packages/client/src/components/Game/enemy.tsx create mode 100644 packages/client/src/components/Game/gameLoop.tsx create mode 100644 packages/client/src/components/Game/gameTypes.tsx create mode 100644 packages/client/src/components/Game/obstacle.tsx create mode 100644 packages/client/src/components/Game/player.tsx create mode 100644 packages/client/src/components/Game/utils.tsx create mode 100644 packages/client/src/pages/Game/GamePage.scss diff --git a/packages/client/src/app/App.tsx b/packages/client/src/app/App.tsx index b960df3..e5d3966 100644 --- a/packages/client/src/app/App.tsx +++ b/packages/client/src/app/App.tsx @@ -7,7 +7,7 @@ import PublicLayout from '@/layouts/public-layout' import RootLayout from '@/layouts/root-layout' import { Error } from '@/pages/Error/Error' import { Forum } from '@/pages/Forum/Forum' -import { Game } from '@/pages/Game/Game' +import { GamePage } from '@/pages/Game/Game' import { Leaderboard } from '@/pages/Leaderboard/Leaderboard' import { Main } from '@/pages/Main/Main' import { ChangePassword } from '@/pages/Profile/ChangePassword' @@ -40,7 +40,7 @@ const routerConfig = createBrowserRouter([ children: [ { path: '/game', - element: , + element: , }, { path: '/forum', diff --git a/packages/client/src/pages/Game/Game.scss b/packages/client/src/components/Game/Game.scss similarity index 50% rename from packages/client/src/pages/Game/Game.scss rename to packages/client/src/components/Game/Game.scss index a1842d2..b640093 100644 --- a/packages/client/src/pages/Game/Game.scss +++ b/packages/client/src/components/Game/Game.scss @@ -3,14 +3,39 @@ .game-container { display: flex; justify-content: center; - align-items: center; - width: 100vw; - height: 100vh; + flex-direction: column; + align-items: stretch; background-color: $c_default-background; font-family: $f_default-font-family; overflow: hidden; + position: relative; } +.modal { + position: absolute; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + background-color: rgba(0, 0, 0, 0.8); + color: white; + padding: 20px; + border-radius: 8px; + text-align: center; +} + +button { + margin-top: 10px; + padding: 10px 20px; + border: none; + background-color: #839d22; + color: white; + border-radius: 8px; + cursor: pointer; +} + +button:hover { + background-color: #53650b; +} canvas { border-radius: $border-radius--default; box-shadow: 0 4px 8px rgba($c_black, 0.5); @@ -40,3 +65,19 @@ canvas { top: 10px; left: 10px; } + +.game-info { + position: absolute; + top: 20px; + left: 20px; + color: $c_font-default; + font-size: 18px; + background: rgba($c_black, 0.5); + padding: 8px 12px; + border-radius: 6px; +} + +.lives { + color: $c_button-top; + font-weight: bold; +} diff --git a/packages/client/src/components/Game/Game.tsx b/packages/client/src/components/Game/Game.tsx new file mode 100644 index 0000000..c664303 --- /dev/null +++ b/packages/client/src/components/Game/Game.tsx @@ -0,0 +1,133 @@ +import React, { useCallback, useEffect, useRef, useState } from 'react' +import './Game.scss' +import { initializeEnemies } from '@/components/Game/enemy' +import { initializePlayer } from '@/components/Game/player' +import { gameLoop } from '@/components/Game/gameLoop' +import { + handleKeyDown, + handleKeyUp, + updatePlayerMovement, +} from '@/components/Game/controls' +import { Obstacle } from '@/components/Game/gameTypes' +import { initializeObstacle } from '@/components/Game/obstacle' + +const speedFactor = 0.3 +const livesUse = 3 + +export const Game: React.FC = () => { + const canvasRef = useRef(null) + let context: CanvasRenderingContext2D | null | undefined + const [lives, setLives] = useState(livesUse) + const [player, setPlayer] = useState(initializePlayer()) + const [enemies, setEnemies] = useState(initializeEnemies()) + const [obstacles, setObstacles] = useState(initializeObstacle()) + const [isPaused, setIsPaused] = useState(false) + const [isGameOver, setIsGameOver] = useState(false) + const [gameStarted, setGameStarted] = useState(false) + const [delay, setDelay] = useState(0) + const [lastTimestamp, setLastTimestamp] = useState(0) + + const togglePause = () => { + setIsPaused(prev => !prev) + } + + const handleGameOver = useCallback(() => { + setIsGameOver(true) + setIsPaused(true) + }, []) + + const handleContinue = () => { + setIsPaused(false) + if (isGameOver) { + setIsGameOver(false) + setLives(livesUse) + setGameStarted(false) + } + } + + const loop = useCallback( + (timestamp: number) => { + if (!isPaused && !isGameOver && context) { + if (lastTimestamp) { + const elapsed = timestamp - lastTimestamp + setDelay(elapsed) + } + updatePlayerMovement(player, setPlayer, speedFactor) + gameLoop( + timestamp, + context, + player, + setPlayer, + enemies, + setEnemies, + obstacles, + lives, + setLives, + speedFactor, + handleGameOver, + isPaused, + isGameOver + ) + requestAnimationFrame(loop) + } + }, + [ + isPaused, + isGameOver, + lastTimestamp, + player, + enemies, + lives, + handleGameOver, + ] + ) + + useEffect(() => { + window.addEventListener('keydown', handleKeyDown) + window.addEventListener('keyup', handleKeyUp) + + return () => { + window.removeEventListener('keydown', handleKeyDown) + window.removeEventListener('keyup', handleKeyUp) + } + }, []) + + useEffect(() => { + if (gameStarted && !isPaused) { + const canvas = canvasRef.current + context = canvas?.getContext('2d') + + if (context) { + requestAnimationFrame(loop) + } + } + }, [isPaused, isGameOver, loop]) + + const startGame = () => { + setGameStarted(true) + handleContinue() + } + + return ( +
+
Жизни: {lives}
+ + + {!gameStarted ? ( + + ) : ( + + )} + + {/* Модальное окно при проигрыше */} + {isGameOver && ( +
+

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

+ +
+ )} +
+ ) +} diff --git a/packages/client/src/components/Game/collision.tsx b/packages/client/src/components/Game/collision.tsx new file mode 100644 index 0000000..e69de29 diff --git a/packages/client/src/components/Game/controls.tsx b/packages/client/src/components/Game/controls.tsx new file mode 100644 index 0000000..6aa62c4 --- /dev/null +++ b/packages/client/src/components/Game/controls.tsx @@ -0,0 +1,46 @@ +import { KeyMap, Player } from '@/components/Game/gameTypes' + +const keyMap: KeyMap = {} + +// Обработчик нажатия клавиш +export const handleKeyDown = (event: KeyboardEvent) => { + keyMap[event.key] = true +} + +// Обработчик отпускания клавиш +export const handleKeyUp = (event: KeyboardEvent) => { + keyMap[event.key] = false +} + +// Функция для обновления позиции игрока на основе нажатых клавиш +export const updatePlayerMovement = ( + player: Player, + setPlayer: React.Dispatch>, + speedFactor: number +) => { + const speed = player.speed * speedFactor + + setPlayer(prevPlayer => { + let newX = prevPlayer.x + let newY = prevPlayer.y + + if (keyMap['ArrowUp'] || keyMap['w'] || keyMap['ц']) { + newY -= speed + } + if (keyMap['ArrowDown'] || keyMap['s'] || keyMap['ы']) { + newY += speed + } + if (keyMap['ArrowLeft'] || keyMap['a'] || keyMap['ф']) { + newX -= speed + } + if (keyMap['ArrowRight'] || keyMap['d'] || keyMap['в']) { + newX += speed + } + + // Ограничение движения по краям экрана + newX = Math.max(0, Math.min(newX, window.innerWidth - prevPlayer.width)) + newY = Math.max(0, Math.min(newY, window.innerHeight - prevPlayer.height)) + + return { ...prevPlayer, x: newX, y: newY } + }) +} diff --git a/packages/client/src/components/Game/enemy.tsx b/packages/client/src/components/Game/enemy.tsx new file mode 100644 index 0000000..4f8aaff --- /dev/null +++ b/packages/client/src/components/Game/enemy.tsx @@ -0,0 +1,61 @@ +import { getRandomEdgePosition } from './utils' +import { Enemy, Obstacle, Player } from '@/components/Game/gameTypes' + +export const initializeEnemies = (): Enemy[] => { + const initialEnemies: Enemy[] = [] + for (let i = 0; i < 5; i++) { + // количество врагов + const { x, y } = getRandomEdgePosition(800, 600) + const enemy: Enemy = { + x, + y, + width: 30, + height: 30, + speed: 2, + direction: { x: 0, y: 0 }, + } + initialEnemies.push(enemy) + } + return initialEnemies +} + +export const updateEnemyPositions = ( + player: Player, + enemies: Enemy[], + setEnemies: React.Dispatch>, + speedFactor: number +) => { + setEnemies(prevEnemies => + prevEnemies.map(enemy => { + const directionX = player.x - enemy.x + const directionY = player.y - enemy.y + + const magnitude = Math.sqrt(directionX ** 5 + directionY ** 5) + const normalizedX = directionX / magnitude + const normalizedY = directionY / magnitude + + const newX = enemy.x + normalizedX * enemy.speed * speedFactor + const newY = enemy.y + normalizedY * enemy.speed * speedFactor + + return { ...enemy, x: newX, y: newY } + }) + ) +} + +export const detectEnemyCollision = ( + rect1: Player | Enemy, + rect2: Obstacle | Enemy +): boolean => { + return ( + rect1.x < rect2.x + rect2.width && + rect1.x + rect1.width > rect2.x && + rect1.y < rect2.y + rect2.height && + rect1.y + rect1.height > rect2.y + ) +} + +export const respawnEnemies = ( + setEnemies: React.Dispatch> +) => { + setEnemies(initializeEnemies()) +} diff --git a/packages/client/src/components/Game/gameLoop.tsx b/packages/client/src/components/Game/gameLoop.tsx new file mode 100644 index 0000000..72af25d --- /dev/null +++ b/packages/client/src/components/Game/gameLoop.tsx @@ -0,0 +1,90 @@ +import { + handlePlayerHit, + resetPlayerPosition, + updatePlayerPosition, +} from './player' +import { + updateEnemyPositions, + respawnEnemies, + detectEnemyCollision, +} from './enemy' +import { clearCanvas, drawPlayer, drawEnemies, drawObstacles } from './utils' +import { Enemy, Obstacle, Player } from '@/components/Game/gameTypes' + +/** + * Основной игровой цикл, который обновляет состояние игры и перерисовывает экран каждый кадр. + * @param timestamp - Время, прошедшее с начала игры, используется для расчета обновлений. + * @param context - Контекст рисования для Canvas. + * @param player - Объект игрока. + * @param setPlayer - Функция для обновления состояния игрока. + * @param enemies - Массив врагов. + * @param setEnemies - Функция для обновления состояния врагов. + * @param obstacles - Массив препятствий. + * @param lives - Текущее количество жизней игрока. + * @param setLives - Функция для изменения количества жизней игрока. + * @param speedFactor - Коэффициент скорости. + */ +export const gameLoop = ( + timestamp: number, + context: CanvasRenderingContext2D, + player: Player, + setPlayer: React.Dispatch>, + enemies: Enemy[], + setEnemies: React.Dispatch>, + obstacles: Obstacle[], + lives: number, + setLives: React.Dispatch>, + speedFactor: number, + handleGameOver: () => void, + isPaused: boolean, + isGameOver: boolean +) => { + clearCanvas(context) + + // Обновление позиций врагов + updateEnemyPositions(player, enemies, setEnemies, speedFactor) + // Отрисовка всех игровых объектов + drawObstacles(context, obstacles) + drawPlayer(context, player) + drawEnemies(context, enemies) + + // Проверка на столкновения между игроком и врагами + enemies.forEach(enemy => { + if (detectEnemyCollision(player, enemy)) { + // Обработка столкновения: уменьшаем жизни + handlePlayerHit( + setPlayer, + setLives, + () => resetPlayerPosition(setPlayer), + respawnEnemies, + setEnemies + ) + + // Проверка на конец игры + if (lives - 1 <= 0) { + handleGameOver() // Вызываем окончание игры, если жизни закончились + } + } + }) + + // Запуск следующего кадра (будет работать только если игра не на паузе) + if (!isPaused && !isGameOver) { + requestAnimationFrame(newTimestamp => + gameLoop( + newTimestamp, + context, + player, + setPlayer, + enemies, + setEnemies, + obstacles, + lives, + setLives, + speedFactor, + handleGameOver, + isPaused, + isGameOver + ) + ) + } +} diff --git a/packages/client/src/components/Game/gameTypes.tsx b/packages/client/src/components/Game/gameTypes.tsx new file mode 100644 index 0000000..619df42 --- /dev/null +++ b/packages/client/src/components/Game/gameTypes.tsx @@ -0,0 +1,28 @@ +export interface Player { + x: number + y: number + width: number + height: number + speed: number + direction: { x: number; y: number } +} + +export interface Enemy { + x: number + y: number + width: number + height: number + speed: number + direction: { x: number; y: number } +} + +export interface Obstacle { + x: number + y: number + width: number + height: number +} + +export interface KeyMap { + [key: string]: boolean +} diff --git a/packages/client/src/components/Game/obstacle.tsx b/packages/client/src/components/Game/obstacle.tsx new file mode 100644 index 0000000..3743838 --- /dev/null +++ b/packages/client/src/components/Game/obstacle.tsx @@ -0,0 +1,12 @@ +import { Obstacle } from '@/components/Game/gameTypes' +import { getRandomEdgePosition } from '@/components/Game/utils' + +export const initializeObstacle = (): Obstacle[] => { + const obstacles: Obstacle[] = [] + for (let i = 0; i < 10; i++) { + const { x, y } = getRandomEdgePosition(800, 600) + const obstacle: Obstacle = { x, y, width: 50, height: 50 } + obstacles.push(obstacle) + } + return obstacles +} diff --git a/packages/client/src/components/Game/player.tsx b/packages/client/src/components/Game/player.tsx new file mode 100644 index 0000000..b67c2dc --- /dev/null +++ b/packages/client/src/components/Game/player.tsx @@ -0,0 +1,80 @@ +import { respawnEnemies } from '@/components/Game/enemy' +import { Enemy, Obstacle, Player } from '@/components/Game/gameTypes' + +export const initializePlayer = () => ({ + x: 400, + y: 300, + width: 30, + height: 30, + speed: 0.1, + direction: { x: 0, y: 0 }, +}) + +const detectCollision = (player: Player, obstacle: Obstacle): boolean => { + return ( + player.x < obstacle.x + obstacle.width && + player.x + player.width > obstacle.x && + player.y < obstacle.y + obstacle.height && + player.y + player.height > obstacle.y + ) +} + +export const updatePlayerPosition = ( + player: Player, + setPlayer: React.Dispatch>, + obstacles: Obstacle[] +) => { + const newX = player.x + player.direction.x * player.speed + const newY = player.y + player.direction.y * player.speed + + let collisionDetected = false + obstacles.forEach(obstacle => { + if (detectCollision({ ...player, x: newX, y: newY }, obstacle)) { + collisionDetected = true + } + }) + if (!collisionDetected) { + setPlayer(prev => ({ + ...prev, + x: newX, + y: newY, + })) + } +} + +export const resetPlayerPosition = ( + setPlayer: React.Dispatch> +) => { + setPlayer(initializePlayer()) +} + +/** + * Функция для обработки столкновения игрока с врагом. + * @param setLives - Функция для изменения количества жизней игрока. + * @param resetPlayerPosition - Функция для сброса позиции игрока. + * @param respawnEnemies - Функция для респауна врагов. + * @param setEnemies - Функция для обновления состояния врагов. + */ +export const handlePlayerHit = ( + setPlayer: React.Dispatch>, + setLives: React.Dispatch>, + resetPlayerPosition: ( + setPlayer: React.Dispatch> + ) => void, + respawnEnemies: ( + setEnemies: React.Dispatch> + ) => void, + setEnemies: React.Dispatch> +) => { + setLives(prevLives => { + const newLives = prevLives - 1 + if (newLives <= 0) { + console.log('Game over!') + window.location.reload() + } else { + resetPlayerPosition(setPlayer) + respawnEnemies(setEnemies) + } + return newLives + }) +} diff --git a/packages/client/src/components/Game/utils.tsx b/packages/client/src/components/Game/utils.tsx new file mode 100644 index 0000000..139dc35 --- /dev/null +++ b/packages/client/src/components/Game/utils.tsx @@ -0,0 +1,52 @@ +import { Enemy, Obstacle, Player } from '@/components/Game/gameTypes' + +export const getRandomEdgePosition = ( + canvasWidth: number, + canvasHeight: number +): { x: number; y: number } => { + const edge = Math.floor(Math.random() * 4) + switch (edge) { + case 0: + return { x: Math.random() * canvasWidth, y: 0 } + case 1: + return { x: canvasWidth, y: Math.random() * canvasHeight } + case 2: + return { x: Math.random() * canvasWidth, y: canvasHeight } + case 3: + return { x: 0, y: Math.random() * canvasHeight } + default: + return { x: 0, y: 0 } + } +} + +export const clearCanvas = (context: CanvasRenderingContext2D) => { + context.clearRect(0, 0, context.canvas.width, context.canvas.height) +} + +export const drawPlayer = ( + context: CanvasRenderingContext2D, + player: Player +) => { + context.fillStyle = 'gray' + context.fillRect(player.x, player.y, player.width, player.height) +} + +export const drawEnemies = ( + context: CanvasRenderingContext2D, + enemies: Enemy[] +) => { + context.fillStyle = 'red' + enemies.forEach(enemy => { + context.fillRect(enemy.x, enemy.y, enemy.width, enemy.height) + }) +} + +export const drawObstacles = ( + context: CanvasRenderingContext2D, + obstacles: Obstacle[] +) => { + context.fillStyle = 'black' + obstacles.forEach(obstacle => { + context.fillRect(obstacle.x, obstacle.y, obstacle.width, obstacle.height) + }) +} diff --git a/packages/client/src/pages/Game/Game.tsx b/packages/client/src/pages/Game/Game.tsx index 5b4a644..78c85ef 100644 --- a/packages/client/src/pages/Game/Game.tsx +++ b/packages/client/src/pages/Game/Game.tsx @@ -1,141 +1,11 @@ -import React, { useEffect, useRef, useState } from 'react' -import './Game.scss' - -interface Player { - x: number - y: number - width: number - height: number - speed: number - direction: { x: number; y: number } -} - -interface Obstacle { - x: number - y: number - width: number - height: number -} - -export const Game: React.FC = () => { - const canvasRef = useRef(null) - const [player, setPlayer] = useState({ - x: 50, - y: 50, - width: 30, - height: 30, - speed: 5, - direction: { x: 0, y: 0 }, - }) - - const obstacles: Obstacle[] = [ - { x: 200, y: 200, width: 50, height: 50 }, - { x: 400, y: 100, width: 50, height: 50 }, - ] - - useEffect(() => { - const canvas = canvasRef.current - if (!canvas) return - - const context = canvas.getContext('2d') - if (!context) return - - const handleKeyDown = (e: KeyboardEvent) => { - switch (e.key) { - case 'ArrowUp': - setPlayer(prev => ({ ...prev, direction: { x: 0, y: -1 } })) - break - case 'ArrowDown': - setPlayer(prev => ({ ...prev, direction: { x: 0, y: 1 } })) - break - case 'ArrowLeft': - setPlayer(prev => ({ ...prev, direction: { x: -1, y: 0 } })) - break - case 'ArrowRight': - setPlayer(prev => ({ ...prev, direction: { x: 1, y: 0 } })) - break - default: - break - } - } - - const handleKeyUp = () => { - setPlayer(prev => ({ ...prev, direction: { x: 0, y: 0 } })) - } - - const detectCollision = (player: Player, obstacle: Obstacle): boolean => { - return ( - player.x < obstacle.x + obstacle.width && - player.x + player.width > obstacle.x && - player.y < obstacle.y + obstacle.height && - player.y + player.height > obstacle.y - ) - } - - const gameLoop = (timestamp: number) => { - const deltaTime = timestamp - lastTime - if (deltaTime > 1000 / 60) { - updatePlayerPosition() - clearCanvas() - drawPlayer() - drawObstacles(context) - lastTime = timestamp - } - requestAnimationFrame(gameLoop) - } - - let lastTime = 0 - const updatePlayerPosition = () => { - const newX = player.x + player.direction.x * player.speed - const newY = player.y + player.direction.y * player.speed - - let collisionDetected = false - obstacles.forEach(obstacle => { - if (detectCollision({ ...player, x: newX, y: newY }, obstacle)) { - collisionDetected = true - } - }) - - if (!collisionDetected) { - setPlayer(prev => ({ - ...prev, - x: newX, - y: newY, - })) - } - } - - const clearCanvas = () => { - context.clearRect(0, 0, canvas.width, canvas.height) - } - - const drawPlayer = () => { - context.fillStyle = 'gray' - context.fillRect(player.x, player.y, player.width, player.height) - } - - const drawObstacles = (context: CanvasRenderingContext2D) => { - context.fillStyle = 'black' - obstacles.forEach(obstacle => { - context.fillRect( - obstacle.x, - obstacle.y, - obstacle.width, - obstacle.height - ) - }) - } - - window.addEventListener('keydown', handleKeyDown) - window.addEventListener('keyup', handleKeyUp) - - gameLoop(0) - - return () => { - window.removeEventListener('keydown', handleKeyDown) - window.removeEventListener('keyup', handleKeyUp) - } - }, [player, obstacles]) - - return +import { Game } from '@/components/Game/Game' +import './GamePage.scss' + +export const GamePage: React.FC = () => { + return ( +
+

Falcon Tanks

+ +
+ ) } diff --git a/packages/client/src/pages/Game/GamePage.scss b/packages/client/src/pages/Game/GamePage.scss new file mode 100644 index 0000000..8ba7e90 --- /dev/null +++ b/packages/client/src/pages/Game/GamePage.scss @@ -0,0 +1,7 @@ +.game-page { + display: flex; + justify-content: center; + align-items: center; + height: 100vh; + background-color: #010101; // Цвет фона для всей страницы +} From 8be6f1b51233c8bc00355cc0a49b87e8c74638e9 Mon Sep 17 00:00:00 2001 From: Daniil <77277774+shamemask@users.noreply.github.com> Date: Mon, 30 Sep 2024 16:50:28 +0300 Subject: [PATCH 03/19] [SOK-27] fix: lives --- packages/client/src/components/Game/Game.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/client/src/components/Game/Game.tsx b/packages/client/src/components/Game/Game.tsx index c664303..2a60f00 100644 --- a/packages/client/src/components/Game/Game.tsx +++ b/packages/client/src/components/Game/Game.tsx @@ -110,7 +110,7 @@ export const Game: React.FC = () => { return (
-
Жизни: {lives}
+
{`Жизни: ${lives}`}
{!gameStarted ? ( From f2af052296408e85fe0eefc03d693d526b19e68d Mon Sep 17 00:00:00 2001 From: Daniil <77277774+shamemask@users.noreply.github.com> Date: Mon, 30 Sep 2024 17:08:15 +0300 Subject: [PATCH 04/19] =?UTF-8?q?[SOK-27]=20fix:=20=D0=B8=D0=B7=D1=8C?= =?UTF-8?q?=D0=B0=D0=B2=D0=B8=D0=BB=D1=81=D1=8F=20=D0=BE=D1=82=20default?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/client/src/components/Game/utils.tsx | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/packages/client/src/components/Game/utils.tsx b/packages/client/src/components/Game/utils.tsx index 139dc35..13cf662 100644 --- a/packages/client/src/components/Game/utils.tsx +++ b/packages/client/src/components/Game/utils.tsx @@ -4,18 +4,17 @@ export const getRandomEdgePosition = ( canvasWidth: number, canvasHeight: number ): { x: number; y: number } => { - const edge = Math.floor(Math.random() * 4) + const edge: 0 | 1 | 2 | 3 = Math.floor(Math.random() * 4) as 0 | 1 | 2 | 3 + switch (edge) { - case 0: + case 0: // Верхний край return { x: Math.random() * canvasWidth, y: 0 } - case 1: + case 1: // Правый край return { x: canvasWidth, y: Math.random() * canvasHeight } - case 2: + case 2: // Нижний край return { x: Math.random() * canvasWidth, y: canvasHeight } - case 3: + case 3: // Левый край return { x: 0, y: Math.random() * canvasHeight } - default: - return { x: 0, y: 0 } } } From 4e0b9b3e3870e444134f6e7a3ed5ef3afd168aea Mon Sep 17 00:00:00 2001 From: Daniil <77277774+shamemask@users.noreply.github.com> Date: Mon, 30 Sep 2024 19:12:58 +0300 Subject: [PATCH 05/19] =?UTF-8?q?[SOK-27]=20fix:=20=D0=B8=D0=B7=D0=BC?= =?UTF-8?q?=D0=B5=D0=BD=D0=B8=D0=BB=20=D1=80=D0=B0=D0=B1=D0=BE=D1=82=D1=83?= =?UTF-8?q?=20handleKey=20=D1=84=D1=83=D0=BD=D0=BA=D1=86=D0=B8=D0=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/client/src/components/Game/Game.tsx | 16 ++++++++++++---- packages/client/src/components/Game/controls.tsx | 8 ++++---- 2 files changed, 16 insertions(+), 8 deletions(-) diff --git a/packages/client/src/components/Game/Game.tsx b/packages/client/src/components/Game/Game.tsx index 2a60f00..c9fcecc 100644 --- a/packages/client/src/components/Game/Game.tsx +++ b/packages/client/src/components/Game/Game.tsx @@ -83,15 +83,23 @@ export const Game: React.FC = () => { ) useEffect(() => { - window.addEventListener('keydown', handleKeyDown) - window.addEventListener('keyup', handleKeyUp) + const handleKeyDownWrapper = (event: KeyboardEvent) => + handleKeyDown(event.key) + const handleKeyUpWrapper = (event: KeyboardEvent) => handleKeyUp(event.key) + + window.addEventListener('keydown', handleKeyDownWrapper) + window.addEventListener('keyup', handleKeyUpWrapper) return () => { - window.removeEventListener('keydown', handleKeyDown) - window.removeEventListener('keyup', handleKeyUp) + window.removeEventListener('keydown', handleKeyDownWrapper) + window.removeEventListener('keyup', handleKeyUpWrapper) } }, []) + const getCanvas = () => canvasRef.current + + const getContext = () => getCanvas()?.getContext('2d') + useEffect(() => { if (gameStarted && !isPaused) { const canvas = canvasRef.current diff --git a/packages/client/src/components/Game/controls.tsx b/packages/client/src/components/Game/controls.tsx index 6aa62c4..1473555 100644 --- a/packages/client/src/components/Game/controls.tsx +++ b/packages/client/src/components/Game/controls.tsx @@ -3,13 +3,13 @@ import { KeyMap, Player } from '@/components/Game/gameTypes' const keyMap: KeyMap = {} // Обработчик нажатия клавиш -export const handleKeyDown = (event: KeyboardEvent) => { - keyMap[event.key] = true +export const handleKeyDown = (key: string) => { + keyMap[key] = true } // Обработчик отпускания клавиш -export const handleKeyUp = (event: KeyboardEvent) => { - keyMap[event.key] = false +export const handleKeyUp = (key: string) => { + delete keyMap[key] } // Функция для обновления позиции игрока на основе нажатых клавиш From ed291458dd9bd70c2befdb3eeb8ea431ccb8cd63 Mon Sep 17 00:00:00 2001 From: Daniil <77277774+shamemask@users.noreply.github.com> Date: Mon, 30 Sep 2024 19:15:41 +0300 Subject: [PATCH 06/19] =?UTF-8?q?[SOK-27]=20fix:=20=D0=9D=D0=B0=D1=81?= =?UTF-8?q?=D1=82=D1=80=D0=BE=D0=B8=D0=BB=20=D1=81=D1=80=D0=B0=D0=B2=D0=BD?= =?UTF-8?q?=D0=B5=D0=BD=D0=B8=D0=B5=20=D1=81=20canvas?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/client/src/components/Game/Game.tsx | 13 ++++++++++--- packages/client/src/components/Game/controls.tsx | 11 +++++++---- 2 files changed, 17 insertions(+), 7 deletions(-) diff --git a/packages/client/src/components/Game/Game.tsx b/packages/client/src/components/Game/Game.tsx index c9fcecc..d516d12 100644 --- a/packages/client/src/components/Game/Game.tsx +++ b/packages/client/src/components/Game/Game.tsx @@ -52,7 +52,15 @@ export const Game: React.FC = () => { const elapsed = timestamp - lastTimestamp setDelay(elapsed) } - updatePlayerMovement(player, setPlayer, speedFactor) + const canvas = getCanvas() + if (!canvas) return + updatePlayerMovement( + player, + setPlayer, + speedFactor, + canvas.width, + canvas.height + ) gameLoop( timestamp, context, @@ -102,8 +110,7 @@ export const Game: React.FC = () => { useEffect(() => { if (gameStarted && !isPaused) { - const canvas = canvasRef.current - context = canvas?.getContext('2d') + context = getContext() if (context) { requestAnimationFrame(loop) diff --git a/packages/client/src/components/Game/controls.tsx b/packages/client/src/components/Game/controls.tsx index 1473555..3bc2e88 100644 --- a/packages/client/src/components/Game/controls.tsx +++ b/packages/client/src/components/Game/controls.tsx @@ -16,7 +16,9 @@ export const handleKeyUp = (key: string) => { export const updatePlayerMovement = ( player: Player, setPlayer: React.Dispatch>, - speedFactor: number + speedFactor: number, + canvasWidth: number, + canvasHeight: number ) => { const speed = player.speed * speedFactor @@ -24,6 +26,7 @@ export const updatePlayerMovement = ( let newX = prevPlayer.x let newY = prevPlayer.y + // Определение направления движения if (keyMap['ArrowUp'] || keyMap['w'] || keyMap['ц']) { newY -= speed } @@ -37,9 +40,9 @@ export const updatePlayerMovement = ( newX += speed } - // Ограничение движения по краям экрана - newX = Math.max(0, Math.min(newX, window.innerWidth - prevPlayer.width)) - newY = Math.max(0, Math.min(newY, window.innerHeight - prevPlayer.height)) + // Ограничение движения по краям canvas + newX = Math.max(0, Math.min(newX, canvasWidth - prevPlayer.width)) + newY = Math.max(0, Math.min(newY, canvasHeight - prevPlayer.height)) return { ...prevPlayer, x: newX, y: newY } }) From 959c1b4f87c9469007ce207ed93f75677ce64cc7 Mon Sep 17 00:00:00 2001 From: Daniil <77277774+shamemask@users.noreply.github.com> Date: Mon, 30 Sep 2024 19:39:50 +0300 Subject: [PATCH 07/19] =?UTF-8?q?[SOK-27]=20fix:=20=D0=9D=D0=B0=D1=81?= =?UTF-8?q?=D1=82=D1=80=D0=BE=D0=B8=D0=BB=20=D0=B4=D0=B2=D0=B8=D0=B6=D0=B5?= =?UTF-8?q?=D0=BD=D0=B8=D0=B5=20=D0=B8=20=D0=BF=D1=80=D0=BE=D0=B2=D0=B5?= =?UTF-8?q?=D1=80=D0=BA=D1=83=20=D0=BA=D0=BE=D0=BB=D0=B8=D0=B7=D0=B8=D0=B8?= =?UTF-8?q?=20=D1=81=20=D0=BE=D0=B1=D1=8A=D0=B5=D0=BA=D1=82=D0=BE=D0=BC=20?= =?UTF-8?q?=D0=B2=20=D0=BE=D0=B4=D0=BD=D0=BE=D0=B9=20=D1=84=D1=83=D0=BD?= =?UTF-8?q?=D0=BA=D1=86=D0=B8=D0=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/client/src/components/Game/Game.tsx | 14 ++++---- .../client/src/components/Game/controls.tsx | 34 +++++++++++-------- .../client/src/components/Game/gameLoop.tsx | 9 +++-- .../client/src/components/Game/gameTypes.tsx | 9 +++++ .../client/src/components/Game/player.tsx | 7 ++-- 5 files changed, 46 insertions(+), 27 deletions(-) diff --git a/packages/client/src/components/Game/Game.tsx b/packages/client/src/components/Game/Game.tsx index d516d12..30965ff 100644 --- a/packages/client/src/components/Game/Game.tsx +++ b/packages/client/src/components/Game/Game.tsx @@ -8,10 +8,10 @@ import { handleKeyUp, updatePlayerMovement, } from '@/components/Game/controls' -import { Obstacle } from '@/components/Game/gameTypes' +import { ControlsProps, Obstacle } from '@/components/Game/gameTypes' import { initializeObstacle } from '@/components/Game/obstacle' -const speedFactor = 0.3 +const speedFactor = 1 const livesUse = 3 export const Game: React.FC = () => { @@ -54,13 +54,15 @@ export const Game: React.FC = () => { } const canvas = getCanvas() if (!canvas) return - updatePlayerMovement( + const moveProps: ControlsProps = { player, setPlayer, speedFactor, - canvas.width, - canvas.height - ) + obstacles, + canvasWidth: canvas.width, + canvasHeight: canvas.height, + } + updatePlayerMovement(moveProps) gameLoop( timestamp, context, diff --git a/packages/client/src/components/Game/controls.tsx b/packages/client/src/components/Game/controls.tsx index 3bc2e88..c0bffc9 100644 --- a/packages/client/src/components/Game/controls.tsx +++ b/packages/client/src/components/Game/controls.tsx @@ -1,4 +1,5 @@ -import { KeyMap, Player } from '@/components/Game/gameTypes' +import { ControlsProps, KeyMap } from '@/components/Game/gameTypes' +import { detectCollision } from '@/components/Game/player' const keyMap: KeyMap = {} @@ -13,16 +14,10 @@ export const handleKeyUp = (key: string) => { } // Функция для обновления позиции игрока на основе нажатых клавиш -export const updatePlayerMovement = ( - player: Player, - setPlayer: React.Dispatch>, - speedFactor: number, - canvasWidth: number, - canvasHeight: number -) => { - const speed = player.speed * speedFactor - - setPlayer(prevPlayer => { +export const updatePlayerMovement = (props: ControlsProps) => { + const speed = props.player.speed * props.speedFactor + + props.setPlayer(prevPlayer => { let newX = prevPlayer.x let newY = prevPlayer.y @@ -40,9 +35,20 @@ export const updatePlayerMovement = ( newX += speed } - // Ограничение движения по краям canvas - newX = Math.max(0, Math.min(newX, canvasWidth - prevPlayer.width)) - newY = Math.max(0, Math.min(newY, canvasHeight - prevPlayer.height)) + // Обработка столкновений с препятствиями + props.obstacles.forEach(obstacle => { + if (detectCollision({ ...prevPlayer, x: newX, y: newY }, obstacle)) { + newX = prevPlayer.x + newY = prevPlayer.y + } else { + // Ограничение движения по краям canvas + newX = Math.max(0, Math.min(newX, props.canvasWidth - prevPlayer.width)) + newY = Math.max( + 0, + Math.min(newY, props.canvasHeight - prevPlayer.height) + ) + } + }) return { ...prevPlayer, x: newX, y: newY } }) diff --git a/packages/client/src/components/Game/gameLoop.tsx b/packages/client/src/components/Game/gameLoop.tsx index 72af25d..4a2c3de 100644 --- a/packages/client/src/components/Game/gameLoop.tsx +++ b/packages/client/src/components/Game/gameLoop.tsx @@ -1,8 +1,4 @@ -import { - handlePlayerHit, - resetPlayerPosition, - updatePlayerPosition, -} from './player' +import { handlePlayerHit, resetPlayerPosition } from './player' import { updateEnemyPositions, respawnEnemies, @@ -23,6 +19,9 @@ import { Enemy, Obstacle, Player } from '@/components/Game/gameTypes' * @param lives - Текущее количество жизней игрока. * @param setLives - Функция для изменения количества жизней игрока. * @param speedFactor - Коэффициент скорости. + * @param handleGameOver - Обработчик события окончания игры. + * @param isPaused - Флаг паузы. + * @param isGameOver - Флаг окончания игры. */ export const gameLoop = ( timestamp: number, diff --git a/packages/client/src/components/Game/gameTypes.tsx b/packages/client/src/components/Game/gameTypes.tsx index 619df42..40b511a 100644 --- a/packages/client/src/components/Game/gameTypes.tsx +++ b/packages/client/src/components/Game/gameTypes.tsx @@ -26,3 +26,12 @@ export interface Obstacle { export interface KeyMap { [key: string]: boolean } + +export interface ControlsProps { + player: Player + setPlayer: React.Dispatch> + speedFactor: number + obstacles: Obstacle[] + canvasWidth: number + canvasHeight: number +} diff --git a/packages/client/src/components/Game/player.tsx b/packages/client/src/components/Game/player.tsx index b67c2dc..8b22038 100644 --- a/packages/client/src/components/Game/player.tsx +++ b/packages/client/src/components/Game/player.tsx @@ -1,4 +1,3 @@ -import { respawnEnemies } from '@/components/Game/enemy' import { Enemy, Obstacle, Player } from '@/components/Game/gameTypes' export const initializePlayer = () => ({ @@ -10,7 +9,10 @@ export const initializePlayer = () => ({ direction: { x: 0, y: 0 }, }) -const detectCollision = (player: Player, obstacle: Obstacle): boolean => { +export const detectCollision = ( + player: Player, + obstacle: Obstacle +): boolean => { return ( player.x < obstacle.x + obstacle.width && player.x + player.width > obstacle.x && @@ -50,6 +52,7 @@ export const resetPlayerPosition = ( /** * Функция для обработки столкновения игрока с врагом. + * @param setPlayer - Функция для обновления состояния игрока. * @param setLives - Функция для изменения количества жизней игрока. * @param resetPlayerPosition - Функция для сброса позиции игрока. * @param respawnEnemies - Функция для респауна врагов. From 11532f6a3205416e2cc93c0b459884e5263ff661 Mon Sep 17 00:00:00 2001 From: Daniil <77277774+shamemask@users.noreply.github.com> Date: Mon, 30 Sep 2024 20:38:59 +0300 Subject: [PATCH 08/19] =?UTF-8?q?[SOK-27]=20fix:=20=D0=9D=D0=B0=D1=81?= =?UTF-8?q?=D1=82=D1=80=D0=BE=D0=B8=D0=BB=20fps=20=D0=B8=D0=B3=D1=80=D1=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/client/src/components/Game/Game.tsx | 65 +++++++++---------- .../client/src/components/Game/collision.tsx | 24 +++++++ .../client/src/components/Game/controls.tsx | 5 +- packages/client/src/components/Game/enemy.tsx | 27 ++------ .../client/src/components/Game/gameLoop.tsx | 17 ++--- .../client/src/components/Game/gameTypes.tsx | 1 - .../client/src/components/Game/player.tsx | 39 +---------- 7 files changed, 71 insertions(+), 107 deletions(-) diff --git a/packages/client/src/components/Game/Game.tsx b/packages/client/src/components/Game/Game.tsx index 30965ff..75e7604 100644 --- a/packages/client/src/components/Game/Game.tsx +++ b/packages/client/src/components/Game/Game.tsx @@ -11,7 +11,7 @@ import { import { ControlsProps, Obstacle } from '@/components/Game/gameTypes' import { initializeObstacle } from '@/components/Game/obstacle' -const speedFactor = 1 +const maxFPS = 60 const livesUse = 3 export const Game: React.FC = () => { @@ -19,12 +19,11 @@ export const Game: React.FC = () => { let context: CanvasRenderingContext2D | null | undefined const [lives, setLives] = useState(livesUse) const [player, setPlayer] = useState(initializePlayer()) - const [enemies, setEnemies] = useState(initializeEnemies()) - const [obstacles, setObstacles] = useState(initializeObstacle()) + const [enemies, setEnemies] = useState(initializeEnemies(5)) + const [obstacles] = useState(initializeObstacle()) const [isPaused, setIsPaused] = useState(false) const [isGameOver, setIsGameOver] = useState(false) const [gameStarted, setGameStarted] = useState(false) - const [delay, setDelay] = useState(0) const [lastTimestamp, setLastTimestamp] = useState(0) const togglePause = () => { @@ -48,37 +47,35 @@ export const Game: React.FC = () => { const loop = useCallback( (timestamp: number) => { if (!isPaused && !isGameOver && context) { - if (lastTimestamp) { - const elapsed = timestamp - lastTimestamp - setDelay(elapsed) + const deltaTime = timestamp - lastTimestamp + + if (deltaTime >= 1000 / maxFPS) { + setLastTimestamp(timestamp) + const canvas = getCanvas() + if (!canvas) return + const moveProps: ControlsProps = { + player, + setPlayer, + obstacles, + canvasWidth: canvas.width, + canvasHeight: canvas.height, + } + updatePlayerMovement(moveProps) + gameLoop( + context, + player, + setPlayer, + enemies, + setEnemies, + obstacles, + lives, + setLives, + handleGameOver, + isPaused, + isGameOver + ) + requestAnimationFrame(loop) } - const canvas = getCanvas() - if (!canvas) return - const moveProps: ControlsProps = { - player, - setPlayer, - speedFactor, - obstacles, - canvasWidth: canvas.width, - canvasHeight: canvas.height, - } - updatePlayerMovement(moveProps) - gameLoop( - timestamp, - context, - player, - setPlayer, - enemies, - setEnemies, - obstacles, - lives, - setLives, - speedFactor, - handleGameOver, - isPaused, - isGameOver - ) - requestAnimationFrame(loop) } }, [ diff --git a/packages/client/src/components/Game/collision.tsx b/packages/client/src/components/Game/collision.tsx index e69de29..8f994e8 100644 --- a/packages/client/src/components/Game/collision.tsx +++ b/packages/client/src/components/Game/collision.tsx @@ -0,0 +1,24 @@ +import { Enemy, Obstacle, Player } from '@/components/Game/gameTypes' + +export const detectCollision = ( + player: Player, + obstacle: Obstacle +): boolean => { + return ( + player.x < obstacle.x + obstacle.width && + player.x + player.width > obstacle.x && + player.y < obstacle.y + obstacle.height && + player.y + player.height > obstacle.y + ) +} +export const detectEnemyCollision = ( + rect1: Player | Enemy, + rect2: Obstacle | Enemy +): boolean => { + return ( + rect1.x < rect2.x + rect2.width && + rect1.x + rect1.width > rect2.x && + rect1.y < rect2.y + rect2.height && + rect1.y + rect1.height > rect2.y + ) +} diff --git a/packages/client/src/components/Game/controls.tsx b/packages/client/src/components/Game/controls.tsx index c0bffc9..b9466a6 100644 --- a/packages/client/src/components/Game/controls.tsx +++ b/packages/client/src/components/Game/controls.tsx @@ -1,5 +1,6 @@ import { ControlsProps, KeyMap } from '@/components/Game/gameTypes' -import { detectCollision } from '@/components/Game/player' + +import { detectCollision } from '@/components/Game/collision' const keyMap: KeyMap = {} @@ -15,7 +16,7 @@ export const handleKeyUp = (key: string) => { // Функция для обновления позиции игрока на основе нажатых клавиш export const updatePlayerMovement = (props: ControlsProps) => { - const speed = props.player.speed * props.speedFactor + const speed = props.player.speed props.setPlayer(prevPlayer => { let newX = prevPlayer.x diff --git a/packages/client/src/components/Game/enemy.tsx b/packages/client/src/components/Game/enemy.tsx index 4f8aaff..ee61657 100644 --- a/packages/client/src/components/Game/enemy.tsx +++ b/packages/client/src/components/Game/enemy.tsx @@ -1,9 +1,9 @@ import { getRandomEdgePosition } from './utils' -import { Enemy, Obstacle, Player } from '@/components/Game/gameTypes' +import { Enemy, Player } from '@/components/Game/gameTypes' -export const initializeEnemies = (): Enemy[] => { +export const initializeEnemies = (numberOfEnemies: number): Enemy[] => { const initialEnemies: Enemy[] = [] - for (let i = 0; i < 5; i++) { + for (let i = 0; i < numberOfEnemies; i++) { // количество врагов const { x, y } = getRandomEdgePosition(800, 600) const enemy: Enemy = { @@ -22,8 +22,7 @@ export const initializeEnemies = (): Enemy[] => { export const updateEnemyPositions = ( player: Player, enemies: Enemy[], - setEnemies: React.Dispatch>, - speedFactor: number + setEnemies: React.Dispatch> ) => { setEnemies(prevEnemies => prevEnemies.map(enemy => { @@ -34,28 +33,16 @@ export const updateEnemyPositions = ( const normalizedX = directionX / magnitude const normalizedY = directionY / magnitude - const newX = enemy.x + normalizedX * enemy.speed * speedFactor - const newY = enemy.y + normalizedY * enemy.speed * speedFactor + const newX = enemy.x + normalizedX * enemy.speed + const newY = enemy.y + normalizedY * enemy.speed return { ...enemy, x: newX, y: newY } }) ) } -export const detectEnemyCollision = ( - rect1: Player | Enemy, - rect2: Obstacle | Enemy -): boolean => { - return ( - rect1.x < rect2.x + rect2.width && - rect1.x + rect1.width > rect2.x && - rect1.y < rect2.y + rect2.height && - rect1.y + rect1.height > rect2.y - ) -} - export const respawnEnemies = ( setEnemies: React.Dispatch> ) => { - setEnemies(initializeEnemies()) + setEnemies(initializeEnemies(5)) } diff --git a/packages/client/src/components/Game/gameLoop.tsx b/packages/client/src/components/Game/gameLoop.tsx index 4a2c3de..876c4a9 100644 --- a/packages/client/src/components/Game/gameLoop.tsx +++ b/packages/client/src/components/Game/gameLoop.tsx @@ -1,15 +1,11 @@ import { handlePlayerHit, resetPlayerPosition } from './player' -import { - updateEnemyPositions, - respawnEnemies, - detectEnemyCollision, -} from './enemy' +import { updateEnemyPositions, respawnEnemies } from './enemy' import { clearCanvas, drawPlayer, drawEnemies, drawObstacles } from './utils' import { Enemy, Obstacle, Player } from '@/components/Game/gameTypes' +import { detectEnemyCollision } from '@/components/Game/collision' /** * Основной игровой цикл, который обновляет состояние игры и перерисовывает экран каждый кадр. - * @param timestamp - Время, прошедшее с начала игры, используется для расчета обновлений. * @param context - Контекст рисования для Canvas. * @param player - Объект игрока. * @param setPlayer - Функция для обновления состояния игрока. @@ -18,13 +14,11 @@ import { Enemy, Obstacle, Player } from '@/components/Game/gameTypes' * @param obstacles - Массив препятствий. * @param lives - Текущее количество жизней игрока. * @param setLives - Функция для изменения количества жизней игрока. - * @param speedFactor - Коэффициент скорости. * @param handleGameOver - Обработчик события окончания игры. * @param isPaused - Флаг паузы. * @param isGameOver - Флаг окончания игры. */ export const gameLoop = ( - timestamp: number, context: CanvasRenderingContext2D, player: Player, setPlayer: React.Dispatch>, @@ -33,7 +27,6 @@ export const gameLoop = ( obstacles: Obstacle[], lives: number, setLives: React.Dispatch>, - speedFactor: number, handleGameOver: () => void, isPaused: boolean, isGameOver: boolean @@ -41,7 +34,7 @@ export const gameLoop = ( clearCanvas(context) // Обновление позиций врагов - updateEnemyPositions(player, enemies, setEnemies, speedFactor) + updateEnemyPositions(player, enemies, setEnemies) // Отрисовка всех игровых объектов drawObstacles(context, obstacles) drawPlayer(context, player) @@ -68,9 +61,8 @@ export const gameLoop = ( // Запуск следующего кадра (будет работать только если игра не на паузе) if (!isPaused && !isGameOver) { - requestAnimationFrame(newTimestamp => + requestAnimationFrame(() => gameLoop( - newTimestamp, context, player, setPlayer, @@ -79,7 +71,6 @@ export const gameLoop = ( obstacles, lives, setLives, - speedFactor, handleGameOver, isPaused, isGameOver diff --git a/packages/client/src/components/Game/gameTypes.tsx b/packages/client/src/components/Game/gameTypes.tsx index 40b511a..893f206 100644 --- a/packages/client/src/components/Game/gameTypes.tsx +++ b/packages/client/src/components/Game/gameTypes.tsx @@ -30,7 +30,6 @@ export interface KeyMap { export interface ControlsProps { player: Player setPlayer: React.Dispatch> - speedFactor: number obstacles: Obstacle[] canvasWidth: number canvasHeight: number diff --git a/packages/client/src/components/Game/player.tsx b/packages/client/src/components/Game/player.tsx index 8b22038..5e0a383 100644 --- a/packages/client/src/components/Game/player.tsx +++ b/packages/client/src/components/Game/player.tsx @@ -1,49 +1,14 @@ -import { Enemy, Obstacle, Player } from '@/components/Game/gameTypes' +import { Enemy, Player } from '@/components/Game/gameTypes' export const initializePlayer = () => ({ x: 400, y: 300, width: 30, height: 30, - speed: 0.1, + speed: 0.5, direction: { x: 0, y: 0 }, }) -export const detectCollision = ( - player: Player, - obstacle: Obstacle -): boolean => { - return ( - player.x < obstacle.x + obstacle.width && - player.x + player.width > obstacle.x && - player.y < obstacle.y + obstacle.height && - player.y + player.height > obstacle.y - ) -} - -export const updatePlayerPosition = ( - player: Player, - setPlayer: React.Dispatch>, - obstacles: Obstacle[] -) => { - const newX = player.x + player.direction.x * player.speed - const newY = player.y + player.direction.y * player.speed - - let collisionDetected = false - obstacles.forEach(obstacle => { - if (detectCollision({ ...player, x: newX, y: newY }, obstacle)) { - collisionDetected = true - } - }) - if (!collisionDetected) { - setPlayer(prev => ({ - ...prev, - x: newX, - y: newY, - })) - } -} - export const resetPlayerPosition = ( setPlayer: React.Dispatch> ) => { From 6b32d3a50088b9efed5f9ba1fadd1a5835d9c976 Mon Sep 17 00:00:00 2001 From: Daniil <77277774+shamemask@users.noreply.github.com> Date: Tue, 1 Oct 2024 10:08:45 +0300 Subject: [PATCH 09/19] [SOK-27] fix: hasCollision --- packages/client/src/components/Game/collision.tsx | 7 ++----- packages/client/src/components/Game/controls.tsx | 4 ++-- packages/client/src/components/Game/gameLoop.tsx | 4 ++-- 3 files changed, 6 insertions(+), 9 deletions(-) diff --git a/packages/client/src/components/Game/collision.tsx b/packages/client/src/components/Game/collision.tsx index 8f994e8..0a83414 100644 --- a/packages/client/src/components/Game/collision.tsx +++ b/packages/client/src/components/Game/collision.tsx @@ -1,9 +1,6 @@ import { Enemy, Obstacle, Player } from '@/components/Game/gameTypes' -export const detectCollision = ( - player: Player, - obstacle: Obstacle -): boolean => { +export const hasCollision = (player: Player, obstacle: Obstacle): boolean => { return ( player.x < obstacle.x + obstacle.width && player.x + player.width > obstacle.x && @@ -11,7 +8,7 @@ export const detectCollision = ( player.y + player.height > obstacle.y ) } -export const detectEnemyCollision = ( +export const hasEnemyCollision = ( rect1: Player | Enemy, rect2: Obstacle | Enemy ): boolean => { diff --git a/packages/client/src/components/Game/controls.tsx b/packages/client/src/components/Game/controls.tsx index b9466a6..fa92ca5 100644 --- a/packages/client/src/components/Game/controls.tsx +++ b/packages/client/src/components/Game/controls.tsx @@ -1,6 +1,6 @@ import { ControlsProps, KeyMap } from '@/components/Game/gameTypes' -import { detectCollision } from '@/components/Game/collision' +import { hasCollision } from '@/components/Game/collision' const keyMap: KeyMap = {} @@ -38,7 +38,7 @@ export const updatePlayerMovement = (props: ControlsProps) => { // Обработка столкновений с препятствиями props.obstacles.forEach(obstacle => { - if (detectCollision({ ...prevPlayer, x: newX, y: newY }, obstacle)) { + if (hasCollision({ ...prevPlayer, x: newX, y: newY }, obstacle)) { newX = prevPlayer.x newY = prevPlayer.y } else { diff --git a/packages/client/src/components/Game/gameLoop.tsx b/packages/client/src/components/Game/gameLoop.tsx index 876c4a9..20fed15 100644 --- a/packages/client/src/components/Game/gameLoop.tsx +++ b/packages/client/src/components/Game/gameLoop.tsx @@ -2,7 +2,7 @@ import { handlePlayerHit, resetPlayerPosition } from './player' import { updateEnemyPositions, respawnEnemies } from './enemy' import { clearCanvas, drawPlayer, drawEnemies, drawObstacles } from './utils' import { Enemy, Obstacle, Player } from '@/components/Game/gameTypes' -import { detectEnemyCollision } from '@/components/Game/collision' +import { hasEnemyCollision } from '@/components/Game/collision' /** * Основной игровой цикл, который обновляет состояние игры и перерисовывает экран каждый кадр. @@ -42,7 +42,7 @@ export const gameLoop = ( // Проверка на столкновения между игроком и врагами enemies.forEach(enemy => { - if (detectEnemyCollision(player, enemy)) { + if (hasEnemyCollision(player, enemy)) { // Обработка столкновения: уменьшаем жизни handlePlayerHit( setPlayer, From 4b02ce2eeba16414ef24daa458b859174c44625b Mon Sep 17 00:00:00 2001 From: Daniil <77277774+shamemask@users.noreply.github.com> Date: Tue, 1 Oct 2024 10:21:54 +0300 Subject: [PATCH 10/19] =?UTF-8?q?[SOK-27]=20fix:=20+hasCollision=20=D0=BE?= =?UTF-8?q?=D0=B3=D1=80=D0=B0=D0=BD=D0=B8=D1=87=D0=B5=D0=BD=D0=B8=D0=B5=20?= =?UTF-8?q?=D0=BA=D0=BE=D0=BB=D0=B8=D1=87=D0=B5=D1=81=D1=82=D0=B2=D0=B0=20?= =?UTF-8?q?=D0=BF=D1=80=D0=BE=D0=B2=D0=B5=D1=80=D0=BE=D0=BA=20=D0=BA=D0=BE?= =?UTF-8?q?=D0=BB=D0=BB=D0=B8=D0=B7=D0=B8=D0=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../client/src/components/Game/collision.tsx | 8 ++++-- .../client/src/components/Game/controls.tsx | 26 +++++++++---------- .../client/src/components/Game/gameLoop.tsx | 4 +-- 3 files changed, 21 insertions(+), 17 deletions(-) diff --git a/packages/client/src/components/Game/collision.tsx b/packages/client/src/components/Game/collision.tsx index 0a83414..6eca1ae 100644 --- a/packages/client/src/components/Game/collision.tsx +++ b/packages/client/src/components/Game/collision.tsx @@ -1,6 +1,9 @@ import { Enemy, Obstacle, Player } from '@/components/Game/gameTypes' -export const hasCollision = (player: Player, obstacle: Obstacle): boolean => { +export const detectCollision = ( + player: Player, + obstacle: Obstacle +): boolean => { return ( player.x < obstacle.x + obstacle.width && player.x + player.width > obstacle.x && @@ -8,7 +11,8 @@ export const hasCollision = (player: Player, obstacle: Obstacle): boolean => { player.y + player.height > obstacle.y ) } -export const hasEnemyCollision = ( + +export const detectEnemyCollision = ( rect1: Player | Enemy, rect2: Obstacle | Enemy ): boolean => { diff --git a/packages/client/src/components/Game/controls.tsx b/packages/client/src/components/Game/controls.tsx index fa92ca5..ad4e3f4 100644 --- a/packages/client/src/components/Game/controls.tsx +++ b/packages/client/src/components/Game/controls.tsx @@ -1,6 +1,6 @@ import { ControlsProps, KeyMap } from '@/components/Game/gameTypes' -import { hasCollision } from '@/components/Game/collision' +import { detectCollision } from '@/components/Game/collision' const keyMap: KeyMap = {} @@ -37,20 +37,20 @@ export const updatePlayerMovement = (props: ControlsProps) => { } // Обработка столкновений с препятствиями - props.obstacles.forEach(obstacle => { - if (hasCollision({ ...prevPlayer, x: newX, y: newY }, obstacle)) { - newX = prevPlayer.x - newY = prevPlayer.y - } else { - // Ограничение движения по краям canvas - newX = Math.max(0, Math.min(newX, props.canvasWidth - prevPlayer.width)) - newY = Math.max( - 0, - Math.min(newY, props.canvasHeight - prevPlayer.height) - ) - } + const hasCollision = props.obstacles.find(obstacle => { + return detectCollision({ ...prevPlayer, x: newX, y: newY }, obstacle) }) + // Если есть столкновение, то вернуть предыдущую позицию + if (hasCollision) { + newX = prevPlayer.x + newY = prevPlayer.y + } else { + // Ограничение движения по краям canvas + newX = Math.max(0, Math.min(newX, props.canvasWidth - prevPlayer.width)) + newY = Math.max(0, Math.min(newY, props.canvasHeight - prevPlayer.height)) + } + return { ...prevPlayer, x: newX, y: newY } }) } diff --git a/packages/client/src/components/Game/gameLoop.tsx b/packages/client/src/components/Game/gameLoop.tsx index 20fed15..876c4a9 100644 --- a/packages/client/src/components/Game/gameLoop.tsx +++ b/packages/client/src/components/Game/gameLoop.tsx @@ -2,7 +2,7 @@ import { handlePlayerHit, resetPlayerPosition } from './player' import { updateEnemyPositions, respawnEnemies } from './enemy' import { clearCanvas, drawPlayer, drawEnemies, drawObstacles } from './utils' import { Enemy, Obstacle, Player } from '@/components/Game/gameTypes' -import { hasEnemyCollision } from '@/components/Game/collision' +import { detectEnemyCollision } from '@/components/Game/collision' /** * Основной игровой цикл, который обновляет состояние игры и перерисовывает экран каждый кадр. @@ -42,7 +42,7 @@ export const gameLoop = ( // Проверка на столкновения между игроком и врагами enemies.forEach(enemy => { - if (hasEnemyCollision(player, enemy)) { + if (detectEnemyCollision(player, enemy)) { // Обработка столкновения: уменьшаем жизни handlePlayerHit( setPlayer, From caf795a7dbc0741c07d0140ae821ecffa93db84a Mon Sep 17 00:00:00 2001 From: Daniil <77277774+shamemask@users.noreply.github.com> Date: Tue, 1 Oct 2024 10:23:17 +0300 Subject: [PATCH 11/19] =?UTF-8?q?[SOK-27]=20fix:=20=D1=83=D0=B1=D1=80?= =?UTF-8?q?=D0=B0=D0=BB=20=D0=BD=D0=B5=20=D0=BD=D1=83=D0=B6=D0=BD=D1=8B?= =?UTF-8?q?=D0=B9=20=D0=BA=D0=BE=D0=BC=D0=BC=D0=B5=D0=BD=D1=82=D0=B0=D1=80?= =?UTF-8?q?=D0=B8=D0=B9=20=D0=BE=20=D0=B7=D0=B0=D0=B2=D0=B5=D1=80=D1=88?= =?UTF-8?q?=D0=B5=D0=BD=D0=B8=D0=B8=20=D0=B8=D0=B3=D1=80=D1=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/client/src/components/Game/player.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/client/src/components/Game/player.tsx b/packages/client/src/components/Game/player.tsx index 5e0a383..76c232b 100644 --- a/packages/client/src/components/Game/player.tsx +++ b/packages/client/src/components/Game/player.tsx @@ -37,7 +37,6 @@ export const handlePlayerHit = ( setLives(prevLives => { const newLives = prevLives - 1 if (newLives <= 0) { - console.log('Game over!') window.location.reload() } else { resetPlayerPosition(setPlayer) From e7499c029f7cd142ff2fcc8497bb4fbb693c40e2 Mon Sep 17 00:00:00 2001 From: Daniil <77277774+shamemask@users.noreply.github.com> Date: Tue, 1 Oct 2024 16:05:11 +0300 Subject: [PATCH 12/19] =?UTF-8?q?[SOK-27]=20fix:=20=D0=BD=D0=B0=D1=81?= =?UTF-8?q?=D1=82=D1=80=D0=BE=D0=B8=D0=BB=20=D0=B7=D0=B0=D0=BF=D1=83=D1=81?= =?UTF-8?q?=D0=BA=20=D0=B8=D0=B3=D1=80=D1=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/client/src/components/Game/Game.tsx | 77 +++++++++----------- 1 file changed, 36 insertions(+), 41 deletions(-) diff --git a/packages/client/src/components/Game/Game.tsx b/packages/client/src/components/Game/Game.tsx index 75e7604..388aa24 100644 --- a/packages/client/src/components/Game/Game.tsx +++ b/packages/client/src/components/Game/Game.tsx @@ -16,7 +16,6 @@ const livesUse = 3 export const Game: React.FC = () => { const canvasRef = useRef(null) - let context: CanvasRenderingContext2D | null | undefined const [lives, setLives] = useState(livesUse) const [player, setPlayer] = useState(initializePlayer()) const [enemies, setEnemies] = useState(initializeEnemies(5)) @@ -37,45 +36,45 @@ export const Game: React.FC = () => { const handleContinue = () => { setIsPaused(false) - if (isGameOver) { - setIsGameOver(false) - setLives(livesUse) - setGameStarted(false) - } + setIsGameOver(false) + setLives(livesUse) + setPlayer(initializePlayer()) + setEnemies(initializeEnemies(5)) } const loop = useCallback( (timestamp: number) => { - if (!isPaused && !isGameOver && context) { + if (!isPaused && !isGameOver && canvasRef.current) { const deltaTime = timestamp - lastTimestamp if (deltaTime >= 1000 / maxFPS) { setLastTimestamp(timestamp) - const canvas = getCanvas() - if (!canvas) return - const moveProps: ControlsProps = { - player, - setPlayer, - obstacles, - canvasWidth: canvas.width, - canvasHeight: canvas.height, + const context = canvasRef.current.getContext('2d') + if (context) { + const moveProps: ControlsProps = { + player, + setPlayer, + obstacles, + canvasWidth: canvasRef.current.width, + canvasHeight: canvasRef.current.height, + } + updatePlayerMovement(moveProps) + gameLoop( + context, + player, + setPlayer, + enemies, + setEnemies, + obstacles, + lives, + setLives, + handleGameOver, + isPaused, + isGameOver + ) } - updatePlayerMovement(moveProps) - gameLoop( - context, - player, - setPlayer, - enemies, - setEnemies, - obstacles, - lives, - setLives, - handleGameOver, - isPaused, - isGameOver - ) - requestAnimationFrame(loop) } + requestAnimationFrame(loop) } }, [ @@ -103,23 +102,19 @@ export const Game: React.FC = () => { } }, []) - const getCanvas = () => canvasRef.current - - const getContext = () => getCanvas()?.getContext('2d') - useEffect(() => { if (gameStarted && !isPaused) { - context = getContext() - - if (context) { - requestAnimationFrame(loop) - } + requestAnimationFrame(loop) } - }, [isPaused, isGameOver, loop]) + }, [gameStarted, isPaused, loop]) const startGame = () => { setGameStarted(true) - handleContinue() + setIsPaused(false) + setIsGameOver(false) + setLives(livesUse) + setPlayer(initializePlayer()) + setEnemies(initializeEnemies(5)) } return ( From 64eb48757e33cde59d30c77e786e8dc7cf7f2dd0 Mon Sep 17 00:00:00 2001 From: Daniil <77277774+shamemask@users.noreply.github.com> Date: Wed, 2 Oct 2024 16:05:50 +0300 Subject: [PATCH 13/19] =?UTF-8?q?[SOK-27]=20fix:=20=D0=BD=D0=B0=D1=81?= =?UTF-8?q?=D1=82=D1=80=D0=BE=D0=B8=D0=BB=20=D0=BF=D0=B5=D1=80=D0=B5=D1=85?= =?UTF-8?q?=D0=BE=D0=B4=20=D0=BD=D0=B0=20=D1=81=D1=82=D1=80=D0=B0=D0=BD?= =?UTF-8?q?=D0=B8=D1=86=D1=83=20=D0=BF=D0=BE=D1=80=D0=B0=D0=B6=D0=B5=D0=BD?= =?UTF-8?q?=D0=B8=D1=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/client/src/components/Game/player.tsx | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/client/src/components/Game/player.tsx b/packages/client/src/components/Game/player.tsx index 76c232b..d944b5b 100644 --- a/packages/client/src/components/Game/player.tsx +++ b/packages/client/src/components/Game/player.tsx @@ -1,4 +1,5 @@ import { Enemy, Player } from '@/components/Game/gameTypes' +import { useNavigate } from 'react-router-dom' export const initializePlayer = () => ({ x: 400, @@ -34,10 +35,11 @@ export const handlePlayerHit = ( ) => void, setEnemies: React.Dispatch> ) => { + const navigate = useNavigate() setLives(prevLives => { const newLives = prevLives - 1 if (newLives <= 0) { - window.location.reload() + navigate('/game-over') } else { resetPlayerPosition(setPlayer) respawnEnemies(setEnemies) From a4d9bbb2f3c3f9657a0a864ba7dca388a2bd80a9 Mon Sep 17 00:00:00 2001 From: Daniil <77277774+shamemask@users.noreply.github.com> Date: Thu, 3 Oct 2024 21:19:14 +0300 Subject: [PATCH 14/19] =?UTF-8?q?[SOK-27]=20fix:=20=D0=BF=D0=B5=D1=80?= =?UTF-8?q?=D0=B5=D0=B4=D0=B5=D0=BB=D0=B0=D0=BB=20=D0=BD=D0=B0=D1=81=D1=82?= =?UTF-8?q?=D1=80=D0=BE=D0=B9=D0=BA=D0=B8=20=D0=B8=D0=B3=D1=80=D0=BE=D0=BA?= =?UTF-8?q?=D0=B0=20=D0=BF=D0=BE=D0=B4=20=D0=BF=D0=B5=D1=80=D0=B5=D0=BC?= =?UTF-8?q?=D0=B5=D0=BD=D0=BD=D1=83=D1=8E=20PLAYER=5FDEFAULT=5FPARAMS?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/client/src/components/Game/Game.tsx | 8 ++++---- packages/client/src/components/Game/player.tsx | 6 +++--- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/packages/client/src/components/Game/Game.tsx b/packages/client/src/components/Game/Game.tsx index 388aa24..d184c0b 100644 --- a/packages/client/src/components/Game/Game.tsx +++ b/packages/client/src/components/Game/Game.tsx @@ -1,7 +1,7 @@ import React, { useCallback, useEffect, useRef, useState } from 'react' import './Game.scss' import { initializeEnemies } from '@/components/Game/enemy' -import { initializePlayer } from '@/components/Game/player' +import { PLAYER_DEFAULT_PARAMS } from '@/components/Game/player' import { gameLoop } from '@/components/Game/gameLoop' import { handleKeyDown, @@ -17,7 +17,7 @@ const livesUse = 3 export const Game: React.FC = () => { const canvasRef = useRef(null) const [lives, setLives] = useState(livesUse) - const [player, setPlayer] = useState(initializePlayer()) + const [player, setPlayer] = useState(PLAYER_DEFAULT_PARAMS) const [enemies, setEnemies] = useState(initializeEnemies(5)) const [obstacles] = useState(initializeObstacle()) const [isPaused, setIsPaused] = useState(false) @@ -38,7 +38,7 @@ export const Game: React.FC = () => { setIsPaused(false) setIsGameOver(false) setLives(livesUse) - setPlayer(initializePlayer()) + setPlayer(PLAYER_DEFAULT_PARAMS) setEnemies(initializeEnemies(5)) } @@ -113,7 +113,7 @@ export const Game: React.FC = () => { setIsPaused(false) setIsGameOver(false) setLives(livesUse) - setPlayer(initializePlayer()) + setPlayer(PLAYER_DEFAULT_PARAMS) setEnemies(initializeEnemies(5)) } diff --git a/packages/client/src/components/Game/player.tsx b/packages/client/src/components/Game/player.tsx index d944b5b..0088924 100644 --- a/packages/client/src/components/Game/player.tsx +++ b/packages/client/src/components/Game/player.tsx @@ -1,19 +1,19 @@ import { Enemy, Player } from '@/components/Game/gameTypes' import { useNavigate } from 'react-router-dom' -export const initializePlayer = () => ({ +export const PLAYER_DEFAULT_PARAMS = { x: 400, y: 300, width: 30, height: 30, speed: 0.5, direction: { x: 0, y: 0 }, -}) +} export const resetPlayerPosition = ( setPlayer: React.Dispatch> ) => { - setPlayer(initializePlayer()) + setPlayer(PLAYER_DEFAULT_PARAMS) } /** From 89b853513c0dc326b57e58f7e395c06d8f1c8e66 Mon Sep 17 00:00:00 2001 From: Daniil <77277774+shamemask@users.noreply.github.com> Date: Thu, 3 Oct 2024 21:20:16 +0300 Subject: [PATCH 15/19] =?UTF-8?q?[SOK-27]=20fix:=20=D0=BF=D0=B5=D1=80?= =?UTF-8?q?=D0=B5=D0=B4=D0=B5=D0=BB=D0=B0=D0=BB=20resetPlayerPosition?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/client/src/components/Game/gameLoop.tsx | 2 +- packages/client/src/components/Game/player.tsx | 7 ++++++- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/packages/client/src/components/Game/gameLoop.tsx b/packages/client/src/components/Game/gameLoop.tsx index 876c4a9..a25bcf4 100644 --- a/packages/client/src/components/Game/gameLoop.tsx +++ b/packages/client/src/components/Game/gameLoop.tsx @@ -47,7 +47,7 @@ export const gameLoop = ( handlePlayerHit( setPlayer, setLives, - () => resetPlayerPosition(setPlayer), + () => resetPlayerPosition(player, setPlayer), respawnEnemies, setEnemies ) diff --git a/packages/client/src/components/Game/player.tsx b/packages/client/src/components/Game/player.tsx index 0088924..277168c 100644 --- a/packages/client/src/components/Game/player.tsx +++ b/packages/client/src/components/Game/player.tsx @@ -11,9 +11,14 @@ export const PLAYER_DEFAULT_PARAMS = { } export const resetPlayerPosition = ( + player: Player, setPlayer: React.Dispatch> ) => { - setPlayer(PLAYER_DEFAULT_PARAMS) + setPlayer({ + ...player, + x: PLAYER_DEFAULT_PARAMS.x, + y: PLAYER_DEFAULT_PARAMS.y, + }) } /** From 8572f09c09df2b4bc7f66b3d7fdf72930addc1f9 Mon Sep 17 00:00:00 2001 From: Daniil <77277774+shamemask@users.noreply.github.com> Date: Fri, 4 Oct 2024 12:42:50 +0300 Subject: [PATCH 16/19] =?UTF-8?q?[SOK-27]=20fix:=20=D0=BF=D0=B5=D1=80?= =?UTF-8?q?=D0=B5=D0=B4=D0=B0=D0=BB=20=D1=86=D0=B2=D0=B5=D1=82=20=D0=BA?= =?UTF-8?q?=D0=BD=D0=BE=D0=BF=D0=BA=D0=B8=20=D0=B8=D0=B7=20=D0=BF=D0=B5?= =?UTF-8?q?=D1=80=D0=B5=D0=BC=D0=B5=D0=BD=D0=BD=D0=BE=D0=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/client/src/components/Game/Game.scss | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/client/src/components/Game/Game.scss b/packages/client/src/components/Game/Game.scss index b640093..ede582f 100644 --- a/packages/client/src/components/Game/Game.scss +++ b/packages/client/src/components/Game/Game.scss @@ -27,14 +27,14 @@ button { margin-top: 10px; padding: 10px 20px; border: none; - background-color: #839d22; + background-color: $c_button; color: white; border-radius: 8px; cursor: pointer; } button:hover { - background-color: #53650b; + background-color: $c_button-bottom; } canvas { border-radius: $border-radius--default; From 024063bd6dce9a265b7ca64b045e80d69916f0c5 Mon Sep 17 00:00:00 2001 From: Daniil <77277774+shamemask@users.noreply.github.com> Date: Fri, 4 Oct 2024 17:50:14 +0300 Subject: [PATCH 17/19] =?UTF-8?q?[SOK-27]=20fix:=20=D1=83=D0=B1=D1=80?= =?UTF-8?q?=D0=B0=D0=BB=20=D0=B8=D0=B7=D0=B1=D1=8B=D1=82=D0=BE=D1=87=D0=BD?= =?UTF-8?q?=D0=BE=D0=B5=20=D0=B7=D0=B0=D1=86=D0=B8=D0=BA=D0=BB=D0=B8=D0=B2?= =?UTF-8?q?=D0=B0=D0=BD=D0=B8=D0=B5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/client/src/components/Game/Game.tsx | 57 +++++++++---------- .../client/src/components/Game/gameLoop.tsx | 29 +--------- .../client/src/components/Game/player.tsx | 4 +- 3 files changed, 33 insertions(+), 57 deletions(-) diff --git a/packages/client/src/components/Game/Game.tsx b/packages/client/src/components/Game/Game.tsx index d184c0b..97a2daf 100644 --- a/packages/client/src/components/Game/Game.tsx +++ b/packages/client/src/components/Game/Game.tsx @@ -44,38 +44,36 @@ export const Game: React.FC = () => { const loop = useCallback( (timestamp: number) => { - if (!isPaused && !isGameOver && canvasRef.current) { - const deltaTime = timestamp - lastTimestamp - - if (deltaTime >= 1000 / maxFPS) { - setLastTimestamp(timestamp) - const context = canvasRef.current.getContext('2d') - if (context) { - const moveProps: ControlsProps = { - player, - setPlayer, - obstacles, - canvasWidth: canvasRef.current.width, - canvasHeight: canvasRef.current.height, - } - updatePlayerMovement(moveProps) - gameLoop( - context, - player, - setPlayer, - enemies, - setEnemies, - obstacles, - lives, - setLives, - handleGameOver, - isPaused, - isGameOver - ) + if (isPaused || isGameOver || !canvasRef.current) return + + const deltaTime = timestamp - lastTimestamp + if (deltaTime >= 1000 / maxFPS) { + setLastTimestamp(timestamp) + const context = canvasRef.current.getContext('2d') + if (context) { + const moveProps: ControlsProps = { + player, + setPlayer, + obstacles, + canvasWidth: canvasRef.current.width, + canvasHeight: canvasRef.current.height, } + updatePlayerMovement(moveProps) + gameLoop( + context, + player, + setPlayer, + enemies, + setEnemies, + obstacles, + lives, + setLives, + handleGameOver + ) } - requestAnimationFrame(loop) } + + requestAnimationFrame(loop) }, [ isPaused, @@ -83,6 +81,7 @@ export const Game: React.FC = () => { lastTimestamp, player, enemies, + obstacles, lives, handleGameOver, ] diff --git a/packages/client/src/components/Game/gameLoop.tsx b/packages/client/src/components/Game/gameLoop.tsx index a25bcf4..d52f5ac 100644 --- a/packages/client/src/components/Game/gameLoop.tsx +++ b/packages/client/src/components/Game/gameLoop.tsx @@ -1,4 +1,4 @@ -import { handlePlayerHit, resetPlayerPosition } from './player' +import { HandlePlayerHit, resetPlayerPosition } from './player' import { updateEnemyPositions, respawnEnemies } from './enemy' import { clearCanvas, drawPlayer, drawEnemies, drawObstacles } from './utils' import { Enemy, Obstacle, Player } from '@/components/Game/gameTypes' @@ -15,8 +15,6 @@ import { detectEnemyCollision } from '@/components/Game/collision' * @param lives - Текущее количество жизней игрока. * @param setLives - Функция для изменения количества жизней игрока. * @param handleGameOver - Обработчик события окончания игры. - * @param isPaused - Флаг паузы. - * @param isGameOver - Флаг окончания игры. */ export const gameLoop = ( context: CanvasRenderingContext2D, @@ -27,9 +25,7 @@ export const gameLoop = ( obstacles: Obstacle[], lives: number, setLives: React.Dispatch>, - handleGameOver: () => void, - isPaused: boolean, - isGameOver: boolean + handleGameOver: () => void ) => { clearCanvas(context) @@ -44,7 +40,7 @@ export const gameLoop = ( enemies.forEach(enemy => { if (detectEnemyCollision(player, enemy)) { // Обработка столкновения: уменьшаем жизни - handlePlayerHit( + HandlePlayerHit( setPlayer, setLives, () => resetPlayerPosition(player, setPlayer), @@ -58,23 +54,4 @@ export const gameLoop = ( } } }) - - // Запуск следующего кадра (будет работать только если игра не на паузе) - if (!isPaused && !isGameOver) { - requestAnimationFrame(() => - gameLoop( - context, - player, - setPlayer, - enemies, - setEnemies, - obstacles, - lives, - setLives, - handleGameOver, - isPaused, - isGameOver - ) - ) - } } diff --git a/packages/client/src/components/Game/player.tsx b/packages/client/src/components/Game/player.tsx index 277168c..0350805 100644 --- a/packages/client/src/components/Game/player.tsx +++ b/packages/client/src/components/Game/player.tsx @@ -6,7 +6,7 @@ export const PLAYER_DEFAULT_PARAMS = { y: 300, width: 30, height: 30, - speed: 0.5, + speed: 0.1, direction: { x: 0, y: 0 }, } @@ -29,7 +29,7 @@ export const resetPlayerPosition = ( * @param respawnEnemies - Функция для респауна врагов. * @param setEnemies - Функция для обновления состояния врагов. */ -export const handlePlayerHit = ( +export const HandlePlayerHit = ( setPlayer: React.Dispatch>, setLives: React.Dispatch>, resetPlayerPosition: ( From 6e5a0cb8009dbb76d1626d5f3d4fef6522944c6b Mon Sep 17 00:00:00 2001 From: Daniil <77277774+shamemask@users.noreply.github.com> Date: Fri, 4 Oct 2024 19:35:30 +0300 Subject: [PATCH 18/19] =?UTF-8?q?[SOK-27]=20fix:=20=D1=83=D0=B1=D1=80?= =?UTF-8?q?=D0=B0=D0=BB=20=D1=81=D0=BE=D1=81=D1=82=D0=BE=D1=8F=D0=BD=D0=B8?= =?UTF-8?q?=D1=8F=20=D0=B8=D0=B7=20=D1=86=D0=B8=D0=BA=D0=BB=D0=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/client/src/components/Game/Game.tsx | 100 +++++++----------- .../client/src/components/Game/controls.tsx | 77 +++++++------- packages/client/src/components/Game/enemy.tsx | 37 +++---- .../client/src/components/Game/gameLoop.tsx | 43 +++----- .../client/src/components/Game/gameTypes.tsx | 3 +- .../client/src/components/Game/player.tsx | 53 ++++------ 6 files changed, 132 insertions(+), 181 deletions(-) diff --git a/packages/client/src/components/Game/Game.tsx b/packages/client/src/components/Game/Game.tsx index 97a2daf..916c93b 100644 --- a/packages/client/src/components/Game/Game.tsx +++ b/packages/client/src/components/Game/Game.tsx @@ -11,81 +11,54 @@ import { import { ControlsProps, Obstacle } from '@/components/Game/gameTypes' import { initializeObstacle } from '@/components/Game/obstacle' -const maxFPS = 60 const livesUse = 3 export const Game: React.FC = () => { const canvasRef = useRef(null) - const [lives, setLives] = useState(livesUse) - const [player, setPlayer] = useState(PLAYER_DEFAULT_PARAMS) - const [enemies, setEnemies] = useState(initializeEnemies(5)) - const [obstacles] = useState(initializeObstacle()) + const playerRef = useRef(PLAYER_DEFAULT_PARAMS) + const enemiesRef = useRef(initializeEnemies(5)) + const obstaclesRef = useRef(initializeObstacle()) + const livesRef = useRef(livesUse) + const [gameStarted, setGameStarted] = useState(false) const [isPaused, setIsPaused] = useState(false) + const isPausedRef = useRef(false) const [isGameOver, setIsGameOver] = useState(false) - const [gameStarted, setGameStarted] = useState(false) - const [lastTimestamp, setLastTimestamp] = useState(0) - const togglePause = () => { + const togglePause = useCallback(() => { setIsPaused(prev => !prev) - } + isPausedRef.current = !isPausedRef.current // Обновляем ref для паузы + }, []) const handleGameOver = useCallback(() => { setIsGameOver(true) setIsPaused(true) + isPausedRef.current = true }, []) - const handleContinue = () => { - setIsPaused(false) - setIsGameOver(false) - setLives(livesUse) - setPlayer(PLAYER_DEFAULT_PARAMS) - setEnemies(initializeEnemies(5)) - } - - const loop = useCallback( - (timestamp: number) => { - if (isPaused || isGameOver || !canvasRef.current) return - - const deltaTime = timestamp - lastTimestamp - if (deltaTime >= 1000 / maxFPS) { - setLastTimestamp(timestamp) - const context = canvasRef.current.getContext('2d') - if (context) { - const moveProps: ControlsProps = { - player, - setPlayer, - obstacles, - canvasWidth: canvasRef.current.width, - canvasHeight: canvasRef.current.height, - } - updatePlayerMovement(moveProps) - gameLoop( - context, - player, - setPlayer, - enemies, - setEnemies, - obstacles, - lives, - setLives, - handleGameOver - ) + const loop = useCallback(() => { + if (!isPausedRef.current && !isGameOver && canvasRef.current) { + const context = canvasRef.current.getContext('2d') + if (context) { + const moveProps: ControlsProps = { + playerRef, + obstacles: obstaclesRef.current, + canvasWidth: canvasRef.current.width, + canvasHeight: canvasRef.current.height, } + updatePlayerMovement(moveProps) + + gameLoop( + context, + playerRef, + enemiesRef, + obstaclesRef.current, + livesRef, + handleGameOver + ) } - requestAnimationFrame(loop) - }, - [ - isPaused, - isGameOver, - lastTimestamp, - player, - enemies, - obstacles, - lives, - handleGameOver, - ] - ) + } + }, [isGameOver, handleGameOver, livesRef]) useEffect(() => { const handleKeyDownWrapper = (event: KeyboardEvent) => @@ -110,15 +83,16 @@ export const Game: React.FC = () => { const startGame = () => { setGameStarted(true) setIsPaused(false) + isPausedRef.current = false setIsGameOver(false) - setLives(livesUse) - setPlayer(PLAYER_DEFAULT_PARAMS) - setEnemies(initializeEnemies(5)) + livesRef.current = livesUse + playerRef.current = PLAYER_DEFAULT_PARAMS + enemiesRef.current = initializeEnemies(5) } return (
-
{`Жизни: ${lives}`}
+
{`Жизни: ${livesRef.current.toString()}`}
{!gameStarted ? ( @@ -133,7 +107,7 @@ export const Game: React.FC = () => { {isGameOver && (

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

- +
)}
diff --git a/packages/client/src/components/Game/controls.tsx b/packages/client/src/components/Game/controls.tsx index ad4e3f4..509690a 100644 --- a/packages/client/src/components/Game/controls.tsx +++ b/packages/client/src/components/Game/controls.tsx @@ -16,41 +16,46 @@ export const handleKeyUp = (key: string) => { // Функция для обновления позиции игрока на основе нажатых клавиш export const updatePlayerMovement = (props: ControlsProps) => { - const speed = props.player.speed - - props.setPlayer(prevPlayer => { - let newX = prevPlayer.x - let newY = prevPlayer.y - - // Определение направления движения - if (keyMap['ArrowUp'] || keyMap['w'] || keyMap['ц']) { - newY -= speed - } - if (keyMap['ArrowDown'] || keyMap['s'] || keyMap['ы']) { - newY += speed - } - if (keyMap['ArrowLeft'] || keyMap['a'] || keyMap['ф']) { - newX -= speed - } - if (keyMap['ArrowRight'] || keyMap['d'] || keyMap['в']) { - newX += speed - } - - // Обработка столкновений с препятствиями - const hasCollision = props.obstacles.find(obstacle => { - return detectCollision({ ...prevPlayer, x: newX, y: newY }, obstacle) - }) - - // Если есть столкновение, то вернуть предыдущую позицию - if (hasCollision) { - newX = prevPlayer.x - newY = prevPlayer.y - } else { - // Ограничение движения по краям canvas - newX = Math.max(0, Math.min(newX, props.canvasWidth - prevPlayer.width)) - newY = Math.max(0, Math.min(newY, props.canvasHeight - prevPlayer.height)) - } - - return { ...prevPlayer, x: newX, y: newY } + const speed = props.playerRef.current.speed + let newX = props.playerRef.current.x + let newY = props.playerRef.current.y + + // Определение направления движения + if (keyMap['ArrowUp'] || keyMap['w'] || keyMap['ц']) { + newY -= speed + } + if (keyMap['ArrowDown'] || keyMap['s'] || keyMap['ы']) { + newY += speed + } + if (keyMap['ArrowLeft'] || keyMap['a'] || keyMap['ф']) { + newX -= speed + } + if (keyMap['ArrowRight'] || keyMap['d'] || keyMap['в']) { + newX += speed + } + + // Обработка столкновений с препятствиями + const hasCollision = props.obstacles.some(obstacle => { + return detectCollision( + { ...props.playerRef.current, x: newX, y: newY }, + obstacle + ) }) + + // Если есть столкновение, то оставить предыдущую позицию + if (!hasCollision) { + // Ограничение движения по краям canvas + newX = Math.max( + 0, + Math.min(newX, props.canvasWidth - props.playerRef.current.width) + ) + newY = Math.max( + 0, + Math.min(newY, props.canvasHeight - props.playerRef.current.height) + ) + + // Обновляем позицию игрока в референсе + props.playerRef.current.x = newX + props.playerRef.current.y = newY + } } diff --git a/packages/client/src/components/Game/enemy.tsx b/packages/client/src/components/Game/enemy.tsx index ee61657..692b85f 100644 --- a/packages/client/src/components/Game/enemy.tsx +++ b/packages/client/src/components/Game/enemy.tsx @@ -1,7 +1,7 @@ import { getRandomEdgePosition } from './utils' import { Enemy, Player } from '@/components/Game/gameTypes' -export const initializeEnemies = (numberOfEnemies: number): Enemy[] => { +export const initializeEnemies = (numberOfEnemies: number) => { const initialEnemies: Enemy[] = [] for (let i = 0; i < numberOfEnemies; i++) { // количество врагов @@ -11,38 +11,33 @@ export const initializeEnemies = (numberOfEnemies: number): Enemy[] => { y, width: 30, height: 30, - speed: 2, + speed: 1, direction: { x: 0, y: 0 }, } initialEnemies.push(enemy) } - return initialEnemies + return initialEnemies as Enemy[] } export const updateEnemyPositions = ( player: Player, - enemies: Enemy[], - setEnemies: React.Dispatch> + enemiesRef: React.MutableRefObject ) => { - setEnemies(prevEnemies => - prevEnemies.map(enemy => { - const directionX = player.x - enemy.x - const directionY = player.y - enemy.y + enemiesRef.current = enemiesRef.current.map(enemy => { + const directionX = player.x - enemy.x + const directionY = player.y - enemy.y - const magnitude = Math.sqrt(directionX ** 5 + directionY ** 5) - const normalizedX = directionX / magnitude - const normalizedY = directionY / magnitude + const magnitude = Math.sqrt(directionX ** 2 + directionY ** 2) + const normalizedX = directionX / magnitude + const normalizedY = directionY / magnitude - const newX = enemy.x + normalizedX * enemy.speed - const newY = enemy.y + normalizedY * enemy.speed + const newX = enemy.x + normalizedX * enemy.speed + const newY = enemy.y + normalizedY * enemy.speed - return { ...enemy, x: newX, y: newY } - }) - ) + return { ...enemy, x: newX, y: newY } + }) } -export const respawnEnemies = ( - setEnemies: React.Dispatch> -) => { - setEnemies(initializeEnemies(5)) +export const respawnEnemies = (enemiesRef: React.MutableRefObject) => { + enemiesRef.current = initializeEnemies(5) } diff --git a/packages/client/src/components/Game/gameLoop.tsx b/packages/client/src/components/Game/gameLoop.tsx index d52f5ac..d777848 100644 --- a/packages/client/src/components/Game/gameLoop.tsx +++ b/packages/client/src/components/Game/gameLoop.tsx @@ -7,51 +7,40 @@ import { detectEnemyCollision } from '@/components/Game/collision' /** * Основной игровой цикл, который обновляет состояние игры и перерисовывает экран каждый кадр. * @param context - Контекст рисования для Canvas. - * @param player - Объект игрока. - * @param setPlayer - Функция для обновления состояния игрока. - * @param enemies - Массив врагов. - * @param setEnemies - Функция для обновления состояния врагов. + * @param playerRef - Ссылка на текущего игрока. + * @param enemiesRef - Ссылка на массив врагов. * @param obstacles - Массив препятствий. - * @param lives - Текущее количество жизней игрока. - * @param setLives - Функция для изменения количества жизней игрока. + * @param livesRef - Ссылка на текущее количество жизней игрока. * @param handleGameOver - Обработчик события окончания игры. */ export const gameLoop = ( context: CanvasRenderingContext2D, - player: Player, - setPlayer: React.Dispatch>, - enemies: Enemy[], - setEnemies: React.Dispatch>, + playerRef: React.MutableRefObject, + enemiesRef: React.MutableRefObject, obstacles: Obstacle[], - lives: number, - setLives: React.Dispatch>, + livesRef: React.MutableRefObject, handleGameOver: () => void ) => { clearCanvas(context) // Обновление позиций врагов - updateEnemyPositions(player, enemies, setEnemies) + updateEnemyPositions(playerRef.current, enemiesRef) + // Отрисовка всех игровых объектов drawObstacles(context, obstacles) - drawPlayer(context, player) - drawEnemies(context, enemies) + drawPlayer(context, playerRef.current) + drawEnemies(context, enemiesRef.current) // Проверка на столкновения между игроком и врагами - enemies.forEach(enemy => { - if (detectEnemyCollision(player, enemy)) { + enemiesRef.current.forEach(enemy => { + if (detectEnemyCollision(playerRef.current, enemy)) { // Обработка столкновения: уменьшаем жизни HandlePlayerHit( - setPlayer, - setLives, - () => resetPlayerPosition(player, setPlayer), - respawnEnemies, - setEnemies + livesRef, + handleGameOver, + () => resetPlayerPosition(playerRef), + () => respawnEnemies(enemiesRef) ) - - // Проверка на конец игры - if (lives - 1 <= 0) { - handleGameOver() // Вызываем окончание игры, если жизни закончились - } } }) } diff --git a/packages/client/src/components/Game/gameTypes.tsx b/packages/client/src/components/Game/gameTypes.tsx index 893f206..e375e95 100644 --- a/packages/client/src/components/Game/gameTypes.tsx +++ b/packages/client/src/components/Game/gameTypes.tsx @@ -28,8 +28,7 @@ export interface KeyMap { } export interface ControlsProps { - player: Player - setPlayer: React.Dispatch> + playerRef: React.MutableRefObject obstacles: Obstacle[] canvasWidth: number canvasHeight: number diff --git a/packages/client/src/components/Game/player.tsx b/packages/client/src/components/Game/player.tsx index 0350805..1de396d 100644 --- a/packages/client/src/components/Game/player.tsx +++ b/packages/client/src/components/Game/player.tsx @@ -1,54 +1,43 @@ -import { Enemy, Player } from '@/components/Game/gameTypes' -import { useNavigate } from 'react-router-dom' +import { Player } from '@/components/Game/gameTypes' export const PLAYER_DEFAULT_PARAMS = { x: 400, y: 300, width: 30, height: 30, - speed: 0.1, + speed: 1, direction: { x: 0, y: 0 }, } export const resetPlayerPosition = ( - player: Player, - setPlayer: React.Dispatch> + playerRef: React.MutableRefObject ) => { - setPlayer({ - ...player, + playerRef.current = { + ...playerRef.current, x: PLAYER_DEFAULT_PARAMS.x, y: PLAYER_DEFAULT_PARAMS.y, - }) + } } - /** * Функция для обработки столкновения игрока с врагом. - * @param setPlayer - Функция для обновления состояния игрока. - * @param setLives - Функция для изменения количества жизней игрока. + * @param livesRef - Ссылка на текущее количество жизней игрока. + * @param handleGameOver - Обработчик события окончания игры. * @param resetPlayerPosition - Функция для сброса позиции игрока. * @param respawnEnemies - Функция для респауна врагов. - * @param setEnemies - Функция для обновления состояния врагов. */ export const HandlePlayerHit = ( - setPlayer: React.Dispatch>, - setLives: React.Dispatch>, - resetPlayerPosition: ( - setPlayer: React.Dispatch> - ) => void, - respawnEnemies: ( - setEnemies: React.Dispatch> - ) => void, - setEnemies: React.Dispatch> + livesRef: React.MutableRefObject, + handleGameOver: () => void, + resetPlayerPosition: () => void, + respawnEnemies: () => void ) => { - const navigate = useNavigate() - setLives(prevLives => { - const newLives = prevLives - 1 - if (newLives <= 0) { - navigate('/game-over') - } else { - resetPlayerPosition(setPlayer) - respawnEnemies(setEnemies) - } - return newLives - }) + const newLives = livesRef.current - 1 + + if (newLives <= 0) { + handleGameOver() + } else { + livesRef.current = newLives + resetPlayerPosition() // Сбрасываем позицию игрока + respawnEnemies() // Респавн врагов + } } From 0168aa7b718d3ff293acf13bdb20b200fb42bf00 Mon Sep 17 00:00:00 2001 From: Iskandarov Timur Date: Tue, 8 Oct 2024 11:10:56 +0500 Subject: [PATCH 19/19] =?UTF-8?q?=D0=9F=D0=BE=D0=B4=D1=80=D1=83=D0=B6?= =?UTF-8?q?=D0=B8=D0=BB=20=D0=B8=D0=B3=D1=80=D1=83=20=D0=B8=20=D1=81=D1=82?= =?UTF-8?q?=D1=80=D0=B0=D0=BD=D0=B8=D1=86=D1=83=20=D0=B8=D0=B3=D1=80=D1=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/client/src/app/App.tsx | 7 +----- packages/client/src/components/Game/Game.scss | 25 ------------------- packages/client/src/components/Game/Game.tsx | 12 ++++----- packages/client/src/pages/Game/Game.scss | 6 ++++- packages/client/src/pages/Game/Game.tsx | 20 +++++++++++---- packages/client/src/pages/Game/GamePage.scss | 7 ------ 6 files changed, 26 insertions(+), 51 deletions(-) delete mode 100644 packages/client/src/pages/Game/GamePage.scss diff --git a/packages/client/src/app/App.tsx b/packages/client/src/app/App.tsx index 82d6ee9..7e87010 100644 --- a/packages/client/src/app/App.tsx +++ b/packages/client/src/app/App.tsx @@ -3,19 +3,14 @@ import RootLayout from '@/layouts/RootLayout/RootLayout' import PrivateLayout from '@/layouts/PrivateLayout/PrivateLayout' import AuthLayout from '@/layouts/AuthLayout/AuthLayout' import PublicLayout from '@/layouts/PublicLayout/PublicLayout' -import RootLayout from '@/layouts/RootLayout/RootLayout' import { Error } from '@/pages/Error/Error' import { Forum } from '@/pages/Forum/Forum' -import { GamePage } from '@/pages/Game/Game' import { Leaderboard } from '@/pages/Leaderboard/Leaderboard' import { Main } from '@/pages/Main/Main' import { SignIn } from '@/pages/SignIn/SignIn' import { SignUp } from '@/pages/SignUp/SignUp' import { Game } from '@/pages/Game/Game' -import { Forum } from '@/pages/Forum/Forum' -import { Error } from '@/pages/Error/Error' import { Thread } from '@/pages/Thread/Thread' -import { Leaderboard } from '@/pages/Leaderboard/Leaderboard' import { Profile } from '@/pages/Profile/Profile' import { ChangePassword } from '@/pages/Profile/ChangePassword' import { SvgSprite } from '@/components/ui/SvgSprite/SvgSprite' @@ -39,7 +34,7 @@ const routerConfig = createBrowserRouter([ children: [ { path: '/game', - element: , + element: , }, { path: '/forum', diff --git a/packages/client/src/components/Game/Game.scss b/packages/client/src/components/Game/Game.scss index ede582f..93e80be 100644 --- a/packages/client/src/components/Game/Game.scss +++ b/packages/client/src/components/Game/Game.scss @@ -11,31 +11,6 @@ position: relative; } -.modal { - position: absolute; - top: 50%; - left: 50%; - transform: translate(-50%, -50%); - background-color: rgba(0, 0, 0, 0.8); - color: white; - padding: 20px; - border-radius: 8px; - text-align: center; -} - -button { - margin-top: 10px; - padding: 10px 20px; - border: none; - background-color: $c_button; - color: white; - border-radius: 8px; - cursor: pointer; -} - -button:hover { - background-color: $c_button-bottom; -} canvas { border-radius: $border-radius--default; box-shadow: 0 4px 8px rgba($c_black, 0.5); diff --git a/packages/client/src/components/Game/Game.tsx b/packages/client/src/components/Game/Game.tsx index 916c93b..46cc8c7 100644 --- a/packages/client/src/components/Game/Game.tsx +++ b/packages/client/src/components/Game/Game.tsx @@ -10,6 +10,7 @@ import { } from '@/components/Game/controls' import { ControlsProps, Obstacle } from '@/components/Game/gameTypes' import { initializeObstacle } from '@/components/Game/obstacle' +import { Modal } from '../common/Modal/Modal' const livesUse = 3 @@ -103,13 +104,10 @@ export const Game: React.FC = () => { )} - {/* Модальное окно при проигрыше */} - {isGameOver && ( -
-

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

- -
- )} + setIsGameOver(false)}> +

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

+ +
) } diff --git a/packages/client/src/pages/Game/Game.scss b/packages/client/src/pages/Game/Game.scss index 05aa4d7..74732fb 100644 --- a/packages/client/src/pages/Game/Game.scss +++ b/packages/client/src/pages/Game/Game.scss @@ -19,7 +19,7 @@ $border-color: #70842f; .game-wrapper { position: relative; width: 800px; - height: 600px; + height: auto; background-color: $c_black; border-radius: 8px; overflow: hidden; @@ -150,6 +150,10 @@ $border-color: #70842f; position: absolute; inset: 12px; background-color: $overlay-color; + + &_hide { + display: none; + } } .game-over-screen { diff --git a/packages/client/src/pages/Game/Game.tsx b/packages/client/src/pages/Game/Game.tsx index 9f8002c..e3f46de 100644 --- a/packages/client/src/pages/Game/Game.tsx +++ b/packages/client/src/pages/Game/Game.tsx @@ -1,13 +1,14 @@ +import './Game.scss' import { CustomPageTitle } from '@/components/ui/CustomPageTitle/CustomPageTitle' import { Arrows } from './components/Arrows/Arrows' import { KillsCounter } from './components/KillsCounter/KillsCounter' -import './Game.scss' import { PauseHelp } from './components/PauseHelp/PauseHelp' import { FireControll } from './components/FireControll/FireControll' import { useEffect, useState } from 'react' import { Modal } from '@/components/common/Modal/Modal' import GameInfo from '@/assets/images/game-info.jpg' import { Button } from '@/components/ui/Button/Button' +import { Game as GamePrototype } from '@/components/Game/Game' export const Game = () => { const [isInfoModalOpen, setIsInfoModalOpen] = useState(false) @@ -18,6 +19,7 @@ export const Game = () => { rightButton: false, fireButton: false, }) + const [isStartedGame, setIsStartedGame] = useState(false) const pauseHandler = () => { console.log('pauseHandler') @@ -83,11 +85,19 @@ export const Game = () => {
- Воот тут игра -
-
-
+
Game Over
diff --git a/packages/client/src/pages/Game/GamePage.scss b/packages/client/src/pages/Game/GamePage.scss deleted file mode 100644 index 8ba7e90..0000000 --- a/packages/client/src/pages/Game/GamePage.scss +++ /dev/null @@ -1,7 +0,0 @@ -.game-page { - display: flex; - justify-content: center; - align-items: center; - height: 100vh; - background-color: #010101; // Цвет фона для всей страницы -}