Skip to content

Commit

Permalink
[SOK-27] fix: убрал состояния из цикла
Browse files Browse the repository at this point in the history
  • Loading branch information
shamemask committed Oct 4, 2024
1 parent 024063b commit 6e5a0cb
Show file tree
Hide file tree
Showing 6 changed files with 132 additions and 181 deletions.
100 changes: 37 additions & 63 deletions packages/client/src/components/Game/Game.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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<HTMLCanvasElement | null>(null)
const [lives, setLives] = useState<number>(livesUse)
const [player, setPlayer] = useState(PLAYER_DEFAULT_PARAMS)
const [enemies, setEnemies] = useState(initializeEnemies(5))
const [obstacles] = useState<Obstacle[]>(initializeObstacle())
const playerRef = useRef(PLAYER_DEFAULT_PARAMS)
const enemiesRef = useRef(initializeEnemies(5))
const obstaclesRef = useRef<Obstacle[]>(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) =>
Expand All @@ -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 (
<div className="game-container">
<div className="lives">{`Жизни: ${lives}`}</div>
<div className="lives">{`Жизни: ${livesRef.current.toString()}`}</div>
<canvas ref={canvasRef} width={800} height={600}></canvas>

{!gameStarted ? (
Expand All @@ -133,7 +107,7 @@ export const Game: React.FC = () => {
{isGameOver && (
<div className="modal">
<h2>Игра окончена</h2>
<button onClick={handleContinue}>Продолжить</button>
<button onClick={startGame}>Заново</button>
</div>
)}
</div>
Expand Down
77 changes: 41 additions & 36 deletions packages/client/src/components/Game/controls.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
}
37 changes: 16 additions & 21 deletions packages/client/src/components/Game/enemy.tsx
Original file line number Diff line number Diff line change
@@ -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++) {
// количество врагов
Expand All @@ -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<React.SetStateAction<Enemy[]>>
enemiesRef: React.MutableRefObject<Enemy[]>
) => {
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<React.SetStateAction<Enemy[]>>
) => {
setEnemies(initializeEnemies(5))
export const respawnEnemies = (enemiesRef: React.MutableRefObject<Enemy[]>) => {
enemiesRef.current = initializeEnemies(5)
}
43 changes: 16 additions & 27 deletions packages/client/src/components/Game/gameLoop.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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<React.SetStateAction<Player>>,
enemies: Enemy[],
setEnemies: React.Dispatch<React.SetStateAction<Enemy[]>>,
playerRef: React.MutableRefObject<Player>,
enemiesRef: React.MutableRefObject<Enemy[]>,
obstacles: Obstacle[],
lives: number,
setLives: React.Dispatch<React.SetStateAction<number>>,
livesRef: React.MutableRefObject<number>,
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() // Вызываем окончание игры, если жизни закончились
}
}
})
}
3 changes: 1 addition & 2 deletions packages/client/src/components/Game/gameTypes.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,7 @@ export interface KeyMap {
}

export interface ControlsProps {
player: Player
setPlayer: React.Dispatch<React.SetStateAction<Player>>
playerRef: React.MutableRefObject<Player>
obstacles: Obstacle[]
canvasWidth: number
canvasHeight: number
Expand Down
Loading

0 comments on commit 6e5a0cb

Please sign in to comment.