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/uk/6-space-game/6-end-condition
softchris 2b844a03b0
🌐 Update translations via Co-op Translator
1 month ago
..
solution 🌐 Update translations via Co-op Translator 3 months ago
your-work 🌐 Update translations via Co-op Translator 3 months ago
README.md 🌐 Update translations via Co-op Translator 1 month ago
assignment.md 🌐 Update translations via Co-op Translator 1 month ago

README.md

Створення космічної гри, частина 6: завершення та перезапуск

Кожна чудова гра потребує чітких умов завершення та плавного механізму перезапуску. Ви створили вражаючу космічну гру з рухом, боєм та підрахунком очок тепер час додати фінальні елементи, які зроблять її завершеною.

Ваша гра наразі працює безкінечно, як зонди Voyager, які NASA запустила у 1977 році вони досі мандрують космосом десятиліття потому. Хоча це чудово для космічних досліджень, ігри потребують визначених кінцевих точок, щоб створити задовільний досвід.

Сьогодні ми реалізуємо правильні умови перемоги/поразки та систему перезапуску. До кінця цього уроку у вас буде відшліфована гра, яку гравці зможуть завершити та переграти, як класичні аркадні ігри, що визначили цей жанр.

Тест перед лекцією

Тест перед лекцією

Розуміння умов завершення гри

Коли ваша гра має завершуватися? Це фундаментальне питання формувало дизайн ігор ще з часів ранньої аркадної ери. Pac-Man завершується, коли вас ловлять привиди або ви очищаєте всі точки, а Space Invaders завершується, коли прибульці досягають низу або ви знищуєте їх усіх.

Як творець гри, ви визначаєте умови перемоги та поразки. Для нашої космічної гри ось перевірені підходи, які створюють захоплюючий геймплей:

  • Знищено N ворожих кораблів: Це досить поширено, якщо ви розділяєте гру на різні рівні, де потрібно знищити N ворожих кораблів, щоб завершити рівень.
  • Ваш корабель знищено: Є ігри, де ви програєте, якщо ваш корабель знищено. Інший поширений підхід концепція життів. Кожного разу, коли ваш корабель знищено, віднімається одне життя. Коли всі життя втрачені, ви програєте.
  • Зібрано N очок: Ще одна поширена умова завершення це збір очок. Як ви отримуєте очки залежить від вас, але часто очки присвоюються за різні дії, як-от знищення ворожого корабля або збір предметів, які випадають при їх знищенні.
  • Завершення рівня: Це може включати кілька умов, таких як знищення X ворожих кораблів, збір Y очок або, можливо, збір конкретного предмета.

Реалізація функціоналу перезапуску гри

Хороші ігри заохочують до повторного проходження через плавні механізми перезапуску. Коли гравці завершують гру (або зазнають поразки), вони часто хочуть спробувати знову негайно щоб побити свій результат або покращити свої навички.

Tetris ідеально демонструє це: коли ваші блоки досягають верху, ви можете миттєво почати нову гру без складних меню. Ми створимо схожу систему перезапуску, яка чисто скидає стан гри і швидко повертає гравців до дії.

Рефлексія: Згадайте ігри, в які ви грали. За яких умов вони завершуються, і як вас спонукають до перезапуску? Що робить досвід перезапуску плавним, а що дратівливим?

Що ви створите

Ви реалізуєте фінальні функції, які перетворять ваш проект на завершений ігровий досвід. Ці елементи відрізняють відшліфовані ігри від базових прототипів.

Ось що ми додаємо сьогодні:

  1. Умова перемоги: Знищіть усіх ворогів і отримайте належне святкування (ви це заслужили!)
  2. Умова поразки: Втратьте всі життя і побачте екран поразки
  3. Механізм перезапуску: Натисніть Enter, щоб одразу повернутися до гри адже однієї гри ніколи не достатньо
  4. Управління станом: Чистий аркуш кожного разу без залишкових ворогів чи дивних глюків з попередньої гри

Початок роботи

Давайте підготуємо ваше середовище розробки. У вас мають бути всі файли вашої космічної гри з попередніх уроків.

Ваш проект має виглядати приблизно так:

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

Запустіть сервер розробки:

cd your-work
npm start

Ця команда:

  • Запускає локальний сервер на http://localhost:5000
  • Правильно обслуговує ваші файли
  • Автоматично оновлює сторінку при внесенні змін

Відкрийте http://localhost:5000 у вашому браузері і переконайтеся, що ваша гра працює. Ви маєте змогу рухатися, стріляти та взаємодіяти з ворогами. Після підтвердження ми можемо перейти до реалізації.

💡 Порада: Щоб уникнути попереджень у Visual Studio Code, оголосіть gameLoopId на початку вашого файлу як let gameLoopId;, а не всередині функції window.onload. Це відповідає сучасним практикам оголошення змінних у JavaScript.

Кроки реалізації

Крок 1: Створення функцій відстеження умов завершення

Нам потрібні функції для моніторингу, коли гра має завершитися. Як датчики на Міжнародній космічній станції, які постійно контролюють критичні системи, ці функції будуть безперервно перевіряти стан гри.

function isHeroDead() {
  return hero.life <= 0;
}

function isEnemiesDead() {
  const enemies = gameObjects.filter((go) => go.type === "Enemy" && !go.dead);
  return enemies.length === 0;
}

Що відбувається за лаштунками:

  • Перевіряє, чи у нашого героя закінчилися життя (ой!)
  • Рахує, скільки ворогів все ще живі
  • Повертає true, коли поле бою очищене від ворогів
  • Використовує просту логіку true/false для збереження простоти
  • Фільтрує всі ігрові об'єкти, щоб знайти тих, хто вижив

Крок 2: Оновлення обробників подій для умов завершення

Тепер ми зв'яжемо ці перевірки умов із системою подій гри. Кожного разу, коли відбувається зіткнення, гра буде оцінювати, чи воно викликає умову завершення. Це створює миттєвий зворотний зв'язок для критичних подій гри.

eventEmitter.on(Messages.COLLISION_ENEMY_LASER, (_, { first, second }) => {
    first.dead = true;
    second.dead = true;
    hero.incrementPoints();

    if (isEnemiesDead()) {
      eventEmitter.emit(Messages.GAME_END_WIN);
    }
});

eventEmitter.on(Messages.COLLISION_ENEMY_HERO, (_, { enemy }) => {
    enemy.dead = true;
    hero.decrementLife();
    if (isHeroDead())  {
      eventEmitter.emit(Messages.GAME_END_LOSS);
      return; // loss before victory
    }
    if (isEnemiesDead()) {
      eventEmitter.emit(Messages.GAME_END_WIN);
    }
});

eventEmitter.on(Messages.GAME_END_WIN, () => {
    endGame(true);
});
  
eventEmitter.on(Messages.GAME_END_LOSS, () => {
  endGame(false);
});

Що тут відбувається:

  • Лазер влучає у ворога: Обидва зникають, ви отримуєте очки, і ми перевіряємо, чи ви перемогли
  • Ворог влучає у вас: Ви втрачаєте життя, і ми перевіряємо, чи ви ще живі
  • Розумний порядок: Спочатку перевіряємо на поразку (ніхто не хоче одночасно виграти і програти!)
  • Миттєві реакції: Як тільки щось важливе трапляється, гра про це знає

Крок 3: Додавання нових констант повідомлень

Вам потрібно додати нові типи повідомлень до вашого об'єкта Messages. Ці константи допомагають підтримувати узгодженість і запобігати помилкам у системі подій.

GAME_END_LOSS: "GAME_END_LOSS",
GAME_END_WIN: "GAME_END_WIN",

У наведеному вище ми:

  • Додали константи для подій завершення гри для підтримки узгодженості
  • Використовували описові назви, які чітко вказують на мету події
  • Дотримувалися існуючої конвенції іменування типів повідомлень

Крок 4: Реалізація контролю перезапуску

Тепер ви додасте клавіатурні контролі, які дозволяють гравцям перезапустити гру. Клавіша Enter це природний вибір, оскільки вона зазвичай асоціюється з підтвердженням дій та початком нових ігор.

Додайте виявлення клавіші Enter до вашого існуючого слухача подій keydown:

else if(evt.key === "Enter") {
   eventEmitter.emit(Messages.KEY_EVENT_ENTER);
}

Додайте нову константу повідомлення:

KEY_EVENT_ENTER: "KEY_EVENT_ENTER",

Що потрібно знати:

  • Розширює вашу існуючу систему обробки подій клавіатури
  • Використовує клавішу Enter як тригер перезапуску для інтуїтивного досвіду користувача
  • Випромінює спеціальну подію, яку можуть слухати інші частини вашої гри
  • Підтримує той самий шаблон, що й ваші інші клавіатурні контролі

Крок 5: Створення системи відображення повідомлень

Ваша гра повинна чітко повідомляти результати гравцям. Ми створимо систему повідомлень, яка відображає стани перемоги та поразки за допомогою тексту з кольоровим кодуванням, схожого на інтерфейси терміналів ранніх комп'ютерних систем, де зелений означав успіх, а червоний помилки.

Створіть функцію displayMessage():

function displayMessage(message, color = "red") {
  ctx.font = "30px Arial";
  ctx.fillStyle = color;
  ctx.textAlign = "center";
  ctx.fillText(message, canvas.width / 2, canvas.height / 2);
}

Крок за кроком, ось що відбувається:

  • Встановлює розмір і сімейство шрифтів для чіткого, читабельного тексту
  • Застосовує параметр кольору з "червоним" за замовчуванням для попереджень
  • Центрує текст горизонтально та вертикально на полотні
  • Використовує сучасні параметри за замовчуванням у JavaScript для гнучких варіантів кольору
  • Застосовує контекст 2D полотна для прямого рендерингу тексту

Створіть функцію endGame():

function endGame(win) {
  clearInterval(gameLoopId);

  // Set a delay to ensure any pending renders complete
  setTimeout(() => {
    ctx.clearRect(0, 0, canvas.width, canvas.height);
    ctx.fillStyle = "black";
    ctx.fillRect(0, 0, canvas.width, canvas.height);
    if (win) {
      displayMessage(
        "Victory!!! Pew Pew... - Press [Enter] to start a new game Captain Pew Pew",
        "green"
      );
    } else {
      displayMessage(
        "You died !!! Press [Enter] to start a new game Captain Pew Pew"
      );
    }
  }, 200)  
}

Що робить ця функція:

  • Заморожує все на місці більше ніяких рухів кораблів чи лазерів
  • Робить невелику паузу (200 мс), щоб останній кадр завершив малювання
  • Очищає екран і фарбує його в чорний для драматичного ефекту
  • Показує різні повідомлення для переможців і переможених
  • Кодує кольори новин зелений для хороших, червоний для... ну, не дуже хороших
  • Повідомляє гравцям, як саме повернутися до гри

Крок 6: Реалізація функціоналу скидання гри

Система скидання повинна повністю очистити поточний стан гри та ініціалізувати нову сесію гри. Це забезпечує чистий старт без залишкових даних з попередньої гри.

Створіть функцію resetGame():

function resetGame() {
  if (gameLoopId) {
    clearInterval(gameLoopId);
    eventEmitter.clear();
    initGame();
    gameLoopId = setInterval(() => {
      ctx.clearRect(0, 0, canvas.width, canvas.height);
      ctx.fillStyle = "black";
      ctx.fillRect(0, 0, canvas.width, canvas.height);
      drawPoints();
      drawLife();
      updateGameObjects();
      drawGameObjects(ctx);
    }, 100);
  }
}

Розберемо кожну частину:

  • Перевіряє, чи поточний цикл гри працює перед скиданням
  • Очищає існуючий цикл гри, щоб зупинити всю поточну активність гри
  • Видаляє всі слухачі подій, щоб уникнути витоків пам'яті
  • Реініціалізує стан гри зі свіжими об'єктами та змінними
  • Запускає новий цикл гри з усіма необхідними функціями гри
  • Підтримує той самий інтервал у 100 мс для стабільної продуктивності гри

Додайте обробник подій клавіші Enter до вашої функції initGame():

eventEmitter.on(Messages.KEY_EVENT_ENTER, () => {
  resetGame();
});

Додайте метод clear() до вашого класу EventEmitter:

clear() {
  this.listeners = {};
}

Основні моменти, які слід пам'ятати:

  • З'єднує натискання клавіші Enter з функціоналом скидання гри
  • Реєструє цей слухач подій під час ініціалізації гри
  • Забезпечує чистий спосіб видалення всіх слухачів подій під час скидання
  • Запобігає витокам пам'яті, очищаючи обробники подій між іграми
  • Скидає об'єкт слухачів до порожнього стану для нової ініціалізації

Вітаємо! 🎉

👽 💥 🚀 Ви успішно створили завершену гру з нуля. Як програмісти, які створили перші відеоігри у 1970-х, ви перетворили рядки коду на інтерактивний досвід із правильними механіками гри та зворотним зв'язком для користувача. 🚀 💥 👽

Ви досягли:

  • Реалізували повні умови перемоги та поразки з зворотним зв'язком для користувача
  • Створили плавну систему перезапуску для безперервного геймплею
  • Розробили чітку візуальну комунікацію для станів гри
  • Управляли складними переходами стану гри та очищенням
  • Зібрали всі компоненти в цілісну, іграбельну гру

Виклик GitHub Copilot Agent 🚀

Використовуйте режим Agent, щоб виконати наступний виклик:

Опис: Покращіть космічну гру, реалізувавши систему прогресії рівнів із зростаючою складністю та бонусними функціями.

Завдання: Створіть багаторівневу систему космічної гри, де кожен рівень має більше ворожих кораблів із підвищеною швидкістю та здоров'ям. Додайте множник очок, який збільшується з кожним рівнем, і реалізуйте бонуси (наприклад, швидкий вогонь або щит), які випадково з'являються при знищенні ворогів. Включіть бонус за завершення рівня та відображайте поточний рівень на екрані поряд із існуючими очками та життями.

Дізнайтеся більше про режим Agent тут.

🚀 Додатковий виклик для покращення

Додайте аудіо до вашої гри: Покращіть ігровий досвід, реалізувавши звукові ефекти! Розгляньте додавання аудіо для:

  • Пострілів лазера, коли гравець стріляє
  • Знищення ворогів, коли кораблі вражені
  • Ураження героя, коли гравець отримує удари
  • Музики перемоги, коли гра виграна
  • Звуку поразки, коли гра програна

Приклад реалізації аудіо:

// Create audio objects
const laserSound = new Audio('assets/laser.wav');
const explosionSound = new Audio('assets/explosion.wav');

// Play sounds during game events
function playLaserSound() {
  laserSound.currentTime = 0; // Reset to beginning
  laserSound.play();
}

Що потрібно знати:

  • Створює об'єкти Audio для різних звукових ефектів
  • Скидає currentTime, щоб дозволити швидке відтворення звукових ефектів
  • Обробляє політики автозапуску браузера, активуючи звуки через взаємодії користувача
  • Керує гучністю та часом аудіо для кращого ігрового досвіду

💡 Ресурс для навчання: Досліджуйте цей аудіо пісочницю, щоб дізнатися більше про реалізацію аудіо в іграх на JavaScript.

Тест після лекції

Тест після лекції

Огляд та самостійне навчання

Ваше завдання створити нову пробну гру, тому досліджуйте деякі цікаві ігри, щоб побачити, яку гру ви могли б створити.

Завдання

Створіть пробну гру


Відмова від відповідальності:
Цей документ був перекладений за допомогою сервісу автоматичного перекладу Co-op Translator. Хоча ми прагнемо до точності, будь ласка, майте на увазі, що автоматичні переклади можуть містити помилки або неточності. Оригінальний документ на його рідній мові слід вважати авторитетним джерелом. Для критичної інформації рекомендується професійний людський переклад. Ми не несемо відповідальності за будь-які непорозуміння або неправильні тлумачення, що виникають внаслідок використання цього перекладу.