Это был взрыв на PAX West, но пришло время вернуться к нашим регулярным публикациям.

Наконец, вернемся к исходному коду в День 3. Я перестал создавать фон, объект игрока и возможность стрелять!

Некоторые из основных тем, о которых говорилось в сегодняшнем уроке, включают:

  • Создание рамки для удаления объектов
  • Создание врагов/препятствий

Давайте начнем!

Границы, опасности и враги

Граница

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

Чем больше вы стреляете, тем больше у вас будет. Так что дает?

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

Это проблема? Вы держите пари, что это! Чем больше экземпляров GameObject мы создадим, тем больше придется вычислять Unity, а это значит, что наша производительность пострадает!

Что было сделано для решения этой проблемы, так это создание гигантского куба, закрывающего всю сцену.

Я прикрепил скрипт к этому кубу и добавил:

using UnityEngine;
using System.Collections;
public class DestroyByBoundary : MonoBehaviour
{
    void OnTriggerExit(Collider other)
    {
        Destroy(other.gameObject);
    }
}

Для этого мы полагаемся на функцию OnTriggerExit(). Как вы могли догадаться, по названию функция вызывается, когда коллайдер покидает объект, с которым сталкивается.

Когда мы запускаем код, мы должны уничтожить () объект, в данном случае это лазер.

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

Создание опасностей

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

We:

  • Использовал предоставленную модель астероида для создания GameObject.
  • Прикрепил к нему компонент капсульного коллайдера.
  • Коллайдер максимально приближен к форме астероида.
  • Добавлен компонент Rigidbody и сделан триггер
  • Добавлен предоставленный скрипт RandomRotator к астероиду.
using UnityEngine;
using System.Collections;
public class RandomRotator : MonoBehaviour
{
    public float tumble;
    void Start ()
    {
        GetComponent<RigidBody>().angularVelocity = Random.insideUnitSphere * tumble; 
    }
}

Угловая скорость

AngularVelocity — скорость вращения объекта.

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

Мы делаем это с помощью случайной функции Unity. В частности, мы решили использовать insideUnitSphere для создания случайного вектора положения, который находится внутри игрового объекта, и умножить его на скорость, с которой мы хотим, чтобы астероид катился.

Уничтожайте астероиды при выстреле

Теперь, когда у нас есть наш первый «враг», мы хотим выстрелить и избавиться от него!

Когда наш лазер коснется астероида, ничего не произойдет, потому что и астероиды, и астероиды являются триггером, поэтому они не сталкиваются друг с другом.

Что нам нужно сделать на этом этапе, так это добавить скрипт к нашему объекту, который имеет логику, которая имеет дело с тем, что происходит, когда он сталкивается с другим объектом.

Прикрепляем этот скрипт к нашему классу астероидов:

using UnityEngine;
using System.Collections;
public class DestroyByContact : MonoBehaviour
{
    void OnTriggerEnter(Collider other) 
    {
        if (other.tag == "Boundary")
        {
            return;
        }
        Destroy(other.gameObject);
        Destroy(gameObject);
    }
}

Мы уже знакомы с этим кодом. Когда наш астероид сталкивается с чем-то, он уничтожает и другой объект, и себя.

Здесь интересно то, что мы проверяем, является ли объект, с которым мы сталкиваемся, созданным нами граничным полем, и если это так, мы останавливаем наш код.

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

Чтобы решить эту проблему, видео создало тег с именем Boundary и прикрепило его к игровому объекту Boundary. При этом всякий раз, когда астероид сталкивается с Boundary GameObject, мы завершаем вызов функции, и ничего не происходит.

Взрывы

В следующем видео мы добавили еще несколько спецэффектов, особенно то, что происходит при столкновении с астероидом.

Открыв скрипт DestroyByContact, который был создан ранее, видео внесло некоторые изменения:

using UnityEngine;
using System.Collections;
public class DestroyByContact : MonoBehaviour
{
    public GameObject explosion;
    public GameObject playerExplosion;
    void OnTriggerEnter(Collider other) 
    {
        if (other.tag == "Boundary")
        {
            return;
        }
        Instantiate(explosion, transform.position, transform.rotation);
        if (other.tag == "Player")
        {
            Instantiate(playerExplosion, other.transform.position, other.transform.rotation);
        }
        Destroy(other.gameObject);
        Destroy(gameObject);
    }
}

В коде 2 GameObject были сделаны общедоступными переменными. Это эффекты взрыва, представленные в учебнике: один — взрыв астероида, а другой — взрыв игрока.

Подобно тому, как мы создаем новый GameObject пули, мы Instantiate() взрываем GameObject для астероида, и если астероид сталкивается с объектом игрока (мы устанавливаем для него тег), мы также заставляем игрока взорваться.

После того, как мы добавили приведенный выше код в скрипт, я вернулся в редактор и прикрепил эффекты взрыва к моему компоненту скрипта.

Повторное использование кода

Также интересно отметить, что в этом видео мы повторно прикрепили наш скрипт Mover к нашему астероиду и установили скорость на -5.

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

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

Игровой контроллер

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

Скрипт GameController мы прикрепили к новому пустому игровому объекту. Мы назовем этот игровой объект GameController и создадим для него наш скрипт GameController.

using UnityEngine;
using System.Collections;
public class GameController : MonoBehaviour
{
    public GameObject hazard;
    public Vector3 spawnValues;
    void Start ()
    {
        SpawnWaves ();
    }
    void SpawnWaves ()
    {
        Vector3 spawnPosition = new Vector3 (Random.Range (-spawnValues.x, spawnValues.x), spawnValues.y, spawnValues.z);
        Quaternion spawnRotation = Quaternion.identity;
        Instantiate (hazard, spawnPosition, spawnRotation);
    }
}

Давайте немного пройдемся по этому коду.

Мы создали несколько публичных переменных:

public GameObject hazard;
public Vector3 spawnValues;

hazard — это астероид, а spawnValues ​​— это диапазон местоположений, где мы будем создавать экземпляры наших астероидов.

Мы создаем новую функцию SpawnWaves() и вызываем ее из функции Start(). Мы увидим, почему видео делает это позже, но читая код в функции:

void SpawnWaves ()
    {
        Vector3 spawnPosition = new Vector3 (Random.Range (-spawnValues.x, spawnValues.x), spawnValues.y, spawnValues.z);
        Quaternion spawnRotation = Quaternion.identity;
        Instantiate (hazard, spawnPosition, spawnRotation);
    }

Мы создаем Vector3, представляющий точку, в которой мы хотим создать астероид. Мы используем Random.Range() для создания рандомизированного значения между двумя заданными значениями. Мы не хотим менять значение Y или Z нашего GameObject, поэтому мы только рандомизируем наше начальное положение X (слева и справа).

Quarternion.identity просто означает отсутствие вращения. Для нашего кода это означает, что мы создаем астероид в случайном месте и без вращения.

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

Нерестовые волны

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

Итак, далее, в этом видео, мы создаем волны астероидов, от которых игрок будет уворачиваться.

Для этого мы могли бы сделать что-то вроде копирования и вставки большего количества префабов в Start() в скрипте GameController, однако это не только заставляет меня немного плакать внутри, но и затрудняет внесение изменений в будущее.
Вот что у нас получилось:

using UnityEngine;
using System.Collections;
public class GameController : MonoBehaviour
{
    public GameObject hazard;
    public Vector3 spawnValues;
    public int hazardCount;
    public float spawnWait;
    public float startWait;
    public float waveWait;
    void Start ()
    {
        StartCoroutine (SpawnWaves ());
    }
    IEnumerator SpawnWaves ()
    {
        yield return new WaitForSeconds (startWait);
        while (true)
        {
            for (int i = 0; i &amp;lt; hazardCount; i++)
            {
                Vector3 spawnPosition = new Vector3 (Random.Range (-spawnValues.x, spawnValues.x), spawnValues.y, spawnValues.z);
                Quaternion spawnRotation = Quaternion.identity;
                Instantiate (hazard, spawnPosition, spawnRotation);
                yield return new WaitForSeconds (spawnWait);
            }
            yield return new WaitForSeconds (waveWait);
        }
    }
}

Корутина

Корутины — это функции, которые запускают ваш код, возвращают и передают управление остальной части Unity, а затем снова возобновляют работу при запуске назад, когда условие ожидания выполнено.

Мы можем увидеть больше об этом в коде выше. Что у нас есть:

yield return new WaitForSeconds (spawnWait);

Это означает, что мы будем ждать сколько угодно секунд, чтобы создать нового врага. Однако, если бы мы сделали что-то вроде:

доходность возвращает ноль;

Код будет выполняться сразу после следующего кадра.

Звучит знакомо? Это потому, что они действуют очень похоже на то, как работает Update()!

Насколько я понимаю, вы почти можете использовать сопрограммы для замены Update(), если хотите, но основное преимущество их использования заключается в предотвращении переполнения кода внутри Update().

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

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

void Start()
    {
        StartCoroutine(test());
        print("end start");
    }
IEnumerator test()
    {
        for (int i = 0; i < 3; i++)
        {
            print("in for loop " + i);
            yield return new WaitForSeconds(1);
        }
    }

Наша консоль напечатает это:

В цикле 0

Конец начала

В для петли 1

В цикле 2

И если бы мы сделали что-то вроде этого:

void Update()
    {
        StartCoroutine(test());
    }
IEnumerator test()
    {
        for (int i = 0; i < 3; i++)
        {
            print("in for loop " + i);
            yield return new WaitForSeconds(1);
        }
    }

У нас было бы что-то вроде этого

В цикле 0

В цикле 0

В цикле 0

В цикле 0

В цикле 0

В цикле 0

В цикле 0

В цикле 0

В цикле 0

… и так далее в течение 1 секунды, а затем у нас будет смесь:

В цикле 0 и в цикле 1

Это происходит потому, что мы вызываем сопрограмму несколько раз, в частности, один раз за кадр, а затем через секунду функция начнет печатать, когда i = 1, в то время как Update() все еще выполняет новые вызовы сопрограммы, которые печатаются, когда i = 0

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

Очистка взрывов

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

Что мы делаем, так это прикрепляем скрипт DestroyByTIme к взрыву. Скрипт уничтожит взрыв GameObject через заданное время.

Код для этого довольно прост.

using UnityEngine;
using System.Collections;
public class DestroyByTime : MonoBehaviour
{
    public float lifetime;
    void Start ()
    {
        Destroy (gameObject, lifetime);
    }
}

Вывод

Уф и это все для дня 3, сегодня мы узнали:

  • Как использовать границы, чтобы очистить часть нашего игрового объекта, оставив его
  • Как создавать, перемещать и уничтожать вражеские волны.
  • Как использовать сопрограммы, которые чем-то похожи на Update()

Я собираюсь положить этому конец! В следующей части серии мы займемся созданием пользовательского интерфейса и звука, чтобы завершить космический шутер.

См. оригинал Учебник 3-го дня

См. главную страницу 100 дней разработки Unity VR