«Танчики» на html5
Реакция на попадание снаряда
А теперь давайте посмотрим, что изменилось.
В первую очередь, конечно же, изменился метод checkTileCollision внутри класс патрона.
checkTileCollision(newX, newY, gameMap) {
const tiles = getTilesUnderEntity(newX, newY, BULLET_SIZE, BULLET_SIZE);
if (tiles.length === 0) return false;
// Находим тайлы на передней кромке по направлению движения
let frontTiles;
switch (this.direction) {
case Direction.UP:
// Минимальный Y = передняя кромка
const minY = Math.min(...tiles.map(t => t.ty));
frontTiles = tiles.filter(t => t.ty === minY);
break;
case Direction.DOWN:
// Максимальный Y = передняя кромка
const maxY = Math.max(...tiles.map(t => t.ty));
frontTiles = tiles.filter(t => t.ty === maxY);
break;
case Direction.LEFT:
// Минимальный X = передняя кромка
const minX = Math.min(...tiles.map(t => t.tx));
frontTiles = tiles.filter(t => t.tx === minX);
break;
case Direction.RIGHT:
// Максимальный X = передняя кромка
const maxX = Math.max(...tiles.map(t => t.tx));
frontTiles = tiles.filter(t => t.tx === maxX);
break;
default:
frontTiles = tiles;
}
let hasCollision = false;
// Проверяем только тайлы на передней кромке
for (const {tx, ty} of frontTiles) {
const tileId = gameMap.getTile(tx, ty);
const tileDef = TILE_DEFS[tileId];
if (!tileDef) continue;
// Пуля блокируется этим тайлом?
if (tileDef.blocksBullet) {
hasCollision = true;
// Пытаемся разрушить тайл
gameMap.damageTile(tx, ty, this.direction, this.power);
}
}
if (hasCollision) {
this.destroy();
return true;
}
return false;
}
Изменился внутренний мир объекта, отвечающего за направления
DamageState = Object.freeze({
//было
FULL: 0b11,
HALF_LEFT: 0b10,
HALF_RIGHT: 0b01,
DESTROYED: 0b00
//стало
FULL: 0,
HALF_LEFT: 1, // Осталась левая половина (вертикальный срез)
HALF_RIGHT: 2, // Осталась правая половина (вертикальный срез)
HALF_TOP: 3, // Осталась верхняя половина (горизонтальный срез)
HALF_BOTTOM: 4, // Осталась нижняя половина (горизонтальный срез)
DESTROYED: 5
});
Эта побитовая маска оказалась весьма нестабильной историей на тестах.
Ну и как говорил в прошлом выпуске, мне не нравилось, как у меня был сделан инпут, поэтому я его переделал. Он больше не принимает на вход карту и коллекцию патронов, а делает только то, что должен: отслеживает инпут. Логика обработки вынесена уже в main.js
export function getPlayerActions() {
const actions = {
movement: null, // Direction.UP/DOWN/LEFT/RIGHT или null
shoot: false
};
// Собираем ввод с клавиатуры
collectKeyboardActions(actions);
// Собираем ввод с геймпада
collectGamepadActions(actions);
return actions;
}
function update(dt) {
// 1. Получаем действия игрока из input
const actions = getPlayerActions();
// 2. Применяем действия к игроку
if (player && !player.destroyed) {
// Движение
if (actions.movement !== null) {
player.move(actions.movement);
}
// Стрельба
if (actions.shoot && player.canShoot()) {
const bullet = player.shoot();
if (bullet) {
bullets.push(bullet);
}
}
player.update(dt);
}
// 3. Обновление пуль
for (const bullet of bullets) {
bullet.update(gameMap);
}
// 4. Удаление неактивных пуль
bullets = bullets.filter(b => b.active);
// 5. Подсчёт FPS для отладки
frameCount++;
fpsTimer += dt;
if (fpsTimer >= 1000) {
fps = frameCount;
frameCount = 0;
fpsTimer = 0;
// Обновляем FPS на странице
const fpsElement = document.getElementById('fps');
if (fpsElement) {
fpsElement.textContent = fps;
}
}
}
Внутри карты поменялось получение и редактирование карты урона
getDamage(x, y) {
const key = `${x},${y}`;
// Если нет в маске — значит либо FULL, либо не разрушаемый
if (!this.damageMask.has(key)) {
return DamageState.FULL;
}
return this.damageMask.get(key);
}
setDamage(x, y, state) {
const key = `${x},${y}`;
if (state === DamageState.DESTROYED) {
this.damageMask.delete(key);
this.tileGrid[y][x] = TileType.EMPTY;
} else if (state === DamageState.FULL) {
// FULL — удаляем из маски (дефолтное состояние)
this.damageMask.delete(key);
} else {
this.damageMask.set(key, state);
}
this.dirty = true;
}
И появился большой новый метод, который и занимается непосредственно разрушением:
damageTile(x, y, direction, power) {
if (x < 0 || x >= GRID_SIZE || y < 0 || y >= GRID_SIZE) return false;
const tileId = this.getTile(x, y);
const tileDef = TILE_DEFS[tileId];
if (!tileDef) return false;
// Проверяем можно ли разрушить этот тайл
if (!canBulletDestroy(tileDef, power)) {
return false;
}
// Получаем текущее состояние повреждения
const currentDamage = this.getDamage(x, y);
// Если уже разрушен — ничего не делаем
if (currentDamage === DamageState.DESTROYED) {
return false;
}
// Определяем новое состояние в зависимости от направления пули
let newDamage;
if (currentDamage === DamageState.FULL) {
// Первое попадание — создаём половину
// Пуля убирает ту часть, в которую летит
switch (direction) {
case Direction.UP:
// Пуля летит вверх → убирает нижнюю часть → остаётся верхняя
newDamage = DamageState.HALF_TOP;
break;
case Direction.DOWN:
// Пуля летит вниз → убирает верхнюю часть → остаётся нижняя
newDamage = DamageState.HALF_BOTTOM;
break;
case Direction.LEFT:
// Пуля летит влево → убирает правую часть → остаётся левая
newDamage = DamageState.HALF_LEFT;
break;
case Direction.RIGHT:
// Пуля летит вправо → убирает левую часть → остаётся правая
newDamage = DamageState.HALF_RIGHT;
break;
}
} else {
// Второе попадание — полное разрушение
newDamage = DamageState.DESTROYED;
}
// Обновляем состояние
this.setDamage(x, y, newDamage);
return true;
}
Пока складывается впечатление, что начинаю наворачивать мрак и ужас. Поэтому со следующего выпуска начну помимо введения новых фишек смотреть, что можно упростить и улучшить по архитектуре и коду. Как я уже в этот раз сделал с системой ввода.
Комментарий