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

16 KiB

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

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

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

В этом уроке вы научитесь стрелять лазерами с помощью JavaScript! Мы добавим две вещи в нашу игру:

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

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

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

Давайте будем героями вместе!

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

Как мы можем обнаружить столкновения? Нам нужно представить игровые объекты как прямоугольники, которые движутся. Почему, спросите вы? Потому что изображение, используемое для отображения игрового объекта, является прямоугольником: у него есть x, y, width и height.

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

  1. Способ получить представление прямоугольника игрового объекта, например:

    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);
    }
    

Как уничтожать объекты

Чтобы уничтожить объект в игре, нужно дать игре понять, что этот объект больше не нужно отображать в игровом цикле, который запускается через определённые интервалы. Один из способов сделать это — пометить игровой объект как мертвый, например:

// collision happened
enemy.dead = true

Затем можно исключить мертвые объекты перед перерисовкой экрана, например:

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

Как стрелять лазером

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

  1. Создать объект лазера: он появляется сверху корабля героя и начинает двигаться вверх к верхней части экрана.
  2. Привязать код к событию нажатия клавиши: нужно выбрать клавишу на клавиатуре, которая будет означать стрельбу лазером.
  3. Создать игровой объект, который выглядит как лазер, при нажатии клавиши.

Задержка стрельбы лазером

Лазер должен выстреливать каждый раз, когда вы нажимаете клавишу, например, пробел. Чтобы предотвратить создание слишком большого количества лазеров за короткое время, нужно это исправить. Решение — реализовать так называемую задержку, таймер, который гарантирует, что лазер может выстреливать только через определённые интервалы времени. Это можно сделать следующим образом:

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

class Weapon {
  constructor {
  }
  fire() {
    if (!this.cooldown || this.cooldown.cool) {
      // produce a laser
      this.cooldown = new Cooldown(500);
    } else {
      // do nothing - it hasn't cooled down yet.
    }
  }
}

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

Что нужно сделать

Вы возьмёте существующий код (который вы должны были очистить и реорганизовать) из предыдущего урока и расширите его. Либо начните с кода из части II, либо используйте код из Part III- starter.

совет: лазер, с которым вы будете работать, уже находится в вашей папке с ресурсами и упоминается в вашем коде.

  • Добавьте обнаружение столкновений, когда лазер сталкивается с чем-то, должны применяться следующие правила:
    1. Лазер попадает в врага: враг уничтожается, если его задевает лазер.
    2. Лазер достигает верхней части экрана: лазер уничтожается, если он достигает верхней части экрана.
    3. Столкновение врага и героя: враг и герой уничтожаются, если сталкиваются друг с другом.
    4. Враг достигает нижней части экрана: враг и герой уничтожаются, если враг достигает нижней части экрана.

Рекомендуемые шаги

Найдите файлы, которые были созданы для вас в подпапке your-work. Она должна содержать следующее:

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

Начните ваш проект в папке your_work, введя:

cd your-work
npm start

Это запустит HTTP-сервер по адресу http://localhost:5000. Откройте браузер и введите этот адрес, сейчас он должен отображать героя и всех врагов, но пока ничего не движется :).

Добавьте код

  1. Настройте представление прямоугольника для игрового объекта, чтобы обрабатывать столкновения. Следующий код позволяет получить представление прямоугольника для GameObject. Измените ваш класс 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
      );
    }
    
  3. Добавьте возможность стрельбы лазером

    1. Добавьте сообщение о событии нажатия клавиши. Клавиша пробел должна создавать лазер прямо над кораблём героя. Добавьте три константы в объект Messages:

       KEY_EVENT_SPACE: "KEY_EVENT_SPACE",
       COLLISION_ENEMY_LASER: "COLLISION_ENEMY_LASER",
       COLLISION_ENEMY_HERO: "COLLISION_ENEMY_HERO",
      
    2. Обработайте клавишу пробел. Измените функцию window.addEventListener для обработки нажатия пробела:

        } else if(evt.keyCode === 32) {
          eventEmitter.emit(Messages.KEY_EVENT_SPACE);
        }
      
    3. Добавьте слушатели событий. Измените функцию initGame(), чтобы герой мог стрелять при нажатии пробела:

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

      и добавьте новую функцию eventEmitter.on(), чтобы обеспечить поведение при столкновении врага с лазером:

      eventEmitter.on(Messages.COLLISION_ENEMY_LASER, (_, { first, second }) => {
        first.dead = true;
        second.dead = true;
      })
      
    4. Переместите объект. Убедитесь, что лазер постепенно движется к верхней части экрана. Вы создадите новый класс Laser, который расширяет GameObject, как вы делали ранее:

        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)
        }
      }
      
    5. Обработайте столкновения. Реализуйте правила столкновений для лазера. Добавьте функцию updateGameObjects(), которая проверяет столкновения объектов:

      function updateGameObjects() {
        const enemies = gameObjects.filter(go => go.type === 'Enemy');
        const lasers = gameObjects.filter((go) => go.type === "Laser");
      // laser hit something
        lasers.forEach((l) => {
          enemies.forEach((m) => {
            if (intersectRect(l.rectFromGameObject(), m.rectFromGameObject())) {
            eventEmitter.emit(Messages.COLLISION_ENEMY_LASER, {
              first: l,
              second: m,
            });
          }
         });
      });
      
        gameObjects = gameObjects.filter(go => !go.dead);
      }  
      

      Убедитесь, что вы добавили updateGameObjects() в игровой цикл в window.onload.

    6. Реализуйте задержку для лазера, чтобы он мог стрелять только через определённые интервалы времени.

      Наконец, измените класс 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;
       }
      }
      

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


🚀 Задание

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

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

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

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

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

Задание

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


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