Давайте продолжим работу над нашей 3D-игрой-раннером и создадим некоторые элементы пользовательского интерфейса!

⬅️ Урок №7: Столкновение с объектами| ТОЦ | Урок №9: игра важнее логики ➡️

«В последнем эпизоде ​​мы увидели, как сталкиваться с нашими объектами на уровне (как с препятствиями, так и с бонусами), и мы подготовили очень простой набросок пользовательского интерфейса, чтобы показать текущее здоровье и значения очков в верхнем левом углу экрана.

Но это не очень красиво и не очень читабельно! Итак, сегодня мы собираемся улучшить этот пользовательский интерфейс, добавить небольшую информационную панель, которая четко сообщает игроку о текущем состоянии игры, а затем создадим вступительная панель с кнопкой «Старт», чтобы игра действительно запускалась только тогда, когда игрок нажимает на нее!

Это руководство доступно как в формате видео, так и в текстовом формате — см. ниже :)

Дизайн нашей информационной панели

Во-первых, давайте сосредоточимся на нашей информационной панели. В итоге я хочу получить простую черную область со светлой рамкой и кучу данных внутри нее: текущее пройденное расстояние, текущее здоровье (названное «целостность корабля») и текущий балл:

Есть два способа сделать это: мы могли бы сделать так, чтобы наш класс Game создавал все элементы DOM "на лету", динамически, так что index.html был бы полностью независим от игра, в которой он работает; или мы могли бы определить некоторые дополнительные элементы в нашей структуре HTML и сослаться на них в нашем экземпляре Game, чтобы обновить их позже.

Первый вариант довольно интересен для создания общего игрового фреймворка, но для первого раза это немного сложнее, так что давайте остановимся на явном наброске DOM.

Примечание: если вы хотите узнать, как динамически создавать DOM, в конце этой серии будет бонусный эпизод, посвященный преобразованию созданной вручную HTML-структуры в динамически генерируемую! :)

Добавление элементов DOM в наш файл HTML

Для начала давайте создадим все DOM-элементы, которые нам понадобятся, в нашем файле index.html. Таким образом, позже все будет готово для стилизации и обновления данных через JavaScript.

Итак, давайте откроем index.html и вернемся к нашему разделу-контейнеру «info». Мы хотим немного реорганизовать его, чтобы получить следующую информацию:

  • сначала небольшой заголовок с надписью «Капитанский журнал», чтобы сделать его более приятным для игрока, чем просто набор данных
  • затем разделитель, который через минуту будет отображаться как простая белая линия.
  • затем счет: мы хотим показать его с меткой, чтобы четко указать, что это за число, поэтому мы поместим наш блок «оценка» в другой блок; этот div будет иметь идентификатор «score-row», и мы напишем метку в нашем HTML-файле как статическое содержимое DOM.
  • мы сделаем то же самое для пройденного расстояния чуть ниже
  • затем мы добавим еще один разделитель
  • и, наконец, мы хотим показать здоровье

На данный момент мы печатаем здоровье в виде числа, точно так же, как счет или текущее расстояние. Но это не очень интуитивно для игрока: мы должны иметь возможность сразу увидеть, какова наша текущая целостность!

Наиболее удобочитаемый способ для отображения данных такого типа – это ввод типа "диапазон": при этом создается ползунок, который можно заполнить или пуст, чтобы правильно отображать оставшееся количество очков здоровья.

В целом, это дает нам следующий HTML-контент:

Для ввода здоровья давайте также установим его минимальное и максимальное значения на 0 и 100 и добавим небольшую метку над ним, чтобы сообщить игроку, что это текущая целостность корабля. Затем, чтобы убедиться, что игрок не может изменить этот ввод вручную, давайте отключим его.

Не беспокойтесь о цветах: очень скоро мы увидим, как изменить CSS, чтобы избежать серого оттенка! ;)

Обновление значений в нашем классе JavaScript

Хорошо: теперь у нас есть простая информационная панель с различными элементами DOM. Пришло время обновить данные с помощью JavaScript!

«В предыдущем эпизоде ​​мы видели, что можем получить доступ к элементам DOM с помощью document.getElementById() встроенного метода. Итак, давайте продолжим и создадим еще одну ссылку на наш див div, который мы назовем divDistance:

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

Чтобы исправить это, нам просто нужно обновить внутреннее содержимое наших разделов в функции инициализации с нашими начальными значениями:

Обратите внимание: поскольку мы изменили divHealth на ввод, нам нужно обновить его свойство value сейчас.

Это означает, что мы также должны изменить нашу функцию _checkCollisions(), чтобы она правильно обновляла этот элемент DOM:

Хорошо, мы позаботились об обновлении счета и показателей здоровья в нашем методе _checkCollisions(), поэтому единственная информация, которую нам еще нужно обновить, — это текущее пройденное расстояние. Это то, что будет изменяться в каждом кадре, поэтому мы будем использовать нашу функцию _updateInfoPanel() для ее изменения.

Чтобы узнать текущее пройденное расстояние, нам просто нужно использовать текущее положение родительского якоря объекта — ведь помните, что наш корабль не движется, поэтому мы можем использовать только положение этого объекта в качестве эталона!

Если я сохраню это, вы увидите, что теперь у меня есть хорошая информационная панель в верхнем левом углу, которая регулярно обновляется.

Но нехорошо иметь все эти десятичные знаки в значении расстояния: давайте немного отформатируем наш текст! Мы будем использовать метод toFixed(), который берет число и преобразует его в строку с заданным количеством знаков после запятой. Здесь мне не нужны десятичные дроби, поэтому я просто поставлю 0.

Таким образом, я получаю приятное округленное целое число, которое не занимает слишком много места на экране:

Стилизация элементов с помощью CSS

Теперь, когда скелет HTML и логика JS готовы, давайте стилизуем эти элементы и сделаем что-нибудь покрасивее!

Во-первых, я хочу, чтобы мой контейнер имел черный фон и границу, а также некоторый отступ, чтобы текст не был слишком близко к границе. Я также буду использовать моноширинный шрифт, чтобы получить больше «компьютерного/научно-фантастического» настроения.

Примечание: вы, конечно, можете использовать пользовательские шрифты, которые вы помещаете в папку вашего проекта, если хотите, но я просто оставлю браузер по умолчанию, чтобы упростить задачу :)

Затем давайте позаботимся о наших счетах и ​​расстояниях строки. Мы хотим, чтобы содержимое было встроенным, вместо того, чтобы значение переносилось на следующую строку. Для этого мы можем просто применить стиль display: flex к обоим нашим div... и теперь у нас есть встроенные метки с соответствующими значениями справа ;)

Конечно, мы также можем стилизовать сами блоки значений, чтобы добавить немного отступа слева, используя свойство margin-left:

Давайте продолжим с нашими разделителями!

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

Пока мы на этом, давайте также используем полужирный стиль для заголовка div.

И, наконец, последнее, но не менее важное: нам нужно стилизовать наш ввод! Стилизация входных диапазонов может быть немного запутанной, потому что вам нужно получить доступ и изменить стиль элементов, которые нельзя выбрать в DOM: эти элементы в основном охвачены самим входом, поэтому вам нужно искать в Интернете, чтобы найти ссылка на эти псевдоэлементы (например, на документы MDN).

Например, этот набор из 3 блоков стилизует мой ввод для браузера Mozilla Firefox:

Здесь я устанавливаю свойства глобального элемента ползунка (-moz-range-track), затем скрываю ручку (-moz-range-thumb) и выбираю синий цвет для части, которая в данный момент заполнена (-moz-range-progress).

Однако вы также должны помнить о стиле ввода для других браузеров, основанных на webkit (а именно, Chrome, Brave, Edge или Safari). Код немного сложнее, но в сети можно найти множество тем, объясняющих, как стилизовать ввод диапазона для всех браузеров:

Примечание. Эти свойства помечены как экспериментальные в документации MDN, поэтому они могут работать не для всех пользователей, но они поддерживаются всеми основными браузерами в их последних версиях, поэтому я будем считать, что этого достаточно для нашего проекта!

В этот момент у нас есть красивый стилизованный информационный блок в верхнем левом углу нашего экрана, который показывает все важные данные для игрока :)

Добавляем вступительную панель

Последнее, что мы хотим сделать в этом руководстве, — это создать вступительную панель, чтобы сначала игра была заморожена и чтобы она запускалась только тогда, когда мы нажимаем кнопку "Кнопка Пуск.

Начнем с создания этой панели и добавления стилей CSS. Я просто создам большую оболочку в своем index.html с кнопкой внутри. Я также покажу название нашей игры «Гиперскорость» над кнопкой:

Затем в моем файле CSS я центрирую все на панели, используя свойства display: grid и place-items: center. Кроме того, я позабочусь о том, чтобы пока это оставалось прозрачным — это временно, чтобы убедиться, что моя игра не запустится слишком рано:

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

Если я сохраню это, вы увидите, что у меня есть кнопка «Пуск» в середине экрана

… но проблема в том, что игра фактически началась в фоновом режиме: корабль уже движется, а пройденное нами расстояние продолжает увеличиваться!

Чтобы игра была заморожена изначально и запускалась только при нажатии кнопки, нам нужно добавить новую переменную к нашему экземпляру Game: логический флаг «работает». (первоначально установлено false):

Мы проверим этот флаг в функции update(): мы хотим запускать логику обновления, только если игра запущена. В противном случае мы немедленно вернемся к прерыванию, поэтому ничего не произойдет:

Если вы сохраните это, то увидите, что при перезагрузке страницы игра зависает!

Итак, тогда: мы хотим, чтобы кнопка запускала игру. Мы снова получим ссылку на наш элемент DOM, используя метод document.getElementById(), чтобы получить дескриптор нашей кнопки; затем мы хотим установить его атрибут onclick: это функция, которая запускается при нажатии кнопки. В этом обратном вызове мы хотим сделать две вещи: во-первых, мы должны установить переменную this.running в true, во-вторых, мы должны скрыть вступительную панель:

Если вы нажмете на кнопку «Старт», то увидите, что теперь игра запускается, а заставка исчезает :)

Чтобы завершить это, мы можем завершить стиль нашей вводной панели и кнопки.

Для панели мы будем использовать черный фон, чтобы сохранить эту «научно-фантастическую» атмосферу. Я также добавлю свойство z-index, чтобы заставить его быть поверх остальной части пользовательского интерфейса и маскировать все. Для кнопки я сделаю что-то очень похожее на информационный контейнер и удостоверюсь, что его фоновый цвет немного меняется, когда я навожу или нажимаю кнопку (чтобы было ясно, что она кликабельна):

Если я сохраню это, вы увидите, что моя панель теперь покрывает весь экран, и мы видим только кнопку запуска. Затем, когда я нажимаю на нее, как и раньше, панель вступления исчезает, и игра начинается!

Вывод

Конечно, мы могли бы улучшить этот пользовательский интерфейс во многих отношениях. Например, было бы неплохо, чтобы вступительная панель затухала, а не просто исчезала вот так…

Примечание: на данный момент этой базовой версии достаточно, но если вы заинтересованы во внедрении этой расширенной вводной панели, в бонусном эпизоде, посвященном пользовательскому интерфейсу в конце этой серии, вы также узнаете, как сделать так, чтобы панель исчезала: )

Но все таки! Сегодня мы увидели, как создавать HTML-элементы, ссылаться на них в JavaScript и стилизовать их с помощью CSS. Мы также немного поговорили о проблемах с разными браузерами и назначили обратный вызов кнопки динамически в нашем коде JS. Наконец, мы добавили вводную панель и позаботились о том, чтобы игра запускалась только при нажатии кнопки «Старт».

В следующем эпизоде ​​мы поработаем над частью game over: посмотрим, что нам нужно делать, если мы достигнем 0 очков здоровья, и как предложить игроку возможность мгновенного повтора…

⬅️ Урок №7: Столкновение с объектами| ТОЦ | Урок №9: игра важнее логики ➡️

Если вам понравилась эта статья, вы можете найти другие записи в блоге о технологиях, искусственном интеллекте и программировании на мой сайт :)