Поведение игровых объектов контролируется с помощью компонентов (Components), которые присоединяются к ним. Несмотря на то, что встроенные компоненты Unity могут быть очень разносторонними, вскоре вы обнаружите, что вам нужно выйти за пределы их возможностей, чтобы реализовать ваши собственные особенности геймплея. Unity позволяет вам создавать свои компоненты, используя скрипты. Они позволяют активировать игровые события, изменять параметры компонентов, и отвечать на ввод пользователя каким вам угодно способом.
Обзор скриптинга
Unity изначально поддерживает три языка программирования:
- C# (произносится как Си-шарп), стандартный в отрасли язык подобный Java или C++;
- UnityScript, язык, разработанный специально для использования в Unity по образцу JavaScript;
В дополнение к этим, с Unity могут быть использованы многие другие языки семейства .NET, если они могут компилировать совместимые DLL - см. эту страницу для получения подробностей.
Изучение искусства программирования и использования этих языкам выходит за рамки данного введения. Однако есть множество книг, обучающих материалов и ресурсов для изучения программирования в среде Unity. Посетите Обучающий раздел на нашем сайте для получения подробной информации.
Создание скриптов
В отличии от других ассетов, скрипты обычно создаются непосредственно в Unity. Вы можете создать скрипт используя меню Create в левом верхнем углу панели Project или выбрав (или JavaScript/Boo скрипт) в главном меню.
Новый скрипт будет создан в папке, которую вы выбрали в панели Project. Имя нового скрипта будет выделено, предлагая вам ввести новое имя.
Лучше ввести новое имя скрипта сразу после создания чем изменять его потом. Имя, которое вы введете будет использовано, чтобы создать начальный текст в скрипте, как описано ниже.
Структура файла скрипта
После двойного щелчка на скрипте в Unity, он будет открыт в текстовом редакторе. По умолчанию Unity будет использовать MonoDevelop, но вы можете выбрать любой редактор из панели External Tools в настройках Unity.
Содержимое файла будет выглядеть примерно так:
using UnityEngine;
using System.Collections;
public class MainPlayer : MonoBehaviour {
// Use this for initialization
void Start () {
}
// Update is called once per frame
void Update () {
}
}
Скрипт взаимодействует с внутренними механизмами Unity за счет создания класса, наследованного от встроенного класса, называемого MonoBehaviour. Вы можете думать о классе как о своего рода плане для создания нового типа компонента, который может быть прикреплен к игровому объекту. Каждый раз, когда вы присоединяете скриптовый компонент к игровому объекту, создается новый экземпляр объекта, определенный планом. Имя класса берется из имени, которое вы указали при создании файла. Имя класса и имя файла должны быть одинаковыми, для того, чтобы скриптовый компонент мог быть присоединен к игровому объекту.
Основные вещи, достойные внимания, это две функции, определенные внутри класса. Функция Update - это место для размещения кода, который будет обрабатывать обновление кадра для игрового объекта. Это может быть движение, срабатывание действий и ответная реакция на ввод пользователя, в основном всё, что должно быть обработано с течением времени во игровом процессе. Чтобы позволить функции Update выполнять свою работу, часто бывает полезно инициализировать переменные, считать свойства и осуществить связь с другими игровыми объектами до того, как будут совершены какие-либо действия. Функция Start будет вызвана Unity до начала игрового процесса (т.е. до первого вызова функции Update), и это идеальное место для выполнения инициализации.
Заметка для опытных программистов: вы можете быть удивлены, что инициализация объекта выполняется не в функции-конструкторе. Это потому, что создание объектов обрабатывается редактором и происходит не в начале игрового процесса, как вы могли бы ожидать. Если вы попытаетесь определить конструктор для скриптового компонента, он будет мешать нормальной работе Unity и может вызвать серьезные проблемы с проектом.
A UnityScript script works a bit differently to C# script:
#pragma strict
function Start () {
}
function Update () {
}
Здесь функции Start и Update имеют такое же значение, но класс не объявлен явно. Предполагается, что скрипт сам по себе определяет класс; он будет неявно производным от MonoBehaviour и получит своё имя от имени файла скриптового ассета.
Управление игровым объектом
Как было сказано ранее, скрипт определяет только план компонента и, таким образом, никакой его код не будет активирован до тех пор, пока экземпляр скрипта не будет присоединен к игровому объекту. Вы можете прикрепить скрипт перетаскиванием ассета скрипта на игровой объект в панели Hierarchy или через окно Inspector выбранного игрового объекта. Имеется также подменю Scripts в меню Component, которое содержит все скрипты, доступные в проекте, включая те, которые вы создали сами. Экземпляр скрипта выглядит так же, как и другие компоненты в окне Inspector:-
После присоединения скрипт начнет работать, когда вы нажмете Play и запустите игру. Вы можете проверить это добавив следующий код в функцию Start:-
// Use this for initialization
void Start () {
Debug.Log("I am alive!");
}
Debug.Log это команда, которая просто выводит сообщение на консольный вывод Unity. Если вы нажмете сейчас Play, вы увидите сообщение внизу основного окна редактора Unity в окне Console (меню: ).
Variables and the Inspector
When creating a script, you are essentially creating your own new type of component that can be attached to Game Objects just like any other component.
Just like other Components often have properties that are editable in the inspector, you can allow values in your script to be edited from the Inspector too.
using UnityEngine;
using System.Collections;
public class MainPlayer : MonoBehaviour {
public string myName;
// Use this for initialization
void Start () {
Debug.Log("I am alive and my name is " + myName);
}
// Update is called once per frame
void Update () {
}
}
This code creates an editable field in the Inspector labelled “My Name”.
Unity создает метку в окне Inspector путем добавления пробела везде, где в имени переменной встречается заглавная буква. Однако это исключительно в целях отображения, и вы должны всегда в коде использовать имя переменной. Если вы отредактируете значение переменной и затем нажмете Play, вы увидите, что сообщение включает введенный вами текст.
In C#, you must declare a variable as public to see it in the Inspector. In UnityScript, variables are public by default unless you specify that they should be private:
#pragma strict
private var invisibleVar: int;
function Start () {
}
Unity позволяет изменять значение переменных скрипта в запущенной игре. Это очень полезно чтобы увидеть эффекты от изменений сразу же, без остановки и перезапуска. Когда проигрывание оканчивается, значения переменных сбрасываются в то состояние, которое они имели до нажатия кнопки Play. Это гарантирует, что вы свободно можете играть с настройками объектов, не боясь что-то испортить.
Управление игровыми объектами (GameObjects) с помощью компонентов
В редакторе Unity вы изменяете свойства Компонента используя окно Inspector. Так, например, изменения позиции компонента Transform приведет к изменению позиции игрового объекта. Аналогично, вы можете изменить цвет материала компонента Renderer или массу твёрдого тела (RigidBody) с соответствующим влиянием на отображение или поведение игрового объекта. По большей части скрипты также изменяют свойства компонентов для управления игровыми объектами. Разница, однако, в том, что скрипт может изменять значение свойства постепенно со временем или по получению ввода от пользователя. За счет изменения, создания и уничтожения объектов в заданное время может быть реализован любой игровой процесс.
Обращение к компонентам
Наиболее простым и распространенным является случай, когда скрипту необходимо обратиться к другим компонентам, присоединенных к тому же GameObject. Как упоминалось во разделе Введение, компонент на самом деле является экземпляром класса, так что первым шагом будет получение ссылки на экземпляр компонента, с которым вы хотите работать. Это делается с помощью функции GetComponent. Типично, объект компонента сохраняют в переменную, это делается в C# посредством следующего синтаксиса:
void Start () {
Rigidbody rb = GetComponent<Rigidbody>();
}
В UnityScript синтаксис немного отличается:
function Start () {
var 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), если вы попытаетесь изменить какие-либо значения у пустого объекта.
Обращение к другим объектам
Пусть иногда они и существуют изолированно, все же, обычно, скрипты отслеживают другие объекты. Например, преследующий враг должен знать позицию игрока. 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 предоставляет два основных способа сделать это, описанных ниже.
Нахождение дочерних объектов
Иногда игровая сцена может использовать несколько объектов одного типа, таких как враги, путевые точки и препятствия. Может возникнуть необходимость отслеживания их в определенном скрипте, который управляет или реагирует на них (например, все путевые точки могут потребоваться для скрипта поиска пути). Можно использовать переменные для связывания этих объектов, но это сделает процесс проектирования утомительным, если каждую новую путевую точку нужно будет перетащить в переменную в скрипте. Аналогично, при удалении путевой точки придется удалять ссылку на отсутствующий объект. В случаях, наподобие этого, чаще всего удобно управлять набором объектов, сделав их дочерними одного родительского объекта. Дочерние объекты могут быть получены, используя компонент 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");
Это может быть полезно, когда объект содержит дочерний элемент, который может быть добавлен или удален в игровом процессе. Хороший пример - оружие, которое может быть подобрано и выброшено.
Нахождение объектов по имени или тегу
Нахождение игровых объектов в любом месте иерархии доступно всегда, когда у вас есть некоторая информация, по которой их можно идентифицировать. Отдельные объекты могут быть получены по имени, используя функцию 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"); }
Функции событий
Скрипт в Unity не похож на традиционную идею программы, где код работает постоянно в цикле, пока не завершит свою задачу. Вместо этого, Unity периодически передаёт управление скрипту при вызове определённых объявленных в нём функций. Как только функция завершает исполнение, управление возвращается обратно к Unity. Эти функции известны как функции событий, т.к. их активирует Unity в ответ на события, которые происходят в процессе игры. Unity использует схему именования, чтобы определить, какую функцию вызвать для определённого события. Например, вы уже видели функцию Update (вызывается перед сменой кадра) и функцию Start (вызывается прямо перед первым кадром объекта). В Unity доступно гораздо большее количество функций событий; полный список с дополнительной информацией по их применению можно найти на странице документации класса MonoBehaviour. Далее указаны одни из самых важных и часто встречающихся событий.
Обычные Update события
Игра - это что-то вроде анимации, в которой кадры генерируются на ходу. Ключевой концепт в программировании игр заключается в изменении позиции, состояния и поведения объектов в игре прямо перед отрисовкой кадра. Такой код в Unity обычно размещают в функции Update. Update вызывается перед отрисовкой кадра и перед расчётом анимаций.
void Update() {
float distance = speed * Time.deltaTime * Input.GetAxis("Horizontal");
transform.Translate(Vector3.right * distance);
}
Физический движок также обновляется фиксированными по времени шагами, аналогично тому как работает отрисовка кадра. Отдельная функция события FixedUpdate вызывается прямо перед каждым обновлением физических данных. Т.к. обновление физики и кадра происходит не с одинаковой частотой, то вы получите более точные результаты от кода физики, если поместите его в функцию FixedUpdate, а не в Update.
void FixedUpdate() {
Vector3 force = transform.forward * driveForce * Input.GetAxis("Vertical");
rigidbody.AddForce(force);
}
Также иногда полезно иметь возможность внести дополнительные изменения в момент, когда у всех объектов в сцене отработали функции Update и FixedUpdate и рассчитались все анимации. В качестве примера, камера должна оставаться привязанной к целевому объекту; подстройка ориентации камеры должна производиться после того, как целевой объект сместился. Другим примером является ситуация, когда код скрипта должен переопределить эффект анимации (допустим, заставить голову персонажа повернуться к целевому объекту в сцене). В ситуациях такого рода можно использовать функцию LateUpdate.
void LateUpdate() {
Camera.main.transform.LookAt(target.transform);
}
События инициализации
Зачастую полезно иметь возможность вызвать код инициализации перед любыми обновлениями, происходящими во время игры. Функция Start вызывается до обновления первого кадра или физики объекта. Функция Awake вызывается для каждого объекта в сцене в момент загрузки сцены. Учтите, что хоть для разных объектов функции Start и Awake и вызываются в разном порядке, все Awake будут выполнены до вызова первого Start. Это значит, что код в функции Start может использовать всё, что было сделано в фазе Awake.
События GUI
В Unity есть система для отрисовки элементов управления GUI поверх всего происходящего в сцене и реагирования на клики по этим элементам. Этот код обрабатывается несколько иначе, нежели обычное обновление кадра, так что он должен быть помещён в функцию OnGUI, которая будет периодически вызываться.
void OnGUI() {
GUI.Label(labelRect, "Game Over");
}
Вы также можете определять события мыши, которые срабатывают у GameObject’а, находящегося в сцене. Это можно использовать для наведения орудий или для отображения информации о персонаже под указателем курсора мыши. Существует набор функций событий OnMouseXXX (например, OnMouseOver, OnMouseDown), который позволяет скрипту реагировать на действия с мышью пользователя. Например, если кнопка мыши нажата в то время, когда курсор мыши находится над определённым объектом, то, если в скрипте этого объекта присутствует функция OnMouseDown, она будет вызвана.
События физики
Физический движок сообщит о столкновениях с объектом с помощью вызова функций событий в скрипте этого объекта. Функции OnCollisionEnter, OnCollisionStay и OnCollisionExit будут вызваны по началу, продолжению и завершению контакта. Соответствующие функции OnTriggerEnter, OnTriggerStay и OnTriggerExit будут вызваны когда коллайдер объекта настроен как Trigger (т.е. этот коллайдер просто определяет, что его что-то пересекает и не реагирует физически). Эти функции могут быть вызваны несколько раз подряд, если обнаружен более чем один контакт во время обновления физики, поэтому в функцию передаётся параметр, предоставляющий дополнительную информацию о столкновении (координаты, “личность” входящего объекта и т.д.).
void OnCollisionEnter(otherObj: Collision) {
if (otherObj.tag == "Arrow") {
ApplyDamage(10);
}
}
Управление временем и частотой кадров
Функция обновления позволяет регулярно отслеживать входные данные и другие события из сценария и предпринимать соответствующие действия. Например, вы можете перемещать символ при нажатии клавиши «вперед». При обработке таких действий, основанных на времени, важно помнить, что частота кадров в игре не постоянна и не является промежутком времени между вызовами функции Update.
В качестве примера рассмотрим задачу постепенного перемещения объекта вперед по одному кадру за раз. Сначала может показаться, что вы можете просто сдвинуть объект на фиксированное расстояние в каждом кадре:
//C# script example
using UnityEngine;
using System.Collections;
public class ExampleScript : MonoBehaviour {
public float distancePerFrame;
void Update() {
transform.Translate(0, 0, distancePerFrame);
}
}
//JS script example
var distancePerFrame: float;
function Update() {
transform.Translate(0, 0, distancePerFrame);
}
Однако, учитывая, что время кадра не является постоянным, объект будет двигаться с нерегулярной скоростью. Если время кадра составляет 10 миллисекунд, то объект будет двигаться вперед с помощью distancePerFrame сто раз в секунду. Но если время кадра увеличивается до 25 миллисекунд (скажем, из-за нагрузки на процессор), то оно будет только шагать вперед сорок раз в секунду и, следовательно, охватывать меньшее расстояние. Решение состоит в том, чтобы масштабировать размер движения по времени кадра, которое вы можете прочитать из свойства Time.deltaTime :
//C# script example
using UnityEngine;
using System.Collections;
public class ExampleScript : MonoBehaviour {
public float distancePerSecond;
void Update() {
transform.Translate(0, 0, distancePerSecond * Time.deltaTime);
}
}
//JS script example
var distancePerSecond: float;
function Update() {
transform.Translate(0, 0, distancePerSecond * Time.deltaTime);
}
Обратите внимание, что движение теперь задается как distancePerSecond, а не distancePerFrame . По мере изменения частоты кадров размер шага перемещения будет меняться соответственно, и поэтому скорость объекта будет постоянной.
Фиксированный временной шаг
В отличии от основного обновления кадров, система физики Юнити делает работу с фиксированным временным шагом, который имеет важное значение для точности и последовательности моделирования. В начале обновления физики Unity устанавливает «сигнал тревоги», добавляя фиксированное значение временного шага ко времени окончания последнего обновления физики. Затем физическая система будет выполнять вычисления, пока не сработает сигнализация.
Вы можете изменить размер фиксированного временного шага из Time Manager и прочитать его из скрипта, используя свойство Time.fixedDeltaTime . Обратите внимание, что более низкое значение временного шага приведет к более частым обновлениям физики и более точному моделированию, но за счет большей загрузки процессора. Вероятно, вам не нужно будет менять фиксированный временной интервал по умолчанию, если вы не предъявляете высокие требования к физическому движку.
Максимально допустимый временной шаг
Фиксированный временной интервал сохраняет точность физического моделирования в реальном времени, но он может вызвать проблемы в тех случаях, когда игра интенсивно использует физику, а частота кадров в игровом процессе также становится низкой (например, из-за большого количества объектов в игре). Обработка обновлений основного кадра должна быть «зажата» между регулярными обновлениями физики, и, если требуется выполнить большую обработку, в течение одного кадра может происходить несколько обновлений физики. Поскольку время кадра, положения объектов и другие свойства заморожены в начале кадра, графика может не синхронизироваться с более часто обновляемой физикой.
Естественно, есть только так много доступной мощности процессора, но Unity имеет возможность, позволяющую вам эффективно замедлять физическое время, чтобы ускорить обработку кадров. Параметр « Максимально допустимый временной шаг» (в « Диспетчере времени» ) ограничивает количество времени, которое Unity будет тратить на обработку физических вызовов и вызовов FixedUpdate во время обновления данного кадра. Если обновление кадра занимает больше времени, чем Максимально допустимый временной шагчтобы обработать, физический движок «остановит время» и позволит обработке кадров наверстать упущенное. Как только обновление кадра закончится, физика возобновится, как будто времени не прошло с момента его остановки. Результатом этого является то, что твердые тела не будут двигаться идеально в режиме реального времени, как это обычно происходит, но будут слегка замедлены. Однако физические «часы» все равно будут отслеживать их, как если бы они двигались нормально. Замедление времени физики обычно не заметно и является приемлемым компромиссом с производительностью геймплея.
Шкала времени
Для специальных эффектов, таких как «время маркера», иногда полезно замедлить прохождение игрового времени, чтобы анимация и отклики сценария происходили с пониженной скоростью. Кроме того, иногда вы можете захотеть полностью заморозить игровое время, например, когда игра приостановлена. Unity имеет свойство Time Scale, которое контролирует, как быстро игровое время протекает относительно реального времени. Если масштаб установлен на 1,0, то игровое время соответствует реальному времени. При значении 2.0 время в Unity увеличивается вдвое быстрее (т.е. действие будет ускорено), а при значении 0.5 замедляется игровой процесс до половины скорости. Нулевое значение заставит время полностью остановиться. Обратите внимание, что шкала времени на самом деле не замедляет выполнение, а просто изменяет шаг по времени, сообщаемый функциям Update и FixedUpdate через Time.deltaTime иTime.fixedDeltaTime . Функция обновления, вероятно, будет вызываться чаще, чем обычно, когда игровое время замедляется, но шаг deltaTime, о котором сообщается, каждый кадр будет просто уменьшен. Шкала времени не влияет на другие функции скрипта, поэтому вы можете, например, отображать графический интерфейс с нормальным взаимодействием, когда игра находится в режиме паузы.
Time Manager имеет свойство , чтобы установить масштаб времени во всем мире , но это , как правило , более полезным , чтобы установить значение из сценария , используя Time.timeScale свойства:
//C# script example
using UnityEngine;
using System.Collections;
public class ExampleScript : MonoBehaviour {
void Pause() {
Time.timeScale = 0;
}
void Resume() {
Time.timeScale = 1;
}
}
//JS script example
function Pause() {
Time.timeScale = 0;
}
function Resume() {
Time.timeScale = 1;
}
Захват кадров
Особый случай управления временем - это когда вы хотите записать игровой процесс в виде видео. Поскольку задача сохранения изображений на экране занимает значительное время, обычная частота кадров в игре будет значительно снижена, если вы попытаетесь сделать это во время обычного игрового процесса. Это приведет к видео, которое не отражает истинную производительность игры.
К счастью, Unity предоставляет свойство Capture Framerate, которое позволяет обойти эту проблему. Если для свойства задано значение, отличное от нуля, игровое время будет замедлено, а обновления кадров будут выпускаться с точными регулярными интервалами. Интервал между кадрами равен 1 / Time.captureFramerate, поэтому, если установлено значение 5.0, обновления происходят каждую пятую секунды. С уменьшением требований к частоте кадров у вас есть время в функции обновления, чтобы сохранить скриншоты или предпринять другие действия:
//C# script example
using UnityEngine;
using System.Collections;
public class ExampleScript : MonoBehaviour {
// Capture frames as a screenshot sequence. Images are
// stored as PNG files in a folder - these can be combined into
// a movie using image utility software (eg, QuickTime Pro).
// The folder to contain our screenshots.
// If the folder exists we will append numbers to create an empty folder.
string folder = "ScreenshotFolder";
int frameRate = 25;
void Start () {
// Set the playback framerate (real time will not relate to game time after this).
Time.captureFramerate = frameRate;
// Create the folder
System.IO.Directory.CreateDirectory(folder);
}
void Update () {
// Append filename to folder name (format is '0005 shot.png"')
string name = string.Format("{0}/{1:D04} shot.png", folder, Time.frameCount );
// Capture the screenshot to the specified file.
Application.CaptureScreenshot(name);
}
}
//JS script example
// Capture frames as a screenshot sequence. Images are
// stored as PNG files in a folder - these can be combined into
// a movie using image utility software (eg, QuickTime Pro).
// The folder to contain our screenshots.
// If the folder exists we will append numbers to create an empty folder.
var folder = "ScreenshotFolder";
var frameRate = 25;
function Start () {
// Set the playback framerate (real time will not relate to game time after this).
Time.captureFramerate = frameRate;
// Create the folder
System.IO.Directory.CreateDirectory(folder);
}
function Update () {
// Append filename to folder name (format is '0005 shot.png"')
var name = String.Format("{0}/{1:D04} shot.png", folder, Time.frameCount );
// Capture the screenshot to the specified file.
Application.CaptureScreenshot(name);
}
Хотя видео, записанное с использованием этой техники, обычно выглядит очень хорошо, в игру может быть трудно играть, когда она сильно замедлена. Возможно, вам придется поэкспериментировать со значением Time.captureFramerate, чтобы обеспечить достаточное время записи без чрезмерного усложнения задачи тестового проигрывателя.
Создание и уничтожение игровых объектов (GameObjects)
Некоторые игры имеют постоянное количество объектов на сцене, однако обычно персонажи, сокровища и другие объекты создаются и удаляются во время игры. В Unity, игровой объект (GameObject) может быть создан используя функцию Instantiate, которая делает копию существующего объекта:-
public GameObject enemy;
void Start() {
for (int i = 0; i < 5; i++) {
Instantiate(enemy);
}
}
Заметьте, что объект с которого берется копия не обязан присутствовать на сцене. Гораздо чаще используется префаб, который был перетащен на открытую переменную (public variable) из файлов проекта в панели Project. Также, копируя игровой объект (GameObject), вы копируете все компоненты оригинального объекта.
Также есть функция Destroy, которая уничтожит объект после того, как загрузка кадра будет завершена или опционально после короткой паузы:-
void OnCollisionEnter(Collision otherObj) {
if (otherObj.gameObject.tag == "Missile") {
Destroy(gameObject,.5f);
}
}
Заметьте что функция Destroy может уничтожать отдельные компоненты без влияния на сам объект. Частая ошибка - писать что-то вроде этого:-
Destroy(this);
…что на самом деле уничтожит только вызывающий скриптовый компонент, вместо того, чтобы уничтожить игровой объект, к которому присоединен этот скрипт.
Сопрограммы
Когда вы вызываете функцию, она должна полностью выполниться, прежде чем вернуть какое-то значение. Фактически, это означает, что любые действия, происходящие в функции, должны выполниться в течение одного кадра; вызовы функций не пригодны для, скажем, процедурной анимации или любой другой временной последовательности. В качестве примера мы рассмотрим задачу по уменьшению прозрачности объекта до его полного исчезновения.
void Fade() {
for (float f = 1f; f >= 0; f -= 0.1f) {
Color c = renderer.material.color;
c.a = f;
renderer.material.color = c;
}
}
Как можно заметить, функция Fade не имеет визуального эффекта, который мы хотели получить. Для скрытия объекта нам нужно было постепенно уменьшить прозрачность, а значит иметь некоторую задержку для того, чтобы были отображены промежуточные значения параметра. Однако функция выполняется в полном объеме, за одно обновление кадра. Промежуточные значения, увы, не будут видны и объект исчезнет мгновенно.
С подобными ситуациями можно справиться, добавив в функцию Update код, изменяющий прозрачность кадр за кадром. Однако для подобных задач зачастую удобнее использовать корутины.
Сопрограмма похожа на функцию, которая может приостановить выполнение и вернуть управление Unity, но затем продолжить с того места, где остановилась в следующем кадре. В C # сопрограмма объявляется так:
IEnumerator Fade() {
for (float f = 1f; f >= 0; f -= 0.1f) {
Color c = renderer.material.color;
c.a = f;
renderer.material.color = c;
yield return null;
}
}
По сути, это функция, объявленная с типом возврата IEnumerator и с оператором yield return, включенным где-то в теле. Линия возврата урожая - это точка, в которой выполнение приостанавливается и возобновляется в следующем кадре. Чтобы запустить сопрограмму, вам нужно использовать функцию StartCoroutine :
void Update() {
if (Input.GetKeyDown("f")) {
StartCoroutine("Fade");
}
}
В UnityScript все немного проще. Любая функция, которая включает оператор yield, понимается как сопрограмма, и тип возвращаемого значения IEnumerator не должен быть явно объявлен:
function Fade() {
for (var f = 1.0; f >= 0; f -= 0.1) {
var c = renderer.material.color;
c.a = f;
renderer.material.color = c;
yield;
}
}
Также сопрограмму можно запустить в UnityScript, вызвав ее, как если бы она была нормальной функцией:
function Update() {
if (Input.GetKeyDown("f")) {
Fade();
}
}
Можно заметить, что счетчик цикла в функции Fade сохраняет правильное значение во время работы корутины. Фактически, любая переменная или параметр будут корректно сохранены между вызовами оператора yield.
По умолчанию сопрограмма возобновляется в кадре после его выдачи, но также можно ввести задержку по времени, используя WaitForSeconds :
IEnumerator Fade() {
for (float f = 1f; f >= 0; f -= 0.1f) {
Color c = renderer.material.color;
c.a = f;
renderer.material.color = c;
yield return new WaitForSeconds(.1f);
}
}
и в UnityScript:
function Fade() {
for (var f = 1.0; f >= 0; f -= 0.1) {
var c = renderer.material.color;
c.a = f;
renderer.material.color = c;
yield WaitForSeconds(0.1);
}
}
Это может использоваться как способ распространения эффекта по времени, но также является полезной оптимизацией. Многие задачи в игре необходимо выполнять периодически, и наиболее очевидный способ сделать это - включить их в функцию обновления. Однако эта функция обычно вызывается много раз в секунду. Когда задачу не нужно повторять достаточно часто, вы можете поместить ее в сопрограмму, чтобы получать обновления регулярно, но не каждый отдельный кадр. Примером этого может быть сигнал тревоги, который предупреждает игрока, если враг находится поблизости. Код может выглядеть примерно так:
function ProximityCheck() {
for (int i = 0; i < enemies.Length; i++) {
if (Vector3.Distance(transform.position, enemies[i].transform.position) < dangerDistance) {
return true;
}
}
return false;
}
Если врагов много, то при вызове этой функции каждый кадр может привести к значительным накладным расходам. Однако вы можете использовать сопрограмму для вызова ее каждую десятую секунды:
IEnumerator DoCheck() {
for(;;) {
ProximityCheck;
yield return new WaitForSeconds(.1f);
}
}
Это значительно уменьшит число проверок, но не окажет заметного влияния на игровой процесс.
Специальные папки и порядок компиляции скриптов
По большей части, вы можете выбирать любые имена для папок вашего проекта. Однако в Unity зарезервированы некоторые имена, которые указывают на то, что содержащийся в папках контент имеет специальное назначение. Некоторые из них влияют на порядок компиляции скриптов. По сути, у компиляции скриптов есть четыре отдельные фазы и порядок их компилирования определяется родительской папкой.
Это имеет большое значение в случае, если скрипт должен обратится к классам, определенным в других скриптах. Основное правило заключается в том, чтобы не было ссылок на скрипты, компилирующиеся в фазе после. Все, что компилируется в текущей или ранее выполненной фазе, должно быть полностью доступно.
Другая ситуация бывает когда скрипт, написанный на одном языке, должен ссылаться на класс, определенный на другом языке(например, скрипт на языке UnityScript объявляет переменные класса, определенные в C# скрипте). В этом случае класс, на который ссылаются, должен быть скомпилирован в более ранней фазе.
Есть следующие фазы компиляции:-
Фаза 1: Выполняются скрипты из папок с именами Standard Assets, Pro Standard Assets и Plugins.
Фаза 1: Выполняются скрипты из папок с именами Standard Assets, Pro Standard Assets и Plugins.
Фаза 3: Все прочие скрипты, не находящиеся в папке Editor.
Фаза 4: Все оставшиеся скрипты (т.е. находящиеся в папке Editor).
Дополнительно, любой скрипт, находящийся в папке WebPlayerTemplates, в самом верху папки Assets, вообще не будет скомпилирован. Это поведение немного отличается для вложенных папок с другими именами (например, Scripts/Editor работает как папка для скриптов редактора и Scripts/WebPlayerTemplates не помешает компиляции).
В общем случае, скрипт UnityScript ссылается на класс, определенный на языке C#. Здесь нужно разместить C# файл в папку Plugins, а UnityScript - в любую не специальную папку. Если этого не сделать, получите ошибку, говорящую о том, что C# класс не найден.
Note. Standard Assets work only in the Assets root folder.
Пространства имён
Когда проект становится больше, а количество классов увеличивается, вероятность совпадения имён классов также возрастает. Такое может произойти, если несколько программистов работают отдельно над различными аспектами игры. Например, один программист пишет код для управления персонажем игрока, в то время как другой пишет эквивалентный код для управления противниками. Оба программиста предпочли назвать главный класс Controller, однако при объединении их проектов это приведет к совпадению имен классов, в результате чего использование класса с таким именем оказалось бы двусмысленным.
В некоторой степени, эта проблема может быть решена путем переименования классов, когда обнаружено совпадение имен (например, классы выше могут иметь имена PlayerController и EnemyController). Однако, это весьма проблематично, когда есть несколько таких классов, или когда объявляются переменные с их использованием - каждое упоминание старого названия класса должно быть заменено, чтобы код мог скомпилироваться.
Язык C# предлагает пространства имен в качестве решения данной проблемы. Пространство имен - это просто набор классов, в котором для всех имён этих классов используется определенный префикс. В приведённом ниже примере классы Controller1 и Controller2 относятся к пространству имен Enemy:-
namespace Enemy {
public class Controller1 : MonoBehaviour {
...
}
public class Controller2 : MonoBehaviour {
...
}
}
В коде эти классы вызываются соответственно
Enemy.Controller1
и Enemy.Controller2
. Это лучше, чем переименование классов, поскольку объявление пространства имен может быть выполнено для существующих объявлений класса (то есть, нет необходимости менять имена всех классов индивидуально). Кроме того, вы можете использовать одинаковые пространства имен для классов, которые хранятся в разных исходных файлах.
Вы можете избежать многократного указания префикса пространства имен, добавив директиву using в верхней части файла.
using Enemy;
Эта строка говорит о том, что использование названия классов Controller1 и Controller2 на самом деле будет обозначать классы Enemy.Controller1 и Enemy.Controller2 соответственно. Если в скрипте предполагается использование классов с таким же именем из другого пространства имен (предположим из пространства Player), то для их вызова нужно будет указать префикс. Если имена используемых классов из двух разных пространств имен указанных в директиве совпадут - компилятор сообщит об ошибке.
Атрибуты
Атрибуты (Attributes) это маркеры, которые могут быть помещены перед классом, свойствами или функциями в скрипте, чтобы указать особое поведение. Например атрибут HideInInspector может быть добавлен перед объявлением свойства для предотвращения отображения этого свойства в инспекторе, даже если оно публичное. В JavaScript имя атрибута начинается со знака “@”, а в C# и Boo, он помещается между квадратными скобками:-
// JS
@HideInInspector
var strength: float;
// C#
[HideInInspector]
public float strength;
Unity предоставляет ряд атрибутов, которые перечислены в справочнике по скриптам (выберите секцию атрибутов Редактора или рантайм атрибутов из выпадающего меню слева). Есть также атрибуты, определенные в .NET библиотеке, которые иногда могут быть полезны в Юнити коде.
Примечание: атрибут ThreadStatic определенный в .NET нельзя использовать, т.к. он вызовет крах Unity.
Порядок выполнения функций событий
В скриптинге Unity есть некоторое количество функций события, которые исполняются в заранее заданном порядке по мере исполнения скрипта. Этот порядок исполнения описан ниже:
Редактор
- Reset: Reset (сброс) вызывается для инициализации свойств скрипта, когда он только присоединяется к объекту и тогда, когда используется команда Reset.
Первая загрузка сцены
Эти функции вызываются при запуске сцены (один раз для каждого объекта на сцене).
- Awake: Эта функция всегда вызывается до любых функций Start и также после того, как префаб был вызван в сцену (если GameObject неактивен на момент старта, Awake не будет вызван, пока GameObject не будет активирован, или функция в каком-нибудь прикреплённом скрипте не вызовет Awake).
- OnEnable: (вызывается только если объект активен): Эта функция вызывается сразу после включения объекта. Это происходит при создании образца MonoBehaviour, например, при загрузке уровня или был вызван GameObject с компонентом скрипта.
- OnLevelWasLoaded: This function is executed to inform the game that a new level has been loaded.
Учтите, что для объектов, добавленных в сцену сразу, функции Awake и OnEnable для всех скриптов будут вызваны до вызова Start, Update и т.д. Естественно, для объектов вызванных во время игрового процесса такого не будет.
Перед первым обновлением кадра
- Start: Функция Start вызывается до обновления первого кадра(first frame) только если скрипт включен.
Для объектов добавленных на сцену, функция Start будет вызываться во всех скриптах до функции Update. Естественно, это не может быть обеспечено при создании объекта непосредственно во время игры.
Между кадрами
- OnApplicationPause: Эта функция вызывается в конце кадра, во время во время которого вызывается пауза, что эффективно между обычными обновлениями кадров. Один дополнительный кадр будет выдан после вызова OnApplicationPause, чтобы позволить игре отобразить графику, которая указывает на состояние паузы.
Порядок обновления
Когда вы отслеживаете игровую логику и взаимодействия, анимации, позиции камеры и т.д. есть несколько разных событий, которые вы можете использовать. По общему шаблону, большая часть задач выполняется внутри функции Update, но есть также ещё другие функции, которые вы можете использовать.
- FixedUpdate: Зачастую случается, что FixedUpdate вызывается чаще чем Update. FU может быть вызван несколько раз за кадр, если FPS низок и функция может быть и вовсе не вызвана между кадрами, если FPS высок. Все физические вычисления и обновления происходят сразу после FixedUpdate. При применении расчётов передвижения внутри FixedUpdate, вам не нужно умножать ваши значения на Time.deltaTime. Потому что FixedUpdate вызывается в соответствии с надёжным таймером, независящим от частоты кадров.
- Update: Update вызывается раз за кадр. Это главная функция для обновлений кадров.
- LateUpdate: LateUpdate вызывается раз в кадр, после завершения Update. Любые вычисления произведённые в Update будут уже выполнены на момент начала LateUpdate. Часто LateUpdate используют для преследующей камеры от третьего лица. Если вы перемещаете и поворачиваете персонажа в Update, вы можете выполнить все вычисления перемещения и вращения камеры в LateUpdate. Это обеспечит то, что персонаж будет двигаться до того, как камера отследит его позицию.
Рендеринг
- OnPreCull: Вызывается до того, как камера отсечёт сцену. Отсечение определяет, какие объекты будут видны в камере. OnPreCull вызывается прямо перед тем, как начинается отсечение.
- OnBecameVisible/OnBecameInvisible: Вызывается тогда, когда объект становится видимым/невидимым любой камере.
- OnWillRenderObject: Вызывается один раз для каждой камеры, если объект в поле зрения.
- OnPreRender: Вызывается перед тем, как камера начнёт рендерить сцену.
- OnRenderObject: Вызывается, после того, как все обычные рендеры сцены завершатся. Вы можете использовать класс GL или Graphics.DrawMeshNow, чтобы рисовать пользовательскую геометрию в данной точке.
- OnPostRender: Вызывается после того, как камера завершит рендер сцены.
- OnRenderImage(только в Pro версии): Вызывается после завершения рендера сцены, для возможности пост-обработки изображения экрана.
- OnGUI: Вызывается несколько раз за кадр и отвечает за элементы интерфейса (GUI). Сначала обрабатываются события макета и раскраски, после чего идут события клавиатуры/мышки для каждого события.
- OnDrawGizmos Используется для отрисовки гизмо в окне Scene View в целях визуализации.
Сопрограммы
Нормальные обновления сопрограмм запускаются после завершения из функции Update. Сопрограмма это функция, которая приостанавливает своё исполнение (yield), пока данные YieldInstruction не завершатся. Разные способы использования сопрограмм:
- yield Сопрограмма продолжит выполнение, после того, как все Update функции были вызваны в следующем кадре.
- yield WaitForSeconds Продолжает выполнение после заданной временной задержки, и после все Update функций, вызванных в итоговом кадре.
- yield WaitForFixedUpdate Продолжает выполнение после того, как все функции FixedUpdate были вызваны во всех скриптах
- yield WWW продолжает выполнение после завершения WWW-загрузки.
- yield StartCoroutine сцепляет сопрограмму, и будет ждать, пока не завершится сопрограмма MyFunc.
Когда объект разрушается
- OnDestroy: Эта функция вызывается после всех обновлений кадра в последнем кадре объекта, пока он ещё существует (объект может быть уничтожен при помощи Object.Destroy или при закрытии сцены).
При выходе
Эти функции вызываются во всех активных объектах в вашей сцене:
- OnApplicationQuit: Эта функция вызывается для всех игровых объектов перед тем, как приложение закрывается. В редакторе вызывается тогда, когда игрок останавливает игровой режим. В веб-плеере вызывается по закрытия веб окна.
- OnDisable: Эта функция вызывается, когда объект отключается или становится неактивным.
Блок-схема жизненного цикла скрипта
Следующая диаграмма совмещает порядок и повтор функций событий в течение жизни скрипта.
Платформенно зависимая компиляция
Unity включает в себя функцию под названием “Платформенно зависимая компиляция”. Она состоит из некоторых директив препроцессора, которые позволяют вам разделить свои скрипты для компиляции и выполнения части кода исключительно для одной из поддерживаемых платформ.
Кроме того, вы можете запустить этот код в редакторе, таким образом, вы можете скомпилировать код специально для вашего мобильного/консоли и проверить его в редакторе!
Определения платформ
Определения платформ для ваших скриптов, которые Unity поддерживает:
Свойство: | Функция: |
---|---|
UNITY_EDITOR | Определение для вызова скриптов редактора Unity из вашего игрового кода. |
UNITY_EDITOR_WIN | Определение платформы для редактора кода на Windows. |
UNITY_EDITOR_OSX | Определение платформы для редактора кода на Windows. |
UNITY_STANDALONE_OSX | Определение платформы для компиляции/выполнения кода специально для Mac OS (это включает в себя Universal, PPC и Intel архитектуры). |
UNITY_STANDALONE_WIN | Используйте, когда вы хотите скомпилировать/выполнить код для приложения на Windows. |
UNITY_STANDALONE_LINUX | Используйте, когда вы хотите скомпилировать/выполнить код для приложения на Linux. |
UNITY_STANDALONE | Используйте, когда вы хотите скомпилировать/выполнить код для любой платформы Mac, Windows или Linux. |
UNITY_WII | Определение платформы для компиляции/выполнения кода для консоли Wii. |
UNITY_IOS | Определение платформы для компиляции/выполнения кода для iPhone. |
UNITY_IPHONE | Эквивалент UNITY_WP8 \ |
UNITY_ANDROID | Определение платформы для Android. |
UNITY_PS3 | Определение платформы для запуска кода на PlayStation 3. |
UNITY_PS4 | Определение платформы для запуска кода на PlayStation 3. |
UNITY_SAMSUNGTV | Определение платформы для выполнения кода на Xbox 360. |
UNITY_XBOX360 | Определение платформы для выполнения кода на Xbox 360. |
UNITY_XBOXONE | Определение платформы для выполнения кода на Xbox 360. |
UNITY_TIZEN | Определение платформы для Android. |
UNITY_TVOS | Определение платформы для Android. |
UNITY_WP_8 | Определение платформы для Windows Phone 8. |
UNITY_WP_8_1 | Определение платформы для Windows Phone 8. |
UNITY_WSA | Определение платформы для Windows Store Apps (дополнительно определяется NETFX_CORE при компиляции C#-файлов в отношении ядра .NET). |
UNITY_WSA_8_0 | Определение платформы для Windows Phone 8. |
UNITY_WSA_8_1 | Определение платформы для Windows Phone 8. |
UNITY_WSA_10_0 | Определение платформы для Windows Store Apps (дополнительно определяется NETFX_CORE при компиляции C#-файлов в отношении ядра .NET). |
UNITY_WINRT | Эквивалент UNITY_WP8 | UNITY_WSA. |
UNITY_WINRT_8_0 | Эквивалент UNITY_WP8 | UNITY_WSA_8_0. |
UNITY_WINRT_8_1 | Эквивалент UNITY_WP8 | UNITY_WSA_8_1. This is also defined when compiling against Universal SDK 8.1. |
UNITY_WINRT_10_0 | Equivalent to UNITY_WSA_10_0 |
UNITY_WEBGL | Определение платформы для Windows Phone 8. |
UNITY_ADS | Определение для вызова скриптов редактора Unity из вашего игрового кода. |
UNITY_ANALYTICS | Определение для вызова скриптов редактора Unity из вашего игрового кода. |
UNITY_ASSERTIONS | #define directive for assertions control process. |
Также вы можете скомпилировать код избирательно, в зависимости от версии движка. В настоящее время поддерживаются: Given a version number X.Y.Z (for example, 2.6.0), Unity exposes three global #define directives in the following formats: UNITY_X, UNITY_X_Y and UNITY_X_Y_Z.
Here is an example of #define directives exposed in Unity 5.0.1:
UNITY_5 | Определение платформы для мажор-версии Unity 2.6. |
UNITY_5_0 | Определение платформы для мажор-версии Unity 3.0. |
UNITY_5_0_1 | Определение платформы для мажор-версии Unity 3.0. |
You can compile code selectively based on the earliest version of Unity required to compile or execute a given portion of code. Given the same version format as above (X.Y.Z), Unity exposes one global #define directive that can be used for this purpose, in the format UNITY_X_Y_OR_NEWER.
The supported #define directives are:
UNITY_5_3_OR_NEWER | Определение платформы для мажор-версии Unity 4.0. |
You can also compile code selectively depending on the scripting back-end.
ENABLE_MONO | Scripting back-end #define directive for Mono. |
ENABLE_IL2CPP | Scripting back-end #define directive for IL2CPP. |
ENABLE_DOTNET | Scripting back-end #define directive for .NET. |
You can also use the
DEVELOPMENT_BUILD
#define directive to identify whether your script is running in a player which was built with the “Development Build” option enabled.Тестирование прекомпилированного кода.
Мы собираемся показать небольшой пример, как использовать прекомпилированный код. В данном примере просто выводится сообщение в зависимости от платформы, которую вы выбрали в качестве целевой для сборки.
Во-первых, выберите платформу, для которой вы хотите проверить ваш код, кликнув на . Откроется окно Build Settings для выбора целевой платформы.
Выберите платформу, для который вы хотите проверить ваш прекомпилированный код, и нажмите кнопку , тем самым указывая Unity целевую платформу.
Создайте скрипт и скопируйте этот код:-
// JS
function Awake() {
#if UNITY_EDITOR
Debug.Log("Unity Editor");
#endif
#if UNITY_IPHONE
Debug.Log("Iphone");
#endif
#if UNITY_STANDALONE_OSX
Debug.Log("Stand Alone OSX");
#endif
#if UNITY_STANDALONE_WIN
Debug.Log("Stand Alone Windows");
#endif
}
// C#
using UnityEngine;
using System.Collections;
public class PlatformDefines : MonoBehaviour {
void Start () {
#if UNITY_EDITOR
Debug.Log("Unity Editor");
#endif
#if UNITY_IOS
Debug.Log("Iphone");
#endif
#if UNITY_STANDALONE_OSX
Debug.Log("Stand Alone OSX");
#endif
#if UNITY_STANDALONE_WIN
Debug.Log("Stand Alone Windows");
#endif
}
}
To test the code, click Play Mode. Confirm that the code works by checking for the relevant message in the Unity console, depending on which platform you selected - for example, if you choose iOS, the message “Iphone” is set to appear in the console.
Учтите, что в C# вы можете использовать атрибут
CONDITIONAL
, который более прозрачен в использовании и является менее подверженным ошибкам способом вырезания функций, см. http://msdn.microsoft.com/en-us/library/4xssyw96(v=vs.90).aspx.
В дополнение к основной #if директиве компилятора, вы также можете использовать многооборотную проверку в C# и JavaScript:-
#if UNITY_EDITOR
Debug.Log("Unity Editor");
#elif UNITY_IOS
Debug.Log("Unity iPhone");
#else
Debug.Log("Any other platform");
#endif
Пользовательские определения платформ
Кроме того, можно добавить определённые вами определения к встроенному набору. На панели Other Settings настроек проигрывателя (Player Settings), вы увидите текстовое поле Scripting Define Symbols.
Здесь вы можете указать имена обозначений, которые хотите определить для конкретной платформы, через точку с запятой. Эти обозначения можно затем использовать в качестве условий для директив #if также как встроенные.
Глобальные пользовательские определения
Вы можете определить свои собственные директивы препроцессора, чтобы контролировать, какой код попадет в результат при компиляции. Для этого необходимо добавить текстовый файл с дополнительными директивами в папку “Assets/”. Имя файла зависит от языка, который вы используете, а расширение rsp.:
C# | <Путь проекта>/Assets/smcs.rsp |
C# - Скрипты редактора | <Путь проекта>/Assets/gmcs.rsp |
UnityScript | <Путь проекта>/Assets/us.rsp |
Например, если вы включите одну строку “
-define:UNITY_DEBUG
” в ваш файл smcs.rsp, то определение UNITY_DEBUG
будет существовать как глобальное определение для скриптов на C#, за исключением скриптов редактора.
Каждый раз, когда вы вносите изменения в .rsp файлы вам нужно перекомпилировать, чтобы изменения были задействованы. Вы можете сделать это путем обновления или повторного импорта одного из файлов скриптов (.js, .cs или .boo).
NOTE
Если вы хотите изменить только глобальные определения, следует использовать Scripting Define Symbols в Player Settings, потому что это будет охватывать все компиляторы. Если вы выбираете .rsp файлы вместо этого, вы должны будете предоставить один файл для каждого компилятора используемый Unity, и вы не будете знать, когда используется тот или иной компилятор.
The use of .rsp files is described in the ‘Help’ section of the smcs application which is included in the Editor installation folder. You can get more information by running
smcs -help
.
Note that the .rsp file needs to match the compiler being invoked. For example:
- when targeting the web player, smcs is used with
smcs.rsp
, - when targeting standalone players, gmcs is used with
gmcs.rsp
, and - when targeting MS compiler, csc is used with
csc.rsp
, etc.
Общие функции
Некоторые функции в справке по скриптам (например, различные функции GetComponent) перечислены в варианте, который содержит букву T или имя типа в угловых скобках после имени функции:-
//C#
void FuncName<T>();
//JS
function FuncName.<T>(): T;
Они известны как общие функции. Их значимость для программирования заключается в том, чтобы указать типы параметров и/или возвращаемого типа при вызове функции. В JavaScript, это может быть использовано, чтобы обойти ограничения динамической типизации:-
// The type is correctly inferred since it is defined in the function call.
//In C#
var obj = GetComponent<Rigidbody>();
//In JS
var obj = GetComponent.<Rigidbody>();
В C# это может сократить количество кода:-
Rigidbody rb = go.GetComponent<Rigidbody>();
// ...as compared with:
Rigidbody rb = (Rigidbody) go.GetComponent(typeof(Rigidbody));
Любая функция, имеющая общий вариант, который указан на своей странице справки, позволяет использовать специальный синтаксис вызова.
Unity События (UnityEvents)
UnityEvents это способ позволяющий поддерживать управляемую пользователем функцию обратного вызова от момента редактирования до момента запуска без необходимости дополнительного программирования и конфигурации скриптов.
UnityEvents полезны для целого ряда вещей:
- Содержит управляемую пользователем функцию обратного вызова
- Системы связей
- Постоянная функция обратного вызова
- Предварительно настроенные события вызова
UnityEvents могут быть добавлены к любому MonoBehavior и вызываться в коде как стандартные .net делегаты. После добавления UnityEvent в MonoBehaviour, оно отобразится в инспекторе и можно будет добавлять постоянные функции обратного вызова.
UnityEvent
s имеют аналогичные ограничения для стандартных делегатов. То есть они содержат ссылки на элемент, который является целью, и это останавливает сборку мусора для цели. Если в качестве цели выбран объект UnityEngine.Object, а собственное представление исчезает, обратный вызов не будет вызываться.
Использование UnityEvents
Вот несколько шагов по настройке обратного вызова в редакторе:
- Убедитесь, что ваш скрипт импортирует / использует
UnityEngine.Events
. - Выберите иконку +, чтобы добавить слот для функции обратного вызова
- Выберите объект UnityEngine.Object к которому вы хотите обратиться посредством функции обратного вызова (Для этого вы можете использовать селектор объектов)
- Выберите функцию, которую вы хотите вызвать
- Вы можете добавить больше одной функции обратного вызова к событию
Во время настройки UnityEvent в инспекторе, вам доступно два типа поддерживаемых функций вызова:
Статические вызовы, это - пред настроенные вызовы с предустановленными значениями, которые настраиваются в UI(пользовательском интерфейсе). Это означает, что при срабатывании функции обратного вызова, целевая функция вызывается с аргументами предварительно введенными в UI. Динамические вызовы при срабатывании используют аргументы получаемые из кода, и ограничены типом вызванного UnityEvent. UI фильтрует функции обратного вызова и показывает только те динамические вызовы, которые действительны для данного UnityEvent.
Общие UnityEvents
По умолчанию UnityEvent в Monobehaviour связывает динамически функции без аргументов. Это не обязательно должно быть так, потому что UnityEvents поддерживает связь функций содержащих до 4-х аргументов. Чтобы сделать это, вам необходимо переопределить стандартный класс UnityEvents как поддерживающий множественные аргументы. Сделать это довольно просто:
[Serializable]
public class StringEvent : UnityEvent <string> {}
Потом это может быть вызвано с помощью вызова функции Invoke со ‘строкой(string)’ в качестве аргумента.
UnityEvents могут быть переопределены с наличием до 4-х аргументов в их общем определении.
Добавление экземпляра этого класса к вашему, вместо базового UnityEvent, даст возможность функции обратного вызова связывать динамические строковые функции.
Что такое исключение нулевой ссылки?
А
NullReferenceException
происходит, когда вы пытаетесь получить доступ к ссылочной переменной, которая не ссылается ни на один объект. Если ссылочная переменная не ссылается на объект, она будет рассматриваться как null
. Среда выполнения сообщит вам, что вы пытаетесь получить доступ к объекту, когда переменная с null
помощью команды a NullReferenceException
.
Ссылочные переменные в c # и JavaScript по своей концепции аналогичны указателям в C и C ++. Типы ссылок по умолчанию
null
указывают, что они не ссылаются ни на один объект. Следовательно, если вы попытаетесь получить доступ к объекту, на который ссылается, а его нет, вы получите a NullReferenceException
.
Когда вы получаете
NullReferenceException
в своем коде, это означает, что вы забыли установить переменную перед ее использованием. Сообщение об ошибке будет выглядеть примерно так:NullReferenceException: Object reference not set to an instance of an object
at Example.Start () [0x0000b] in /Unity/projects/nre/Assets/Example.cs:10
//c# example
using UnityEngine;
using System.Collections;
public class Example : MonoBehaviour {
// Use this for initialization
void Start () {
GameObject go = GameObject.Find("wibble");
Debug.Log(go.name);
}
}
Нулевые проверки
Хотя это может расстраивать, когда это происходит, это просто означает, что сценарий должен быть более осторожным. Решение в этом простом примере состоит в том, чтобы изменить код следующим образом:
using UnityEngine;
using System.Collections;
public class Example : MonoBehaviour {
void Start () {
GameObject go = GameObject.Find("wibble");
if (go) {
Debug.Log(go.name);
} else {
Debug.Log("No game object called wibble found");
}
}
}
Попробуйте / поймать блоки
Другой причиной для
NullReferenceException
является использование переменной, которая должна быть инициализирована в Инспекторе. Если вы забудете это сделать, переменная будет null
. Другой способ справиться с этим NullReferenceException
- использовать блок try / catch. Например, этот код:using UnityEngine;
using System;
using System.Collections;
public class Example2 : MonoBehaviour {
public Light myLight; // set in the inspector
void Start () {
try {
myLight.color = Color.yellow;
}
catch (NullReferenceException ex) {
Debug.Log("myLight was not set in the inspector");
}
}
}
Сводка
NullReferenceException
происходит, когда ваш код скрипта пытается использовать переменную, которая не установлена (ссылка) и объект.- Появляющееся сообщение об ошибке многое говорит о том, где в коде возникает проблема.
NullReferenceException
этого можно избежать, написав код, проверяющийnull
перед доступом к объекту, или использующий блоки try / catch. Это сообщение об ошибке говорит о том, чтоNullReferenceException
произошла в строке 10 файла сценарияExample.cs
. Также в сообщении говорится, что исключение произошло внутриStart()
функции. Это позволяет легко найти и исправить исключение Null Reference. В этом примере код выглядит следующим образом: код просто ищет игровой объект с именем «вибля». В этом примере нет игрового объекта с таким именем, поэтомуFind()
функция возвращаетnull
. На следующей строке (строка 9) мы используемgo
переменную и пытаемся распечатать имя игрового объекта, на который она ссылается. Поскольку мы обращаемся к игровому объекту, который не существует, среда выполнения дает намNullReferenceException
Сейчас, прежде чем мы попытаемся что-либо сделать сgo
переменная, мы проверяем, что это не такnull
. Если это такnull
, то мы показываем сообщение. В этом примере кода вызываемая переменнаяmyLight
- это переменная ,Light
которая должна быть установлена в окне инспектора. Если эта переменная не установлена, то по умолчанию она будет иметь значениеnull
. Попытка изменить цвет света вtry
блоке приводит к тому,NullReferenceException
что блок воспринимаетсяcatch
блоком.catch
Блок отображает сообщение , которое могло бы быть более полезным для художников и дизайнеров игры, и напоминает им , чтобы установить свет в инспекторе.
Пространства имён
Комментариев нет:
Отправить комментарий