feat: Добавлена обработка столкновения ракетки и бонуса, логика обработки бонусов подключена в tick
This commit is contained in:
@@ -10,6 +10,7 @@ import {
|
|||||||
CONTAINER_WIDTH,
|
CONTAINER_WIDTH,
|
||||||
PADDLE_HEIGHT,
|
PADDLE_HEIGHT,
|
||||||
PADDLE_WIDTH,
|
PADDLE_WIDTH,
|
||||||
|
PERK_BALL_SPEED_DECREASE,
|
||||||
} from './config';
|
} from './config';
|
||||||
import { Ball } from './entities/ball/ball';
|
import { Ball } from './entities/ball/ball';
|
||||||
import { layBricks } from './entities/brick/layBricks/layBricks';
|
import { layBricks } from './entities/brick/layBricks/layBricks';
|
||||||
@@ -33,6 +34,7 @@ export class Game {
|
|||||||
this.paddle = new Paddle(0, CONTAINER_HEIGHT - PADDLE_HEIGHT, PADDLE_WIDTH, PADDLE_HEIGHT);
|
this.paddle = new Paddle(0, CONTAINER_HEIGHT - PADDLE_HEIGHT, PADDLE_WIDTH, PADDLE_HEIGHT);
|
||||||
this.ball = new Ball(0, 0, BALL_RADIUS, BALL_SPEED, BALL_INITIAL_ANGLE);
|
this.ball = new Ball(0, 0, BALL_RADIUS, BALL_SPEED, BALL_INITIAL_ANGLE);
|
||||||
this.bricks = layBricks(this.levels[this.currentLevel], BRICK_WIDTH, BRICK_HEIGHT);
|
this.bricks = layBricks(this.levels[this.currentLevel], BRICK_WIDTH, BRICK_HEIGHT);
|
||||||
|
this.perks = [];
|
||||||
|
|
||||||
this._placeBallOnPaddle();
|
this._placeBallOnPaddle();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,11 +4,16 @@ import {
|
|||||||
MAX_PADDLE_REFLECTION_ANGLE,
|
MAX_PADDLE_REFLECTION_ANGLE,
|
||||||
MIN_PADDLE_REFLECTION_ANGLE,
|
MIN_PADDLE_REFLECTION_ANGLE,
|
||||||
PADDLE_SECTOR_AMOUNT,
|
PADDLE_SECTOR_AMOUNT,
|
||||||
|
PERK_DROP_CHANCE,
|
||||||
|
PERK_HEIGHT,
|
||||||
|
PERK_WIDTH,
|
||||||
} from '../../config';
|
} from '../../config';
|
||||||
import { Game } from '../../game';
|
import { Game } from '../../game';
|
||||||
import { calculateCollision } from '../calculateCollision/calculateCollision';
|
import { calculateCollision } from '../calculateCollision/calculateCollision';
|
||||||
import { calculateDirection } from '../calculateDirection/calculateDirection';
|
import { calculateDirection } from '../calculateDirection/calculateDirection';
|
||||||
import { processReflection } from '../processReflection/processReflection';
|
import { processReflection } from '../processReflection/processReflection';
|
||||||
|
import { spawnRandomPerk } from '../spawnRandomPerk/spawnRandomPerk';
|
||||||
|
import { updatePerks } from '../updatePerks/updatePerks';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Функция для вычисления взаимодействий сущностей игры в зависимости от времени из Ticker
|
* Функция для вычисления взаимодействий сущностей игры в зависимости от времени из Ticker
|
||||||
@@ -39,6 +44,9 @@ export function tick(game, containerWidth, containerHeight, deltaTime) {
|
|||||||
|
|
||||||
ball.moveForward(deltaTime);
|
ball.moveForward(deltaTime);
|
||||||
|
|
||||||
|
// Бонусы падают и ловятся ракеткой каждый кадр
|
||||||
|
updatePerks(game, containerHeight, deltaTime);
|
||||||
|
|
||||||
const leftBoundary = ball.radius;
|
const leftBoundary = ball.radius;
|
||||||
const rightBoundary = containerWidth - ball.radius;
|
const rightBoundary = containerWidth - ball.radius;
|
||||||
const topBoundary = ball.radius;
|
const topBoundary = ball.radius;
|
||||||
@@ -92,6 +100,17 @@ export function tick(game, containerWidth, containerHeight, deltaTime) {
|
|||||||
ball.horizontalSpeed *= directions[0];
|
ball.horizontalSpeed *= directions[0];
|
||||||
ball.verticalSpeed *= directions[1];
|
ball.verticalSpeed *= directions[1];
|
||||||
brick.kill();
|
brick.kill();
|
||||||
|
|
||||||
|
if (!brick.alive) {
|
||||||
|
if (Math.random() < PERK_DROP_CHANCE) {
|
||||||
|
const perk = spawnRandomPerk(
|
||||||
|
brick.x + brick.width / 2 - PERK_WIDTH / 2,
|
||||||
|
brick.y + brick.height / 2 - PERK_HEIGHT / 2,
|
||||||
|
);
|
||||||
|
game.perks.push(perk);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,67 @@
|
|||||||
|
import { PERK_BALL_SPEED_DECREASE } from '../../config';
|
||||||
|
import { Game } from '../../game';
|
||||||
|
import { calculateCollision } from '../calculateCollision/calculateCollision';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Обновляет состояние бонусов, меняет положение, проверяет столкновение с ракеткой и выход за границу поля
|
||||||
|
* @param {Game} game экземпляр класса игры
|
||||||
|
* @param {number} containerHeight высота игрового контейнера в пикселях
|
||||||
|
* @param {number} deltaTime изменение времени из Ticker
|
||||||
|
*/
|
||||||
|
export function updatePerks(game, containerHeight, deltaTime) {
|
||||||
|
try {
|
||||||
|
if (!(game instanceof Game)) {
|
||||||
|
throw new Error('Аргумент game должен быть экземпляром класса игры');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof containerHeight !== 'number' || containerHeight <= 0) {
|
||||||
|
throw new Error('Высота игрового контейнера должна быть положительным числом');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof deltaTime !== 'number' || deltaTime <= 0) {
|
||||||
|
throw new Error('Значение изменения времени должно быть положительным числом');
|
||||||
|
}
|
||||||
|
|
||||||
|
const { paddle } = game;
|
||||||
|
|
||||||
|
for (const perk of game.perks) {
|
||||||
|
perk.moveForward(deltaTime);
|
||||||
|
|
||||||
|
const perkObject = {
|
||||||
|
left: perk.x,
|
||||||
|
right: perk.x + perk.width,
|
||||||
|
top: perk.y,
|
||||||
|
bottom: perk.y + perk.height,
|
||||||
|
};
|
||||||
|
const paddleObject = {
|
||||||
|
left: paddle.x,
|
||||||
|
right: paddle.x + paddle.width,
|
||||||
|
top: paddle.y,
|
||||||
|
bottom: paddle.y + paddle.height,
|
||||||
|
};
|
||||||
|
|
||||||
|
const isCollided = calculateCollision(perkObject, paddleObject);
|
||||||
|
|
||||||
|
if (isCollided) {
|
||||||
|
switch (perk.type) {
|
||||||
|
case 'slow':
|
||||||
|
if (game.ball.speed - PERK_BALL_SPEED_DECREASE > 0) {
|
||||||
|
game.ball.increaseSpeed(-1 * PERK_BALL_SPEED_DECREASE);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
perk.alive = false;
|
||||||
|
} else if (perk.y > containerHeight) {
|
||||||
|
perk.alive = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
game.perks = game.perks.filter((perk) => perk.alive);
|
||||||
|
} catch (err) {
|
||||||
|
console.error(err);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,47 @@
|
|||||||
|
import { Perk } from '../../entities/perk/perk';
|
||||||
|
import { Game } from '../../game';
|
||||||
|
import { updatePerks } from './updatePerks';
|
||||||
|
|
||||||
|
describe('updatePerks', () => {
|
||||||
|
it('Возвращает корректное значение при неверных типах аргументов', () => {
|
||||||
|
const game = new Game([[[1]]]);
|
||||||
|
|
||||||
|
expect(updatePerks(null, 100, 1)).toBeNull();
|
||||||
|
expect(updatePerks(game, null, 1)).toBeNull();
|
||||||
|
expect(updatePerks(game, -1, 1)).toBeNull();
|
||||||
|
expect(updatePerks(game, 100, null)).toBeNull();
|
||||||
|
expect(updatePerks(game, 100, -1)).toBeNull();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Двигает бонус вниз в зависимости от времени', () => {
|
||||||
|
const game = new Game([[[1]]]);
|
||||||
|
const perk = new Perk(500, 10, 10, 10, 'slow', 3);
|
||||||
|
game.perks.push(perk);
|
||||||
|
|
||||||
|
updatePerks(game, 600, 1);
|
||||||
|
|
||||||
|
expect(perk.y).toBe(13);
|
||||||
|
expect(game.perks).toContain(perk);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Ловит бонус ракеткой, применяет эффект и убирает его', () => {
|
||||||
|
const game = new Game([[[1]]]);
|
||||||
|
const speedBefore = game.ball.speed;
|
||||||
|
const { paddle } = game;
|
||||||
|
game.perks.push(new Perk(paddle.x, paddle.y - 1, 10, 10, 'slow', 0));
|
||||||
|
|
||||||
|
updatePerks(game, 600, 1);
|
||||||
|
|
||||||
|
expect(game.perks).toHaveLength(0);
|
||||||
|
expect(game.ball.speed).toBeLessThan(speedBefore);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Убирает бонус, улетевший за нижнюю границу', () => {
|
||||||
|
const game = new Game([[[1]]]);
|
||||||
|
game.perks.push(new Perk(0, 601, 10, 10, 'slow', 0));
|
||||||
|
|
||||||
|
updatePerks(game, 600, 1);
|
||||||
|
|
||||||
|
expect(game.perks).toHaveLength(0);
|
||||||
|
});
|
||||||
|
});
|
||||||
+2
-1
@@ -17,7 +17,7 @@ import { LEVELS } from './const/levels';
|
|||||||
import { Game } from './game';
|
import { Game } from './game';
|
||||||
import { calculateCollision } from './lib/calculateCollision/calculateCollision';
|
import { calculateCollision } from './lib/calculateCollision/calculateCollision';
|
||||||
import { calculateDirection } from './lib/calculateDirection/calculateDirection';
|
import { calculateDirection } from './lib/calculateDirection/calculateDirection';
|
||||||
import { createGameView, rebuildBrickViews, syncronizeViewsWithGame } from './view';
|
import { createGameView, managePerkViewsLifetime, rebuildBrickViews, syncronizeViewsWithGame } from './view';
|
||||||
|
|
||||||
(async () => {
|
(async () => {
|
||||||
// Create a new application
|
// Create a new application
|
||||||
@@ -60,6 +60,7 @@ import { createGameView, rebuildBrickViews, syncronizeViewsWithGame } from './vi
|
|||||||
rebuildBrickViews(views, game, container);
|
rebuildBrickViews(views, game, container);
|
||||||
currentLevel = game.currentLevel;
|
currentLevel = game.currentLevel;
|
||||||
}
|
}
|
||||||
|
managePerkViewsLifetime(views, game, container);
|
||||||
syncronizeViewsWithGame(views, game);
|
syncronizeViewsWithGame(views, game);
|
||||||
});
|
});
|
||||||
})();
|
})();
|
||||||
|
|||||||
+48
@@ -2,6 +2,7 @@ import { Container, Graphics } from 'pixi.js';
|
|||||||
import { Ball } from './entities/ball/ball';
|
import { Ball } from './entities/ball/ball';
|
||||||
import { Brick } from './entities/brick/brick';
|
import { Brick } from './entities/brick/brick';
|
||||||
import { Paddle } from './entities/paddle/paddle';
|
import { Paddle } from './entities/paddle/paddle';
|
||||||
|
import { Perk } from './entities/perk/perk';
|
||||||
import { Game } from './game';
|
import { Game } from './game';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -39,6 +40,22 @@ function createBrickView(brick) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Создает визуальное отображение бонуса с помощью Pixi.js
|
||||||
|
* @param {Perk} perk экземпляр класса бонус
|
||||||
|
* @returns {Graphics} графическое отображение бонуса
|
||||||
|
*/
|
||||||
|
function createPerkView(perk) {
|
||||||
|
const perkView = new Graphics().rect(0, 0, perk.width, perk.height);
|
||||||
|
|
||||||
|
switch (perk.type) {
|
||||||
|
case 'slow':
|
||||||
|
return perkView.fill('#00ffff');
|
||||||
|
default:
|
||||||
|
return perkView.fill('#ffffff');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Создает визуальное отображение всех сущностей в игре с помощью Pixi.js и добавляет в контейнер
|
* Создает визуальное отображение всех сущностей в игре с помощью Pixi.js и добавляет в контейнер
|
||||||
* @param {Game} game экземпляр класса игра
|
* @param {Game} game экземпляр класса игра
|
||||||
@@ -62,6 +79,7 @@ export function createGameView(game, container) {
|
|||||||
ball,
|
ball,
|
||||||
paddle,
|
paddle,
|
||||||
bricks,
|
bricks,
|
||||||
|
perks: new Map(),
|
||||||
};
|
};
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error(err);
|
console.error(err);
|
||||||
@@ -69,6 +87,30 @@ export function createGameView(game, container) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Создает отображения для новых бонусов и удаляет отображения исчезнувших
|
||||||
|
* @param {object} views объект с визуальными отображениями сущностей игры
|
||||||
|
* @param {Game} game экземпляр класса игра
|
||||||
|
* @param {Container} container экземпляр класса контейнер Pixi.js
|
||||||
|
*/
|
||||||
|
export function managePerkViewsLifetime(views, game, container) {
|
||||||
|
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);
|
||||||
|
container.addChild(perkView);
|
||||||
|
views.perks.set(perk, perkView);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Синхронизирует отображение сущностей Pixi.js с логикой игры (координаты и тд.)
|
* Синхронизирует отображение сущностей Pixi.js с логикой игры (координаты и тд.)
|
||||||
* @param {object} views объект с визуальными отображениями сущностей игры
|
* @param {object} views объект с визуальными отображениями сущностей игры
|
||||||
@@ -93,6 +135,12 @@ export function syncronizeViewsWithGame(views, game) {
|
|||||||
brickView.y = brick.y;
|
brickView.y = brick.y;
|
||||||
brickView.visible = brick.alive;
|
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) {
|
} catch (err) {
|
||||||
console.error(err);
|
console.error(err);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user