Найти тему

Unity 2D. Ruby's Adventure. Part 15

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

Теперь давайте попробуем реализовать нанесение урона персонажу при нахождении в определенной зоне, используя триггеры.

Но сначала проверьте, что в методе Start текущее здоровье устанавливается в максимальное, а строка currentHealth = 1 удалена, так как теперь наша задача начинать игру с максимальным здоровьем.

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

Начнем с зоны повреждения. Найдите спрайт для зоны в папке Assets > Art > Sprites > Environment под названием Damageable и перенесите его в окно иерархии (помните, что из-за особенностей отрисовки тайлов при переносе спрайта просто на сцену он не импортируется). Обратите внимание, если нужно разместить зону урона далеко от места старта, то на время тестирования работы этой зоны можно передвинуть персонажа поближе к ней, чтобы не тратить время на перемещение персонажа до зоны во время игры.

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

Настройте параметр Order In Layer, чтобы зона не перекрывалась другими объектами, но при этом этот спрайт должен перекрывать спрайт Ruby, когда он попадет в него:

Зона урона
Зона урона

Добавьте Box Collider 2D к объекту Damageable , подкорректируйте его размер и установите флажок Is Trigger в инспекторе:

Зона урона
Зона урона

Создайте новый скрипт DamageZone для объекта Damageable:

Создание скрипта
Создание скрипта

Поскольку скрипт урона будет очень похож на скрипт объекта здоровья, давайте скопируем метод OnTriggerEnter2D из скрипта HealthCollectible и вставим его в скрипт DamageZone. Методы Start и Update нам не понадобятся, можно их удалить. Проверьте скрипт:

Скрипт DamageZone
Скрипт DamageZone

Теперь давайте этот код немного модифицируем, иначе зона урона будет работать как элемент здоровья!

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

controller.ChangeHealth(-1);

Во-вторых, давайте уберем вызов метода Destroy, так как зона урона - это постоянный объект, который не будет пропадать.

В-третьих, поскольку мы уменьшаем здоровье, нам не нужна проверка того, что текущее здоровье меньше максимального.

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

Скрипт DamageZone
Скрипт DamageZone

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

Это можно исправить, изменив имя функции с OnTriggerEnter2D на OnTriggerStay2D. Функция OnTriggerStay2D вызывается каждый кадр, пока объект с компонентом Rigidbody2D находится внутри триггера, а не только один раз, когда объект входит в триггер.

Скрипт DamageZone
Скрипт DamageZone

Запустите игру и вы увидите, что теперь Ruby получает урон с огромной скоростью, здоровье доходит до 0 за долю секунды. Это правильно, ведь в секунду перерисовывается несколько десятков кадров! И, возможно, вы также заметили, что на консоли нет никаких сообщений, когда Ruby останавливается, то есть снятие здоровья не происходит, когда Ruby стоит на месте.

Чтобы решить последнюю проблему, установите свойство Sleeping Mode на Never Sleep в компоненте Rigidbody2D у объекта Ruby:

Настройка Rigidbody2D
Настройка Rigidbody2D

Чтобы оптимизировать ресурсы, физическая система прекращает вычисление столкновения для твердого тела, когда оно перестает двигаться. Но мы хотим, чтобы вычисления происходили все время, так как зона урона должна снимать здоровье, даже если Ruby стоит на месте. Режим Never Sleep заставляет физическую систему выполнять расчеты для этого физического тела всегда.

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

  • в первый кадр зона урона снимает у персонажа здоровье;
  • затем на одну секунду персонаж становится неуязвимым и зона урона не может снять у него здоровье;
  • после этой секунды мы делаем персонажа опять уязвимым ровно на один кадр (на один вызов метода Update) и зона урона опять снимает здоровье;
  • после этого кадра мы делаем персонажа неуязвимым опять на одну секунду, чтобы зона урона не действовала на Ruby.

И так по кругу... Таким образом мы добьемся, что здоровье будет сниматься не каждый кадр, а с периодичностью в одну секунду.

Давайте реализуем эту логику в коде скрипта персонажа RubyController. Для начала добавьте три новые переменные:

  • PeriodInvincible - публичная переменная дробного типа, которая будет хранить длительность периода в секундах, когда персонаж неуязвим, то есть зона урона не может ему нанести урон. По умолчанию сделаем значение этой переменной равной одной секунде.
  • isInvincible - непубличная переменная логического типа bool (сокращение от boolean), которая позволяет хранить значения либо «true», либо «false». Это особенно полезно в операторах if. В этой переменной мы будем хранить свойство неуязвимости нашего персонажа. Если значение переменной будет равно «true», то зона урона не может снять у него здоровье. Если значение переменной равно «false», то здоровье можно снять.
  • invincibleTimer - непубличная переменная дробного типа, которая будет хранить значение в секундах, сколько времени Ruby еще будет неуязвимым, прежде чем зона урона опять снимет у него здоровье.

Попробуйте по описанию самостоятельно внести в код определение новых переменных и потом проверьте скрипт:

Скрипт персонажа
Скрипт персонажа

Теперь необходимо доработать метод ChangeHealth и добавить в него проверки:

if (amount < 0)
{
if (isInvincible)
return;

isInvincible = true;
invincibleTimer = timeInvincible;
}
currentHealth = Mathf.Clamp(currentHealth + amount, 0, maxHealth);
Debug.Log(currentHealth + "/" + maxHealth);

Разберем новый код по порядку. Наша задача вставить проверку неуязвимости только в том случае, если здоровье отнимается, поэтому первое условие if (amount < 0) позволит нам выделить только эти случаи.

Итак, если нужно отнять здоровье, то следующее условие if (isInvincible) проверяет является ли персонаж неуязвимым. Если условие выполняется, то есть истинное, то с помощью ключевого слова return выполнение метода просто прерывается. Это действительно так - если персонаж в данный момент неуязвим, то снять здоровье мы не можем и значит делать ничего не нужно.

Если условие не выполняется, то есть ложное, то работа метода не прерывается, а код выполняется дальше. А это именно тот единственный кадр, когда надо снять здоровье. Но предварительно мы сделаем персонажа неуязвимым isInvincible = true, чтобы в следующий кадр здоровье уже не снималось. И установим значение таймера invincibleTimer равным периоду неуязвимости Periodinvincible. После это изменим значение здоровья и выведем его на экран.

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

Изменения любого таймера делается ВСЕГДА только в методе Update. Это единственный метод, который вызывается каждый кадр и в нем можно отследить время, прошедшее между этими кадрами.

horizontal = Input.GetAxis("Horizontal");
vertical = Input.GetAxis("Vertical");

if (isInvincible)
{
invincibleTimer = invincibleTimer - Time.deltaTime;
if (invincibleTimer < 0)
isInvincible = false;
}

Итак, если Ruby неуязвимый, что проверяется условием if (isInvincible), значит нам нужно отслеживать время, сколько длится период его неуязвимости. Значит мы уменьшаем значение таймера на время, прошедшее между кадрами (Time.deltaTime). Как только таймер станет меньше 0, значит период неуязвимости завершен и нужно установить флаг неуязвимости персонажа в ложно значение.

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

Проверьте скрипт и сохраните его:

Скрипт персонажа
Скрипт персонажа
Скрипт персонажа
Скрипт персонажа

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