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.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.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 ddef2c4..abedf67 100644 --- a/packages/client/src/components/Game/Game.tsx +++ b/packages/client/src/components/Game/Game.tsx @@ -7,7 +7,7 @@ import { import { PLAYER_DEFAULT_PARAMS } from '@/components/Game/player' import { gameLoop } from '@/components/Game/gameLoop' import { handleKeyDown, handleKeyUp } from '@/components/Game/controls' -import { AbstractEntity, Obstacle } from '@/components/Game/gameTypes' +import { AbstractEntity, Effect, Obstacle } from '@/components/Game/gameTypes' import { initializeCompanyMapObstacle, initializeRandomObstacle, @@ -22,6 +22,7 @@ export const Game: React.FC = () => { 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) @@ -50,6 +51,7 @@ export const Game: React.FC = () => { enemiesRef, bulletsRef, obstaclesRef, + effectsRef, livesRef, handleGameOver ) diff --git a/packages/client/src/components/Game/bullet.tsx b/packages/client/src/components/Game/bullet.tsx index 2a69dc4..e527003 100644 --- a/packages/client/src/components/Game/bullet.tsx +++ b/packages/client/src/components/Game/bullet.tsx @@ -1,4 +1,10 @@ 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 // Задайте скорость пули @@ -10,29 +16,31 @@ export const createBullet = (enemy: AbstractEntity): AbstractEntity => { if (enemy.direction.y < 0) { // Вверх bulletX = enemy.x + enemy.width / 2 // Центр по X - bulletY = enemy.y // Верхняя часть врага + bulletY = enemy.y - bulletSize.height // Верхняя часть врага } else if (enemy.direction.y > 0) { // Вниз bulletX = enemy.x + enemy.width / 2 // Центр по X - bulletY = enemy.y + enemy.height // Нижняя часть врага + bulletY = enemy.y + enemy.height + bulletSize.height // Нижняя часть врага } else if (enemy.direction.x < 0) { // Влево - bulletX = enemy.x // Левый край врага + bulletX = enemy.x - bulletSize.height // Левый край врага bulletY = enemy.y + enemy.height / 2 // Центр по Y } else if (enemy.direction.x > 0) { // Вправо - bulletX = enemy.x + enemy.width // Правый край врага + 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, - y: bulletY, - width: 5, // Ширина пули - height: 5, // Высота пули + x: bulletX - bulletSize.width / 2, + y: bulletY - bulletSize.height / 2, + width: bulletSize.width, // Ширина пули + height: bulletSize.height, // Высота пули speed: bulletSpeed, direction: bulletDirection, } diff --git a/packages/client/src/components/Game/collision.tsx b/packages/client/src/components/Game/collision.tsx index aa62ce3..1b7fd49 100644 --- a/packages/client/src/components/Game/collision.tsx +++ b/packages/client/src/components/Game/collision.tsx @@ -8,7 +8,8 @@ export const detectCollision = ( 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 ) } 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 20b2526..7c8a3fc 100644 --- a/packages/client/src/components/Game/enemy.tsx +++ b/packages/client/src/components/Game/enemy.tsx @@ -1,7 +1,10 @@ import { getRandomEdgePosition } from './utils' import { AbstractEntity, Enemy, Obstacle } from '@/components/Game/gameTypes' import { createBullet } from '@/components/Game/bullet' -import { detectCollision } from '@/components/Game/collision' +import { + detectCollision, + detectEnemyCollision, +} from '@/components/Game/collision' const enemyParams = { width: 70, @@ -37,8 +40,8 @@ export const initializeCampanyEnemies = (): Enemy[] => { { ...enemyParams, id: 0, - x: 50, - y: 55, + x: 360, + y: 0, animation: { currentFrame: 0, totalFrames: 4, @@ -49,8 +52,8 @@ export const initializeCampanyEnemies = (): Enemy[] => { { ...enemyParams, id: 1, - x: 320, - y: 250, + x: 0, + y: 108, animation: { currentFrame: 0, totalFrames: 4, @@ -61,8 +64,8 @@ export const initializeCampanyEnemies = (): Enemy[] => { { ...enemyParams, id: 2, - x: 715, - y: 60, + x: 720, + y: 108, animation: { currentFrame: 0, totalFrames: 4, @@ -119,7 +122,7 @@ export const updateEnemyPositions = ( // Проверка столкновений с другими врагами const hasEnemyCollision = enemiesRef.current.some(otherEnemy => { if (otherEnemy === enemy) return false - return detectCollision({ ...enemy, x: newX, y: newY }, otherEnemy) + return detectEnemyCollision({ ...enemy, x: newX, y: newY }, otherEnemy) }) // Проверка коллизий с препятствиями @@ -143,7 +146,7 @@ const isPositionOccupied = ( enemies: AbstractEntity[] ) => { return enemies.some(enemy => - detectCollision({ ...enemy, x: position.x, y: position.y }, enemy) + detectEnemyCollision({ ...enemy, x: position.x, y: position.y }, enemy) ) } diff --git a/packages/client/src/components/Game/gameLoop.tsx b/packages/client/src/components/Game/gameLoop.tsx index 5061a44..e1c8833 100644 --- a/packages/client/src/components/Game/gameLoop.tsx +++ b/packages/client/src/components/Game/gameLoop.tsx @@ -6,12 +6,14 @@ import { drawEnemies, drawObstacles, drawBullets, + drawEffects, } from './utils' import { ControlsProps, AbstractEntity, Obstacle, Enemy, + Effect, } from '@/components/Game/gameTypes' import { detectBulletCollision, @@ -20,6 +22,7 @@ import { import { updatePlayerAction } from '@/components/Game/controls' import { updateBullets } from '@/components/Game/bullet' import { handleBulletObstacleCollisions } from '@/components/Game/obstacle' +import { createBangEffect, initEffects } from './effects' /** * Основной игровой цикл, который обновляет состояние игры и перерисовывает экран каждый кадр. @@ -39,6 +42,7 @@ export const gameLoop = ( enemiesRef: React.MutableRefObject, bulletsRef: React.MutableRefObject, obstaclesRef: React.MutableRefObject, + effectsRef: React.MutableRefObject, livesRef: React.MutableRefObject, handleGameOver: () => void ) => { @@ -62,6 +66,8 @@ export const gameLoop = ( // Обработка столкновений с препятствиями handleBulletObstacleCollisions(bulletsRef.current, obstaclesRef.current) + initEffects(effectsRef) + bulletsRef.current = updateBullets( bulletsRef.current, canvasRef.current.width, @@ -69,9 +75,10 @@ export const gameLoop = ( ) // Отрисовка всех игровых объектов - drawObstacles(context, obstaclesRef.current) drawPlayer(context, playerRef.current) drawEnemies(context, enemiesRef.current) + drawObstacles(context, obstaclesRef.current) + drawEffects(context, effectsRef.current) drawBullets(context, bulletsRef.current) // Отрисовка пуль // Проверка на столкновения пуль с врагами @@ -81,6 +88,11 @@ export const gameLoop = ( 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 @@ -90,6 +102,11 @@ export const gameLoop = ( 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) // Проверка на окончание игры diff --git a/packages/client/src/components/Game/gameTypes.tsx b/packages/client/src/components/Game/gameTypes.tsx index 7978ee5..b28238a 100644 --- a/packages/client/src/components/Game/gameTypes.tsx +++ b/packages/client/src/components/Game/gameTypes.tsx @@ -1,18 +1,23 @@ interface AnimationParams { currentFrame: number totalFrames: number - frameInterval: 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 } - animation: AnimationParams + direction: Direction + animation?: AnimationParams } export interface Enemy extends AbstractEntity { @@ -21,10 +26,24 @@ export interface Enemy extends AbstractEntity { } export interface Obstacle { + type: string x: number y: number width: number height: number + hp: number + isCollide: boolean + animation: AnimationParams +} + +export interface Effect { + type: string + x: number + y: number + width: number + height: number + direction?: Direction + animation: AnimationParams } export interface ControlsProps { diff --git a/packages/client/src/components/Game/obstacle.tsx b/packages/client/src/components/Game/obstacle.tsx index e1e9d7e..64314ab 100644 --- a/packages/client/src/components/Game/obstacle.tsx +++ b/packages/client/src/components/Game/obstacle.tsx @@ -4,35 +4,164 @@ 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 [ - { 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 }, - ] + 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 = ( @@ -42,7 +171,13 @@ export const initializeRandomObstacle = ( while (obstacles.length < numberOfObstacles) { const { x, y } = getRandomPosition(800, 600) - const obstacle: Obstacle = { x, y, width: 50, height: 50 } + const obstacle: Obstacle = createObstacle( + Types.Wall, + x, + y, + OBSTACLE_SIZE, + OBSTACLE_SIZE + )[0] // Проверяем, нет ли коллизий с существующими препятствиями const hasCollision = obstacles.some(existingObstacle => @@ -67,6 +202,10 @@ export const handleBulletObstacleCollisions = ( if (detectCollision(bullet, obstacle)) { // Логика для уничтожения пули, если она попала в препятствие bullets.splice(bullets.indexOf(bullet), 1) // Пример, удаляем пулю, если попала в препятствие + createBangEffect( + bullet.x + bullet.width / 2, + bullet.y + bullet.height / 2 + ) killObstacle(obstacles, obstacle) } }) @@ -74,5 +213,20 @@ export const handleBulletObstacleCollisions = ( } const killObstacle = (obstacles: Obstacle[], obstacle: Obstacle) => { - obstacles.splice(obstacles.indexOf(obstacle), 1) + 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 92a46dc..c432ff7 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: 560, width: 70, height: 70, - speed: 2, + speed: 3, direction: { x: 0, y: 0 }, animation: { currentFrame: 0, // Текущий кадр спрайта diff --git a/packages/client/src/components/Game/utils.tsx b/packages/client/src/components/Game/utils.tsx index 64c25cb..13284bb 100644 --- a/packages/client/src/components/Game/utils.tsx +++ b/packages/client/src/components/Game/utils.tsx @@ -1,8 +1,18 @@ 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.svg' -import { AbstractEntity, Enemy, Obstacle } from '@/components/Game/gameTypes' +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, @@ -53,15 +63,17 @@ const darawTank = ( } // Если смещение кратно 10 меняем кадр - if (moovment % animation.frameInterval === 0) { + if (animation?.frameInterval && moovment % animation.frameInterval === 0) { animation.currentFrame = (animation.currentFrame + 1) % animation?.totalFrames } - spriteSettings.width = sprite.width / animation.totalFrames - spriteSettings.height = sprite.height - spriteSettings.sourceX = animation.currentFrame * spriteSettings.width - spriteSettings.sourceY = 0 + 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(data.x + data.width / 2, data.y + data.height / 2) @@ -96,8 +108,6 @@ const enemiesSprite = new Image() enemiesSprite.src = enemiesSpritePath -const lastEnemyDirection: Record = {} - export const drawEnemies = ( context: CanvasRenderingContext2D, enemies: Enemy[] @@ -108,29 +118,53 @@ export const drawEnemies = ( } 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 - 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 + break + } - const width = Math.min(SPRITE_SIZE, obstacle.width - i * SPRITE_SIZE) - const height = Math.min(SPRITE_SIZE, obstacle.height - j * SPRITE_SIZE) + const spriteSize = sprite.width / obstacle.animation.totalFrames - context.drawImage(wallSprite, 0, 0, width, height, x, y, width, height) - }) - }) + context.drawImage( + sprite, + obstacle.animation.currentFrame * spriteSize, + 0, + spriteSize, + spriteSize, + obstacle.x, + obstacle.y, + spriteSize, + spriteSize + ) }) } @@ -162,3 +196,83 @@ export const drawBullets = ( 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) + + if (effect.direction) + context.rotate(Math.atan2(effect.direction.x, -effect.direction.y)) + + context.drawImage( + sprite, + spriteSettings.sourceX, + spriteSettings.sourceY, + spriteSettings.width, + spriteSettings.height, + -effect.width / 2, + -effect.height / 2, + effect.width, + effect.height + ) + + context.restore() + }) +}