You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
Web-Dev-For-Beginners/translations/ru/6-space-game/4-collision-detection/README.md

28 KiB

Создание космической игры, часть 4: добавление лазера и обнаружение столкновений

Викторина перед лекцией

Викторина перед лекцией

Вспомните момент из "Звездных войн", когда протонные торпеды Люка попали в выхлопное отверстие Звезды Смерти. Это точное обнаружение столкновения изменило судьбу галактики! В играх обнаружение столкновений работает так же — оно определяет, когда объекты взаимодействуют и что происходит дальше.

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

В конце урока у вас будет функционирующая система боя, где лазеры уничтожают врагов, а столкновения запускают игровые события. Эти же принципы обнаружения столкновений используются везде — от физических симуляций до интерактивных веб-интерфейсов.

Проведите небольшое исследование о самой первой компьютерной игре, которая была написана. Какова была ее функциональность?

Обнаружение столкновений

Обнаружение столкновений работает как датчики приближения на лунном модуле "Аполлон" — оно постоянно проверяет расстояния и выдает предупреждения, когда объекты подходят слишком близко. В играх эта система определяет, когда объекты взаимодействуют и что должно произойти дальше.

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

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

Каждому игровому объекту нужны границы координат, как марсоход Pathfinder определял свое местоположение на поверхности Марса. Вот как мы определяем эти координаты границ:

rectFromGameObject() {
  return {
    top: this.y,
    left: this.x,
    bottom: this.y + this.height,
    right: this.x + this.width
  }
}

Разберем это:

  • Верхняя граница: Это просто место, где ваш объект начинается вертикально (его позиция y)
  • Левая граница: Место, где он начинается горизонтально (его позиция x)
  • Нижняя граница: Добавьте высоту к позиции y — теперь вы знаете, где он заканчивается!
  • Правая граница: Добавьте ширину к позиции x — и у вас есть полные границы.

Алгоритм пересечения

Обнаружение пересечения прямоугольников использует логику, похожую на то, как космический телескоп Хаббл определяет, перекрываются ли небесные объекты в его поле зрения. Алгоритм проверяет разделение:

function intersectRect(r1, r2) {
  return !(r2.left > r1.right ||
    r2.right < r1.left ||
    r2.top > r1.bottom ||
    r2.bottom < r1.top);
}

Тест на разделение работает как системы радара:

  • Прямоугольник 2 полностью справа от прямоугольника 1?
  • Прямоугольник 2 полностью слева от прямоугольника 1?
  • Прямоугольник 2 полностью ниже прямоугольника 1?
  • Прямоугольник 2 полностью выше прямоугольника 1?

Если ни одно из этих условий не выполняется, прямоугольники должны перекрываться. Этот подход напоминает, как операторы радара определяют, находятся ли два самолета на безопасном расстоянии.

Управление жизненным циклом объектов

Когда лазер попадает во врага, оба объекта должны быть удалены из игры. Однако удаление объектов в середине цикла может вызвать сбои — урок, усвоенный на горьком опыте в ранних компьютерных системах, таких как компьютер управления "Аполлон". Вместо этого мы используем подход "пометить для удаления", который безопасно удаляет объекты между кадрами.

Вот как мы помечаем объект для удаления:

// Mark object for removal
enemy.dead = true;

Почему этот подход работает:

  • Мы помечаем объект как "мертвый", но не удаляем его сразу.
  • Это позволяет текущему игровому кадру завершиться безопасно.
  • Нет сбоев из-за попытки использовать то, что уже удалено!

Затем фильтруем помеченные объекты перед следующим циклом рендеринга:

gameObjects = gameObjects.filter(go => !go.dead);

Что делает это фильтрование:

  • Создает новый список только с "живыми" объектами.
  • Удаляет все, что помечено как мертвое.
  • Поддерживает стабильную работу игры.
  • Предотвращает накопление удаленных объектов в памяти.

Реализация механики лазера

Лазерные снаряды в играх работают по тому же принципу, что и фотонные торпеды в "Звездном пути" — это отдельные объекты, которые движутся по прямой линии, пока не попадут во что-то. Каждое нажатие пробела создает новый лазерный объект, который перемещается по экрану.

Чтобы это работало, нам нужно согласовать несколько разных элементов:

Основные компоненты для реализации:

  • Создание лазерных объектов, которые появляются из позиции героя.
  • Обработка ввода с клавиатуры для запуска создания лазера.
  • Управление движением и жизненным циклом лазера.
  • Реализация визуального представления лазерных снарядов.

Реализация контроля скорости стрельбы

Неограниченная скорость стрельбы перегрузила бы игровой движок и сделала бы игру слишком легкой. Реальные системы оружия сталкиваются с аналогичными ограничениями — даже фазеры USS Enterprise нуждаются во времени для перезарядки между выстрелами.

Мы реализуем систему охлаждения, которая предотвращает спам-стрельбу, сохраняя при этом отзывчивое управление:

class Cooldown {
  constructor(time) {
    this.cool = false;
    setTimeout(() => {
      this.cool = true;
    }, time);
  }
}

class Weapon {
  constructor() {
    this.cooldown = null;
  }
  
  fire() {
    if (!this.cooldown || this.cooldown.cool) {
      // Create laser projectile
      this.cooldown = new Cooldown(500);
    } else {
      // Weapon is still cooling down
    }
  }
}

Как работает система охлаждения:

  • При создании оружие становится "горячим" (еще не может стрелять).
  • После тайм-аута оно становится "холодным" (готово к стрельбе).
  • Перед стрельбой мы проверяем: "Оружие холодное?"
  • Это предотвращает спам-клики, сохраняя отзывчивость управления.

Обратитесь к уроку 1 из серии космических игр, чтобы напомнить себе о системе охлаждения.

Создание системы обнаружения столкновений

Вы расширите существующий код своей космической игры, чтобы создать систему обнаружения столкновений. Как автоматизированная система предотвращения столкновений Международной космической станции, ваша игра будет постоянно отслеживать позиции объектов и реагировать на пересечения.

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

💡 Полезный совет: Спрайт лазера уже включен в вашу папку с ресурсами и упомянут в вашем коде, готов к реализации.

Правила столкновений для реализации

Игровая механика для добавления:

  1. Лазер попадает во врага: Объект врага уничтожается при попадании лазерного снаряда.
  2. Лазер достигает границы экрана: Лазер удаляется при достижении верхней границы экрана.
  3. Столкновение врага и героя: Оба объекта уничтожаются при пересечении.
  4. Враг достигает нижней границы: Условие окончания игры, когда враги достигают нижней границы экрана.

Настройка среды разработки

Хорошие новости — мы уже подготовили большую часть основы для вас! Все ваши игровые ресурсы и базовая структура находятся в папке your-work, готовые для добавления крутых функций обнаружения столкновений.

Структура проекта

-| assets
  -| enemyShip.png
  -| player.png
  -| laserRed.png
-| index.html
-| app.js
-| package.json

Понимание структуры файлов:

  • Содержит все изображения спрайтов, необходимые для игровых объектов.
  • Включает основной HTML-документ и файл JavaScript приложения.
  • Предоставляет конфигурацию пакета для локального сервера разработки.

Запуск локального сервера разработки

Перейдите в папку проекта и запустите локальный сервер:

cd your-work
npm start

Эта последовательность команд:

  • Меняет каталог на вашу рабочую папку проекта.
  • Запускает локальный HTTP-сервер на http://localhost:5000.
  • Обслуживает ваши игровые файлы для тестирования и разработки.
  • Позволяет живую разработку с автоматической перезагрузкой.

Откройте браузер и перейдите на http://localhost:5000, чтобы увидеть текущее состояние вашей игры с героем и врагами, отображенными на экране.

Пошаговая реализация

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

1. Добавление границ прямоугольников столкновений

Сначала научим наши игровые объекты описывать свои границы. Добавьте этот метод в ваш класс GameObject:

rectFromGameObject() {
    return {
      top: this.y,
      left: this.x,
      bottom: this.y + this.height,
      right: this.x + this.width,
    };
  }

Этот метод выполняет:

  • Создает объект прямоугольника с точными координатами границ.
  • Вычисляет нижнюю и правую границы, используя позицию плюс размеры.
  • Возвращает объект, готовый для алгоритмов обнаружения столкновений.
  • Предоставляет стандартизированный интерфейс для всех игровых объектов.

2. Реализация обнаружения пересечений

Теперь создадим нашего "детектива столкновений" — функцию, которая может определить, перекрываются ли два прямоугольника:

function intersectRect(r1, r2) {
  return !(
    r2.left > r1.right ||
    r2.right < r1.left ||
    r2.top > r1.bottom ||
    r2.bottom < r1.top
  );
}

Этот алгоритм работает, проверяя:

  • Тестирует четыре условия разделения между прямоугольниками.
  • Возвращает false, если любое условие разделения истинно.
  • Указывает на столкновение, когда разделения нет.
  • Использует логику отрицания для эффективного тестирования пересечений.

3. Реализация системы стрельбы лазером

Вот где начинается самое интересное! Настроим систему стрельбы лазером.

Константы сообщений

Сначала определим типы сообщений, чтобы разные части нашей игры могли взаимодействовать друг с другом:

KEY_EVENT_SPACE: "KEY_EVENT_SPACE",
COLLISION_ENEMY_LASER: "COLLISION_ENEMY_LASER",
COLLISION_ENEMY_HERO: "COLLISION_ENEMY_HERO",

Эти константы обеспечивают:

  • Стандартизацию названий событий в приложении.
  • Обеспечение согласованной связи между игровыми системами.
  • Предотвращение опечаток при регистрации обработчиков событий.
Обработка ввода с клавиатуры

Добавьте обнаружение нажатия пробела в ваш обработчик событий клавиатуры:

} else if(evt.keyCode === 32) {
  eventEmitter.emit(Messages.KEY_EVENT_SPACE);
}

Этот обработчик ввода:

  • Обнаруживает нажатия пробела, используя keyCode 32.
  • Излучает стандартизированное сообщение события.
  • Позволяет независимую логику стрельбы.
Настройка слушателя событий

Зарегистрируйте поведение стрельбы в вашей функции initGame():

eventEmitter.on(Messages.KEY_EVENT_SPACE, () => {
 if (hero.canFire()) {
   hero.fire();
 }
});

Этот слушатель событий:

  • Реагирует на события нажатия пробела.
  • Проверяет статус охлаждения стрельбы.
  • Запускает создание лазера, если это разрешено.

Добавьте обработку столкновений для взаимодействий лазера и врага:

eventEmitter.on(Messages.COLLISION_ENEMY_LASER, (_, { first, second }) => {
  first.dead = true;
  second.dead = true;
});

Этот обработчик столкновений:

  • Получает данные события столкновения с обоими объектами.
  • Помечает оба объекта для удаления.
  • Обеспечивает правильную очистку после столкновения.

4. Создание класса Laser

Реализуйте лазерный снаряд, который движется вверх и управляет своим жизненным циклом:

class Laser extends GameObject {
  constructor(x, y) {
    super(x, y);
    this.width = 9;
    this.height = 33;
    this.type = 'Laser';
    this.img = laserImg;
    
    let id = setInterval(() => {
      if (this.y > 0) {
        this.y -= 15;
      } else {
        this.dead = true;
        clearInterval(id);
      }
    }, 100);
  }
}

Эта реализация класса:

  • Расширяет GameObject для наследования базовой функциональности.
  • Устанавливает соответствующие размеры для спрайта лазера.
  • Создает автоматическое движение вверх с использованием setInterval().
  • Обрабатывает самоуничтожение при достижении верхней границы экрана.
  • Управляет своим собственным временем анимации и очисткой.

5. Реализация системы обнаружения столкновений

Создайте комплексную функцию обнаружения столкновений:

function updateGameObjects() {
  const enemies = gameObjects.filter(go => go.type === 'Enemy');
  const lasers = gameObjects.filter(go => go.type === "Laser");
  
  // Test laser-enemy collisions
  lasers.forEach((laser) => {
    enemies.forEach((enemy) => {
      if (intersectRect(laser.rectFromGameObject(), enemy.rectFromGameObject())) {
        eventEmitter.emit(Messages.COLLISION_ENEMY_LASER, {
          first: laser,
          second: enemy,
        });
      }
    });
  });

  // Remove destroyed objects
  gameObjects = gameObjects.filter(go => !go.dead);
}

Эта система столкновений:

  • Фильтрует игровые объекты по типу для эффективного тестирования.
  • Тестирует каждый лазер против каждого врага на пересечения.
  • Излучает события столкновений при обнаружении пересечений.
  • Очищает уничтоженные объекты после обработки столкновений.

⚠️ Важно: Добавьте updateGameObjects() в ваш основной игровой цикл в window.onload, чтобы включить обнаружение столкновений.

6. Добавление системы охлаждения в класс Hero

Улучшите класс Hero, добавив механику стрельбы и ограничение скорости:

class Hero extends GameObject {
  constructor(x, y) {
    super(x, y);
    this.width = 99;
    this.height = 75;
    this.type = "Hero";
    this.speed = { x: 0, y: 0 };
    this.cooldown = 0;
  }
  
  fire() {
    gameObjects.push(new Laser(this.x + 45, this.y - 10));
    this.cooldown = 500;

    let id = setInterval(() => {
      if (this.cooldown > 0) {
        this.cooldown -= 100;
      } else {
        clearInterval(id);
      }
    }, 200);
  }
  
  canFire() {
    return this.cooldown === 0;
  }
}

Понимание улучшенного класса Hero:

  • Инициализирует таймер охлаждения на нуле (готов к стрельбе).
  • Создает лазерные объекты, расположенные над кораблем героя.
  • Устанавливает период охлаждения для предотвращения быстрой стрельбы.
  • Уменьшает таймер охлаждения с помощью обновлений на основе интервалов.
  • Предоставляет проверку статуса стрельбы через метод canFire().

Тестирование вашей реализации

Теперь ваша космическая игра оснащена полной системой обнаружения столкновений и механикой боя. 🚀 Проверьте эти новые возможности:

  • Перемещайтесь с помощью стрелок, чтобы проверить управление движением.
  • Стреляйте лазерами с помощью пробела — обратите внимание, как система охлаждения предотвращает спам-клики.
  • Наблюдайте столкновения, когда лазеры попадают во врагов, вызывая их удаление.
  • Проверьте очистку, когда уничтоженные объекты исчезают из игры.

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

Вызов GitHub Copilot Agent 🚀

Используйте режим Agent, чтобы выполнить следующий вызов:

Описание: Улучшите систему обнаружения столкновений, добавив бонусы, которые появляются случайным образом и предоставляют временные способности при сборе героем.

Задание: Создайте класс PowerUp, который расширяет GameObject, и реализуйте обнаружение столкновений между героем и бонусами. Добавьте как минимум два типа бонусов: один увеличивает скорость стрельбы (уменьшает время охлаждения), а другой создает временный щит. Включите логику появления бонусов, которая создает их в случайные интервалы и позиции.


🚀 Вызов

Добавьте взрыв! Посмотрите на игровые ресурсы в репозитории Space Art и попробуйте добавить взрыв, когда лазер попадает в пришельца.

Викторина после лекции

Викторина после лекции

Обзор и самостоятельное изучение

Экспериментируйте с интервалами в вашей игре. Что происходит, когда вы их изменяете? Прочитайте больше о событиях таймера в JavaScript.

Задание

Изучите столкновения


Отказ от ответственности:
Этот документ был переведен с использованием сервиса автоматического перевода Co-op Translator. Хотя мы стремимся к точности, пожалуйста, учитывайте, что автоматические переводы могут содержать ошибки или неточности. Оригинальный документ на его родном языке следует считать авторитетным источником. Для получения критически важной информации рекомендуется профессиональный перевод человеком. Мы не несем ответственности за любые недоразумения или неправильные интерпретации, возникающие в результате использования данного перевода.