diff --git a/packages/client/index.html b/packages/client/index.html index 34ac846..d7b32d0 100644 --- a/packages/client/index.html +++ b/packages/client/index.html @@ -3,7 +3,7 @@ - + diff --git a/packages/client/public/serviceWorker.js b/packages/client/public/serviceWorker.js index 7a7c762..00f3c3d 100644 --- a/packages/client/public/serviceWorker.js +++ b/packages/client/public/serviceWorker.js @@ -1,13 +1,12 @@ const CACHE_NAME = 'cache-data-v1' -const urlsToCache = [ - '/' -] +const urlsToCache = ['/'] -const networkFirst = async (request) => { +const networkFirst = async request => { const cache = await caches.open(CACHE_NAME) try { const response = await fetch(request) - const cachePutCondition = response && response.status === 200 && response.type === 'basic' + const cachePutCondition = + response && response.status === 200 && response.type === 'basic' if (cachePutCondition) { await cache.put(request, response.clone()) } @@ -25,21 +24,21 @@ const networkFirst = async (request) => { } } -self.addEventListener('install', (event) => { +self.addEventListener('install', event => { event.waitUntil( caches .open(CACHE_NAME) - .then((cache) => { + .then(cache => { return cache.addAll(urlsToCache) }) .then(() => self.skipWaiting()) - .catch((err) => { + .catch(err => { console.error('Cache installation failed:', err) }) ) }) -self.addEventListener('fetch', (event) => { +self.addEventListener('fetch', event => { const { request } = event const { url, method } = request diff --git a/packages/client/src/assets/images/sprites/Bullet.png b/packages/client/src/assets/images/sprites/Bullet.png new file mode 100644 index 0000000..d2a21d5 Binary files /dev/null and b/packages/client/src/assets/images/sprites/Bullet.png differ diff --git a/packages/client/src/assets/images/sprites/bang.png b/packages/client/src/assets/images/sprites/bang.png new file mode 100644 index 0000000..b220ec0 Binary files /dev/null and b/packages/client/src/assets/images/sprites/bang.png differ diff --git a/packages/client/src/assets/images/sprites/enemy.png b/packages/client/src/assets/images/sprites/enemy.png new file mode 100644 index 0000000..44c3154 Binary files /dev/null and b/packages/client/src/assets/images/sprites/enemy.png differ diff --git a/packages/client/src/assets/images/sprites/enemy.svg b/packages/client/src/assets/images/sprites/enemy.svg deleted file mode 100644 index 51a4469..0000000 --- a/packages/client/src/assets/images/sprites/enemy.svg +++ /dev/null @@ -1,14 +0,0 @@ - - - - - - - - - - - - - - diff --git a/packages/client/src/assets/images/sprites/shot.png b/packages/client/src/assets/images/sprites/shot.png new file mode 100644 index 0000000..991d07e Binary files /dev/null and b/packages/client/src/assets/images/sprites/shot.png differ diff --git a/packages/client/src/assets/images/sprites/steel.png b/packages/client/src/assets/images/sprites/steel.png new file mode 100644 index 0000000..ad85f90 Binary files /dev/null and b/packages/client/src/assets/images/sprites/steel.png differ diff --git a/packages/client/src/assets/images/sprites/tank.png b/packages/client/src/assets/images/sprites/tank.png new file mode 100644 index 0000000..56e0a69 Binary files /dev/null and b/packages/client/src/assets/images/sprites/tank.png differ diff --git a/packages/client/src/assets/images/sprites/tank.svg b/packages/client/src/assets/images/sprites/tank.svg deleted file mode 100644 index 1c8083e..0000000 --- a/packages/client/src/assets/images/sprites/tank.svg +++ /dev/null @@ -1,14 +0,0 @@ - - - - - - - - - - - - - - diff --git a/packages/client/src/assets/images/sprites/tree.png b/packages/client/src/assets/images/sprites/tree.png new file mode 100644 index 0000000..51bd31a Binary files /dev/null and b/packages/client/src/assets/images/sprites/tree.png differ diff --git a/packages/client/src/assets/images/sprites/wall.png b/packages/client/src/assets/images/sprites/wall.png new file mode 100644 index 0000000..c84a5c9 Binary files /dev/null and b/packages/client/src/assets/images/sprites/wall.png differ diff --git a/packages/client/src/assets/images/sprites/wall.svg b/packages/client/src/assets/images/sprites/wall.svg deleted file mode 100644 index 380f0dd..0000000 --- a/packages/client/src/assets/images/sprites/wall.svg +++ /dev/null @@ -1,9 +0,0 @@ - - - - - - - - - diff --git a/packages/client/src/components/Game/Game.tsx b/packages/client/src/components/Game/Game.tsx index dfb0664..abedf67 100644 --- a/packages/client/src/components/Game/Game.tsx +++ b/packages/client/src/components/Game/Game.tsx @@ -1,15 +1,17 @@ import React, { useCallback, useEffect, useRef, useState } from 'react' import './Game.scss' -import { initializeEnemies } from '@/components/Game/enemy' +import { + initializeCampanyEnemies, + initializeRandomEnemies, +} from '@/components/Game/enemy' import { PLAYER_DEFAULT_PARAMS } from '@/components/Game/player' import { gameLoop } from '@/components/Game/gameLoop' +import { handleKeyDown, handleKeyUp } from '@/components/Game/controls' +import { AbstractEntity, Effect, Obstacle } from '@/components/Game/gameTypes' import { - handleKeyDown, - handleKeyUp, - updatePlayerMovement, -} from '@/components/Game/controls' -import { ControlsProps, Obstacle } from '@/components/Game/gameTypes' -import { initializeObstacle } from '@/components/Game/obstacle' + initializeCompanyMapObstacle, + initializeRandomObstacle, +} from '@/components/Game/obstacle' import { Modal } from '../common/Modal/Modal' const livesUse = 3 @@ -17,8 +19,10 @@ const livesUse = 3 export const Game: React.FC = () => { const canvasRef = useRef(null) const playerRef = useRef(PLAYER_DEFAULT_PARAMS) - const enemiesRef = useRef(initializeEnemies()) - const obstaclesRef = useRef(initializeObstacle()) + const enemiesRef = useRef(initializeRandomEnemies(5)) + const bulletsRef = useRef([]) + const obstaclesRef = useRef(initializeRandomObstacle(20)) + const effectsRef = useRef([]) const livesRef = useRef(livesUse) const [gameStarted, setGameStarted] = useState(false) const [isPaused, setIsPaused] = useState(false) @@ -40,19 +44,14 @@ export const Game: React.FC = () => { 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, + canvasRef, playerRef, enemiesRef, - obstaclesRef.current, + bulletsRef, + obstaclesRef, + effectsRef, livesRef, handleGameOver ) @@ -86,9 +85,21 @@ export const Game: React.FC = () => { setIsPaused(false) isPausedRef.current = false setIsGameOver(false) - livesRef.current = 3 + livesRef.current = livesUse + playerRef.current = { ...PLAYER_DEFAULT_PARAMS } + enemiesRef.current = initializeRandomEnemies(5) + obstaclesRef.current = initializeRandomObstacle(20) + } + + const startCompany = () => { + setGameStarted(true) + setIsPaused(false) + isPausedRef.current = false + setIsGameOver(false) + livesRef.current = livesUse playerRef.current = { ...PLAYER_DEFAULT_PARAMS } - enemiesRef.current = initializeEnemies() + enemiesRef.current = initializeCampanyEnemies() + obstaclesRef.current = initializeCompanyMapObstacle() } return ( @@ -97,7 +108,10 @@ export const Game: React.FC = () => { {!gameStarted ? ( - + <> + + + ) : ( + + ) diff --git a/packages/client/src/components/Game/bullet.tsx b/packages/client/src/components/Game/bullet.tsx new file mode 100644 index 0000000..e527003 --- /dev/null +++ b/packages/client/src/components/Game/bullet.tsx @@ -0,0 +1,66 @@ +import { AbstractEntity } from '@/components/Game/gameTypes' +import { createShotEffect } from './effects' + +const bulletSize = { + width: 12, + height: 18, +} + +export const createBullet = (enemy: AbstractEntity): AbstractEntity => { + const bulletSpeed = 5 // Задайте скорость пули + const bulletDirection = { x: enemy.direction.x, y: enemy.direction.y } + + // Начальная позиция пули в зависимости от направления врага + let bulletX, bulletY + + if (enemy.direction.y < 0) { + // Вверх + bulletX = enemy.x + enemy.width / 2 // Центр по X + bulletY = enemy.y - bulletSize.height // Верхняя часть врага + } else if (enemy.direction.y > 0) { + // Вниз + bulletX = enemy.x + enemy.width / 2 // Центр по X + bulletY = enemy.y + enemy.height + bulletSize.height // Нижняя часть врага + } else if (enemy.direction.x < 0) { + // Влево + bulletX = enemy.x - bulletSize.height // Левый край врага + bulletY = enemy.y + enemy.height / 2 // Центр по Y + } else if (enemy.direction.x > 0) { + // Вправо + bulletX = enemy.x + enemy.width + bulletSize.height // Правый край врага + bulletY = enemy.y + enemy.height / 2 // Центр по Y + } else { + bulletX = enemy.x + enemy.width / 2 // По умолчанию - центр + bulletY = enemy.y + enemy.height / 2 // По умолчанию - центр + } + + createShotEffect(bulletX, bulletY, bulletDirection) + + return { + x: bulletX - bulletSize.width / 2, + y: bulletY - bulletSize.height / 2, + width: bulletSize.width, // Ширина пули + height: bulletSize.height, // Высота пули + speed: bulletSpeed, + direction: bulletDirection, + } +} + +export const updateBullets = ( + bullets: AbstractEntity[], + canvasWidth: number, + canvasHeight: number +) => { + return bullets.filter(bullet => { + bullet.x += bullet.direction.x * bullet.speed + bullet.y += bullet.direction.y * bullet.speed + + // Удаляем пулю, если она выходит за пределы экрана + return ( + bullet.x >= 0 && + bullet.x <= canvasWidth && + bullet.y >= 0 && + bullet.y <= canvasHeight + ) + }) +} diff --git a/packages/client/src/components/Game/collision.tsx b/packages/client/src/components/Game/collision.tsx index 6eca1ae..1b7fd49 100644 --- a/packages/client/src/components/Game/collision.tsx +++ b/packages/client/src/components/Game/collision.tsx @@ -1,20 +1,21 @@ -import { Enemy, Obstacle, Player } from '@/components/Game/gameTypes' +import { Obstacle, AbstractEntity } from '@/components/Game/gameTypes' export const detectCollision = ( - player: Player, + player: AbstractEntity, 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 + player.y + player.height > obstacle.y && + obstacle.isCollide ) } export const detectEnemyCollision = ( - rect1: Player | Enemy, - rect2: Obstacle | Enemy + rect1: AbstractEntity, + rect2: Obstacle | AbstractEntity ): boolean => { return ( rect1.x < rect2.x + rect2.width && @@ -23,3 +24,27 @@ export const detectEnemyCollision = ( rect1.y + rect1.height > rect2.y ) } + +export const detectBulletCollision = ( + bullet: AbstractEntity, + entity: AbstractEntity +): boolean => { + return ( + bullet.x < entity.x + entity.width && + bullet.x + bullet.width > entity.x && + bullet.y < entity.y + entity.height && + bullet.y + bullet.height > entity.y + ) +} + +export const detectObstacleCollision = ( + obstacle1: Obstacle, + obstacle2: Obstacle +): boolean => { + return ( + obstacle1.x < obstacle2.x + obstacle2.width && + obstacle1.x + obstacle1.width > obstacle2.x && + obstacle1.y < obstacle2.y + obstacle2.height && + obstacle1.y + obstacle1.height > obstacle2.y + ) +} diff --git a/packages/client/src/components/Game/controls.tsx b/packages/client/src/components/Game/controls.tsx index 9e71fc7..1a1945f 100644 --- a/packages/client/src/components/Game/controls.tsx +++ b/packages/client/src/components/Game/controls.tsx @@ -1,14 +1,18 @@ import { ControlsProps } from '@/components/Game/gameTypes' - import { detectCollision } from '@/components/Game/collision' +import { createBullet } from '@/components/Game/bullet' let pressedKeys: string[] = [] +let shootPressed = false // Флаг для стрельбы +let lastShotTime = 0 // Время последнего выстрела +const SHOOT_DELAY = 500 // Задержка между выстрелами (в миллисекундах) enum Action { MoveUp = 'MoveUp', MoveDown = 'MoveDown', MoveLeft = 'MoveLeft', MoveRight = 'MoveRight', + Shoot = 'Shoot', } type Vector = { @@ -16,11 +20,12 @@ type Vector = { y: -1 | 0 | 1 } -const MOVEMENT_CONTROLS: Record = { +const ACTION_CONTROLS: Record = { [Action.MoveUp]: ['ArrowUp', 'w', 'ц', 'W', 'Ц'], [Action.MoveDown]: ['ArrowDown', 's', 'ы', 'S', 'Ы'], [Action.MoveLeft]: ['ArrowLeft', 'a', 'ф', 'A', 'Ф'], [Action.MoveRight]: ['ArrowRight', 'd', 'в', 'D', 'В'], + [Action.Shoot]: [' '], } const VECTORS: Record = { @@ -28,20 +33,31 @@ const VECTORS: Record = { [Action.MoveDown]: { x: 0, y: 1 }, [Action.MoveLeft]: { x: -1, y: 0 }, [Action.MoveRight]: { x: 1, y: 0 }, + [Action.Shoot]: { x: 0, y: 0 }, } export const handleKeyDown = (key: string) => { if (!pressedKeys.includes(key)) { pressedKeys.push(key) } + + // Устанавливаем флаг, если нажата клавиша пробела (стрельба) + if (key === ' ') { + shootPressed = true + } } export const handleKeyUp = (key: string) => { pressedKeys = pressedKeys.filter(currentKey => currentKey !== key) + + // Сбрасываем флаг, если клавиша пробела отпущена + if (key === ' ') { + shootPressed = false + } } -const getMovementControlByKey = (key: string): Action | null => { - for (const [action, keys] of Object.entries(MOVEMENT_CONTROLS)) { +const getActionControlByKey = (key: string): Action | null => { + for (const [action, keys] of Object.entries(ACTION_CONTROLS)) { if (keys.includes(key)) { return action as Action } @@ -50,9 +66,9 @@ const getMovementControlByKey = (key: string): Action | null => { return null } -const getLastMovementAction = (): Action | null => { +const getLastAction = (): Action | null => { for (let i = pressedKeys.length - 1; i >= 0; i--) { - const action = getMovementControlByKey(pressedKeys[i]) + const action = getActionControlByKey(pressedKeys[i]) if (action) { return action @@ -62,12 +78,12 @@ const getLastMovementAction = (): Action | null => { return null } -export const updatePlayerMovement = (props: ControlsProps) => { +export const updatePlayerAction = (props: ControlsProps) => { const speed = props.playerRef.current.speed let newX = props.playerRef.current.x let newY = props.playerRef.current.y - const lastMovementAction = getLastMovementAction() + const lastMovementAction = getLastAction() if (!lastMovementAction) return @@ -75,10 +91,20 @@ export const updatePlayerMovement = (props: ControlsProps) => { if (!vector) return - props.playerRef.current.direction = vector + const currentTime = Date.now() // Получаем текущее время + + if (lastMovementAction === Action.Shoot) { + // Ограничение скорости стрельбы с помощью таймера + if (shootPressed && currentTime - lastShotTime >= SHOOT_DELAY) { + props.bulletsRef.current.push(createBullet(props.playerRef.current)) + lastShotTime = currentTime // Обновляем время последнего выстрела + } + } else { + props.playerRef.current.direction = vector - newX += vector.x * speed - newY += vector.y * speed + newX += vector.x * speed + newY += vector.y * speed + } const hasCollision = props.obstacles.some(obstacle => { return detectCollision( diff --git a/packages/client/src/components/Game/effects.tsx b/packages/client/src/components/Game/effects.tsx new file mode 100644 index 0000000..c9d67ed --- /dev/null +++ b/packages/client/src/components/Game/effects.tsx @@ -0,0 +1,74 @@ +import { Direction, Effect } from '@/components/Game/gameTypes' +import { MutableRefObject } from 'react' + +interface EffectSettings { + [key: string]: { + width: number + height: number + animation: Effect['animation'] + } +} + +let effectsRef: MutableRefObject + +const effectSettings: EffectSettings = { + bang: { + width: 60, + height: 60, + animation: { + frameInterval: 5, + frameCount: 0, + totalFrames: 8, + currentFrame: 0, + }, + }, + shot: { + width: 40, + height: 40, + animation: { + frameInterval: 5, + frameCount: 0, + totalFrames: 4, + currentFrame: 0, + }, + }, +} + +export const initEffects = (effects: MutableRefObject) => { + effectsRef = effects +} + +export const createEffect = ( + type: string, + x: number, + y: number, + direction?: Direction +) => { + if (effectsRef) { + effectsRef.current.push({ + type, + x: x - effectSettings[type].width / 2, + y: y - effectSettings[type].height / 2, + width: effectSettings[type].width, + height: effectSettings[type].height, + direction, + animation: { ...effectSettings[type].animation }, + }) + } +} + +export const createBangEffect = (x: number, y: number) => { + createEffect('bang', x, y) +} + +export const createShotEffect = ( + x: number, + y: number, + direction: Direction +) => { + createEffect('shot', x, y, direction) +} + +export const deleteEffect = (effect: Effect) => { + effectsRef.current = effectsRef.current.filter(i => i !== effect) +} diff --git a/packages/client/src/components/Game/enemy.tsx b/packages/client/src/components/Game/enemy.tsx index e358723..7c8a3fc 100644 --- a/packages/client/src/components/Game/enemy.tsx +++ b/packages/client/src/components/Game/enemy.tsx @@ -1,40 +1,207 @@ -import React from 'react' -import { Enemy, Player } from '@/components/Game/gameTypes' +import { getRandomEdgePosition } from './utils' +import { AbstractEntity, Enemy, Obstacle } from '@/components/Game/gameTypes' +import { createBullet } from '@/components/Game/bullet' +import { + detectCollision, + detectEnemyCollision, +} from '@/components/Game/collision' const enemyParams = { width: 70, height: 70, - speed: 0, + speed: 1, direction: { x: 0, y: 0 }, } -export const initializeEnemies = (): Enemy[] => { +export const initializeRandomEnemies = (numberOfEnemies: number) => { + const initialEnemies: Enemy[] = [] + for (let i = 0; i < numberOfEnemies; i++) { + // количество врагов + const { x, y } = getRandomEdgePosition(800, 600) + const enemy: Enemy = { + ...enemyParams, + id: i, + x, + y, + animation: { + currentFrame: 0, + totalFrames: 4, + frameInterval: 10, + frameCount: 0, + }, + } + initialEnemies.push(enemy) + } + return initialEnemies as Enemy[] +} + +export const initializeCampanyEnemies = (): Enemy[] => { return [ - { ...enemyParams, x: 50, y: 55 }, - { ...enemyParams, x: 320, y: 250 }, - { ...enemyParams, x: 715, y: 60 }, + { + ...enemyParams, + id: 0, + x: 360, + y: 0, + animation: { + currentFrame: 0, + totalFrames: 4, + frameInterval: 10, + frameCount: 0, + }, + }, + { + ...enemyParams, + id: 1, + x: 0, + y: 108, + animation: { + currentFrame: 0, + totalFrames: 4, + frameInterval: 10, + frameCount: 0, + }, + }, + { + ...enemyParams, + id: 2, + x: 720, + y: 108, + animation: { + currentFrame: 0, + totalFrames: 4, + frameInterval: 10, + frameCount: 0, + }, + }, ] } export const updateEnemyPositions = ( - player: Player, - enemiesRef: React.MutableRefObject + player: AbstractEntity, + enemiesRef: React.MutableRefObject, + obstacles: Obstacle[] ) => { enemiesRef.current = enemiesRef.current.map(enemy => { + // Определяем разницу по X и Y const directionX = player.x - enemy.x const directionY = player.y - enemy.y - const magnitude = Math.sqrt(directionX ** 2 + directionY ** 2) - const normalizedX = directionX / magnitude - const normalizedY = directionY / magnitude + // Выбираем ближайшую ось для начала движения + const moveAlongX = Math.abs(directionX) > Math.abs(directionY) + + let stepX = 0 + let stepY = 0 + + let newDirection: { x: number; y: number } + + if (moveAlongX) { + // Если разница по X больше, двигаемся по X + if (Math.abs(directionX) > enemy.speed) { + stepX = Math.sign(directionX) * enemy.speed + newDirection = { x: Math.sign(directionX), y: 0 } // направление по X + } else { + // Если по оси X выровнялись, двигаемся по Y + stepY = Math.sign(directionY) * enemy.speed + newDirection = { x: 0, y: Math.sign(directionY) } // направление по Y + } + } else { + // Если разница по Y больше, двигаемся по Y + if (Math.abs(directionY) > enemy.speed) { + stepY = Math.sign(directionY) * enemy.speed + newDirection = { x: 0, y: Math.sign(directionY) } // направление по Y + } else { + // Если по оси Y выровнялись, двигаемся по X + stepX = Math.sign(directionX) * enemy.speed + newDirection = { x: Math.sign(directionX), y: 0 } // направление по X + } + } + + const newX = enemy.x + stepX + const newY = enemy.y + stepY + + // Проверка столкновений с другими врагами + const hasEnemyCollision = enemiesRef.current.some(otherEnemy => { + if (otherEnemy === enemy) return false + return detectEnemyCollision({ ...enemy, x: newX, y: newY }, otherEnemy) + }) - const newX = enemy.x + normalizedX * enemy.speed - const newY = enemy.y + normalizedY * enemy.speed + // Проверка коллизий с препятствиями + const hasObstacleCollision = obstacles.some(obstacle => { + return detectCollision({ ...enemy, x: newX, y: newY }, obstacle) + }) - return { ...enemy, x: newX, y: newY } + // Если нет коллизии, обновляем позицию врага + if (!hasEnemyCollision && !hasObstacleCollision) { + enemy.direction = newDirection + return { ...enemy, x: newX, y: newY, direction: newDirection } + } + + // Если есть коллизия, возвращаем текущую позицию + return enemy }) } -export const respawnEnemies = (enemiesRef: React.MutableRefObject) => { - enemiesRef.current = initializeEnemies() +const isPositionOccupied = ( + position: { x: number; y: number }, + enemies: AbstractEntity[] +) => { + return enemies.some(enemy => + detectEnemyCollision({ ...enemy, x: position.x, y: position.y }, enemy) + ) +} + +const respawnEnemy = ( + enemy: Enemy, + enemies: Enemy[], + canvasWidth: number, + canvasHeight: number +) => { + let newPosition + do { + newPosition = { + x: Math.random() * canvasWidth, // Предполагается, что canvasWidth доступен + y: Math.random() * canvasHeight, // Предполагается, что canvasHeight доступен + } + } while (isPositionOccupied(newPosition, enemies)) + + return { ...enemy, ...newPosition } // Возвращаем врага с новой позицией +} + +// Пример использования respawnEnemy в вашем коде +export const respawnEnemies = ( + enemiesRef: React.MutableRefObject, + canvasRef: React.MutableRefObject +) => { + if (!canvasRef.current) { + return + } + const { width: canvasWidth, height: canvasHeight } = + canvasRef.current.getBoundingClientRect() + enemiesRef.current = enemiesRef.current.map(enemy => + respawnEnemy(enemy, enemiesRef.current, canvasWidth, canvasHeight) + ) +} + +export const killEnemy = ( + enemiesRef: React.MutableRefObject, + enemy: AbstractEntity +) => { + enemiesRef.current = enemiesRef.current.filter(e => e !== enemy) +} + +export const handleEnemyShooting = ( + enemies: Enemy[], + bulletsRef: React.MutableRefObject +) => { + const currentTime = Date.now() // Текущее время в миллисекундах + + enemies.forEach(enemy => { + // Проверяем, если прошло больше 2 секунд (2000 миллисекунд) с последнего выстрела + if (!enemy.lastShotTime || currentTime - enemy.lastShotTime >= 2000) { + bulletsRef.current.push(createBullet(enemy)) // Создаём новую пулю для врага + + // Обновляем время последнего выстрела + enemy.lastShotTime = currentTime + } + }) } diff --git a/packages/client/src/components/Game/gameLoop.tsx b/packages/client/src/components/Game/gameLoop.tsx index 4fd92f6..e1c8833 100644 --- a/packages/client/src/components/Game/gameLoop.tsx +++ b/packages/client/src/components/Game/gameLoop.tsx @@ -1,50 +1,126 @@ import { HandlePlayerHit } from './player' -import { updateEnemyPositions } from './enemy' -import { clearCanvas, drawPlayer, drawEnemies, drawObstacles } from './utils' -import { Enemy, Obstacle, Player } from '@/components/Game/gameTypes' -import { detectEnemyCollision } from '@/components/Game/collision' +import { updateEnemyPositions, killEnemy, handleEnemyShooting } from './enemy' +import { + clearCanvas, + drawPlayer, + drawEnemies, + drawObstacles, + drawBullets, + drawEffects, +} from './utils' +import { + ControlsProps, + AbstractEntity, + Obstacle, + Enemy, + Effect, +} from '@/components/Game/gameTypes' +import { + detectBulletCollision, + detectEnemyCollision, +} from '@/components/Game/collision' +import { updatePlayerAction } from '@/components/Game/controls' +import { updateBullets } from '@/components/Game/bullet' +import { handleBulletObstacleCollisions } from '@/components/Game/obstacle' +import { createBangEffect, initEffects } from './effects' /** * Основной игровой цикл, который обновляет состояние игры и перерисовывает экран каждый кадр. * @param context - Контекст рисования для Canvas. + * @param canvasRef - Ссылка на Canvas. * @param playerRef - Ссылка на текущего игрока. * @param enemiesRef - Ссылка на массив врагов. - * @param obstacles - Массив препятствий. + * @param bulletsRef - Ссылка на массив пуль. + * @param obstaclesRef - Ссылка на массив препятствий. * @param livesRef - Ссылка на текущее количество жизней игрока. * @param handleGameOver - Обработчик события окончания игры. */ export const gameLoop = ( context: CanvasRenderingContext2D, - playerRef: React.MutableRefObject, + canvasRef: React.MutableRefObject, + playerRef: React.MutableRefObject, enemiesRef: React.MutableRefObject, - obstacles: Obstacle[], + bulletsRef: React.MutableRefObject, + obstaclesRef: React.MutableRefObject, + effectsRef: React.MutableRefObject, livesRef: React.MutableRefObject, handleGameOver: () => void ) => { clearCanvas(context) // Обновление позиций врагов - updateEnemyPositions(playerRef.current, enemiesRef) + updateEnemyPositions(playerRef.current, enemiesRef, obstaclesRef.current) + if (!canvasRef.current) return + const moveProps: ControlsProps = { + playerRef, + bulletsRef, + obstacles: obstaclesRef.current, + canvasWidth: canvasRef.current.width, + canvasHeight: canvasRef.current.height, + } + updatePlayerAction(moveProps) + + // Стрельба врагов каждые 2 секунды + handleEnemyShooting(enemiesRef.current, bulletsRef) + + // Обработка столкновений с препятствиями + handleBulletObstacleCollisions(bulletsRef.current, obstaclesRef.current) + + initEffects(effectsRef) + + bulletsRef.current = updateBullets( + bulletsRef.current, + canvasRef.current.width, + canvasRef.current.height + ) // Отрисовка всех игровых объектов - drawObstacles(context, obstacles) drawPlayer(context, playerRef.current) drawEnemies(context, enemiesRef.current) + drawObstacles(context, obstaclesRef.current) + drawEffects(context, effectsRef.current) + drawBullets(context, bulletsRef.current) // Отрисовка пуль + + // Проверка на столкновения пуль с врагами + bulletsRef.current.forEach(bullet => { + enemiesRef.current = enemiesRef.current.filter(enemy => { + const hit = detectBulletCollision(bullet, enemy) + if (hit) { + // Убираем врага, если попали + killEnemy(enemiesRef, enemy) + // Эффект поподания + createBangEffect( + bullet.x + bullet.width / 2, + bullet.y + bullet.height / 2 + ) + // Убираем пулю, если попали + bulletsRef.current = bulletsRef.current.filter(b => b !== bullet) + return false + } + return true + }) + if (detectBulletCollision(bullet, playerRef.current)) { + // Уменьшаем жизни игрока + livesRef.current -= 1 + // Эффект поподания + createBangEffect( + bullet.x + bullet.width / 2, + bullet.y + bullet.height / 2 + ) + // Удаляем пулю после попадания + bulletsRef.current = bulletsRef.current.filter(b => b !== bullet) + // Проверка на окончание игры + if (livesRef.current <= 0) { + handleGameOver() + } + } + }) const collidedEnemy = enemiesRef.current.find(enemy => detectEnemyCollision(playerRef.current, enemy) ) if (collidedEnemy) { - HandlePlayerHit( - livesRef, - handleGameOver, - () => { - playerRef.current = { ...playerRef.current, x: 400, y: 560 } - }, - () => { - enemiesRef.current = enemiesRef.current.map(e => ({ ...e })) - } - ) + HandlePlayerHit(livesRef, playerRef, enemiesRef, canvasRef, handleGameOver) } } diff --git a/packages/client/src/components/Game/gameTypes.tsx b/packages/client/src/components/Game/gameTypes.tsx index c59e901..b28238a 100644 --- a/packages/client/src/components/Game/gameTypes.tsx +++ b/packages/client/src/components/Game/gameTypes.tsx @@ -1,30 +1,54 @@ -export interface Player { +interface AnimationParams { + currentFrame: number + totalFrames: number + frameInterval?: number + frameCount?: number +} + +export interface Direction { + x: number + y: number +} + +export interface AbstractEntity { x: number y: number width: number height: number speed: number - direction: { x: number; y: number } + direction: Direction + animation?: AnimationParams } -export interface Enemy { +export interface Enemy extends AbstractEntity { + id: number + lastShotTime?: number +} + +export interface Obstacle { + type: string x: number y: number width: number height: number - speed: number - direction: { x: number; y: number } + hp: number + isCollide: boolean + animation: AnimationParams } -export interface Obstacle { +export interface Effect { + type: string x: number y: number width: number height: number + direction?: Direction + animation: AnimationParams } export interface ControlsProps { - playerRef: React.MutableRefObject + playerRef: React.MutableRefObject + bulletsRef: React.MutableRefObject obstacles: Obstacle[] canvasWidth: number canvasHeight: number diff --git a/packages/client/src/components/Game/obstacle.tsx b/packages/client/src/components/Game/obstacle.tsx index 9cfbf50..64314ab 100644 --- a/packages/client/src/components/Game/obstacle.tsx +++ b/packages/client/src/components/Game/obstacle.tsx @@ -1,31 +1,232 @@ -import { Obstacle } from '@/components/Game/gameTypes' - -export const initializeObstacle = (): Obstacle[] => { - return [ - { x: 0, y: 0, width: 120, height: 50 }, - { x: 200, y: 0, width: 70, height: 50 }, - { x: 350, y: 0, width: 70, height: 50 }, - { x: 500, y: 0, width: 120, height: 50 }, - { x: 700, y: 0, width: 100, height: 50 }, - - { x: 0, y: 130, width: 50, height: 120 }, - { x: 130, y: 130, width: 50, height: 70 }, - { x: 260, y: 130, width: 50, height: 200 }, - { x: 390, y: 130, width: 50, height: 70 }, - { x: 520, y: 130, width: 50, height: 120 }, - { x: 650, y: 130, width: 50, height: 125 }, - - { x: 0, y: 330, width: 120, height: 50 }, - { x: 200, y: 330, width: 150, height: 50 }, - { x: 350, y: 330, width: 70, height: 50 }, - { x: 500, y: 330, width: 120, height: 50 }, - { x: 700, y: 330, width: 100, height: 100 }, - - { x: 0, y: 460, width: 50, height: 140 }, - { x: 130, y: 530, width: 50, height: 70 }, - { x: 260, y: 480, width: 50, height: 120 }, - { x: 390, y: 460, width: 50, height: 70 }, - { x: 520, y: 490, width: 50, height: 120 }, - { x: 650, y: 480, width: 50, height: 120 }, - ] +import { AbstractEntity, Obstacle } from '@/components/Game/gameTypes' +import { getRandomPosition } from '@/components/Game/utils' +import { + detectCollision, + detectObstacleCollision, +} from '@/components/Game/collision' +import { createBangEffect } from './effects' + +enum Types { + Wall = 'wall', + Steel = 'steel', + Tree = 'tree', +} + +export const OBSTACLE_SIZE = 36 + +const ObstacleSettings = { + [Types.Wall]: { + hp: 2, + isCollide: true, + frames: [ + { + hp: 1, + index: 1, + }, + { + hp: 2, + index: 0, + }, + ], + }, + [Types.Steel]: { + hp: 1000, + isCollide: true, + frames: [ + { + hp: 1000, + index: 0, + }, + ], + }, + [Types.Tree]: { + hp: 1000, + isCollide: false, + frames: [ + { + hp: 1000, + index: 0, + }, + ], + }, +} + +export const initializeCompanyMapObstacle = (): Obstacle[] => { + return createMap([ + { type: Types.Steel, x: 72, y: 108, width: 72, height: 72 }, + { type: Types.Steel, x: 648, y: 108, width: 72, height: 72 }, + + { type: Types.Steel, x: 144, y: 360, width: 72, height: 72 }, + { type: Types.Steel, x: 576, y: 360, width: 72, height: 72 }, + { type: Types.Steel, x: 360, y: 360, width: 72, height: 72 }, + + { type: Types.Wall, x: 252, y: 72, width: 108, height: 36 }, + { type: Types.Wall, x: 324, y: 108, width: 36, height: 144 }, + { type: Types.Wall, x: 252, y: 216, width: 72, height: 36 }, + { type: Types.Wall, x: 252, y: 144, width: 72, height: 36 }, + { type: Types.Wall, x: 252, y: 108, width: 36, height: 36 }, + + { type: Types.Wall, x: 432, y: 72, width: 108, height: 36 }, + { type: Types.Wall, x: 504, y: 108, width: 36, height: 108 }, + { type: Types.Wall, x: 432, y: 108, width: 36, height: 108 }, + { type: Types.Wall, x: 432, y: 216, width: 108, height: 36 }, + + { type: Types.Wall, x: 72, y: 324, width: 72, height: 180 }, + { type: Types.Wall, x: 216, y: 324, width: 72, height: 180 }, + { type: Types.Wall, x: 504, y: 324, width: 72, height: 180 }, + { type: Types.Wall, x: 648, y: 324, width: 72, height: 180 }, + + { type: Types.Tree, x: 0, y: 324, width: 72, height: 180 }, + { type: Types.Tree, x: 720, y: 324, width: 72, height: 180 }, + { type: Types.Tree, x: 0, y: 504, width: 180, height: 72 }, + { type: Types.Tree, x: 612, y: 504, width: 180, height: 72 }, + ]) + return createMap([ + // /{ type: Types.Steel, x: 0, y: 0, width: 120, height: 50 }, + { type: Types.Steel, x: 200, y: 0, width: 70, height: 36 }, + { type: Types.Steel, x: 350, y: 0, width: 70, height: 36 }, + { type: Types.Steel, x: 500, y: 0, width: 120, height: 36 }, + // { type: Types.Wall, x: 700, y: 0, width: 100, height: 50 }, + + // { type: Types.Wall, x: 0, y: 130, width: 50, height: 120 }, + { type: Types.Wall, x: 130, y: 130, width: 50, height: 70 }, + // { type: Types.Wall, x: 260, y: 130, width: 50, height: 200 }, + { type: Types.Wall, x: 390, y: 130, width: 50, height: 70 }, + { type: Types.Wall, x: 520, y: 130, width: 50, height: 120 }, + // { type: Types.Wall, x: 650, y: 130, width: 50, height: 125 }, + + // { type: Types.Wall, x: 0, y: 330, width: 120, height: 50 }, + { type: Types.Wall, x: 200, y: 330, width: 150, height: 36 }, + { type: Types.Wall, x: 350, y: 330, width: 70, height: 36 }, + { type: Types.Wall, x: 500, y: 330, width: 120, height: 36 }, + // { type: Types.Wall, x: 700, y: 330, width: 100, height: 100 }, + + { type: Types.Wall, x: 0, y: 460, width: 50, height: 140 }, + { type: Types.Wall, x: 130, y: 530, width: 50, height: 70 }, + { type: Types.Wall, x: 260, y: 480, width: 50, height: 120 }, + //{ type: Types.Wall, x: 390, y: 460, width: 50, height: 70 }, + { type: Types.Wall, x: 520, y: 490, width: 50, height: 120 }, + { type: Types.Wall, x: 650, y: 480, width: 50, height: 120 }, + ]) +} + +export const createMap = ( + params: { + type: Types + x: number + y: number + width: number + height: number + }[] +) => { + let obstacles: Obstacle[] = [] + + params.forEach(obstacle => { + const { type, x, y, width, height } = obstacle + obstacles = [...obstacles, ...createObstacle(type, x, y, width, height)] + }) + + return obstacles +} + +export const createObstacle = ( + type: Types, + x: number, + y: number, + width: number, + height: number +): Obstacle[] => { + const obstacles: Obstacle[] = [] + const horizontalCount = Math.ceil(width / OBSTACLE_SIZE) + const verticalCount = Math.ceil(height / OBSTACLE_SIZE) + + Array.from({ length: horizontalCount }).forEach((_, i) => { + Array.from({ length: verticalCount }).forEach((_, j) => { + const obstacleX = x + i * OBSTACLE_SIZE + const obstacleY = y + j * OBSTACLE_SIZE + + obstacles.push({ + type, + x: obstacleX, + y: obstacleY, + width: OBSTACLE_SIZE, + height: OBSTACLE_SIZE, + hp: ObstacleSettings[type].hp, + isCollide: ObstacleSettings[type].isCollide, + animation: { + currentFrame: 0, + totalFrames: ObstacleSettings[type].frames.length, + }, + }) + }) + }) + + return obstacles +} + +export const initializeRandomObstacle = ( + numberOfObstacles: number +): Obstacle[] => { + const obstacles: Obstacle[] = [] + + while (obstacles.length < numberOfObstacles) { + const { x, y } = getRandomPosition(800, 600) + const obstacle: Obstacle = createObstacle( + Types.Wall, + x, + y, + OBSTACLE_SIZE, + OBSTACLE_SIZE + )[0] + + // Проверяем, нет ли коллизий с существующими препятствиями + const hasCollision = obstacles.some(existingObstacle => + detectObstacleCollision(obstacle, existingObstacle) + ) + + if (!hasCollision) { + obstacles.push(obstacle) // Добавляем препятствие, если нет коллизий + } + } + + return obstacles +} + +export const handleBulletObstacleCollisions = ( + bullets: AbstractEntity[], + obstacles: Obstacle[] +) => { + bullets.forEach(bullet => { + // Проверка коллизий с препятствиями + obstacles.forEach(obstacle => { + if (detectCollision(bullet, obstacle)) { + // Логика для уничтожения пули, если она попала в препятствие + bullets.splice(bullets.indexOf(bullet), 1) // Пример, удаляем пулю, если попала в препятствие + createBangEffect( + bullet.x + bullet.width / 2, + bullet.y + bullet.height / 2 + ) + killObstacle(obstacles, obstacle) + } + }) + }) +} + +const killObstacle = (obstacles: Obstacle[], obstacle: Obstacle) => { + const settings = ObstacleSettings[obstacle.type as Types] + const { frames } = settings + + obstacle.hp = obstacle.hp - 1 + + const currentFrame = frames.find( + (frame: { hp: number; index: number }) => obstacle.hp <= frame.hp + ) + + if (currentFrame) { + obstacle.animation.currentFrame = currentFrame.index + } + + if (obstacle.hp === 0) { + obstacles.splice(obstacles.indexOf(obstacle), 1) + } } diff --git a/packages/client/src/components/Game/player.tsx b/packages/client/src/components/Game/player.tsx index 0f2387b..c432ff7 100644 --- a/packages/client/src/components/Game/player.tsx +++ b/packages/client/src/components/Game/player.tsx @@ -1,4 +1,5 @@ -import { Player } from '@/components/Game/gameTypes' +import { AbstractEntity, Enemy } from '@/components/Game/gameTypes' +import { respawnEnemies } from '@/components/Game/enemy' export const PLAYER_DEFAULT_PARAMS = { x: 400, @@ -7,10 +8,16 @@ export const PLAYER_DEFAULT_PARAMS = { height: 70, speed: 3, direction: { x: 0, y: 0 }, + animation: { + currentFrame: 0, // Текущий кадр спрайта + totalFrames: 4, // Общее количество кадров в спрайте + frameInterval: 10, // Интервал для смены кадра (в циклах или миллисекундах) + frameCount: 0, // Счетчик кадров для контроля интервала + }, } export const resetPlayerPosition = ( - playerRef: React.MutableRefObject + playerRef: React.MutableRefObject ) => { playerRef.current = { ...playerRef.current, @@ -21,22 +28,24 @@ export const resetPlayerPosition = ( /** * Функция для обработки столкновения игрока с врагом. * @param livesRef - Ссылка на текущее количество жизней игрока. + * @param playerRef - Ссылка на игрока. + * @param enemiesRef - Ссылка на массив врагов. + * @param canvasRef - Ссылка на HTML-элемент canvas. * @param handleGameOver - Обработчик события окончания игры. - * @param resetPlayerPosition - Функция для сброса позиции игрока. - * @param respawnEnemies - Функция для респауна врагов. */ export const HandlePlayerHit = ( livesRef: React.MutableRefObject, - handleGameOver: () => void, - resetPlayerPosition: () => void, - respawnEnemies: () => void + playerRef: React.MutableRefObject, + enemiesRef: React.MutableRefObject, + canvasRef: React.MutableRefObject, + handleGameOver: () => void ) => { livesRef.current -= 1 if (livesRef.current <= 0) { handleGameOver() } else { - resetPlayerPosition() - respawnEnemies() + resetPlayerPosition(playerRef) + respawnEnemies(enemiesRef, canvasRef) } } diff --git a/packages/client/src/components/Game/utils.tsx b/packages/client/src/components/Game/utils.tsx index 4fc83d5..13284bb 100644 --- a/packages/client/src/components/Game/utils.tsx +++ b/packages/client/src/components/Game/utils.tsx @@ -1,7 +1,18 @@ -import enemiesSpritePath from '@/assets/images/sprites/enemy.svg' -import tankSpritePath from '@/assets/images/sprites/tank.svg' -import wallSpritePath from '@/assets/images/sprites/wall.svg' -import { Enemy, Obstacle, Player } from '@/components/Game/gameTypes' +import enemiesSpritePath from '@/assets/images/sprites/enemy.png' +import playerSpritePath from '@/assets/images/sprites/tank.png' +import bulletSpritePath from '@/assets/images/sprites/bullet.png' +import wallSpritePath from '@/assets/images/sprites/wall.png' +import steelSpritePath from '@/assets/images/sprites/steel.png' +import treeSpritePath from '@/assets/images/sprites/tree.png' +import bangSpritePath from '@/assets/images/sprites/bang.png' +import shotSpritePath from '@/assets/images/sprites/shot.png' +import { + AbstractEntity, + Effect, + Enemy, + Obstacle, +} from '@/components/Game/gameTypes' +import { deleteEffect } from './effects' export const getRandomEdgePosition = ( canvasWidth: number, @@ -21,48 +32,80 @@ export const getRandomEdgePosition = ( } } +export const getRandomPosition = ( + canvasWidth: number, + canvasHeight: number, + obstacleWidth = 50, // Ширина препятствия + obstacleHeight = 50 // Высота препятствия +): { x: number; y: number } => { + const x = Math.random() * (canvasWidth - obstacleWidth) // Генерируем случайное X + const y = Math.random() * (canvasHeight - obstacleHeight) // Генерируем случайное Y + return { x, y } +} + export const clearCanvas = (context: CanvasRenderingContext2D) => { context.clearRect(0, 0, context.canvas.width, context.canvas.height) } -const tankSprite = new Image() -tankSprite.src = tankSpritePath - -let lastPlayerDirection = { x: 0, y: 0 } - -export const drawPlayer = ( +const darawTank = ( + sprite: HTMLImageElement, context: CanvasRenderingContext2D, - player: Player + data: AbstractEntity ) => { - let direction = { ...player.direction } + const { animation } = data + const { direction } = data + const moovment = direction.x !== 0 ? data.x : data.y + const spriteSettings = { + width: 0, + height: 0, + sourceX: 0, + sourceY: 0, + } - const isPlayerIdle = direction.x === 0 && direction.y === 0 + // Если смещение кратно 10 меняем кадр + if (animation?.frameInterval && moovment % animation.frameInterval === 0) { + animation.currentFrame = + (animation.currentFrame + 1) % animation?.totalFrames + } - if (isPlayerIdle) { - direction = lastPlayerDirection - } else { - lastPlayerDirection = direction + if (animation) { + spriteSettings.width = sprite.width / animation.totalFrames + spriteSettings.height = sprite.height + spriteSettings.sourceX = animation.currentFrame * spriteSettings.width + spriteSettings.sourceY = 0 } context.save() - - context.translate(player.x + player.width / 2, player.y + player.height / 2) - - const angle = Math.atan2(direction.x, -direction.y) - context.rotate(angle) - + context.translate(data.x + data.width / 2, data.y + data.height / 2) + context.rotate(Math.atan2(direction.x, -direction.y)) context.drawImage( - tankSprite, - -player.width / 2, - -player.height / 2, - player.width, - player.height + sprite, + spriteSettings.sourceX, + spriteSettings.sourceY, + spriteSettings.width, + spriteSettings.height, + -data.width / 2, + -data.height / 2, + data.width, + data.height ) context.restore() } +const playerSprite = new Image() + +playerSprite.src = playerSpritePath + +export const drawPlayer = ( + context: CanvasRenderingContext2D, + player: AbstractEntity +) => { + darawTank(playerSprite, context, player) +} + const enemiesSprite = new Image() + enemiesSprite.src = enemiesSpritePath export const drawEnemies = ( @@ -70,33 +113,166 @@ export const drawEnemies = ( enemies: Enemy[] ) => { enemies.forEach(enemy => { - context.drawImage(enemiesSprite, enemy.x, enemy.y) + darawTank(enemiesSprite, context, enemy) }) } const wallSprite = new Image() +const steelSprite = new Image() +const treeSprite = new Image() + wallSprite.src = wallSpritePath +steelSprite.src = steelSpritePath +treeSprite.src = treeSpritePath export const drawObstacles = ( context: CanvasRenderingContext2D, obstacles: Obstacle[] ) => { - const SPRITE_SIZE = 50 - obstacles.forEach(obstacle => { - const horizontalCount = Math.ceil(obstacle.width / SPRITE_SIZE) - const verticalCount = Math.ceil(obstacle.height / SPRITE_SIZE) + let sprite: HTMLImageElement + + switch (obstacle.type) { + case 'steel': + sprite = steelSprite + + break + case 'wall': + sprite = wallSprite + + break + case 'tree': + sprite = treeSprite + + break + + default: + sprite = treeSprite + + break + } + + const spriteSize = sprite.width / obstacle.animation.totalFrames + + context.drawImage( + sprite, + obstacle.animation.currentFrame * spriteSize, + 0, + spriteSize, + spriteSize, + obstacle.x, + obstacle.y, + spriteSize, + spriteSize + ) + }) +} + +const bulletSprite = new Image() + +bulletSprite.src = bulletSpritePath + +export const drawBullets = ( + context: CanvasRenderingContext2D, + bullets: AbstractEntity[] +) => { + bullets.forEach(bullet => { + context.save() + context.translate(bullet.x + bullet.width / 2, bullet.y + bullet.height / 2) + context.rotate(Math.atan2(bullet.direction.x, -bullet.direction.y)) + + context.drawImage( + bulletSprite, + 0, + 0, + bulletSprite.width, + bulletSprite.height, + -bulletSprite.width / 2, + -bulletSprite.height / 2, + bulletSprite.width, + bulletSprite.height + ) + + context.restore() + }) +} + +const bangSprite = new Image() +const shotSprite = new Image() + +bangSprite.src = bangSpritePath +shotSprite.src = shotSpritePath + +export const drawEffects = ( + context: CanvasRenderingContext2D, + effects: Effect[] +) => { + effects.forEach(effect => { + const spriteSettings = { + width: 0, + height: 0, + sourceX: 0, + sourceY: 0, + } + let sprite: HTMLImageElement + + if (typeof effect.animation.frameCount === 'number') { + effect.animation.frameCount++ + + if ( + effect.animation?.frameInterval && + effect.animation.frameCount % effect.animation.frameInterval === 0 + ) { + effect.animation.currentFrame = effect.animation.currentFrame + 1 + + if ( + effect.animation.currentFrame === + effect.animation.totalFrames - 1 + ) { + deleteEffect(effect) + } + } + } + + switch (effect.type) { + case 'shot': + sprite = shotSprite + + break + + case 'bang': + sprite = bangSprite + + break + + default: + sprite = bangSprite + } + + spriteSettings.width = sprite.width / effect.animation.totalFrames + spriteSettings.height = sprite.height + spriteSettings.sourceX = + effect.animation.currentFrame * spriteSettings.width + spriteSettings.sourceY = 0 + + context.save() + context.translate(effect.x + effect.width / 2, effect.y + effect.height / 2) - Array.from({ length: horizontalCount }).forEach((_, i) => { - Array.from({ length: verticalCount }).forEach((_, j) => { - const x = obstacle.x + i * SPRITE_SIZE - const y = obstacle.y + j * SPRITE_SIZE + if (effect.direction) + context.rotate(Math.atan2(effect.direction.x, -effect.direction.y)) - const width = Math.min(SPRITE_SIZE, obstacle.width - i * SPRITE_SIZE) - const height = Math.min(SPRITE_SIZE, obstacle.height - j * SPRITE_SIZE) + context.drawImage( + sprite, + spriteSettings.sourceX, + spriteSettings.sourceY, + spriteSettings.width, + spriteSettings.height, + -effect.width / 2, + -effect.height / 2, + effect.width, + effect.height + ) - context.drawImage(wallSprite, 0, 0, width, height, x, y, width, height) - }) - }) + context.restore() }) } diff --git a/packages/client/src/pages/SignUp/SignUp.scss b/packages/client/src/pages/SignUp/SignUp.scss index 27eae7d..46015ec 100644 --- a/packages/client/src/pages/SignUp/SignUp.scss +++ b/packages/client/src/pages/SignUp/SignUp.scss @@ -1,6 +1,11 @@ @import '../../scss/form-mixin'; .registration-page { - @include page-background-layout($background-image: ($img_registration-background, $img_page_background_left)); + @include page-background-layout( + $background-image: ( + $img_registration-background, + $img_page_background_left, + ) + ); @include form-styles(); } diff --git a/packages/client/vite.config.ts.timestamp-1728127189350.mjs b/packages/client/vite.config.ts.timestamp-1728127189350.mjs index 2be45ef..3b1c00d 100644 --- a/packages/client/vite.config.ts.timestamp-1728127189350.mjs +++ b/packages/client/vite.config.ts.timestamp-1728127189350.mjs @@ -1,30 +1,28 @@ // vite.config.ts -import { defineConfig } from "vite"; -import react from "@vitejs/plugin-react"; -import dotenv from "dotenv"; -import path from "path"; -var __vite_injected_original_dirname = "/home/user/pr/tanks/packages/client"; -dotenv.config(); +import { defineConfig } from 'vite' +import react from '@vitejs/plugin-react' +import dotenv from 'dotenv' +import path from 'path' +var __vite_injected_original_dirname = '/home/user/pr/tanks/packages/client' +dotenv.config() var vite_config_default = defineConfig({ server: { port: Number(process.env.CLIENT_PORT) || 3e3, - host: "0.0.0.0", + host: '0.0.0.0', watch: { - usePolling: true - } + usePolling: true, + }, }, define: { - __SERVER_PORT__: process.env.SERVER_PORT + __SERVER_PORT__: process.env.SERVER_PORT, }, resolve: { alias: { - "@": path.resolve(__vite_injected_original_dirname, "src") - } + '@': path.resolve(__vite_injected_original_dirname, 'src'), + }, }, plugins: [react()], - envDir: "../../" -}); -export { - vite_config_default as default -}; + envDir: '../../', +}) +export { vite_config_default as default } //# sourceMappingURL=data:application/json;base64,ewogICJ2ZXJzaW9uIjogMywKICAic291cmNlcyI6IFsidml0ZS5jb25maWcudHMiXSwKICAic291cmNlc0NvbnRlbnQiOiBbImNvbnN0IF9fdml0ZV9pbmplY3RlZF9vcmlnaW5hbF9kaXJuYW1lID0gXCIvaG9tZS91c2VyL3ByL3RhbmtzL3BhY2thZ2VzL2NsaWVudFwiO2NvbnN0IF9fdml0ZV9pbmplY3RlZF9vcmlnaW5hbF9maWxlbmFtZSA9IFwiL2hvbWUvdXNlci9wci90YW5rcy9wYWNrYWdlcy9jbGllbnQvdml0ZS5jb25maWcudHNcIjtjb25zdCBfX3ZpdGVfaW5qZWN0ZWRfb3JpZ2luYWxfaW1wb3J0X21ldGFfdXJsID0gXCJmaWxlOi8vL2hvbWUvdXNlci9wci90YW5rcy9wYWNrYWdlcy9jbGllbnQvdml0ZS5jb25maWcudHNcIjtpbXBvcnQgeyBkZWZpbmVDb25maWcgfSBmcm9tICd2aXRlJ1xuaW1wb3J0IHJlYWN0IGZyb20gJ0B2aXRlanMvcGx1Z2luLXJlYWN0J1xuaW1wb3J0IGRvdGVudiBmcm9tICdkb3RlbnYnXG5kb3RlbnYuY29uZmlnKClcbmltcG9ydCBwYXRoIGZyb20gJ3BhdGgnXG5cbi8vIGh0dHBzOi8vdml0ZWpzLmRldi9jb25maWcvXG5leHBvcnQgZGVmYXVsdCBkZWZpbmVDb25maWcoe1xuICBzZXJ2ZXI6IHtcbiAgICBwb3J0OiBOdW1iZXIocHJvY2Vzcy5lbnYuQ0xJRU5UX1BPUlQpIHx8IDMwMDAsXG4gICAgaG9zdDogJzAuMC4wLjAnLFxuICAgIHdhdGNoOiB7XG4gICAgICB1c2VQb2xsaW5nOiB0cnVlLFxuICAgIH0sXG4gIH0sXG4gIGRlZmluZToge1xuICAgIF9fU0VSVkVSX1BPUlRfXzogcHJvY2Vzcy5lbnYuU0VSVkVSX1BPUlQsXG4gIH0sXG4gIHJlc29sdmU6IHtcbiAgICBhbGlhczoge1xuICAgICAgJ0AnOiBwYXRoLnJlc29sdmUoX19kaXJuYW1lLCAnc3JjJyksXG4gICAgfSxcbiAgfSxcbiAgcGx1Z2luczogW3JlYWN0KCldLFxuICBlbnZEaXI6ICcuLi8uLi8nLFxufSlcbiJdLAogICJtYXBwaW5ncyI6ICI7QUFBMlIsU0FBUyxvQkFBb0I7QUFDeFQsT0FBTyxXQUFXO0FBQ2xCLE9BQU8sWUFBWTtBQUVuQixPQUFPLFVBQVU7QUFKakIsSUFBTSxtQ0FBbUM7QUFHekMsT0FBTyxPQUFPO0FBSWQsSUFBTyxzQkFBUSxhQUFhO0FBQUEsRUFDMUIsUUFBUTtBQUFBLElBQ04sTUFBTSxPQUFPLFFBQVEsSUFBSSxXQUFXLEtBQUs7QUFBQSxJQUN6QyxNQUFNO0FBQUEsSUFDTixPQUFPO0FBQUEsTUFDTCxZQUFZO0FBQUEsSUFDZDtBQUFBLEVBQ0Y7QUFBQSxFQUNBLFFBQVE7QUFBQSxJQUNOLGlCQUFpQixRQUFRLElBQUk7QUFBQSxFQUMvQjtBQUFBLEVBQ0EsU0FBUztBQUFBLElBQ1AsT0FBTztBQUFBLE1BQ0wsS0FBSyxLQUFLLFFBQVEsa0NBQVcsS0FBSztBQUFBLElBQ3BDO0FBQUEsRUFDRjtBQUFBLEVBQ0EsU0FBUyxDQUFDLE1BQU0sQ0FBQztBQUFBLEVBQ2pCLFFBQVE7QUFDVixDQUFDOyIsCiAgIm5hbWVzIjogW10KfQo=