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

17 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. Несмотря на наши усилия обеспечить точность, автоматические переводы могут содержать ошибки или неточности. Оригинальный документ на его родном языке следует считать авторитетным источником. Для получения критически важной информации рекомендуется профессиональный перевод человеком. Мы не несем ответственности за любые недоразумения или неправильные интерпретации, возникающие в результате использования данного перевода.