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

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

«Танчики» на 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;
  }

Пока складывается впечатление, что начинаю наворачивать мрак и ужас. Поэтому со следующего выпуска начну помимо введения новых фишек смотреть, что можно упростить и улучшить по архитектуре и коду. Как я уже в этот раз сделал с системой ввода.


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

Читать дальше...
8
+8 / -0
0
34
ТГ ВК
Войти

Вход

Регистрация

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

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

    хотя может и не такие, но похоже. в инете куча рецептов и все разные.

    +1
  • Alida
    Alida
    +1
  • rammdarkfunny
    rammdarkfunny

    Ну, в гречку я пихну овощей, а куру сделаю в виде котлет и суну в сметану с томатной пастой. Я ж не враг себе)))

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