Skip to content

Commit

Permalink
[SOK-27] +enemys +obstacles +start +pause
Browse files Browse the repository at this point in the history
  • Loading branch information
shamemask committed Sep 26, 2024
1 parent c3c4097 commit ab6292a
Show file tree
Hide file tree
Showing 13 changed files with 565 additions and 145 deletions.
4 changes: 2 additions & 2 deletions packages/client/src/app/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand Down Expand Up @@ -40,7 +40,7 @@ const routerConfig = createBrowserRouter([
children: [
{
path: '/game',
element: <Game />,
element: <GamePage />,
},
{
path: '/forum',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -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;
}
133 changes: 133 additions & 0 deletions packages/client/src/components/Game/Game.tsx
Original file line number Diff line number Diff line change
@@ -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<HTMLCanvasElement | null>(null)
let context: CanvasRenderingContext2D | null | undefined
const [lives, setLives] = useState<number>(livesUse)
const [player, setPlayer] = useState(initializePlayer())
const [enemies, setEnemies] = useState(initializeEnemies())
const [obstacles, setObstacles] = useState<Obstacle[]>(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 (
<div className="game-container">
<div className="lives">Жизни: {lives}</div>
<canvas ref={canvasRef} width={800} height={600}></canvas>

{!gameStarted ? (
<button onClick={startGame}>Начать игру</button>
) : (
<button onClick={togglePause}>
{isPaused ? 'Продолжить' : 'Пауза'}
</button>
)}

{/* Модальное окно при проигрыше */}
{isGameOver && (
<div className="modal">
<h2>Игра окончена</h2>
<button onClick={handleContinue}>Продолжить</button>
</div>
)}
</div>
)
}
Empty file.
46 changes: 46 additions & 0 deletions packages/client/src/components/Game/controls.tsx
Original file line number Diff line number Diff line change
@@ -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<React.SetStateAction<Player>>,
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 }
})
}
61 changes: 61 additions & 0 deletions packages/client/src/components/Game/enemy.tsx
Original file line number Diff line number Diff line change
@@ -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<React.SetStateAction<Enemy[]>>,
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<React.SetStateAction<Enemy[]>>
) => {
setEnemies(initializeEnemies())
}
Loading

0 comments on commit ab6292a

Please sign in to comment.