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

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

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

Контроллер пользовательского ввода и перемещение персонажа

У нас есть заготовка для рендера и отрисовка игрового уровня. Пора по нему побегать.

В инициализации появилась пара новых строчек

    // Инициализация обработчика ввода
  initInput();

Так внутри всё достаточно просто

    export function initInput() {
  // Слушатели клавиатуры
  window.addEventListener('keydown', handleKeyDown);
  window.addEventListener('keyup', handleKeyUp);

  // Слушатели геймпада
  window.addEventListener('gamepadconnected', handleGamepadConnected);
  window.addEventListener('gamepaddisconnected', handleGamepadDisconnected);

  console.log('Input handler initialized');
}

Изначально я хотел добавить поддержку геймпада только на финальном этапе разработки, но получилось так, что это делается достаточно просто, поэтому засунул сразу.

Пойдём по порядку

    function handleKeyDown(e) {
  keys[e.code] = true; // аккумулируем все нажатия, чтоб потом обработать

  // Предотвращаем прокрутку страницы стрелками и пробелом
  if (['ArrowUp', 'ArrowDown', 'ArrowLeft', 'ArrowRight', 'Space'].includes(e.code)) {
    e.preventDefault();
  }
}
function handleKeyUp(e) {
  keys[e.code] = false; // "забываем" отпущенную кнопку
}
function handleGamepadConnected(e) {
  console.log(`Gamepad connected: ${e.gamepad.id}`);
  gamepadIndex = e.gamepad.index;
}

function handleGamepadDisconnected(e) {
  console.log(`Gamepad disconnected: ${e.gamepad.id}`);
  if (gamepadIndex === e.gamepad.index) {
    gamepadIndex = null;
  }
}

В отличии от клавиатуры, которая отдаёт браузеру события о нажатии на кнопка, с геймпадом такой финт ушами не прокатывает и надо опрашивать его состояние самостоятельно (прям как на привычных движках в старые добрые времена).

Дальше заспавним наш "танк" на игровой сцене

    const spawnX = 4 * CELL_SIZE;  // 4 клетки от левого края = 64 px
  const spawnY = 24 * 8 - 32;    // Чуть выше базы = 160 px
  player = new Tank(spawnX, spawnY, TANK_PLAYER);

Класс танка достаточно большой, он ко конструкторе принимает значения координат спавна и типа танка (танк игрока или тип врага). Ему устанавливается "здоровье" (сколько раз в него надо попасть, чтобы убить), сколько активных патронов есть и т.д.

    constructor(x, y, type = TANK_PLAYER) {
    this.x = x;
    this.y = y;
    // Направление (0=вверх, 1=вправо, 2=вниз, 3=влево)
    this.direction = DIR_UP;
    this.speed = TANK_SPEED_NORMAL; // в пекселях за кадр

    // Тип танка
    this.type      = type;
    this.health    = 1;
    this.destroyed = false;

    // Флаг движения в текущем кадре
    this.moving = false; // Пока только задаётся, но нигде не используется. Есть мысли на будущее, но если не понадобится, удалю

    // Апгрейды (для игрока)
    this.bulletLevel = 0;    // 0=обычная, 1=быстрая, 2=усиленная
    this.bulletCount = 1;    // Макс. пуль на экране
    this.hasShield = false;  // Временная защита
    this.hasBoat = false;    // Движение по воде

    // Активные пули (для подсчёта лимита)
    this.activeBullets = 0;
  }

Непосредственно движение выгляди таким образом:

    move(direction) {
    // Сначала поворачиваем, если нужно
    if (this.direction !== direction) {
      this.turn(direction);
      return; // В оригинале, если повернуть, танк не двигается в этом кадре, может уберу, если плейтесты покажут необходимость 
    }

    // Вычисляем смещение по направлению
    let dx = 0;
    let dy = 0;

    switch (direction) {
      case DIR_UP:    dy = -this.speed; break;
      case DIR_DOWN:  dy = this.speed;  break;
      case DIR_LEFT:  dx = -this.speed; break;
      case DIR_RIGHT: dx = this.speed;  break;
    }

    // Новая позиция
    let newX = this.x + dx;
    let newY = this.y + dy;

    // Ограничение границами игрового поля
    newX = Math.max(0, Math.min(newX, LOGICAL_FIELD_SIZE - TANK_SIZE));
    newY = Math.max(0, Math.min(newY, LOGICAL_FIELD_SIZE - TANK_SIZE));

    // TODO: Проверка коллизий с тайлами (Этап 4)
    // TODO: Проверка коллизий с другими танками (Этап 7)

    // Применяем новую позицию
    this.x = newX;
    this.y = newY;
    this.moving = true;
  }

Ну и рисование танка на игровом поле

    render() {
    if (this.destroyed) return;

    const ctx = foregroundCtx;

    // Конвертация логических координат в физические
    const px   = GAME_FIELD_X + this.x * GAME_SCALE;
    const py   = GAME_FIELD_Y + this.y * GAME_SCALE;
    const size = TANK_SIZE * GAME_SCALE;

    // Цвет танка в зависимости от типа, пока всего два, потом будут новые типы и новые цвета
    // Основной квадрат танка
    ctx.fillStyle = this.type === TANK_PLAYER ? COLOR_TANK_PLAYER : COLOR_TANK_ENEMY;
    ctx.fillRect(px, py, size, size);

    // Индикатор направления (треугольник)
    this.renderDirectionIndicator(ctx, px, py, size); // понял, что просто квадрато недостаточно, поэтому добавил "морду"
  }

Рендер танка вызывается в методе основного рендера после очистки всего динамического слоя

    // Foreground canvas — очищаем и рисуем сущности каждый кадр
  clearForeground();

  // Рендер танка игрока
  if (player) {
    player.render();
  }
  // TODO: Здесь будет рендер врагов, пуль, эффектов

Результат всего этого безобразия

Танк ездит сквозь стены, потому что "физического" движка ещё нет. Проверка коллизий как раз на очереди.

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

Вход

Регистрация

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

Войти через Google
Порог горячего 15
  • Pepels
    Pepels

    Из широких штанин...

    +1
  • Kukabara
    Kukabara

    кто ещё в списке был?))

    +1
  • Kukabara
    Kukabara

    Я всегда с этой мыслью готовить шла что-то новое)) даже если хуйня получится, то хотя бы сожрать можно будет всё равно:D

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