5. Создание скриптов

5.   Программирование на C#

5.1.     Что такое скрпитинг в Unity?

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

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

Например, если сравнивать с телевидением, в Северной Америке оно работает с частотой смены кадров в 29,5 кадров/сек, и движку Unity нужно делать то же самое. Он прокручивает отдельные дискретные кадры, один за другим. Вы отдаете Unity прямые команды, записанные в ваших скриптах, и Unity выполняет их кадр за кадров, насколько быстро он может.

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

К объекту GameObject, находящемуся в сцене, должен быть приписан скрипт, чтобы вызываться Unity. Скрипты пишутся на специальном языке, понятном движку Unity. На этом языке мы можем взаимодействовать с движком и отдавать ему свои команды.


Обзор

Консоль

Работа с объектами

Скрипт взаимодействует с внутренними механизмами Unity за счет создания класса, наследованного от встроенного класса, называемого MonoBehaviour. Вы можете думать о классе как о своего рода плане для создания нового типа компонента, который может быть прикреплен к игровому объекту. Каждый раз, когда вы присоединяете скриптовый компонент к игровому объекту, создается новый экземпляр объекта, определенный планом. Имя класса берется из имени, которое вы указали при создании файла. Имя класса и имя файла должны быть одинаковыми, для того, чтобы скриптовый компонент мог быть присоединен к игровому объекту.

Пример скрипта:

using System.Collections;

using System.Collections.Generic;

using UnityEngine;

 public class NewBehaviourScript : MonoBehaviour

{

    public int Var1;

    private int Var2;

 

    // Start is called before the first frame update

    void Start()

    {

 

    }

 

    // Update is called once per frame

    void Update()

    {

 

    }

}

 

Как вы видите, в программном коде присутствуют переменные (variables), функции (functions) и классы (classes).

Переменные содержат значения и ссылки на объекты (вы можете рассматривать объекты как “более крупные” переменные). Они подобны ящику, содержащему что-то для использования. Переменные начинаются с маленькой буквы.

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

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

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

Если вы в редакторе кода создадите скрипт с приведенным выше текстом, а затем вернетесь в Unity и добавите этот скрипт объекту GameObject, вы увидите, что вам доступна переменная типа Var 1, объявленная как общая (public), но вы не увидите частной переменной (private) Var 2. Это происходит из-за того, что переменная, объявленная как частная (private), может быть доступна только из частного скрипта в пределах частного класса.


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

5.2.     Функции

Скрипты манипулируют переменными с помощью функций. Есть ряд функций, выполняющихся автоматически в Unity.

Основные вещи, достойные внимания, это две функции, определенные внутри класса.

1. Функция Update - это место для размещения кода, который будет обрабатывать обновление кадра для игрового объекта. Это может быть движение, срабатывание действий и ответная реакция на ввод пользователя, в основном всё, что должно быть обработано с течением времени во игровом процессе. Чтобы позволить функции Update выполнять свою работу, часто бывает полезно инициализировать переменные, считать свойства и осуществить связь с другими игровыми объектами до того, как будут совершены какие-либо действия.

2. Функция Start будет вызвана Unity до начала игрового процесса (т.е. до первого вызова функции Update), и это идеальное место для выполнения инициализации.

Дополнительный набор функций:

Awake вызывается только раз, когда инициализируется GameObject с таким компонентом. Если объект GameObject неактивен, то он не будет вызываться, пока не станет активным. Однако, Awake вызывается даже в случае, если GameObject активен, но компонент не включен (когда стоит флажок в поле рядом с его именем). Вы можете использовать Awake для инициализации всех переменных, которым вам нужно приписать значение.

FixedUpdate используется для расчетов физики. Функции Fixed Update и Update описаны в учебном разделе по скриптам, и вы можете узнать как вызывать изменения в каждом кадре с помощью функций Update и FixedUpdate, и об их отличиях.

LateUpdate - функция, аналогичная Update, но LateUpdate вызывается в конце кадра. Unity просматривает все игровые объекты, находит все Update и вызывает LateUpdate. Этот прием хорошо использовать для таких элементов, как камера. Скажем, вы хотите переместить персонажа в своей игре. И затем он сталкивается с другим персонажем и оказывается в другой позиции. Если мы будем перемещать камеру во время перемещения персонажа, получится болтанка, и камера окажется не там, где она должна быть. Так что этот второй цикл оказывается как нельзя кстати.

Состав и последовательность выполнения всех функций показана на рисунке.

5.3.     Управление игровыми объектами (GameObjects) с помощью компонентов

В редакторе Unity вы изменяете свойства Компонента используя окно Inspector. Так, например, изменения позиции компонента Transform приведет к изменению позиции игрового объекта. Аналогично, вы можете изменить цвет материала компонента Renderer или массу твёрдого тела (RigidBody) с соответствующим влиянием на отображение или поведение игрового объекта. По большей части скрипты также изменяют свойства компонентов для управления игровыми объектами. Разница, однако, в том, что скрипт может изменять значение свойства постепенно со временем или по получению ввода от пользователя. За счет изменения, создания и уничтожения объектов в заданное время может быть реализован любой игровой процесс.

5.4.     Обращение к компонентам

Наиболее простым и распространенным является случай, когда скрипту необходимо обратиться к другим компонентам, присоединенных к тому же GameObject. Как упоминалось во разделе Введение, компонент на самом деле является экземпляром класса, так что первым шагом будет получение ссылки на экземпляр компонента, с которым вы хотите работать. Это делается с помощью функции GetComponent. Типично, объект компонента сохраняют в переменную, это делается в C# посредством следующего синтаксиса:

void Start () {

    Rigidbody rb = GetComponent<Rigidbody>();

}

 Как только у вас есть ссылка на экземпляр компонента, вы можете устанавливать значения его свойств, тех же, которые вы можете изменить в окне Inspector:

void Start () {

    Rigidbody rb = GetComponent<Rigidbody>();

   

    // Change the mass of the object's Rigidbody.

    rb.mass = 10f;

}

 Дополнительная возможность, недоступная в окне Inspector - вызов функций экземпляра компонента:

void Start () {

    Rigidbody rb = GetComponent<Rigidbody>();

   

    // Add a force to the Rigidbody.

    rb.AddForce(Vector3.up * 10f);

}

 

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

Если вы попытаетесь извлечь Компонент, который не был добавлен к Игровому Объекту, тогда GetComponent вернет null; возникнет ошибка пустой ссылки при выполнении (null reference error at runtime), если вы попытаетесь изменить какие-либо значения у пустого объекта.

5.5.     Связывание объектов через переменные

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

Самый простой способ найти нужный игровой объект - добавить в скрипт переменную типа GameObject с уровнем доступа public:

public class Enemy : MonoBehaviour {

    public GameObject player;

   

    // Other variables and functions...

}

Переменная будет видна в окне Inspector, как и любые другие:

Теперь вы можете перетащить объект со сцены или из панели Hierarchy в эту переменную, чтобы назначить его. Функция GetComponent и доступ к переменным компонента доступны как для этого объекта, так и для других, то есть вы можете использовать следующий код:

public class Enemy : MonoBehaviour {

    public GameObject player;

   

    void Start() {

        // Start the enemy ten units behind the player character.

        transform.position = player.transform.position - Vector3.forward * 10f;

    }

}

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

public Transform playerTransform;

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

5.6.     Нахождение дочерних объектов

Иногда игровая сцена может использовать несколько объектов одного типа, таких как враги, путевые точки и препятствия. Может возникнуть необходимость отслеживания их в определенном скрипте, который управляет или реагирует на них (например, все путевые точки могут потребоваться для скрипта поиска пути). Можно использовать переменные для связывания этих объектов, но это сделает процесс проектирования утомительным, если каждую новую путевую точку нужно будет перетащить в переменную в скрипте. Аналогично, при удалении путевой точки придется удалять ссылку на отсутствующий объект. В случаях, наподобие этого, чаще всего удобно управлять набором объектов, сделав их дочерними одного родительского объекта. Дочерние объекты могут быть получены, используя компонент Transform родителя (так как все игровые объекты неявно содержат Transform):

using UnityEngine;

 

public class WaypointManager : MonoBehaviour {

    public Transform[] waypoints;

   

    void Start() {

        waypoints = new Transform[transform.childCount];

        int i = 0;

       

        foreach (Transform t in transform) {

            waypoints[i++] = t;

        }

    }

}

Вы можете также найти заданный дочерний объект по имени, используя  функцию Transform.Find:

transform.Find("Gun");

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

5.7. Нахождение объектов по имени или тегу

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

GameObject player;

 

void Start() {

    player = GameObject.Find("MainHeroCharacter");

}

Объект или коллекция объектов могут быть также найдены по их тегу, используя функции GameObject.FindWithTag и GameObject.FindGameObjectsWithTag:-

GameObject player;

GameObject[] enemies;

 

void Start() {

    player = GameObject.FindWithTag("Player");

    enemies = GameObject.FindGameObjectsWithTag("Enemy");

}

Интерфейс >>>

Комментарии

Популярные сообщения из этого блога

Лабораторные работы

О курсе...