Перезарядка.
Оружие обладает определенной частотой выстрелов. Без этого параметра можно было бы выпускать неограниченное количество патронов в каждом кадре.
Поэтому нам нужен простой механизм охлаждения. Если его значение превышает 0
, мы просто не можем стрелять. Мы вычитаем прошедшее время из каждого кадра.
3. Публичный метод создания атаки
Главная цель этого скрипта – активироваться через другой скрипт. Поэтому для создания снаряда мы используем публичный метод.
Создав екземпляр снаряда, мы извлекаем скрипты объекта выстрела и оверрайдим некоторые переменные.
Внимание: С помощью метода GetComponent<TypeOfComponent>()
можно создать точный компонент (а значит, и скрипт, потому что скрипт – тоже компонент) объекта. Используйте generic (<TypeOfComponent>
) для обозначения конкретного компонента, который вам нужен.
Кроме того, у нас есть GetComponents<TypeOfComponent>()
, вызывающий список вместо первого и т.д.
Использование оружия с классом игрока
Если вы запустите сейчас игру, то увидите, что ничего не изменилось. Мы создали оружие, но оно совершенно бесполезно.
В самом деле, если "WeaponScript" был бы привязан к классу, мы никогда не смогли бы использовать метод Attack(bool)
.
Давайте вернемся к нашему "PlayerScript".
В функции Update()
добавьте этот кусочек кода:
void Update()
{
// ...
// 5 - Стрельба
bool shoot = Input.GetButtonDown("Fire1");
shoot |= Input.GetButtonDown("Fire2");
// Замечание: Для пользователей Mac, Ctrl + стрелка - это плохая идея
if (shoot)
{
WeaponScript weapon = GetComponent<WeaponScript>();
if (weapon != null)
{
// ложь, так как игрок не враг
weapon.Attack(false);
}
}
// ...
}
На данном этапе неважно, поставите вы его перед или после движения.
Что мы сделали?
- Мы определяем нажатие кнопки стрельбы (
click
или ctrl
по умолчанию).
- Извлекаем скрипт объекта.
- Мы запускаем
Attack(false)
.
Button down: Вы уже наверняка заметили, что мы используем метод GetButtonDown()
для обеспечения ввода. "Down" в конце позволяет нам ввести данные при нажании кнопки и только один раз. GetButton()
будет выводить true
в каждом кадре, пока гирок не отпустит кнопку. В нашем случае нам явно необходимо поведение, обеспечиваемое методом GetButtonDown()
. Попробуйте использовать GetButton()
и почувствуйте разницу.
Запустите игру с помощью кнопки "Play". Вот что вы должны получить:
Пули летят слишком медленно? Поэкспериментируйте с префабом "Shot" чтобы выбрать ортимальное значение. Попробуйте также добавить вращение игроку: (0, 0, 45)
. Пули двигаються под углом 45 градусов, даже если вращение спрайта выстрела является некорректным – а ведь мы его не изменили.
Итак, у нас уже есть нечто похожее на шутер! Теперь вы умеете создавать оружие, которое может стрелять и уничтожить другие объекты. Давайте двигаться дальше. Мы хотим чтобы враги тоже могли стрелять.
Вражеский снаряд
Мы создадим новый снаряд с помощью этого спрайта:
Если вы так же ленивы, как я, продублируйте префаб "PlayerShot", переименуйте его в "EnemyShot1" и измените спрайт, как описано выше.
Для дублирования создайте экземпляр, перетащив его на сцену, переименовав созданный игровой объект и, наконец, сохранив его как `Prefab’.
Или можно просто продублировать Prefab
напрямую внутри папки с помощью ярлыков cmd+D
(OS X) или ctrl+D
(для Windows).
Если вы не выбираете легких путей, вы можете создать новый спрайт с параметром rigibody, коллайдером с триггером и т.д.
Правильный масштаб - (0.35, 0.35, 1)
.
Вот, что у вас должно получиться.
При нажатии "Play" произойдет выстрел, который потенциально может уничтожить врага. Это из-за свойств "ShotScript" (которые по умолчанию плохо совместимы с Poulpi).
Не изменяйте ничего. Помните наш "WeaponScript"? Он то и установит правильные значения.
У нас есть префаб "EnemyShot1". Удалите экземпляры со сцены, если они есть.
Также, как мы делали для игрока, также нам нужно добавить оружие и врагу, а потом вызывать Attack()
чтобы выстрелить. Вот, что нам надо сделать:
- Добавьте "WeaponScript" врагу.
- Перетащите префаб "EnemyShot1" в переменную "Shot Prefab" скрипта.
- Создайте новый скрипт под названием "EnemyScript". Он просто будет запускать стрельбу в каждом кадре. Что-то вроде автострельбы.
using UnityEngine;
/// <summary>
/// Enemy generic behavior
/// </summary>
public class EnemyScript : MonoBehaviour
{
private WeaponScript weapon;
void Awake()
{
// Получить оружие только один раз
weapon = GetComponent<WeaponScript>();
}
void Update()
{
// автоматическая стрельба
if (weapon != null && weapon.CanAttack)
{
weapon.Attack(true);
}
}
}
Прикрепите этот скрипт к осьминогу. У вас должно получиться следующее (заметьте, что частота стрельбы немного звеличилась до0.75
):
Замечание: Если вы модифицируете игровой объект в сцене, не забудьте сохранить все изменения в префабе
, использовав кнопку "Применить" справа сверху от панели "Инспектор".
Попробуйте сыграть и посмотреть!
Итак, мы сделали то, что хотели и теперь и по нам тоже стреляют.
Если повернуть врага, вы можете сделать его стреляющим в его слева, но, хм... спрайт повернулся вверх ногами, а нам это не нужно.
Давайте исправим это недоразумение.
Стрельба в любом направлении.
"WeaponScript" был написан особым образом: вы можете выбрать направление стрельбы, просто вращая прикрепленный игровой объект. Мы уже видели это раньше, когда вращали спрайт врага. Суть в том, чтобы создать пустой игровой объект как ребенка префаба
врага. Итак, нам нужно:
- Создать пустой игровой объект. Назовем его "WeaponObject".
- Удалим "WeaponScript", прикрепленный к префабу врага.
- Добавим "WeaponScript" к "WeaponObject" и установить свойства префба выстрела как мы это делали раньше.
- Повернем "WeaponObject" вот так
(0, 0, 180)
.
Если вы проделали это все на игровом объекте, а не на префабе, то не забудьте нажать на кнопку "Применить" для сохранения изменений. Вот, что у нас получилось:
However, we have a small change to make on the "EnemyScript" script.
В своем нынешнем состоянии вызов GetComponent<WeaponScript>()
в "EnemyScript" возвращает null. В самом деле, "WeaponScript" больше не привязан к одному объекту игры.
К счастью, в Unity также доступен метод, использующий детскую иерархию игрового объекта, который называется GetComponentInChildren<Type>()
.
Для GetComponent<>()
, GetComponentInChildren<>()
также существует в форме множественного числа: GetComponentsInChildren<Type>()
. Обратите внимание на s после "Component". Этот метод возвращает список вместо первого соответствующего компонента.
На самом деле, просто для удовольствия, мы также добавили возможность управления несколькими видами оружия. Мы просто манипулируем списком вместо одного экземпляра компонента. Взгляните на весь "EnemyScript":
using System.Collections.Generic;
using UnityEngine;
/// <summary>
/// Enemy generic behavior
/// </summary>
public class EnemyScript : MonoBehaviour
{
private WeaponScript[] weapons;
void Awake()
{
// Получить оружие только один раз
weapons = GetComponentsInChildren<WeaponScript>();
}
void Update()
{
foreach (WeaponScript weapon in weapons)
{
// автоматическая стрельба
if (weapon != null && weapon.CanAttack)
{
weapon.Attack(true);
}
}
}
}
Наконец, нужно обновить скорость выстрела путем настройки публичной пременной "MoveScript" из префаба "EnemyShot1". Скорость выстрела должна быть больше скорости движения спрута:
Мы сделали великого и ужасного осьминога. А давайте еще реализуем стрельбу в двух направлениях?
Стрельба а двух направлениях
Эта задача реализуеся всего в пару кликов. Для этого не нужны никакие скрипты:
- Добавьте другое оружие врагу (дублируя первый "WeaponObject").
- Измените угол поворота второго "WeaponObject".
Враг должен сейчас стрелять в двух направлениях. Возможный результат:
Это хороший пример правильной работы в Unity: создавая независимые скрипты вроде этого и делая публичными некоторые полезные переменные, можно значительно уменшить количество кода. Меньше кода - меньше ошибок.
Нанесение урона игроку
Наши осьминоги внушают ужас? Как бы не так! Да, они могут стрелять, но это не наносит повреждения игроку. Может, у них холостые патроны? Давайте разбираться.
Просто добавьте "HealthScript" на игрока. Убедитесь, что сняли галку с поля "IsEnemy".
Запустите игру и почувствуйте разницу:
Бонус
Вот вам некоторые советы для улучшения аспекта стрельбы в вашей будущей игре. Вы можете пропустить эту часть, если вас не интересуют подробности, относящиеся к жанру шмапа.
Солкновения игрока с врагом
Давайте посмотрим, как мы можем обработать столкновения между игроком и врагом, поскольку сейчас они сталкиваются друг с другом без последствий. Столкновение - это результат пересечения двух не-триггерных 2D коллайдеров. Нам просто нужно обрабатывать событие OnCollisionEnter2D
в PlayerScript
:
//PlayerScript.cs
//....
void OnCollisionEnter2D(Collision2D collision)
{
bool damagePlayer = false;
// Столкновение с врагом
EnemyScript enemy = collision.gameObject.GetComponent<EnemyScript>();
if (enemy != null)
{
// Смерть врага
HealthScript enemyHealth = enemy.GetComponent<HealthScript>();
if (enemyHealth != null) enemyHealth.Damage(enemyHealth.hp);
damagePlayer = true;
}
// Повреждения у игрока
if (damagePlayer)
{
HealthScript playerHealth = this.GetComponent<HealthScript>();
if (playerHealth != null) playerHealth.Damage(1);
}
}
При столкновении мы наносим урон как врагу, так и игроку благодаря наличию компонента HealthScript
. К нему привязано все, что относится к здоровью/урону.
Массив снарядов
Когда вы играете, вы можете наблюдать ва вкладке "Иерархия" (Hierarchy), что игровые объекты создаются и удаляются только через 20 секунд (если они не сталкиваются с игроком или врагом).
Если ваша цель создание огневой завесы для которой требуется МНОГО пуль, эта техника вряд ли подойдет.
Один из способов увеличить количество пуль – использовать массив. По сути, это набор пуль ограниченного размера. Когда массив заполнен, удалите старый объект и замените его на новый.
Мы не будем использовать его здесь, но в этом нет ничего сложного. Мы использовали ту же технику для скрипта рисования.
Кроме того, можно сократить время жизни пули и тогда она исчезнет быстрее.
Имейте в виду, что использование метода Instantiate
довольно затратное удовольствие. Применяйте его осторожно.
Поведение пули
В хорошем шутере должны быть запоминающиеся боевые сцены.
Некоторые библиотеки вроде BulletML значительно упрощают определение сложных и зрелищных bullet patterns.
Если вы хотите сделать полную версию игры в жанре Shoot'Em Up, ознакомьтесь с нашим плагином BulletML for Unity
Задержка выстрела
Добавьте несколько вооруженных противников в сцену и запустите игру. Вы увидете как синхронны все враги.
Можно просто добавить в оружие задержку: поставьте охлаждение на любое значение выше 0. Вы можете использовать алгоритм или просто поставить случайную цифру.
Скорость врагов также может определяться случайной величиной.
Еще раз, это зависит от вас. Все зависит исключительно от того, чего вы хотите достичь с вашим геймплеем.
В следующем уроке
Мы только что узнали, как дать оружие нашим врагам. Мы также увидели как повторно использовать некоторые скрипты для улучшения геймплея.
Не стесняйтесь добавлять еще больше врагов, оружия и экспериментировать со свойствами. В следующем уроке мы узнаем как изменить фон и сцену, чтобы создать большой по размеру уровень.