Меню

Полоска здоровья над головой

Как разные игры отнимают здоровье персонажа. «Полоска Жизни».

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

Такая система применяется и в серии Uncharted , и в серии Tomb Raider , и в серии Call of Duty . У всех них может быть разный геймплей, но здоровье пополняется одинаковым образом. И в этом нет ничего плохого. Менеджмент аптечек и забота о каждой полученной пуле только замедлила бы геймплей, заставляя игроков тратить на ненужные действия лишние минуты.

Однако не каждой игре подходит подобная система. Возьмите, например, серию Dark Souls . Если бы здоровье там восстанавливалось автоматически, весь смысл игры потерялся бы. Жадные поиски лечащих предметов в этой кровожадной игре как раз и даёт ей этот неуловимый шарм.

Но что насчет шутеров? В них есть тоже много примеров, когда здоровье пополняется не автоматически. Самый яркий пример такой игры – Doom 2016 . Для игры этих годов полоска здоровья выглядит крайне архаичной, но она, тем не менее, заставляет игроков больше наслаждаться игрой и даже побуждает их пробовать новые эксперименты с механиками.

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

Тем не менее, более казуальным игрокам нравится система автолечения. Откуда же она появилась? Впервые автоматическое исцеление после полученных ран появилось в игре на PS2 под названием The Getaway . Чтобы восстановить здоровье, персонажу нужно было облокотиться об стену и находиться в этом состоянии пару секунд.

Популярной игрой, в которой впервые появилось авто восстановление, стала игра Halo . Однако там восстанавливалось не здоровье, а энергетический щит, выступающий как здоровье. Само здоровье тоже присутствовало, но оно уменьшалось и само не восстанавливалось, когда враги пробивали щит.

Настоящим популяризатором этой системы стала игра Call of Duty , в которой впервые нужно было выйти из зоны поражения противника на несколько секунд, чтобы здоровье автоматически восстановилось.

Чем же ещё полезно автолечение, помимо большего оказуаливания игры и ускорения геймплея?

С этой системой игрок будет чувствовать себя непробиваемым «Рэмбо», которому нужно всего лишь отлежаться пару секунд, чтобы пойти дальше месить врагов. Это же и помогает относиться к каждому врагу одинаково. Не нужно как-то особенно готовиться к сражению с каждым новым противником, потому что вы всегда будете его встречать во всеоружии и с полным здоровьем.

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

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

Вывод

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

Буду весьма признателен за лайк и подписку на данный блог. Здесь ещё будет много интересного контента на тему игровой индустрии, а ваша подписка будет меня мотивировать.

Читайте также:  Открытки с пожеланием здоровья выздоравливай

Источник

Unity: отрисовываем множество полосок здоровья за один drawcall

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

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

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

Метод, к которому я в результате пришёл, немного отличается от всего того, что я видел у других, и не использует вообще никаких классов UI (в том числе и Canvas), поэтому я решил задокументировать его для общества. А для тех, кто хочет изучить исходный код, я выложил его на Github.

Почему бы не использовать Canvas?

По одному Canvas для каждого врага — это очевидно плохое решение, но я мог использовать общий Canvas для всех врагов; единственный Canvas тоже бы привёл к батчингу вызовов отрисовки.

Однако мне не нравится связанный с таким подходом объём выполняемой в каждом кадре работы. Если вы используете Canvas, то в каждом кадре придётся выполнять следующие операции:

  • Определять, какие из врагов находятся на экране, и выделять каждому из них из пула полоску UI.
  • Проецировать позицию врага в камеру, чтобы расположить полоску.
  • Изменять размер «заливки» части полоски, вероятно, как Image.
  • Скорее всего изменять размер полосок в соответствии с типом врагов; например, у крупных врагов должны быть большие полоски, чтобы это не выглядело глупо.

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

Вкратце о моём решении

Краткое описание процесса моей работы:

  • Прикрепляем объекты полосок энергии к врагам в 3D.
    • Это позволяет автоматически располагать и усекать полоски.
    • Позицию/размер полоски можно настраивать в соответствии с типом врага.
    • Полоски мы направим на камеру в коде с помощью transform, который всё равно есть.
    • Шейдер гарантирует, что они всегда будут рендериться поверх всего.
  • Используем Instancing для рендеринга всех полосок за один вызов отрисовки.
  • Используем простые процедурные UV-координаты для отображения уровня заполненности полоски.

А теперь давайте рассмотрим решение подробнее.

Что такое Instancing?

В работе с графикой уже давно используется стандартная техника: несколько объектов объединяются вместе, чтобы они имели общие данные вершин и материалы и их можно было отрендерить за один вызов отрисовки. Нам нужно именно это, потому что каждый вызов отрисовки — это дополнительная нагрузка на ЦП и GPU. Вместо выполнения по одному вызову отрисовки на каждый объект мы рендерим их все одновременно и используем шейдер для добавления вариативности в каждую копию.

Можно делать это вручную, дублируя данные вершин меша X раз в одном буфере, где X — максимальное количество копий, которое может быть отрендерено, а затем использовав массив параметров шейдера для преобразования/окраски/варьирования каждой копии. Каждая копия должна хранить знание о том, каким нумерованным экземпляром она является, чтобы использовать это значение как индекс массива. Затем мы можем использовать индексированный вызов рендера, который приказывает «рендерить только до N», где N — это число экземпляров, которое на самом деле нужно в текущем кадре, меньшее, чем максимальное количество X.

В большинстве современных API код для этого уже есть, поэтому вручную это делать не требуется. Эта операция называется «Instancing»; по сути, она автоматизирует описанный выше процесс с заранее заданными ограничениями.

Движок Unity тоже поддерживает instancing, в нём есть собственный API и набор макросов шейдеров, помогающие в его реализации. Он использует определённые допущения, например, о том, что в каждом экземпляре требуется полное 3D-преобразование. Строго говоря, для 2D-полосок оно нужно не полностью — мы можем обойтись упрощениями, но поскольку они есть, мы будем использовать их. Это упростит наш шейдер, а также обеспечит возможность использования 3D-индикаторов, например, кругов или дуг.

Читайте также:  Реферат по физкультуре физкультура залог здоровья

Класс Damageable

У наших врагов будет компонент под названием Damageable , дающий им здоровье и позволяющий им получать урон от коллизий. В нашем примере он довольно прост:

Объект HealthBar: позиция/поворот

Объект полосы здоровья очень прост: по сути, это всего лишь прикреплённый к врагу Quad.

Мы используем scale этого объекта, чтобы сделать полоску длинной и тонкой, и разместим её прямо над врагом. Не беспокойтесь о её повороте, мы исправим его с помощью кода, прикреплённого к объекту в HealthBar.cs :

Этот код всегда направляет quad в сторону камеры. Мы можем выполнять изменение размера и поворота в шейдере, но я реализую их здесь по двум причинам.

Во-первых, instancing в Unity всегда использует полный transform каждого объекта, и поскольку мы всё равно передаём все данные, можно их и использовать. Во-вторых, задание масштаба/поворота здесь гарантирует, что ограничивающий параллелограмм для усечения полоски всегда будет верным. Если бы мы сделали задание размера и поворота обязанностью шейдера, то Unity мог бы усекать полоски, которые должны быть видимы, когда они находятся близко к краям экрана, потому что размер и поворот их ограничивающего параллелограмма не будут соответствовать тому, что мы собираемся рендерить. Разумеетсяя, мы могли бы реализовать свой собственный способ усечения, но обычно при возможности лучше использовать то, что у нас есть (код Unity является нативным и имеет доступ к большему количеству пространственных данных, чем мы).

Я объясню, как рендерится полоска, после того, как мы рассмотрим шейдер.

Шейдер HealthBar

В этой версии мы создадим простую классическую красно-зелёную полоску.

Я используют текстуру размером 2×1, с одним зелёным пикселем слева и одним красным справа. Естественно, я отключил mipmapping, фильтрацию и сжатие, а для параметра addressing mode задал значение Clamp — это значит, что пиксели нашей полоски всегда будут идеально зелёными или красными, а не растекутся по краям. Это позволит нам изменять в шейдере координаты текстуры для сдвига линии, разделяющей красный и зелёный пиксели вниз и вверх по полоске.

(Так как здесь всего два цвета, я мог просто использовать в шейдере функцию step для возврата в точке одного или другого. Однако в этом методе удобно то, что при желании можно использовать более сложную текстуру, и это будет работать аналогично, пока переход находится в середине текстуры.)

Для начала мы объявим необходимые нам свойства:

_MainTex — это красно-зелёная текстура, а _Fill — значение от 0 до 1, где 1 — это полное здоровье.

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

Следующая часть — это сам код шейдера. Мы пишем шейдер без освещения (unlit), поэтому нам не нужно беспокоиться об интеграции с различными моделями поверхностных шейдеров Unity, это простая пара вершинного/фрагментного шейдеров. Для начала напишем бутстреппинг:

По большей мере это стандартный bootstrap, за исключением #pragma multi_compile_instancing , которая сообщает компилятору Unity, что нужно компилировать для Instancing.

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

Также нам нужно задать, что именно будет находиться в данных экземляров, кроме того, что за нас обрабатывает Unity (transform):

Так мы сообщаем, что Unity должен создать буфер под названием «Props» для хранения данных каждого экземпляра, и внутри него мы будем использовать по одному float на экземпляр для свойства под названием _Fill .

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

Читайте также:  Фреоны чем вредны для здоровья человека

Наш вершинный шейдер почти полностью выполняет стандартную работу, потому что размер, позиция и поворот и так передаются в transform. Это реализуется с помощью UnityObjectToClipPos , который автоматически использует transform каждого экземпляра. Можно представить, что без instancing это обычно было бы простым использованием одного матричного свойства. но при использовании instancing внутри движка это выглядит как массив матриц, и Unity самостоятельно выбирает матрицу, подходящую к данному экземпляру.

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

UNITY_SETUP_INSTANCE_ID и UNITY_ACCESS_INSTANCED_PROP выполняют всю магию, осуществляя доступ к нужной версии свойства _Fill из буфера констант для этого экземпляра.

Мы знаем, что в обычном состоянии UV-координаты четырёхугольника (quad) покрывают весь интервал текстуры, и что разделительная линия полоски находится посередине текстуры по горизонтали. Поэтому небольшие математические расчёты по горизонтали сдвигают полоску влево или вправо, а значение Clamp текстуры обеспечивают заполнение оставшейся части.

Фрагментный шейдер не мог быть проще, потому что вся работа уже выполнена:

Полный код шейдера с комментариями доступен в репозитории GitHub.

Материал Healthbar

Дальше всё просто — нам всего лишь нужно назначить нашей полоске материал, который использует этот шейдер. Больше почти ничего делать не нужно, достаточно только выбрать нужный шейдер в верхней части, назначить красно-зелёную текстуру, и, что самое важное, поставить флажок «Enable GPU Instancing».

Обновление свойства HealthBar Fill

Итак, у нас есть объект полоски здоровья, шейдер и материал, который нужно рендерить, теперь нужно задать для каждого экземпляра свойство _Fill . Мы делаем это внутри HealthBar.cs следующим образом:

Мы превращаем CurrentHealth класса Damageable в значение от 0 до 1, разделив его на MaxHealth . Затем мы передаём его свойству _Fill с помощью MaterialPropertyBlock .

Если вы ещё не использовали MaterialPropertyBlock для передачи данных в шейдеры, даже без instancing, то вам нужно изучить его. Он не так хорошо объяснён в документации Unity, но является самым эффективным способом передачи данных каждого объекта в шейдеры.

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

Здесь нет почти ничего, кроме бойлерплейта для задания переменных, и код довольно скучный; подробности см. в репозитории GitHub.

В репозитории GitHub есть тестовое демо, в котором куча злых синих кубиков уничтожается героическими красными сферами (ура!), получая урон, отображаемый описанными в статье полосками. Демо написано в Unity 2018.3.6f1.

Эффект от использования instancing можно наблюдать двумя способами:

Панель Stats

Нажав Play, щёлкните на кнопку Stats над панелью Game. Здесь вы можете увидеть, сколько вызовов отрисовки экономится благодаря instancing (батчингу):

Запустив игру, вы можете нажать на материал HealthBar и снять флажок с «Enable GPU Instancing», после чего число сэкономленных вызовов снизится до нуля.

Отладчик кадров

Запустив игру, перейдите в меню Window > Analysis > Frame Debugger, а затем нажмите в появившемся окне «Enable».

Слева внизу вы увидите все выполняемые операции рендеринга. Заметьте, что пока там есть множество отдельных вызовов для врагов и снарядов (при желании можно реализовать instancing и для них). Если прокрутить до самого низа, то вы увидите пункт «Draw Mesh (instanced) Healthbar».

Этот единственный вызов рендерит все полоски. Если нажать на эту операцию, а затем на операцию над ней, то вы увидите, что все полоски исчезнут, потому что они отрисовываются за один вызов. Если находясь во Frame Debugger, вы снимете у материала флажок «Enable GPU Instancing», то увидите, что одна строка превратилась в несколько, а после установки флажка — снова в одну.

Источник

Adblock
detector