logo
LIS PUBLICA
☰
  • Новое
  • Горячее
  • Сокровищница
  • Лучшее
  • Сообщества
  • Видео
  • Обсуждаемое

VariusSoft
VariusSoft Серия: Пишем танчики на JavaScript Сообщество: GameDev Опубликовано 1 неделю назад
  • [моё]
  • GameDev
  • Программирование
  • Battle City

«Танчики» на html5

Коллизии

В прошлый раз мы нарисовали наш танк и научили его передвигаться. Теперь необходимо научить его врезаться в стены/воду.

Для этого создаём сущность коллизии и после инициализации уровня храним в памяти все коллизии и в момент попытки двигаться проверяем столкновение коллизий.

Это некая базовая версия физического движка. Нужно понимать логику: то, что игрок видит на экране не имеет никакого значения. Физика просто работает и говорит можно ехать дальше или нет. В какой-то мере это почти тоже самое, что происходит, например, в Unity. Но там система сложнее и она работает скорее подобно тому, как у меня сделано в тетрисе:

  • Двигаем
  • Проверяем пересечение
  • Если оно есть, то двигаем обратно

Здесь же я просчёт сделал по принципу экстраполяции (С Юнити тоже есть такой вариант у коллизий), когда просчёт пересечений происходит как бы с взглядом в будущее, просчёт потенциального пересечение ещё до того, как оно произошло.

Давайте же посмотрим, как это выглядит.

Коллизия – это не класс как таковой, это просто набор общедоступных методов.

    /**
 * Получить список субтайлов, покрываемых прямоугольником
 *
 * @returns {Array<{tx: number, ty: number}>} Массив координат субтайлов
 */
export function getTilesUnderEntity(x, y, width, height) {
  const left = Math.floor(x / TILE_SIZE);
  const right = Math.floor((x + width - 1) / TILE_SIZE);
  const top = Math.floor(y / TILE_SIZE);
  const bottom = Math.floor((y + height - 1) / TILE_SIZE);

  const tiles = [];
  for (let ty = top; ty <= bottom; ty++) {
    for (let tx = left; tx <= right; tx++) {
      // Проверяем границы сетки
      if (tx >= 0 && tx < GRID_SIZE && ty >= 0 && ty < GRID_SIZE) {
        tiles.push({ tx, ty });
      }
    }
  }

  return tiles;
}

/**
 * Проверить, может ли танк войти на данный тайл
 *
 * @returns {boolean} true если танк может войти
 */
export function canTankEnter(tileDef, tank) {
  if (!tileDef) return true;

  // Вода: проходима только с бонусом "лодка"
  if (tileDef.id === TileType.WATER) {
    return tank && tank.hasBoat;
  }

  return !tileDef.blocksTank;
}

/**
 * Проверить коллизию танка с картой при движении в новую позицию
 * та самая экстраполяция
 *
 * @returns {boolean} true если коллизия есть (нельзя двигаться)
 */
export function checkTankMapCollision(tank, newX, newY, gameMap) {
  // Получаем субтайлы под новой позицией танка
  const tiles = getTilesUnderEntity(newX, newY, TANK_SIZE, TANK_SIZE);

  // Проверяем каждый субтайл
  for (const { tx, ty } of tiles) {
    const tileId = gameMap.getTile(tx, ty);
    const tileDef = TILE_DEFS[tileId];

    if (!canTankEnter(tileDef, tank)) {
      return true; // Есть пересечение
    }
  }

  return false; // Нет пересечений
}

/**
 * Проверить коллизию двух AABB (Axis-Aligned Bounding Box)
 *
 * @returns {boolean} true если боксы пересекаются
 */
export function checkAABBCollision(a, b) {
  return (
    a.x < b.x + b.width &&
    a.x + a.width > b.x &&
    a.y < b.y + b.height &&
    a.y + a.height > b.y
  );
}

/**
 * Проверить коллизию двух танков
 *
 * @returns {boolean} true если есть коллизия с другим танком
 */
export function checkTankTankCollision(tank, newX, newY, tanks) {
  const tankBox = {
    x: newX,
    y: newY,
    width: TANK_SIZE,
    height: TANK_SIZE
  };

  for (const other of tanks) {
    // Пропускаем себя и уничтоженных
    if (other === tank || other.destroyed) continue;

    const otherBox = other.getBounds();
    if (checkAABBCollision(tankBox, otherBox)) {
      return true; // Коллизия с другим танком
    }
  }

  return false;
}

А ещё с прошлого поста был проведён рефакторинг. У меня чесалось нёбо от неудобных некрасивых констант, с которыми были вездесущие сравнения. В целом, не магические числа – уже хорошо, но тем не менее. Енамки JS не поддерживает, поэтому делаем костыль через замороженные объекты.

    // Было
export const TILE_EMPTY = 0;
export const TILE_BRICK = 1;
export const TILE_STEEL = 2;
export const TILE_WATER = 3;
export const TILE_FOREST = 4;
export const TILE_ICE = 5;
export const TILE_BASE = 6;
// Стало
export const TileType = Object.freeze({
  EMPTY: 0,
  BRICK: 1,
  STEEL: 2,
  WATER: 3,
  FOREST: 4,
  ICE: 5,
  BASE: 6
});

Проверка самих коллизий происходит в методе передвижения танка

    newX = Math.max(0, Math.min(newX, LOGICAL_FIELD_SIZE - TANK_SIZE));
        newY = Math.max(0, Math.min(newY, LOGICAL_FIELD_SIZE - TANK_SIZE));

        if (gameMap && checkTankMapCollision(this, newX, newY, gameMap)) {
            return; // не применяем результат передвижения, просто выходим
        }

Ну и в сам метод передаётся информация о карте в инпут менеджере

    if (gp.buttons[12]?.pressed) player.move(Direction.UP, gameMap);
if (gp.buttons[13]?.pressed) player.move(Direction.DOWN, gameMap);
if (gp.buttons[14]?.pressed) player.move(Direction.LEFT, gameMap);
if (gp.buttons[15]?.pressed) player.move(Direction.RIGHT, gameMap);

А сам gameMap у нас и главного скрипта, а там он формируется как и раньше в

    loadLevel(TEST_LEVEL_1);

Результат:

Далее по плану научить танк стрелять и, в идеале, отрабатывать попадания снарядов в стены.

Всем хороших игр

Читать дальше...
10
+10 / -0
9
31
ТГ ВК
GuRu
GuRu Опубликовано 1 неделю назад

Нейронка?

-3
+0 / -3
[ Свернуть ]
VariusSoft
VariusSoft Опубликовано 1 неделю назад
Ответ на Комментарий от GuRu

Нейронка?

Я? Нет. Танчики? Тоже нет, руками пишу. Но консультируюсь с нейронкой иногда. А что?

1
+1 / -0
GuRu
GuRu Опубликовано 1 неделю назад
Ответ на Комментарий от VariusSoft

Я? Нет. Танчики? Тоже нет, руками пишу. Но консультируюсь с нейронкой иногда. А что?

У функции getTilesUnderEntity сложность O(n^2), а вызов происходит при каждом передвижении.

-3
+0 / -3
VariusSoft
VariusSoft Опубликовано 1 неделю назад
Ответ на Комментарий от GuRu

У функции getTilesUnderEntity сложность O(n^2), а вызов происходит при каждом передвижении.

Так, и?

1
+1 / -0
GuRu
GuRu Опубликовано 1 неделю назад
Ответ на Комментарий от VariusSoft

Так, и?

Тормозить будет, кушать память, и, как следствие, будет не игра, а тошнотворное тормозилово с вылетами.

-3
+0 / -3
VariusSoft
VariusSoft Опубликовано 1 неделю назад
Ответ на Комментарий от GuRu

Тормозить будет, кушать память, и, как следствие, будет не игра, а тошнотворное тормозилово с вылетами.

Два вопроса:

1. Слышал ли ты когда-нибудь про прототипирование и рефакторинг?

2. Ты собираешься запускать игру на электронном градуснике?

0
+0 / -0
GuRu
GuRu Опубликовано 1 неделю назад
Ответ на Комментарий от VariusSoft

Два вопроса:

1. Слышал ли ты когда-нибудь про прототипирование и рефакторинг?

2. Ты собираешься запускать игру на электронном градуснике?

Я слышал, что современный gamedev даже тетрис сделает невозможным к запуску на RTX 6090.

-3
+0 / -3
VariusSoft
VariusSoft Опубликовано 1 неделю назад
Ответ на Комментарий от GuRu

Я слышал, что современный gamedev даже тетрис сделает невозможным к запуску на RTX 6090.

Грустно тебе, наверное, в твоём мире живётся. В моём как-то всё лучше работает

2
+2 / -0
VariusSoft
VariusSoft Опубликовано 1 неделю назад
Ответ на Комментарий от GuRu

У функции getTilesUnderEntity сложность O(n^2), а вызов происходит при каждом передвижении.

А ещё я тут перечитал, у тебя с математикой проблемки. Там нет никакого н в квадрате...

0
+0 / -0
Войти

Вход

Регистрация

Я не помню пароль

Войти через Google
Порог горячего 13
  • Kukabara
    Kukabara

    нужно этим пользоваться:D

    +1
  • Kukabara
    Kukabara

    ебёнарот, нарушители!1111!!!1!1

    +1
  • GuRu
    GuRu

    В моём мухосранске такого нет. Да и обычные магазины половина закрылась ещё в 2025 году из-за новых налогов.

    +1
Правила сайта
Пользовательское соглашение
О ПД
Принципы самоуправления
Нашёл ошибку?
©2026 Varius Soft