# Створення космічної гри, частина 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). Хоча ми прагнемо до точності, будь ласка, майте на увазі, що автоматичні переклади можуть містити помилки або неточності. Оригінальний документ на його рідній мові слід вважати авторитетним джерелом. Для критичної інформації рекомендується професійний людський переклад. Ми не несемо відповідальності за будь-які непорозуміння або неправильні тлумачення, що виникають внаслідок використання цього перекладу.