В предыдущих частях мы добавили сломанного робота, настроенного враждебно по отношению к Ruby, при столкновении у Ruby снимается здоровье. Теперь наша задача реализовать "стрельбу" шестеренками для персонажа, чтобы при попадании шестеренки сломанный робот становился исправным и больше не наносил вреда Ruby.
Для начала создайте игровой объект снаряда для "стрельбы". Используйте спрайт в папке Art > Sprites > VFX под названием CogBullet.
Измените для спрайта параметр Pixels Per Unit на 300 (либо на свое значение) и перетащите спрайт в окно иерархии.
Созданному игровому объекту добавьте компоненты Rigidbody2D и BoxCollider2D, чтобы шестеренка могла двигаться и врезаться в препятствия.
Значение параметра Gravity Scale у компонента Rigidbody2D установите равным 0, а также зафиксируйте вращение по оси Z.
Добавьте компонент скрипта с именем Projectile.
Вместо того, чтобы перемещать снаряд самостоятельно, каждый раз определяя новую позицию, как вы это делали для главного героя или врагов, на этот раз мы будем использовать физическую Систему .
Прикладывая силу к объекту с компонентом Rigidbody2D, нам не нужно будет заботится о задании объекту новой позиции, так как физическая система будет сама перемещать этот объект в зависимости от силы, которую мы приложили.
Для этого как обычно создадим переменную типа Rigidbody2D и в методе Start привяжем ее к компоненту Rigidbody2D:
Затем создайте функцию под названием Launch, с помощью которой мы будем запускать снаряд в нужный момент. Чтобы запустить снаряд необходимо знать направление, в котором снаряд полетит и силу, с которой этот снаряд будет запущен (быстрее или медленнее). Поэтому у метода Launch будет два входных параметра: Vector2 direction (направление) и float force (сила).
Чтобы запустить снаряд будем использовать метод AddForce, которая толкает Rigidbody с силой, равной направлению, умноженному на величину силы:
rb.AddForce(direction * force);
Когда эта сила применяется к объекту с Rigidbody2D, физический движок будет перемещать объект каждый кадр в указанном направлении.
Поскольку нам нужно зафиксировать столкновение шестеренки с роботом, вам понадобится функция OnCollisionEnter, в которой мы пока просто уничтожим объект шестеренки, а чуть позже добавим еще и починку робота.
Проверьте скрипт:
Создайте префаб из возданного объекта шестеренки (перетащите объект из окна ИЕРАРХИИ в окно проекта в папку Prefabs). И теперь удалите шестеренку со сцены, потому что при запуске игры ее там быть не должно.
Теперь, когда у yас есть префаб снаряда, нам нужно реализовать его бросок персонажем Ruby, чтобы он смог починить сломанных роботов.
Для этого в скрипте RubyController создайте публичную переменную с именем projectilePrefab типа GameObject:
public GameObject projectilePrefab;
Эта переменная появится в редакторе Unity как свойство скрипта, в которое мы можем назначить любой игровой объект, в том числе и префаб, потому что префабы - это и есть игровые объекты, которые сохранены как ассеты.
Перетащите созданный префаб снаряда в это свойство:
Теперь в этом же скрипте RubyController напишем метод Launch, который будет вызываться при нажатии клавиши стрельбы на клавиатуре.
В этом методе мы воспользуемся новым методом для создания игрового объекта прямо в коде.
Выглядеть это будет так:
GameObject projectileObject =
Instantiate(projectilePrefab, rb.position + Vector2.up * 0.5f, Quaternion.identity);
Для начала создаем переменную projectileObject, в которую сохраним объект, который создаст метод Instantiate. Этот метод принимает в качестве первого входного параметра объект, который нужно создать и создает копию этого объекта в позиции, указанной во втором входном параметре, с вращением, указанном в третьем входном параметре.
Получается, что Unity создаст объект, который является копией префаба шестеренки, и разместит его в положение чуть выше вашего Rigidbody, чтобы объект находился рядом с руками Ruby, а не с его ногами. Угол поворота шестеренки при создании задается через Quaternion.identity.
Кватернионы — это математические операторы, при помощи которых можно задавать вращение. Это достаточно сложные операторы, поэтому пока что нам достаточно знать, что Quaternion.identity означает отсутствие вращения. Поэтому объект шестеренки создастся в таком же виде, как и префаб.
После создания объекта шестеренки прямо в коде нам нужно запустить ее. Поэтому нам надо получить скрипт Projectile из этого нового объекта и вызвать метод Launch, которую мы написали ранее, с направлением, в котором смотрит ваш персонаж, и величиной силы, установленной на 300:
Projectile projectile = projectileObject.GetComponent<Projectile>(); projectile.Launch(lookDirection, 300);
Значение силы установлено очень высоким, поскольку оно выражается в единицах Ньютона. Это значение является хорошим для этой игры, но вы можете завести публичную переменную типа float и задавать силу броска через нее.
И нам нужно активировать триггер с именем Launch, чтобы сообщить аниматору о необходимости воспроизвести анимацию броска:
animator.SetTrigger("Launch");
Последнее, что нужно сделать — определить, когда игрок нажимает клавишу стрельбы, и вызвать в этот момент метод Launch. Определение нажатия клавиши осуществляется ТОЛЬКО в методе Update, он единственный вызывается каждый кадр. Для определения нажатия клавиши воспользуемся методом Input.GetKeyDown, который определяет единичное нажатие клавиши, которая указана в качестве входного параметра, в нашем случае это клавиша "С":
if(Input.GetKeyDown(KeyCode.C))
{
Launch();
}
Проверьте скрипт:
Сохраните скрипт и запустите игру. Если внимательно приглядеться, то вы увидите, что шестеренка появляется на доли секунды, а потом пропадает. А в консоли появятся ошибки:
Ошибки все одинаковые, поскольку это одна и та же ошибка, которая возникает каждый кадр. Она ссылается на 23 строку скрипта Projectile. Кликните два раза мышкой на любой строке с описанием ошибки и откроется редактор кода с курсором на той строке, где возникла ошибка (на скрине эта строка выделена):
В описании ошибки сказано:
NullReferenceException: Object reference not set to an instance of an object...
Это означает, что ссылка на объект является пустой, нулевой. Объект в этой строке всего один - это rb, компонент Rigidbody2D. Но почему он стал пустым?
Это произошло потому, что Unity запускает метод Start не прямо при создании объекта, а в следующем кадре. Для создания и запуска шестеренки мы используем следующий код:
GameObject projectileObject = Instantiate(....);
Projectile projectile = projectileObject.GetComponent<Projectile>();
projectile.Launch(lookDirection, 300);
Объект шестеренки в первой строке создался, а затем Unity пытается сразу запустить шестеренку в третьей строке. Все это происходит очень быстро и метод Start просто не успевает отработать.
Поэтому, когда вызывается Launch для шестернки, оказывается, что переменная rb еще пустая. Чтобы это исправить, переименуйте функцию void Start() в скрипте Projectile в void Awake() .
В отличие от Start, метод Awake вызывается сразу при создании объекта (когда вызывается Instantiate), поэтому rb правильно инициализируется перед вызовом Launch.
Можете запустить игру и проверить, что ошибка пропала.
Теперь давайте разберемся, куда шестеренка сразу же пропадает. Дело в том, что ваш снаряд сталкивается с Ruby. Мы создали шестеренку, при этом ее коллайдер автоматически сразу же соприкоснулся с коллайдером Ruby. Естественно сработал метод OnCollisitonEnter для шестеренки в скрипте Projectile, в котором мы вызываем Destroy и уничтожаем объект шестеренки.
Правильный способ убрать это ненужное столкновение — использовать Layers (слои), которые позволяют группировать игровые объекты вместе, чтобы их можно было фильтровать.
Нам нужно создать слой с персонажем, чтобы поместить в него Ruby, и слой со снарядом, чтобы поместить туда все наши снаряды. Затем нужно сообщить физической системе, что объекты слоев персонажа и снаряда не могут сталкиваться, поэтому она будет игнорировать все столкновения между объектами в этих слоях.
Чтобы увидеть, в каком слое находится игровой объект, щелкните раскрывающийся список «Layer» в правом верхнем углу инспектора. Все объекты располагаются по умолчанию на слое "Default" (номер слоя 0). В игре может быть до тридцати двух различных слоев.
Нам нужно создать свой собственный слой, поэтому выберите «Add Layer», чтобы открыть диспетчер слоев.
Слои с 0 по 5 уже заняты (третий слой свободен, но мы его пропустим), и вы не можете их изменить. Поэтому в поле Layer 6 введите название Character, а в Layer 7 - Projectile:
Теперь выберите объект Ruby и измените его Layer на Character. А у префаба шестеренки установите слой на Projectile.
Затем откройте меню Edit > Project Settings > Physics 2D и посмотрите на матрицу столкновений слоев в нижней части открытого окна, чтобы увидеть, какие слои с какими могут сталкиваться:
По умолчанию галочки стоят везде, поэтому объекты каждого слоя сталкиваются с объектами всех остальных слоев. Но нам нужно снять отметку на пересечении строки « Character» и столбца «Projectile», чтобы объекты этих двух слоев больше не сталкивались.
Просто закройте окно и запустите игру. Теперь шестеренка больше не сталкивается с Ruby, но будет сталкиваться с другими объектами, такими как ящики или здания.