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/6-end-condition/README.md

25 KiB

Создание космической игры, часть 6: завершение и перезапуск

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

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

Сегодня мы реализуем правильные условия победы/поражения и систему перезапуска. К концу этого урока у вас будет отшлифованная игра, которую игроки смогут завершить и переигрывать снова и снова, как классические аркадные игры, которые стали легендой.

Тест перед лекцией

Тест перед лекцией

Понимание условий завершения игры

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

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

  • Уничтожено N вражеских кораблей: Это довольно распространено, если вы делите игру на уровни, где нужно уничтожить N вражеских кораблей, чтобы завершить уровень.
  • Ваш корабль уничтожен: Есть игры, где вы проигрываете, если ваш корабль уничтожен. Другой распространенный подход — это концепция жизней. Каждый раз, когда ваш корабль уничтожается, вы теряете одну жизнь. Когда все жизни исчерпаны, игра заканчивается.
  • Вы набрали N очков: Еще одно распространенное условие завершения — это сбор очков. Как вы их получаете, зависит от вас, но часто очки присваиваются за различные действия, такие как уничтожение вражеского корабля или сбор предметов, которые выпадают при его уничтожении.
  • Завершение уровня: Это может включать несколько условий, таких как уничтожение X вражеских кораблей, сбор Y очков или, возможно, сбор определенного предмета.

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

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

Тетрис прекрасно демонстрирует это: когда блоки достигают верха, вы можете мгновенно начать новую игру, не проходя через сложные меню. Мы создадим аналогичную систему перезапуска, которая чисто сбрасывает состояние игры и быстро возвращает игроков в действие.

Рефлексия: Вспомните игры, в которые вы играли. При каких условиях они заканчиваются, и как вас побуждают к перезапуску? Что делает опыт перезапуска плавным, а что — раздражающим?

Что вы создадите

Вы реализуете финальные функции, которые преобразуют ваш проект в полноценный игровой опыт. Эти элементы отличают отшлифованные игры от простых прототипов.

Вот что мы добавим сегодня:

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