# Създаване на космическа игра, част 4: Добавяне на лазер и откриване на сблъсъци ## Предварителен тест [Предварителен тест](https://ff-quizzes.netlify.app/web/quiz/35) Помислете за момента в "Междузвездни войни", когато протонните торпеда на Люк попаднаха в отдушника на Звездата на смъртта. Точно това откриване на сблъсък промени съдбата на галактиката! В игрите откриването на сблъсъци работи по същия начин - определя кога обектите взаимодействат и какво се случва след това. В този урок ще добавите лазерни оръжия към вашата космическа игра и ще внедрите система за откриване на сблъсъци. Точно както плановиците на мисии в НАСА изчисляват траекториите на космическите кораби, за да избегнат отломки, вие ще научите как да откривате пресичания на игрови обекти. Ще разделим това на управляеми стъпки, които се надграждат една върху друга. До края ще имате функционираща бойна система, в която лазерите унищожават враговете, а сблъсъците задействат игрови събития. Тези принципи за откриване на сблъсъци се използват във всичко - от симулации на физика до интерактивни уеб интерфейси. ✅ Направете малко проучване за първата компютърна игра, която някога е била написана. Каква беше нейната функционалност? ## Откриване на сблъсъци Откриването на сблъсъци работи като сензорите за близост на лунния модул "Аполо" - постоянно проверява разстоянията и задейства предупреждения, когато обектите се приближат твърде много. В игрите тази система определя кога обектите взаимодействат и какво трябва да се случи след това. Подходът, който ще използваме, третира всеки игрови обект като правоъгълник, подобно на начина, по който системите за контрол на въздушния трафик използват опростени геометрични форми за проследяване на самолети. Този метод с правоъгълници може да изглежда прост, но е изчислително ефективен и работи добре за повечето игрови сценарии. ### Представяне на правоъгълник Всеки игрови обект се нуждае от координатни граници, подобно на начина, по който марсоходът Mars Pathfinder картографира местоположението си на повърхността на Марс. Ето как определяме тези граници: ```javascript rectFromGameObject() { return { top: this.y, left: this.x, bottom: this.y + this.height, right: this.x + this.width } } ``` **Нека разгледаме това:** - **Горен ръб**: Това е просто мястото, където вашият обект започва вертикално (неговата y позиция) - **Ляв ръб**: Където започва хоризонтално (неговата x позиция) - **Долен ръб**: Добавете височината към y позицията - сега знаете къде свършва! - **Десен ръб**: Добавете ширината към x позицията - и имате пълната граница ### Алгоритъм за пресичане Откриването на пресичане на правоъгълници използва логика, подобна на начина, по който космическият телескоп Хъбъл определя дали небесни обекти се припокриват в неговото зрително поле. Алгоритъмът проверява за разделение: ```javascript 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? Ако нито едно от тези условия не е вярно, правоъгълниците трябва да се припокриват. Този подход отразява начина, по който операторите на радар определят дали два самолета са на безопасно разстояние. ## Управление на жизнения цикъл на обектите Когато лазер удари враг, и двата обекта трябва да бъдат премахнати от играта. Въпреки това, изтриването на обекти по време на цикъл може да причини сривове - урок, научен по трудния начин в ранните компютърни системи като компютъра за управление на Аполо. Вместо това използваме подход "маркиране за изтриване", който безопасно премахва обекти между кадрите. Ето как маркираме нещо за премахване: ```javascript // Mark object for removal enemy.dead = true; ``` **Защо този подход работи:** - Маркираме обекта като "мъртъв", но не го изтриваме веднага - Това позволява текущият игрови кадър да завърши безопасно - Няма сривове от опити за използване на нещо, което вече е изтрито! След това филтрираме маркираните обекти преди следващия цикъл на рендиране: ```javascript gameObjects = gameObjects.filter(go => !go.dead); ``` **Какво прави това филтриране:** - Създава нов списък само с "живите" обекти - Изхвърля всичко, което е маркирано като мъртво - Поддържа играта да работи гладко - Предотвратява натрупването на памет от унищожени обекти ## Внедряване на механика за лазери Лазерните снаряди в игрите работят на същия принцип като фотонните торпеда в "Стар Трек" - те са отделни обекти, които се движат по прави линии, докато ударят нещо. Всяко натискане на интервал създава нов лазерен обект, който се движи по екрана. За да направим това, трябва да координираме няколко различни елемента: **Основни компоненти за внедряване:** - **Създаване** на лазерни обекти, които се появяват от позицията на героя - **Обработка** на входа от клавиатурата за задействане на създаването на лазери - **Управление** на движението и жизнения цикъл на лазерите - **Внедряване** на визуално представяне за лазерните снаряди ## Внедряване на контрол на скоростта на стрелба Неограничената скорост на стрелба би претоварила игровия двигател и би направила играта твърде лесна. Реалните оръжейни системи се сблъскват със сходни ограничения - дори фазерите на USS Enterprise се нуждаят от време за презареждане между изстрелите. Ще внедрим система за охлаждане, която предотвратява прекомерното стрелба, като същевременно поддържа отзивчиви контроли: ```javascript 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`, готови за добавяне на страхотните функции за откриване на сблъсъци. ### Структура на проекта ```bash -| assets -| enemyShip.png -| player.png -| laserRed.png -| index.html -| app.js -| package.json ``` **Разбиране на структурата на файловете:** - **Съдържа** всички изображения на спрайтове, необходими за игровите обекти - **Включва** основния HTML документ и JavaScript файл за приложението - **Осигурява** конфигурация на пакета за локален сървър за разработка ### Стартиране на сървъра за разработка Навигирайте до папката на проекта и стартирайте локалния сървър: ```bash cd your-work npm start ``` **Тази последователност от команди:** - **Променя** директорията към работната папка на проекта - **Стартира** локален HTTP сървър на `http://localhost:5000` - **Сервира** вашите игрови файлове за тестване и разработка - **Позволява** разработка в реално време с автоматично презареждане Отворете браузъра си и навигирайте до `http://localhost:5000`, за да видите текущото състояние на играта си с героя и враговете, изобразени на екрана. ### Стъпка по стъпка внедряване Подобно на систематичния подход, който НАСА използва за програмиране на космическия кораб Voyager, ще внедрим системата за откриване на сблъсъци методично, като изграждаме всеки компонент стъпка по стъпка. #### 1. Добавяне на граници за сблъсък на правоъгълници Първо, нека научим нашите игрови обекти как да описват своите граници. Добавете този метод към вашия клас `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 ); } ``` **Този алгоритъм работи чрез:** - **Тества** четири условия за разделение между правоъгълниците - **Връща** `false`, ако някое условие за разделение е вярно - **Показва** сблъсък, когато няма разделение - **Използва** логика на отрицание за ефективно тестване на пресичане #### 3. Внедряване на система за стрелба с лазери Тук става интересно! Нека настроим системата за стрелба с лазери. ##### Константи за съобщения Първо, нека дефинираме някои типове съобщения, за да могат различните части на нашата игра да комуникират помежду си: ```javascript KEY_EVENT_SPACE: "KEY_EVENT_SPACE", COLLISION_ENEMY_LASER: "COLLISION_ENEMY_LASER", COLLISION_ENEMY_HERO: "COLLISION_ENEMY_HERO", ``` **Тези константи осигуряват:** - **Стандартизират** имената на събитията в цялото приложение - **Позволяват** последователна комуникация между игровите системи - **Предотвратяват** грешки при регистрация на обработка на събития ##### Обработка на вход от клавиатурата Добавете откриване на натискане на интервал към вашия слушател на събития от клавиатурата: ```javascript } else if(evt.keyCode === 32) { eventEmitter.emit(Messages.KEY_EVENT_SPACE); } ``` **Този обработчик на вход:** - **Открива** натискания на интервал с помощта на keyCode 32 - **Изпраща** стандартизирано съобщение за събитие - **Позволява** отделена логика за стрелба ##### Настройка на слушател за събития Регистрирайте поведението на стрелба във вашата функция `initGame()`: ```javascript eventEmitter.on(Messages.KEY_EVENT_SPACE, () => { if (hero.canFire()) { hero.fire(); } }); ``` **Този слушател на събития:** - **Реагира** на събития за натискане на интервал - **Проверява** състоянието на охлаждане за стрелба - **Задейства** създаването на лазер, когато е позволено Добавете обработка на сблъсъци за взаимодействия между лазер и враг: ```javascript eventEmitter.on(Messages.COLLISION_ENEMY_LASER, (_, { first, second }) => { first.dead = true; second.dead = true; }); ``` **Този обработчик на сблъсъци:** - **Получава** данни за събитията на сблъсък с двата обекта - **Маркира** и двата обекта за премахване - **Осигурява** правилно почистване след сблъсък #### 4. Създаване на клас Laser Внедрете лазерен снаряд, който се движи нагоре и управлява своя жизнен цикъл: ```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); } } ``` **Тази имплементация на класа:** - **Разширява** GameObject, за да наследи основната функционалност - **Задава** подходящи размери за спрайта на лазера - **Създава** автоматично движение нагоре с помощта на `setInterval()` - **Обработва** самоунищожение при достигане на горния ръб на екрана - **Управлява** времето за анимация и почистване #### 5. Внедряване на система за откриване на сблъсъци Създайте цялостна функция за откриване на сблъсъци: ```javascript 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 с механика за стрелба и ограничаване на скоростта: ```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; } } ``` **Разбиране на подобрения клас Hero:** - **Инициира** таймер за охлаждане на нула (готов за стрелба) - **Създава** лазерни обекти, позиционирани над кораба на героя - **Задава** период на охлаждане, за да предотврати прекомерно стрелба - **Намалява** таймера за охлаждане с актуализации на базата на интервали - **Осигурява** проверка на състоянието на стрелба чрез метода `canFire()` ### Тестване на вашата имплементация Вашата космическа игра вече разполага с пълна система за открив --- **Отказ от отговорност**: Този документ е преведен с помощта на AI услуга за превод [Co-op Translator](https://github.com/Azure/co-op-translator). Въпреки че се стремим към точност, моля, имайте предвид, че автоматизираните преводи може да съдържат грешки или неточности. Оригиналният документ на неговия роден език трябва да се счита за авторитетен източник. За критична информация се препоръчва професионален човешки превод. Не носим отговорност за недоразумения или погрешни интерпретации, произтичащи от използването на този превод.