Введение в скрипты Unity — часть 2

Во второй части урока по созданию скриптов в Unity вы завершите игру «Спасение овец», в которой вам нужно спасти паникующих овец от падения с острова!

Версия: C# 7.3, Unity 2019.2, Unity

В первой части урока вы узнали, как перемещать игровые объекты, обрабатывать ввод игрока и как использовать систему физики с помощью скриптов. Конечно, вы можете сделать гораздо больше с помощью C# и Unity API! Во второй и последней части этого руководства вы узнаете, как:

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

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

Перед тем как начать, убедитесь, что на вашем компьютере установлена стабильная версия Unity.

Приступая к работе

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

Откройте сцену Game в разделе RW / Scenes, если она не открывается автоматически.

В первой части вы заставили овцу бежать к уступу, которая могла быть сбита тюками сена, запущенными сеноуборочной машиной. Sheep безопасно хранится в папке RW / Prefabs и готов к использованию.

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

Списки и отслеживание игровых объектов

Начните с создания нового пустого GameObject в корне Иерархии. Назовите его Sheep Spawn Points и сбросьте его Transform.

Затем создайте еще три пустых игровых объекта в качестве дочерних для Sheep Spawn Points и назовите их Spawn Point.

Игровые объекты в окне Hierarchy редактора Unity

Теперь установите положение каждой точки появления следующим образом:

  • (X: -16, Y: 0, Z: 60)
  • (X: 0, Y: 0, Z: 60)
  • (X: 16, Y: 0, Z: 60)

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

Точки появления овец в сцене редактора Unity

Определив точки появления, вы готовы создать скрипт для управления овцами. Создайте новый скрипт C# в RW / Scripts, назовите его SheepSpawner и откройте его в редакторе кода. Добавьте следующие объявления переменных над Start:

public bool canSpawn = true; // 1

public GameObject sheepPrefab; // 2
public List<Transform> sheepSpawnPositions = new List<Transform>(); // 3
public float timeBetweenSpawns; // 4

private List<GameObject> sheepList = new List<GameObject>(); // 5

Вот для чего они будут использоваться:

  1. Пока переменная имеет значение true, скрипт будет продолжать порождать овец.
  2. Ссылка на префаб Sheep.
  3. Позиции, откуда будут появляться овцы. Вам необходимо создать список с ключевым словом new, поскольку иначе Unity не инициализирует его правильно, что приведет к ошибкам, если вы попытаетесь добавить что-нибудь в список.
  4. Время в секундах между появлением овец.
  5. Список всех овец, находящихся в сцене.

Теперь добавьте самый важный метод этого скрипта, SpawnSheep:

private void SpawnSheep()
{
    Vector3 randomPosition = sheepSpawnPositions[Random.Range(0, sheepSpawnPositions.Count)].position; // 1
    GameObject sheep = Instantiate(sheepPrefab, randomPosition, sheepPrefab.transform.rotation); // 2
    sheepList.Add(sheep); // 3
    sheep.GetComponent<Sheep>().SetSpawner(this); // 4
}

Этот метод порождает одну овцу в случайном месте:

  1. Используем класс Unity Random, чтобы получить позицию одного из преобразований точки появления. Random.Range (min, max) возвращает случайное целое число от 0 до количества доступных точек появления (в данном случае три), поэтому возвращаемое значение будет 0, 1 или 2.
  2. Создать новую овцу и добавить ее в сцену в случайном месте, выбранном в предыдущей строке. Сохраняем эту овцу во временной переменной sheep.
  3. Добавляем ссылку на только что созданную овцу в список овец.
  4. Эта строка еще не компилируется, поскольку SetSpawner еще не реализован, поэтому игнорируйте ошибку. Он добавит ссылку на спаунер овец, которому овцы будут передавать данные.

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

Добавьте следующее объявление переменной ниже остальных:

private SheepSpawner sheepSpawner;

Переменная сохраняет ссылку на спаунер овец. Теперь добавьте следующий метод, чтобы заполнить ссылку:

public void SetSpawner(SheepSpawner spawner)
{
    sheepSpawner = spawner;
}

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

Сохраните скрипт Sheep и вернитесь к скрипту SheepSpawner.

Добавьте эту сопрограмму под методом SpawnSheep:

private IEnumerator SpawnRoutine() // 1
{
    while (canSpawn) // 2
    {
        SpawnSheep(); // 3
        yield return new WaitForSeconds(timeBetweenSpawns); // 4
    }
}

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

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

  1. Все сопрограммы должны использовать возвращаемый тип IEnumerator. Это позволяет вам уступить (приостановить и возобновить выполнение) в любой момент.
  2. Пока canSpawn равен значению true
  3. Создать новую овцу.
  4. Приостановить выполнение этой сопрограммы на количество секунд, указанное в timeBetweenSpawns, используя инструкцию yield, в данном случае WaitUntilSeconds.

Примечание. Можно использовать гораздо больше yield инструкций! Например, есть такие функции, как WaitForEndOfFrame, WaitForFixedUpdate и WaitUntil. На этой странице вы найдете пример использования WaitUntil и ссылки на все инструкции.

Теперь запуск сопрограммы немного отличается от простого вызова метода. Добавьте следующую строку внутри Start:

StartCoroutine(SpawnRoutine());

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

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

public void RemoveSheepFromList(GameObject sheep)
{
    sheepList.Remove(sheep);
}

Этот метод принимает овцу в качестве параметра и удаляет ее запись из списка овец.

Теперь добавьте следующий метод:

public void DestroyAllSheep()
{
    foreach (GameObject sheep in sheepList) // 1
    {
        Destroy(sheep); // 2
    }

    sheepList.Clear();
}

Этот метод уничтожает всех овец в сцене. Вот как это происходит:

  1. Перебрать каждую овцу в списке и уничтожить игровой объект, на который ссылается каждая запись.
  2. Очистить список ссылок на овец.

Сохраните скрипт, откройте скрипт Sheep и добавьте эту строку в начало методов Drop и HitByHay:

sheepSpawner.RemoveSheepFromList(gameObject);

Этот код удаляет овцу из списка спаунера, когда она падает с края острова или в нее попадает сено.

Теперь сохраните скрипт Sheep и вернитесь в редактор.

Добавьте новый пустой GameObject в Иерархию, назовите его Sheep Spawner и добавьте компонент Sheep Spawner.

Чтобы настроить генератор, начните с перетаскивания Sheep из папки RW / Prefabs в слот Sheep Prefab в Sheep Spawner.

Затем разверните Sheep Spawn Points и перетащите точки появления одну за другой в список Sheep Spawn Positions.

Назначение компоненту точек появления овец в окне Inspector редактора Unity

Назначив точки появления, установите для параметра Time Between Spawns значение 2, чтобы завершить настройку.

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

Появление и движение овец в окне Game редактора Unity

Хорошо смотрится! Основной игровой процесс готов, но вы можете пойти дальше, добавив несколько интересных эффектов.

Полировка игры

Хотя это не обязательно для работы игры, некоторая полировка улучшает общее впечатление. В этом разделе вы добавите некоторые визуальные улучшения и звуковые эффекты.

Визуальные эффекты

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

Модель сердца, которая отображается при спасении овец в редакторе Unity

Это сердце должно делать следующее:

  • Появляться, когда овцу сбивает сено.
  • Двигаться вверх.
  • Вращаться.
  • Уменьшаться.
  • Исчезать через короткое время.

Прежде чем вы сможете это реализовать, вам нужно создать префаб сердца. Создайте новый пустой GameObject в Иерархии, назовите его Heart и установите его положение на (X: 0, Y: 5, Z: 0).

Теперь добавьте к нему модель, перетащив Heart из RW / Models на Heart в иерархии.

Назовите этот дочерний объект Heart Model, сбросьте его Transform и установите его поворот на (X: -90, Y: -45, Z: 0).

Теперь у вас должно появиться большое красное сердце, парящее над поверхностью:

Модель парящего сердца над поверхностью в окне сцены редактора Unity

Движение и вращение добавить довольно легко, так как вы уже написали служебные скрипты для них в первой части урока. Добавьте компонент Move к Heart и установите для него параметр Movement Speed на (X: 0, Y: 10, Z: 0), чтобы заставить его двигаться вверх.

Теперь добавьте компонент Rotate и установите для него Rotation Speed на (X: 0, Y: 180, Z: 0).

Настройка компонента Rotate в окне Inspector редактора Unity

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

Модель сердца взлетает вверх в окне сцены редактора Unity

Сердце должно уменьшаться, пока оно движется вверх. Итак, создайте новый скрипт C# в RW / Scripts, назовите его TweenScale и откройте его в редакторе кода.

Название этого скрипта происходит от «tweening», что является сокращением от «inbetweening», процесса в анимации для создания кадров между ключевыми кадрами, который существовал с начала 1900-х годов.

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

Чтобы начать создание скрипта, добавьте следующие объявления переменных прямо над Start:

public float targetScale; // 1
public float timeToReachTarget; // 2
private float startScale;  // 3
private float percentScaled; // 4

Вот для чего будут использоваться переменные:

  1. Итоговый масштаб. Это значение будет применяться ко всем осям.
  2. Время в секундах, необходимое для достижения целевого масштаба.
  3. Это масштаб игрового объекта на момент активации скрипта.
  4. Это значение в процентах от 0,0 до 1,0 будет увеличиваться и использоваться в расчетах для изменения шкалы от начального значения до целевого значения.

Затем добавьте это в Start:

startScale = transform.localScale.x;

Здесь вы получаете значение шкалы по оси X и сохраняете его в startScale.

Теперь, чтобы выполнить масштабирование, добавьте этот код в Update:

if (percentScaled < 1f) // 1
{
    percentScaled += Time.deltaTime / timeToReachTarget; // 2
    float scale = Mathf.Lerp(startScale, targetScale, percentScaled); // 3
    transform.localScale = new Vector3(scale, scale, scale); // 4
}

Вот что это делает:

  1. Если масштабированный процент не равен 1 (100%) ...
  2. Добавить время между этим кадром и предыдущим, разделенное на количество секунд, необходимое для достижения окончательного значения. Если бы вы просто добавили Time.deltaTime, масштабирование заняло бы ровно одну секунду. Разделив это значение на фактическое время, которое необходимо потратить, вы увеличите эту единственную секунду до желаемого времени.
  3. Создать временную переменную с именем scale и сохранить в ней полученное значение шкалы. Mathf.Lerp выполняет lerp, линейную интерполяцию между двумя значениями. Он принимает 3 параметра: начальное значение, конечное значение и процент, при котором должно быть возвращено выходное значение. Например, если вы вызвали Mathf.Lerp (0f, 50f, 0.5f);, возвращаемое значение будет 25, так как это 50% от 50.
  4. Получить значение масштаба от лерпа и применить его к преобразованию по всем осям.

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

Помимо Mathf.Lerp, существует также Vector3.Lerp для лерпинга двух векторов и Color.Lerp для лерпинга между двумя цветами. Два последних в основном существуют для вашего удобства, поскольку вы можете добиться тех же результатов с помощью только Mathf.Lerp.

Теперь сохраните скрипт и вернитесь в редактор. Выберите Heart и добавьте компонент Tween Scale. Установите его Target Scale на 0,5 и измените Time To Reach target на 1,5. Запустите воспроизведение сцены и посмотрите на сердце, теперь оно сжимается при движении вверх.

Модель сердца взлетает вверх и уменьшается в окне сцены редактора Unity

Все, что осталось, - это заставить сердце исчезнуть после того, как оно какое-то время взлетело. К счастью, реализовать это несложно!

Создайте новый скрипт C# в RW / Scripts, назовите его DestroyTimer и откройте в среде разработки кода IDE.

Добавьте следующую строку прямо над Start:

public float timeToDestroy;

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

Теперь добавьте следующий код в Start:

Destroy(gameObject, timeToDestroy);

Этот знакомый фрагмент кода уничтожает игровой объект после задержки, установленной в timeToDestroy.

Это должно сработать! Сохраните скрипт и вернитесь в редактор. Затем добавьте компонент Destroy Timer в Heart и установите для него Time To Destroy значение 1,5.

Теперь перетащите Heart в папку RW / Prefabs, чтобы превратить его в префаб. Наконец, удалите Heart из Иерархии и снова откройте скрипт Sheep.

Добавьте следующие объявления переменных ниже остальных:

public float heartOffset; // 1
public GameObject heartPrefab; // 2

Вот для чего они нужны:

  1. Смещение по оси Y, где будет появляться сердце.
  2. Это ссылка на только что созданный префаб Heart.

Затем добавьте эту строку кода в HitByHay:

Instantiate(heartPrefab, transform.position + new Vector3(0, heartOffset, 0), Quaternion.identity);

Данная строчка кода создает новое сердце и размещает его над овцой, с добавлением heartOffset к позиции Y.

Теперь, чтобы «оживить» овцу, вы можете динамически добавить к ней компонент TweenScale после того, как в нее попало сено. Добавьте следующие строки под последней, которую вы только что добавили:

TweenScale tweenScale = gameObject.AddComponent<TweenScale>();; // 1
tweenScale.targetScale = 0; // 2
tweenScale.timeToReachTarget = gotHayDestroyDelay; // 3

Эти строки демонстрируют, насколько легко добавить компонент и настроить его во время выполнения:

  1. Добавить компонент TweenScale к игровому объекту, к которому прикреплен этот скрипт, и поместить ссылку на него в переменной tweenScale.
  2. Установить target scale объекта TweenScale на 0, чтобы овца сжималась.
  3. Установить время, которое потребуется TweenScale, равным времени, необходимому для уничтожения овцы.

Сохраните скрипт и вернитесь в редактор. Выберите Sheep в RW / Prefabs, установите Heart Offset на 4 и перетащите Heart из той же папки в слот Heart Prefab.

Установка параметров компонента Sheep в окне Inspector редактора Unity

Нажмите кнопку воспроизведения и постреляйте в овец. Теперь они проявят немного любви и уменьшатся после удара.

Воспроизведение игровой сцены в окне Game редактора Unity

С добавленными визуальными эффектами пора добавить звуковые эффекты!

Звуковые эффекты

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

Создайте новую папку в RW / Scripts и назовите ее Managers. Создайте в этой папке новый скрипт C#, назовите его SoundManager и откройте его в редакторе кода.

Добавьте следующие объявления переменных прямо над Start:

public static SoundManager Instance; // 1

public AudioClip shootClip; // 2
public AudioClip sheepHitClip; // 3
public AudioClip sheepDroppedClip; // 4

private Vector3 cameraPosition; // 5

Вот для чего они используются:

  1. Это общедоступная статическая переменная, поэтому к ней можно получить доступ из любого другого скрипта. Она сохраняет ссылку на компонент SoundManager.
  2. Ссылка на AudioClip, содержащий звуковой эффект стрельбы сена.
  3. Ссылка на звуковой эффект, когда овцу сбивает сено.
  4. Ссылка на звук, который издает овца, когда падает с края острова.
  5. Положение камеры в кэше.

Теперь замените Start на Awake и добавьте этот код в метод:

Instance = this; // 1
cameraPosition = Camera.main.transform.position; // 2

Это довольно просто:

  1. Кэшировать этот скрипт внутри переменной экземпляра.
  2. Кешировать положение основной камеры (камера с тегом MainCamera).

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

Затем добавьте этот метод:

private void PlaySound(AudioClip clip) // 1
{
    AudioSource.PlayClipAtPoint(clip, cameraPosition); // 2
}

В двух словах:

  1. Метод принимает AudioClip для воспроизведения.
  2. Создается временный AudioSource, который воспроизводит аудиоклип, переданный в качестве параметра в местоположении камеры.

И наконец, добавьте эти 3 метода, которые запускают фактическое воспроизведение звуковых эффектов:

public void PlayShootClip()
{
    PlaySound(shootClip);
}

public void PlaySheepHitClip()
{
    PlaySound(sheepHitClip);
}

public void PlaySheepDroppedClip()
{
    PlaySound(sheepDroppedClip);
}

Каждый из вышеперечисленных методов вызывает PlaySound и передает соответствующий аудиоклип.

Теперь, когда аудио менеджер готов, вам нужно добавить код в другие скрипты, чтобы использовать его возможности воспроизведения звука. Сохраните этот скрипт, откройте скрипт HayMachine и добавьте эту строку в ShootHay:

SoundManager.Instance.PlayShootClip();

Эта строчка кода вызывает PlayShootClip в диспетчере звуков. Теперь сохраните скрипт HayMachine и откройте скрипт Sheep. Добавьте следующую строку в HitByHay:

SoundManager.Instance.PlaySheepHitClip();

Будет воспроизведен другой звуковой клип.

Затем добавьте эту строку в Drop:

SoundManager.Instance.PlaySheepDroppedClip();

Воспроизводится звук падающей овцы.

Сохраните скрипт и вернитесь в редактор. Звуковой менеджер должен быть добавлен к игровому объекту для правильной работы, поэтому создайте новый пустой GameObject в корне Иерархии и назовите его Managers. Теперь создайте еще один пустой GameObject, назовите его Sound Manager и сделайте его дочерним по отношению к Managers.

Создание игрового объекта Managers в окне Hierarchy редактора Unity

Выберите Sound Manager и добавьте к нему компонент Sound Manager. Теперь перетащите аудиоклипы из RW / Sounds в соответствующие слоты в Sound Manager.

Добавление компонента Sound Manager и установка параметров в окне Inspector редактора Unity

Теперь воспроизведите сцену и снова постреляйте в овец, вы услышите звуковые эффекты!

Ведение счета

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

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

Добавьте новый скрипт C# в RW / Scripts / Managers, назовите его GameStateManager и откройте его в редакторе кода.

Для начала добавьте это под другими операторами using:

using UnityEngine.SceneManagement;

Это позволяет использовать методы, связанные со сценой.

Теперь добавьте эти объявления переменных прямо над Start:

public static GameStateManager Instance; // 1

[HideInInspector]
public int sheepSaved; // 2

[HideInInspector]
public int sheepDropped; // 3

public int sheepDroppedBeforeGameOver; // 4
public SheepSpawner sheepSpawner; // 5

Вот для чего они используются:

  1. Сохраняет ссылку на сам скрипт, который может быть вызван из любого другого скрипта.
  2. Количество овец, которые были спасены сеном. Атрибут [HideInInspector] позволяет Unity не отображать переменную, которой он назначен, в редакторе, но по-прежнему может быть доступен из других скриптов. Сделать это для общедоступных переменных, которые полностью управляются скриптами, - хорошая практика!
  3. Количество упавших овец.
  4. Количество овец, которые могут упасть до завершения игры.
  5. Ссылка на компонент Sheep Spawner.

Затем замените Start на Awake и добавьте к нему эту строку:

Instance = this;

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

Теперь добавьте небольшой метод:

public void SavedSheep()
{
    sheepSaved++;
}

Метод увеличивает SheepSaved, чтобы вести счет каждый раз, когда овца спасается.

Затем добавьте следующий метод:

private void GameOver()
{
    sheepSpawner.canSpawn = false; // 1
    sheepSpawner.DestroyAllSheep(); // 2
}

Данный метод будет вызван, когда слишком много овец упадут. Вот что он делает:

  1. Остановить появление овец.
  2. Уничтожить всех овец, которые все еще бегают.

И наконец, добавьте метод, который вызывается каждый раз, когда овца падает.

public void DroppedSheep()
{
    sheepDropped++; // 1

    if (sheepDropped == sheepDroppedBeforeGameOver) // 2
    {
        GameOver();
    }
}

Вот что делает код:

  1. Приращение sheepDropped
  2. Если количество выпавших овец равно количеству брошенных овец до окончания игры, вызвать GameOver.

Наконец, добавьте этот код в метод Update:

if (Input.GetKeyDown(KeyCode.Escape))
{
    SceneManager.LoadScene("Title");
}

Когда нажата клавиша Escape, вызывается SceneManager и загружает сцену главного экрана. SceneManager предоставляет несколько вариантов загрузки и выгрузки сцен, как синхронно, так и асинхронно.

Этот скрипт сейчас готов! Сохраните его и откройте скрипт Sheep.

Затем вам нужно вызвать методы Game State Manager, чтобы он знал как о счастливых, так и о менее счастливых овцах.

Добавьте эту строку в HitByHay:

GameStateManager.Instance.SavedSheep();

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

Затем добавьте этот код в Drop, прямо над SheepSpawner.RemoveSheepFromList (gameObject);:

GameStateManager.Instance.DroppedSheep();

Этой строкой менеджер получает уведомление о том, что выпала овца.

Теперь сохраните скрипт и вернитесь в редактор. Создайте новый пустой GameObject как дочерний для Managers, назовите его Game State Manager и добавьте компонент Game State Manager.

Установите Sheep Dropped Before Game Over на 3 и перетащите Sheep Spawner из Иерархии в слот с тем же именем.

Настройка компонента Game State Manager в окне Inspector редактора Unity

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

Воспроизведение игровой сцены с падающими овцами в окне Game редактора Unity

Управление пользовательскими интерфейсами

Конечно, немного глупо следить за счетом и ошибками, если нет возможности их посмотреть. Чтобы исправить это, перетащите Game UI из RW / Prefabs в корень иерархии. Это готовый Canvas с некоторым текстом, несколькими изображениями и очень простым отключенным окном игры.

Примечание. Если вы хотите узнать больше о создании пользовательских интерфейсов в Unity, ознакомьтесь с серией руководств по Unity UI!

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

Наименования игровых объектов пользовательского интерфейса в окне Hierarchy редактора Unity

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

Создайте новый скрипт C# в RW / Scripts / Managers, назовите его UIManager и откройте его в редакторе кода.

Большинство классов, используемых для управления элементами пользовательского интерфейса, находятся в пространстве имен UnityEngine.UI. Итак, добавьте этот оператор using под другими:

using UnityEngine.UI;

После указания оператора, вы можете начать добавлять ссылки на компоненты пользовательского интерфейса. Добавьте следующие объявления переменных над Start:

public static UIManager Instance; // 1

public Text sheepSavedText; // 2
public Text sheepDroppedText; // 3
public GameObject gameOverWindow; // 4

Вот что это такое:

  1. Ссылка на UI Manager.
  2. Кэшированная ссылка на компонент Text игрового объекта DroppedSheepText.
  3. Эта переменная ссылается на компонент Text игрового ообъекта SavedSheepText.
  4. Ссылка на окно Game Over.

Затем измените Start на Awake и добавьте к нему эту строку:

Instance = this;

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

Теперь удалите Update и добавьте на его место следующие методы:

public void UpdateSheepSaved() // 1
{
    sheepSavedText.text = GameStateManager.Instance.sheepSaved.ToString();
}

public void UpdateSheepDropped() // 2
{
    sheepDroppedText.text = GameStateManager.Instance.sheepDropped.ToString();
}

Эти методы обновляют текст в верхней части экрана:

  1. Получить количество сохраненных овец из Game State Manager, преобразовать в строку и использовать для установки текста sheepSavedText.
  2. То же, что и другой метод, но вместо этого использовать количество выпавших овец.

И наконец, добавьте этот метод:

public void ShowGameOverWindow()
{
    gameOverWindow.SetActive(true);
}

Это активирует окно Game Over Window, и делает видимым.

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

Сохраните этот скрипт и откройте скрипт GameStateManager.

Добавьте в SavedSheep следующую строку:

UIManager.Instance.UpdateSheepSaved();

Это обновляет текст, который показывает количество сохраненных овец.

Затем добавьте эту строку в DroppedSheep, прямо под sheepDropped++;:

UIManager.Instance.UpdateSheepDropped();

Это вызывает диспетчер пользовательского интерфейса для обновления текста упавшей овцы.

И наконец, добавьте эту строку в GameOver:

UIManager.Instance.ShowGameOverWindow();

Окно game over отображается после завершения игры с вызовом этого метода. Это должно сработать! Сохраните скрипт и вернитесь в редактор.

Добавьте новый пустой GameObject в качестве дочернего объекта Managers и назовите его UI Manager. Добавьте компонент UI Manager и перетащите дочерние элементы Game Canvas в их соответствующие слоты.

Настройка компонента UI Manager в окне Inspector редактора Unity

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

Элементы пользовательского интерфейса для игры в Unity

Если вы позволите упасть трем овцам, появится простое окно с игрой. Если вы нажмете клавишу Escape, сцена Title загрузится, но вы пока не сможете нажимать ни одну из кнопок. Пора это исправить!

Окно главного экрана игры в редакторе Unity

Кнопки главного экрана

Сохраните текущую открытую сцену и откройте сцену Title из RW / Scenes. Осмотрите сцену, это та же среда, что и сама игра, но с некоторыми добавленными вместе реквизитами и другим видом камеры.

Расположение элементов главного экрана в окне сцены редактора Unity

Теперь обратите внимание на Иерархию и разверните Main Camera, которая скрывает элементы трехмерного меню Title, Start Button и Quit Button.

Элементы трехмерного меню в окне Hierarchy редактора Unity

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

Начните с выбора Start Button и Quit Button в Иерархии. Добавьте к ним коллайдер Box Collider и установите для коллайдеров размер (X: 2,5, Y: 1, Z: 0,4). Это касается физики.

Теперь добавьте систему событий в Иерархию, выбрав GameObject > UI > Event System в верхнем меню. Это добавит в сцену игровой объект с именем EventSystem с присоединенный компонентом Standalone Input Module.

Компонент системы событий в редакторе Unity

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

Выберите Main Camera и добавьте к ней компонент Physics Raycaster. Этот компонент, как следует из его названия, бросает лучи в сцену и передает события в систему событий при обнаружении 3D-коллайдера (например, при наведении курсора на кнопку).

Должно сработать, теперь сцена готова к взаимодействию! Чтобы использовать компонент, вам нужно создать другой скрипт. Создайте новую папку в RW / Scripts и назовите Title.

Теперь создайте там новый скрипт C# с именем StartButton и откройте его в редакторе кода.

Для начала добавьте операторы using под другими:

using UnityEngine.EventSystems;
using UnityEngine.SceneManagement;

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

Теперь замените эту строку:

public class StartButton : MonoBehaviour

На эту:

public class StartButton : MonoBehaviour, IPointerClickHandler

Наследование от интерфейса IPointerClickHandler позволяет этому классу получать обратные вызовы OnPointerClick от системы событий. Фактически, требуется использовать этот метод при наследовании от этого интерфейса, поэтому вы, вероятно, увидите несколько красных волнистых линий. Удалите Start и Update. Добавьте следующий метод, чтобы исправить это:

public void OnPointerClick(PointerEventData eventData) // 1
{
    SceneManager.LoadScene("Game"); // 2
}
  1. У этого метода должна быть определенная сигнатура для работы с системой событий, вам нужен единственный параметр PointerEventData, содержащий всю информацию, которая нужна о событии щелчка.
  2. Используйте диспетчер сцен, чтобы загрузить сцену Game.

Теперь сохраните скрипт и вернитесь в редактор. Выберите Start Button, добавьте к нему компонент StartButton и посмотрите на нижнюю часть окна Inspector. Теперь он показывает дополнительный раздел под названием Intercepted Events, в котором перечислены все события, которые будут переданы из системы событий в выбранный игровой объект.

Раздел с отображением событий в окне Inspector редактора Unity

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

Затем воспроизведите сцену и нажмите кнопку Start, сцена Game будет загружена.

Воспроизведение сцены главного экрана в окне Game редактора Unity

Пока все хорошо! Чтобы кнопка выхода заработала, продублируйте скрипт StartButton, назовите копию QuitButton и откройте ее в редакторе кода.

Создание копии скрипта кнопки главного меню в Unity

Замените эту строку:

public class StartButton : MonoBehaviour, IPointerClickHandler

На следующую строку кода:

public class QuitButton : MonoBehaviour, IPointerClickHandler

Это необходимо для переименования класса в соответствии с именем файла. Скрипт не будет компилироваться, если имена не совпадают.

Теперь удалите строку внутри OnPointerClick:

SceneManager.LoadScene("Game");

И наконец, добавьте код на свое место:

Application.Quit();

Как вы уже догадались, происходит выход из игры. Сохраните скрипт и вернитесь в редактор.

Выберите кнопку Quit Button, добавьте компонент Quit Button и запустите сцену. Попробуйте нажать на кнопку Quit, и вы заметите, что ничего не происходит. Что такое?

Application.Quit действительно вызывается, но он будет работать только в реальной сборке. Вы можете попробовать, собрав игру в исполняемый файл, нажав CTRL (или CMD) + B и выбрав папку для развертывания. После запуска игры попробуйте еще раз нажать на кнопку Quit, она закроет игру, как и ожидалось.

Нажатие на кнопку выхода главного экрана игры в редакторе Unity

Есть одна последняя настройка кнопок, которая сделает более понятным игроку, что на них можно нажимать, и это изменение их цвета при наведении курсора мыши. Создайте новый скрипт C# в RW / Scripts / Title, назовите его ChangeColorOnMouseOver и откройте в редакторе кода.

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

using UnityEngine.EventSystems;

Теперь замените строку:

public class ChangeColorOnMouseOver : MonoBehaviour

На эту строку:

public class ChangeColorOnMouseOver : MonoBehaviour, IPointerEnterHandler, IPointerExitHandler

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

Теперь добавьте эти объявления переменных прямо над Start:

public MeshRenderer model; // 1
public Color normalColor; // 2
public Color hoverColor; // 3

Вот для чего они используются:

  1. Ссылка на средство визуализации сетки, которому нужно изменить цвет.
  2. Цвет модели по умолчанию.
  3. Цвет, который следует применить к модели при наведении на нее указателя.

Теперь добавьте код в Start:

model.material.color = normalColor;

Это меняет цвет модели на нормальный.

Теперь удалите Update и добавьте на его место эти методы:

public void OnPointerEnter(PointerEventData eventData) // 1
{
    model.material.color = hoverColor;
}

public void OnPointerExit(PointerEventData eventData) // 2
{
    model.material.color = normalColor;
}

Эти методы будут вызываться, когда указатель входит в присоединенный игровой объект и выходит из него:

  1. Это еще один метод, для которого в качестве параметра требуется одна переменная PointerEventData. Он вызывается, когда указатель входит в игровой объект и меняет цвет материала модели.
  2. Вызывается, когда указатель выходит из игрового объекта. Это сбрасывает цвет материала до его нормального значения.

Вот так! Сохраните скрипт и вернитесь в редактор. Выберите обе кнопки и добавьте к каждой компонент Change Color On Mouse Over.

Установите альфа-значение для Normal Color на 100 и измените его шестнадцатеричное значение на CCCCCC (светло-серый) на обеих кнопках.

Настройка цвета кнопки главного меню в окне выбора цвета редактора Unity

Теперь сделайте то же самое для цвета Hover Color, но установите его шестнадцатеричное значение цвета на чистый белый, FFFFFF. Затем выберите только Start Button и перетащите ее дочерний элемент в слот Model.

Настройка компонента с указанием модели кнопки главного экрана в Unity

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

Воспроизведение главного экрана игры с наведением курсора мыши на кнопки в Unity

Игра готова! Однако есть еще одна техника, которой можно с вами поделиться: как передавать данные между сценами.

Передача данных между сценами

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

Для начала вам нужно на что-нибудь нажать, чтобы произошло изменение цвета. Создайте пустой GameObject в корне Иерархии, назовите его Hay Machines и установите его положение (X: 16,5, Y: -0,5, Z: -20). Он будет служить контейнером для разноцветных сеноуборочных машин.

Теперь выберите все уборочные машины в RW / Prefabs / Hay Machine Models и перетащите их в Hay Machines.

Установка моделей сеноуборочной машины для главного экрана в Unity

Затем, выбрав в Иерархии три сенокосилки, установите для них Rotation на (X: -90, Y: 0, Z: 0). Сенокосилки теперь в вертикальном положении:

Отображение сеноуборочных машин в сцене главного меню редактора Unity

Прямо сейчас все машины перекрывают друг друга, поэтому отображается случайная. Цвет машины по умолчанию синий, поэтому отключите два других, установив флажок рядом с их именами или нажав Alt + Shift + A.

Чтобы сеноуборочные машины были кликабельными, им нужен коллайдер. Добавьте Box Collider в Hay Machines, установите его Center на (X: 0, Y: 3.5, Z: 0) и его размер на (X: 5, Y: 6, Z: 8).

Теперь вы готовы к написанию скриптов! Создайте новую папку в RW / Scripts, назовите ее Shared и создайте новый скрипт C# с именем HayMachineColor.

Откройте его в редакторе кода и удалите все операторы using и оба метода.

Теперь замените эту строку:

public class HayMachineColor : MonoBehaviour

На эту:

public enum HayMachineColor

Как вы, возможно, догадались, этот скрипт не предназначен для использования в качестве компонента, на самом деле это простое перечисление enum, которое будет использоваться другими скриптами. Перечисления на самом деле представляют собой просто постоянные целые числа под покрытием (например, 0, 1, 2 и т. Д.), Но их использование делает гораздо более доступный и читаемый способ доступа к переменным. Добавьте названия внутри перечисления, чтобы установить несколько значений:

Blue, Yellow, Red

Вот и все, что касается этого файла, сохраните его, вернитесь в редактор и создайте еще один скрипт C# внутри RW / Scripts / Shared. Назовите его GameSettings и откройте его в редакторе кода. Снова удалите операторы using и методы, это будет обычный класс.

Замените эту строку:

public class GameSettings : MonoBehaviour

На строку кода:

public static class GameSettings

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

Теперь добавьте это объявление переменной в тело класса:

public static HayMachineColor hayMachineColor = HayMachineColor.Blue;

Здесь происходит волшебство! Эта переменная будет использоваться для установки и получения цвета сенокосилки. Он использует только что созданное перечисление для удобства чтения.

Сохраните скрипт, вернитесь в редактор и создайте еще один скрипт в RW / Scripts / Shared с именем HayMachineSwitcher.

Откройте скрипт в редакторе кода и добавьте эти операторы using под другими:

using UnityEngine.EventSystems;
using System;

Это необходимо для использования события щелчка указателя.

Замените эту строку:

public class HayMachineSwitcher : MonoBehaviour

На эту:

public class HayMachineSwitcher : MonoBehaviour, IPointerClickHandler

Как и раньше, вы получите сообщение об отсутствии части интерфейса, но не беспокойтесь об этом!

Теперь добавьте эти объявления переменных:

// 1
public GameObject blueHayMachine;
public GameObject yellowHayMachine;
public GameObject redHayMachine;

private int selectedIndex; // 2
  1. Эти переменные ссылаются на каждую из цветных моделей сеноуборочных машин, которые вы добавили ранее как дочерние к Hay Machines.
  2. Это индекс, который будет увеличиваться каждый раз при нажатии переключателя сеноуборочной машины, вызывая выбор следующего цвета машины. Что это влечет за собой, станет ясно, как только вы добавите следующий метод.

Теперь удалите метод Start и Update и добавьте самый важный метод этого скрипта:

public void OnPointerClick(PointerEventData eventData) // 1
{
    selectedIndex++; // 2
    selectedIndex %= Enum.GetValues(typeof(HayMachineColor)).Length; // 3

    GameSettings.hayMachineColor = (HayMachineColor)selectedIndex; // 4

    // 5
    switch (GameSettings.hayMachineColor)
    {
        case HayMachineColor.Blue:
            blueHayMachine.SetActive(true);
            yellowHayMachine.SetActive(false);
            redHayMachine.SetActive(false);
        break;

        case HayMachineColor.Yellow:
            blueHayMachine.SetActive(false);
            yellowHayMachine.SetActive(true);
            redHayMachine.SetActive(false);
        break;

        case HayMachineColor.Red:
            blueHayMachine.SetActive(false);
            yellowHayMachine.SetActive(false);
            redHayMachine.SetActive(true);
        break;
    }
}

Вот что делает метод:

  1. Это тот же метод, который вы использовали с кнопками главного экрана; он вызывается при нажатии на игровой объект и имеет единственный параметр, который содержит кучу информации о вводе указателя.
  2. Увеличить selectedIndex, чтобы выбрать следующий цвет.
  3. В этой строке используется алгебра для предотвращения написания ненужных операторов if. Используя оператор по модулю (%), чтобы получить остаток от деления, индекс можно «зациклить». Правая часть этого уравнения выглядит устрашающе, но она просто получает список всех значений, содержащихся в перечислении HayMachineColor, и считает их. Его значение в данном случае будет 3. Вот пример того, как они работают вместе: если индекс равен 3, остаток после деления на количество цветов (3) равен 0. Таким образом, окончательное значение индекса будет 0. С другой стороны, если индекс равен 2 и он делится на количество цветов (3), остаток равен 2. Таким образом, значение индекса остается 2. Вот веб-сайт, где вы можете попробовать поиграть с этими значения. Делимое - это текущее значение индекса, делитель - это количество цветов, а остаток - это окончательное значение индекса.
  4. Установите выбранный цвет в GameSettings в зависимости от выбранного индекса, который приводится к перечислению путем добавления типа приведения в скобки перед переменной.
  5. Включить и отключить модели сенокосилки на основе выбранного цвета машины с помощью оператора switch.

Это был самый сложный блок кода в проекте, но конечный результат будет довольно крутым!

Сохраните скрипт и вернитесь в редактор. Выберите Hay Machines, добавьте компонент Hay Machine Switcher и перетащите дочерние машины сена в соответствующие слоты.

Добавление компонента Hay Machine Switcher и настройка его параметров в редакторе Unity

А теперь момент истины! Запустите воспроизведение сцены и нажмите несколько раз на сеноуборочную машину справа. Вы увидите, как она меняет цвета!

Выбор сеноуборочной машины в окне главного меню игры редактора Unity

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

Откройте скрипт HayMachine и добавьте следующие объявления переменных ниже остальных:

public Transform modelParent; // 1

// 2
public GameObject blueModelPrefab;
public GameObject yellowModelPrefab;
public GameObject redModelPrefab;

Они будут использоваться для замены модели машины:

  1. Родительский объект модели Transform.
  2. Ссылки на префабы модели сеноуборочной машины.

Теперь добавьте следующий метод под Start:

private void LoadModel()
{
    Destroy(modelParent.GetChild(0).gameObject); // 1

    switch (GameSettings.hayMachineColor) // 2
    {
        case HayMachineColor.Blue:
            Instantiate(blueModelPrefab, modelParent);
        break;

        case HayMachineColor.Yellow:
            Instantiate(yellowModelPrefab, modelParent);
        break;

        case HayMachineColor.Red:
            Instantiate(redModelPrefab, modelParent);
        break;
    }
}

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

  1. Уничтожить текущую модель.
  2. Создать экземпляр префаба модели сенокосилки на основе выбранного цвета и сделать его родительским для modelParent.

И наконец, добавьте эту строку в Start:

LoadModel();

Это вызывает только что созданный метод LoadModel.

Теперь сохраните скрипт и вернитесь в редактор. Убедитесь, что сцена Title сохранена. Откройте сцену Game, выберите Hay Machine и разверните ее в Иерархии.

Компонент Hay Machine теперь имеет несколько дополнительных полей, которые необходимо заполнить для работы переключения модели. Перетащите Model Parent в слот Model Parent и перетащите префабы из RW / Prefabs / Hay Machine Models в соответствующие слоты.

Настройка дополнительных параметров компонента Hay Machine в окне Inspector редактора Unity

Когда сенокосилка полностью настроена, сохраните сцену Game. Снова откройте сцену Title и нажмите кнопку Play. Измените цвет сенокосилки на какой-нибудь другой, кроме синего, и нажмите кнопку Start.

Теперь вы увидите, что сенокосилка имеет выбранный вами цвет!

Сцена главного меню с выбором сеноуборочной машины и изменение цвета в игре редактора Unity

Поздравляю! Вы создали законченную игру всего из нескольких моделей и реализовали несколько классных механик, попутно изучая API Unity.

Куда двигаться дальше?

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

Если вы хотите узнать больше об API Unity, ознакомьтесь с этими полезными ресурсами:

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

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

Мы надеемся, что вам понравился урок. Если у вас есть какие-либо вопросы или комментарии, присоединяйтесь к обсуждению!

Автор перевода: Jean Winters

Источник: Introduction to Unity Scripting – Part 2

Комментировать

Почта не публикуется.Обязательные поля отмечены *