import { AnimatedSprite, Container, Graphics, NineSliceSprite, Sprite, Text } from 'pixi.js'; import { CONTAINER_HEIGHT, CONTAINER_WIDTH } from './config'; import { Ball } from './entities/ball/ball'; import { Brick } from './entities/brick/brick'; import { Paddle } from './entities/paddle/paddle'; import { Perk } from './entities/perk/perk'; import { Game } from './game'; /** * Создает визуальное отображение мяча с помощью Pixi.js * @param {Ball} ball экземпляр класса мяч * @param {object} textures объект с текстурами для визуального отображения */ function createBallView(ball, textures) { const ballFrames = [ textures['/sprites/fire_ball_1.png'], textures['/sprites/fire_ball_2.png'], textures['/sprites/fire_ball_3.png'], textures['/sprites/fire_ball_4.png'], textures['/sprites/fire_ball_5.png'], textures['/sprites/fire_ball_6.png'], textures['/sprites/fire_ball_7.png'], textures['/sprites/fire_ball_8.png'], ]; const ballSprite = new AnimatedSprite(ballFrames); ballSprite.animationSpeed = 0.15; ballSprite.anchor.set(0.5); ballSprite.width = ball.radius * 2; ballSprite.height = ball.radius * 2; ballSprite.play(); return ballSprite; } /** * Создает визуальное отображение ракетки с помощью Pixi.js * @param {Paddle} paddle экземпляр класса ракетка * @param {object} textures объект с текстурами для визуального отображения */ function createPaddleView(paddle, textures) { const texture = textures['/sprites/paddle.png']; const paddleSprite = new NineSliceSprite({ texture, leftWidth: 150, rightWidth: 150, topHeight: 0, bottomHeight: 0 }); const paddleAspectRatio = paddle.height / texture.height; paddleSprite.scale.set(paddleAspectRatio); paddleSprite.width = paddle.width / paddleAspectRatio; return paddleSprite; } /** * Создает визуальное отображение кирпича с помощью Pixi.js * @param {Brick} brick экземпляр класса кирпич * @returns {Graphics} графическое отображение кирпича * @param {object} textures объект с текстурами для визуального отображения */ function createBrickView(brick, textures) { const bricksTextures = [ textures['/sprites/block_1.png'], textures['/sprites/block_2.png'], textures['/sprites/block_3.png'], ]; const brickSprite = new Sprite(bricksTextures[brick.type - 1]); brickSprite.width = brick.width; brickSprite.height = brick.height; return brickSprite; } /** * Создает визуальное отображение бонуса с помощью Pixi.js * @param {Perk} perk экземпляр класса бонус * @returns {Graphics} графическое отображение бонуса * @param {object} textures текстуры блоков */ function createPerkView(perk, textures) { let texture; switch (perk.type) { case 'slow': texture = textures['/sprites/perk_1.png']; break; case 'wide': texture = textures['/sprites/perk_2.png']; break; case 'life': texture = textures['/sprites/perk_3.png']; break; case 'clone': texture = textures['/sprites/perk_3.png']; break; default: texture = textures['/sprites/perk_1.png']; break; } const perkSprite = new Sprite(texture); perkSprite.width = perk.width; perkSprite.height = perk.height; return perkSprite; } /** * Возвращает текстуру фона для текущего уровня или дефолтную текстуру фона * @param {Game} game экземпляр класса игра * @param {object} textures объект с текстурами для визуального отображения */ function backgroundTextureForLevel(game, textures) { const levelBackground = textures[`/sprites/background_${game.currentLevel + 1}.png`]; if (!levelBackground) { return textures['/sprites/background_1.png']; } return levelBackground; } /** * Создает визуальное отображение всех сущностей в игре с помощью Pixi.js и добавляет в контейнер * @param {Game} game экземпляр класса игра * @param {Container} container экземпляр класса контейнер Pixi.js * @param {object} textures объект с текстурами для визуального отображения */ export function createGameView(game, container, textures) { try { const background = new Sprite(backgroundTextureForLevel(game, textures)); background.width = CONTAINER_WIDTH; background.height = CONTAINER_HEIGHT; container.addChildAt(background, 0); const header = new Text({ style: { fill: '#ffffff', fontSize: 16 } }); header.x = 8; header.y = 8; container.addChild(header); const balls = new Map(); for (const ball of game.balls) { const ballView = createBallView(ball, textures); container.addChild(ballView); balls.set(ball, ballView); } const paddle = createPaddleView(game.paddle, textures); container.addChild(paddle); const bricks = game.bricks.map((brick) => { const brickView = createBrickView(brick, textures); container.addChild(brickView); return brickView; }); const perks = new Map(); return { background, header, balls, paddle, bricks, perks, }; } catch (err) { console.error(err); return null; } } /** * Создает отображения для новых бонусов и удаляет отображения исчезнувших * @param {object} views объект с визуальными отображениями сущностей игры * @param {Game} game экземпляр класса игра * @param {Container} container экземпляр класса контейнер Pixi.js * @param {object} textures объект с текстурами для визуального отображения */ export function managePerkViewsLifetime(views, game, container, textures) { // Изменяем ширину отображения ракетки основываясь на aspect ratio по оси X и ширине ракетки if (views.paddle.width * views.paddle.scale.x !== game.paddle.width) { views.paddle.width = game.paddle.width / views.paddle.scale.x; } for (const [ball, ballView] of views.balls) { if (!game.balls.includes(ball)) { container.removeChild(ballView); ballView.destroy(); views.balls.delete(ball); } } for (const ball of game.balls) { if (!views.balls.has(ball)) { const ballView = createBallView(ball, textures); container.addChild(ballView); views.balls.set(ball, ballView); } } for (const [perk, perkView] of views.perks) { if (!game.perks.includes(perk)) { container.removeChild(perkView); perkView.destroy(); views.perks.delete(perk); } } for (const perk of game.perks) { if (!views.perks.has(perk)) { const perkView = createPerkView(perk, textures); container.addChild(perkView); views.perks.set(perk, perkView); } } } /** * Синхронизирует отображение сущностей Pixi.js с логикой игры (координаты и тд.) * @param {object} views объект с визуальными отображениями сущностей игры * @param {Game} game экземпляр класса игра */ export function syncronizeViewsWithGame(views, game) { try { if (!(game instanceof Game)) { throw new Error('Аргумент game должен быть экземпляром класса Game'); } views.header.text = `Уровень: ${game.currentLevel + 1} Количество жизней: ${game.livesAmount}`; for (const ball of game.balls) { const ballView = views.balls.get(ball); ballView.x = ball.x; ballView.y = ball.y; } views.paddle.x = game.paddle.x; views.paddle.y = game.paddle.y; for (let i = 0; i < game.bricks.length; i++) { const brick = game.bricks[i]; const brickView = views.bricks[i]; brickView.x = brick.x; brickView.y = brick.y; brickView.visible = brick.alive; } for (const perk of game.perks) { const perkView = views.perks.get(perk); perkView.x = perk.x; perkView.y = perk.y; } } catch (err) { console.error(err); } } /** * Меняет текстуру фона под текущий уровень игры. * @param {object} views объект с визуальными отображениями сущностей игры * @param {Game} game экземпляр класса игра * @param {object} textures объект с текстурами для визуального отображения */ export function updateBackgroundView(views, game, textures) { views.background.texture = backgroundTextureForLevel(game, textures); } /** * Пересоздает отображения кирпичей под текущий уровень игры. * @param {object} views объект с визуальными отображениями сущностей игры * @param {Game} game экземпляр класса игра * @param {Container} container экземпляр класса контейнер Pixi.js * @param {object} textures объект с текстурами для визуального отображения */ export function rebuildBrickViews(views, game, container, textures) { for (const brickView of views.bricks) { container.removeChild(brickView); brickView.destroy(); } views.bricks = game.bricks.map((brick) => { const brickView = createBrickView(brick, textures); container.addChild(brickView); return brickView; }); }