# Створення космічної гри, частина 4: Додавання лазера та виявлення зіткнень ## Передлекційна вікторина [Передлекційна вікторина](https://ff-quizzes.netlify.app/web/quiz/35) У цьому уроці ви навчитеся стріляти лазерами за допомогою JavaScript! Ми додамо до нашої гри дві речі: - **Лазер**: цей лазер стріляє з корабля героя вертикально вгору. - **Виявлення зіткнень**: у рамках реалізації можливості *стріляти* ми також додамо кілька цікавих правил гри: - **Лазер влучає в ворога**: ворог знищується, якщо його вразив лазер. - **Лазер досягає верхньої частини екрану**: лазер знищується, якщо досягає верхньої частини екрану. - **Зіткнення ворога та героя**: ворог і герой знищуються, якщо стикаються. - **Ворог досягає нижньої частини екрану**: ворог і герой знищуються, якщо ворог досягає нижньої частини екрану. Коротко кажучи, ви — *герой* — повинні вразити всіх ворогів лазером, перш ніж вони досягнуть нижньої частини екрану. ✅ Проведіть невелике дослідження про першу комп'ютерну гру, яка була створена. Яка була її функціональність? Давайте будемо героями разом! ## Виявлення зіткнень Як ми можемо виявляти зіткнення? Нам потрібно уявляти наші ігрові об'єкти як прямокутники, що рухаються. Чому саме так? Тому що зображення, яке використовується для відображення ігрового об'єкта, є прямокутником: воно має `x`, `y`, `width` і `height`. Якщо два прямокутники, наприклад, герой і ворог, *перетинаються*, це означає зіткнення. Що має відбутися далі, залежить від правил гри. Для реалізації виявлення зіткнень вам потрібно наступне: 1. Спосіб отримати прямокутне представлення ігрового об'єкта, щось на кшталт цього: ```javascript rectFromGameObject() { return { top: this.y, left: this.x, bottom: this.y + this.height, right: this.x + this.width } } ``` 2. Функція порівняння, яка може виглядати так: ```javascript function intersectRect(r1, r2) { return !(r2.left > r1.right || r2.right < r1.left || r2.top > r1.bottom || r2.bottom < r1.top); } ``` ## Як знищувати об'єкти Щоб знищити об'єкт у грі, потрібно повідомити грі, що цей об'єкт більше не потрібно відображати в ігровому циклі, який викликається через певний інтервал. Один із способів зробити це — позначити ігровий об'єкт як *мертвий*, коли щось відбувається, наприклад: ```javascript // collision happened enemy.dead = true ``` Потім можна видалити *мертві* об'єкти перед повторним відображенням екрану, наприклад: ```javascript gameObjects = gameObject.filter(go => !go.dead); ``` ## Як стріляти лазером Стрільба лазером означає реагування на подію натискання клавіші та створення об'єкта, який рухається в певному напрямку. Для цього потрібно виконати наступні кроки: 1. **Створити об'єкт лазера**: з верхньої частини корабля героя, який після створення починає рухатися вгору до верхньої частини екрану. 2. **Прив'язати код до події натискання клавіші**: потрібно вибрати клавішу на клавіатурі, яка буде відповідати за стрільбу лазером. 3. **Створити ігровий об'єкт, який виглядає як лазер**, коли натискається клавіша. ## Затримка для лазера Лазер повинен стріляти щоразу, коли ви натискаєте клавішу, наприклад, *пробіл*. Щоб запобігти створенню занадто великої кількості лазерів за короткий час, потрібно це виправити. Виправлення полягає у впровадженні так званої *затримки* — таймера, який гарантує, що лазер можна стріляти лише через певний інтервал. Це можна реалізувати наступним чином: ```javascript 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](../../../../../../../../../your-work). > підказка: лазер, з яким ви працюватимете, вже знаходиться у вашій папці з ресурсами та підключений до вашого коду. - **Додайте виявлення зіткнень**, коли лазер стикається з чимось, повинні застосовуватися наступні правила: 1. **Лазер влучає в ворога**: ворог знищується, якщо його вразив лазер. 2. **Лазер досягає верхньої частини екрану**: лазер знищується, якщо досягає верхньої частини екрану. 3. **Зіткнення ворога та героя**: ворог і герой знищуються, якщо стикаються. 4. **Ворог досягає нижньої частини екрану**: ворог і герой знищуються, якщо ворог досягає нижньої частини екрану. ## Рекомендовані кроки Знайдіть файли, які були створені для вас у підпапці `your-work`. Вона повинна містити наступне: ```bash -| assets -| enemyShip.png -| player.png -| laserRed.png -| index.html -| app.js -| package.json ``` Запустіть ваш проєкт у папці `your_work`, ввівши: ```bash cd your-work npm start ``` Це запустить HTTP-сервер за адресою `http://localhost:5000`. Відкрийте браузер і введіть цю адресу, зараз він повинен відображати героя та всіх ворогів, але нічого ще не рухається :). ### Додайте код 1. **Налаштуйте прямокутне представлення вашого ігрового об'єкта для обробки зіткнень**. Наведений нижче код дозволяє отримати прямокутне представлення `GameObject`. Відредагуйте ваш клас GameObject, щоб розширити його: ```javascript rectFromGameObject() { return { top: this.y, left: this.x, bottom: this.y + this.height, right: this.x + this.width, }; } ``` 2. **Додайте код, який перевіряє зіткнення**. Це буде нова функція, яка перевіряє, чи перетинаються два прямокутники: ```javascript function intersectRect(r1, r2) { return !( r2.left > r1.right || r2.right < r1.left || r2.top > r1.bottom || r2.bottom < r1.top ); } ``` 3. **Додайте можливість стріляти лазером** 1. **Додайте повідомлення про подію натискання клавіші**. Клавіша *пробіл* повинна створювати лазер прямо над кораблем героя. Додайте три константи в об'єкт Messages: ```javascript KEY_EVENT_SPACE: "KEY_EVENT_SPACE", COLLISION_ENEMY_LASER: "COLLISION_ENEMY_LASER", COLLISION_ENEMY_HERO: "COLLISION_ENEMY_HERO", ``` 1. **Обробіть клавішу пробіл**. Відредагуйте функцію `window.addEventListener` для обробки натискання пробілу: ```javascript } else if(evt.keyCode === 32) { eventEmitter.emit(Messages.KEY_EVENT_SPACE); } ``` 1. **Додайте слухачі подій**. Відредагуйте функцію `initGame()`, щоб герой міг стріляти, коли натискається пробіл: ```javascript eventEmitter.on(Messages.KEY_EVENT_SPACE, () => { if (hero.canFire()) { hero.fire(); } ``` і додайте нову функцію `eventEmitter.on()`, щоб забезпечити поведінку, коли ворог стикається з лазером: ```javascript eventEmitter.on(Messages.COLLISION_ENEMY_LASER, (_, { first, second }) => { first.dead = true; second.dead = true; }) ``` 1. **Рух об'єкта**. Переконайтеся, що лазер поступово рухається до верхньої частини екрану. Ви створите новий клас Laser, який розширює `GameObject`, як ви робили раніше: ```javascript 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) } } ``` 1. **Обробіть зіткнення**. Реалізуйте правила зіткнень для лазера. Додайте функцію `updateGameObjects()`, яка перевіряє об'єкти, що стикаються: ```javascript 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`. 4. **Реалізуйте затримку** для лазера, щоб його можна було стріляти лише через певний інтервал. Нарешті, відредагуйте клас Hero, щоб він міг мати затримку: ```javascript 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](../../../../6-space-game/solution/spaceArt/readme.txt) і спробуйте додати вибух, коли лазер влучає в прибульця. ## Післялекційна вікторина [Післялекційна вікторина](https://ff-quizzes.netlify.app/web/quiz/36) ## Огляд і самостійне навчання Експериментуйте з інтервалами у вашій грі. Що відбувається, коли ви їх змінюєте? Читайте більше про [таймінгові події в JavaScript](https://www.freecodecamp.org/news/javascript-timing-events-settimeout-and-setinterval/). ## Завдання [Дослідження зіткнень](assignment.md) --- **Відмова від відповідальності**: Цей документ був перекладений за допомогою сервісу автоматичного перекладу [Co-op Translator](https://github.com/Azure/co-op-translator). Хоча ми прагнемо до точності, будь ласка, майте на увазі, що автоматичні переклади можуть містити помилки або неточності. Оригінальний документ на його рідній мові слід вважати авторитетним джерелом. Для критичної інформації рекомендується професійний людський переклад. Ми не несемо відповідальності за будь-які непорозуміння або неправильні тлумачення, що виникають внаслідок використання цього перекладу.