Найти тему

Unity 2D. Ruby's Adventure. Part 17

Побродив по пустому миру, мы понимаем, что в игре не хватает других персонажей. Давайте добавим врагов, потому что враги — это своего рода движущаяся зона повреждения.

Для начала скачайте и импортируйте в проект спрайт врага-робота:

Bot.png

Настройте параметр Pixel Per Unit у спрайта, чтобы сделать размеры робота пропорционально главному герою. Не перенести точку Pivot вниз по центру:

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

Поскольку враги должны двигаться, сталкиваться с вашим главным героем и окружающей средой, ему нужны компоненты Rigidbody2D и BoxCollider2D.

Не забудьте установить шкалу гравитации на 0 и ограничить вращение, как вы делали с главным героем.

Создание врага
Создание врага

Также создайте скрипт EnemyController и откройте его в редакторе кода.

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

Таким образом:

  • нужно получить Rigidbody2D в переменной вашего скрипта и использовать MovePosition для перемещения игрового объекта бота. Не забудьте сделать это в FixedUpdate.
  • скорость перемещения лучше сделать через публичную переменную, чтобы можно было изменять ее непосредственно в редакторе.

Проверьте скрипт:

Скрипт врага
Скрипт врага

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

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

public bool vertical;

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

Согласно этому флажку перемещение теперь будет реализовано следующим образом:

if (vertical)
{
position.y = position.y + Time.deltaTime * speed;
}
else
{
position.x = position.x + Time.deltaTime * speed;
}

Мы воспользуемся составным условным оператором. Ключевое слово else означает «иначе». Когда у нас два варианта развития события, а в данном случае у нас либо горизонтальное, либо вертикальное движение, то очень удобно использовать такой условный оператор.

Получается, если движение нужно осуществлять по вертикали, то изменяем позицию по оси Y, иначе изменяем позицию по оси X.

Проверьте скрипт:

Скрипт бота
Скрипт бота

Сейчас наш враг просто бесконечно идет в одном направлении и в конце концов пропадает с экрана. Гораздо интереснее, если он будет идти какое-то время вправо, потом разворачивать и идти влево. И таким образом бесконечно перемещаться туда сюда, например, охраняя какой-то важный проход.

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

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

Любой таймер организовывается одинаково. Необходимо три переменные:

  • Публичная переменная, которая отвечает за величину периода, в течение которого будет происходить какое-то действие, в нашем случае движение в одном направлении - public float changeTime = 3.0f;
  • Непубличная переменная, которая будет хранить текущее значение таймера, то есть сколько на данный момент осталось до смены направления движения - float timer;
  • Непубличная переменная, хранящая текущее состояние объекта, в нашем случае направление движения. Эта переменная чаще всего бывает логического типа, потому что так удобнее хранить состояние объекта. Но в нашем случае мы используем целое число, которое будет означать направление оси. Если переменная будет равна 1, то перемещение будет в положительном направлении оси X (вправо) или Y (вверх). Если переменная будет равна -1, то перемещение будет в отрицательном направлении оси X(влево) или Y(вниз) - int direction = 1;

Итак, мы завели три новых переменных:

public float PeriodDirection = 3.0f;

float timer;

int direction = 1;

При старте игры установим таймер на значение периода:

timer = changeTime;

Теперь в методе Update нам нужно вычитать из таймера время, которое прошло с предыдущего кадра

timer = timer - Time.deltaTime;

И если таймер истек, то изменить направление движение на противоположное (с положительного на отрицательное или наоборот) и опять завести таймер:

if (timer < 0)
{
direction = -direction;
timer = changeTime;
}

И последнее изменение - это при изменении позиции нужно учитывать направление. За счет того, что мы храним направление в целой переменной, мы можем просто умножить на нее дельту перемещения. Если направление имеет положительное значение, то позиция будет увеличиваться, а если отрицательное, то уменьшаться:

position.y = position.y + Time.deltaTime * speed * direction;

position.x = position.x + Time.deltaTime * speed * direction;

Проверьте скрипт:

Скрипт бота
Скрипт бота
Скрипт бота
Скрипт бота

Сохраните скрипт и проверьте два разных варианта работы бота. Не забудьте также сохранить изменения в проекте.

Теперь у нас есть движущийся враг! Давайте заставим его наносить урон Ruby при столкновении.

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

Для обычных коллайдеров, без флажка Is Trigger, в Unity есть в второй набор функций!

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

Давайте обобщим правило вызова этих двух методов. При столкновении дву объектов, один из которых обладает компонентом Rigidbody2D вызывается:

  • метод OnCollisionEnter2D, если оба коллайдера этих двух объектов столкновения обычные, то есть не имеют свойства триггера;
  • метод OnTriggerEnter2D, если хотя бы один или оба коллайдера этих двух объектов столкновения имеют флажок Is Trigger.

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

RubyController player = collision.gameObject.GetComponent<RubyController>();
if (player != null)
{
player.ChangeHealth(-1);
}

Обратите внимание, что обращение к скрипту объекта столкновения здесь происходит немного по-другому:

collision.gameObject.GetComponent

Это потому, что тип параметра здесь — Collision2D , а не Collider2D, как в методе OnTriggerEnter2D. Collision2D не имеет функции GetComponent , но содержит много данных о столкновении, например gameObject, с которым столкнулся враг.

Проверьте скрипт, сохраните его и запустите игру для тестирования:

Скрипт бота
Скрипт бота

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

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

Сохраните изменения в проекте.