Pavel Nakonechnyy

Руководство по UnityScript

Опубликовано by Pavel Nakonechnyy on (изменено: ) в GameDev.

Unity Script

О мануале

Данный мануал не написан мною. Я являюсь лишь его переводчиком, но также мною в данный мануал было включено кое-что из своих знаний, дабы весь туториал получился более развёрнутым и давал хорошие знания по UnityScript. Здесь собраны переведённые мною статью в официальногоScriptingReference, а также с unifycommunity. Все они объединены в один мануал, состоящий из трёх глав, где первые две главы это перевод статей с unifycommunity, а третья (И самая большая глава) содержит пока что незаконченный, но уже готовый более чем наполовину официальныйScriptingReference.

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

Приятного чтения!

Оригиналы уроков:

http://www.unifycommunity.com/wiki

http://unity3d.com/support/documentation/ScriptReference/index.html

Глава 1

Введение

UnityScript представляет собой специальную версию популярного в среде Web-программирования языка JavaScript. Эта глава представляет широкий обзор языка. В последующих главах мы будем подробнее вдаваться в технику программирования на нём.

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

С чего обычно начинается изучение любого языка программирования? Правильно, конечно же, с написания самого простого сценария на нём, и таким уже давно является «Hello, World». Итак, вот как будет выглядеть он на UnityScript:

function Start()

{

print("Hello, World");

}

Для того чтобы назначить наш скрипт какому-либо объекту в сцене, для начала следует его создать. Итак, давайте создадим самый простой объект, на который и назначим наш скрипт. Для этого в верхнем меню Unity, необходимо выбрать пункт “GameObject –> CreateEmpty”. После этого в окне “Hierarchy” вы можете наблюдать только что созданный нами пустой объект с именем “GameObject”. Ну что же, теперь дело за малым, осталось назначить наш скрипт на этот объект, для этого просто перетащите созданный нами скрипт на данный объект.

Примечание

Все назначенные вами на объект скрипты, вы всегда можете увидеть в окне “Inspector”, после того как выберете его в окне “Hierarchy”. В этом же окне вы можете изменить и значения переменных ваших скриптов.

Ну что же, после того как скрипт назначен на объект, мы можем протестировать его. Для этого просто нажмите кнопку “Play”. И в нижнем углу, в окне консоли вы сможете наблюдать появившуюся надпись “Hello, World”.

Весь код JavaScript, который написан или будет написан вами для ваших проектов. Как правило, хранится в текстовом файле с расширением .js, как и в нашем примере “helloworld.js”. Также, стоить отметить то, что после того как вы написали сценарий во встроенном редакторе Unity и сохранили его, движок автоматически компилирует его и проверяет на наличие ошибок. Это помогает вам предотвратить ошибки в игре в ходе её тестирования. И если ваш скрипт имеет ошибку, то Unity автоматически оповестит вас об этом в консоли.

Типы

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

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

Вот пример:

class SomeClass

{

var someValue=0;

}

function Start()

{

print(“Value types”);

var intValue = 1;

var anotherValue = 2;

print(intValue);

print(anotherValue);

anotherIntValue=intValue;

intValue = 999;

print(intValue);

print(anotherValue);

print(“Reference types”);

var classVar = someClass();

var anotherClassVar = classVar;

print(classVar.someValue);

print(anotherClassVar.someValue);

classVar.someValue = 111;

print(classVar.someValue);

print(anotherClassVar.SomeValue);

}

Вывод:

Типызначений

1

2

999

1

Ссылочные типы

0

0

111

111

После того как вы запустите игры, вы увидите что назначение на intValueanotherValue копирует данные, в то время как назначение на classVaranotherValue копирует указывает две переменные с теми же данными.

Предопределённые типы

UnityScript имеет следующие стандартные типы:

Тип Описание Пример
sbyte 8-разрядное число var n : sbyte = -2
short 16-разрядное число var n : short = -2
int 32-разрядное число var n : int = 2
long 64-разрядное число var n : long = 2
byte 8-разрядное число без знака var n : byte = 2
ushort 16-разрядное число без знака var n : ushort = 2
uint 32-разрядное число без знака var n : uint = 2
ulong 64-разрядное число без знака var n : ulong = 2
float Одинарная точность с плавающей точкой varf : float = 2.0
double Двойная точность с плавающей точкой varf : double = 2.0
boolean Представляет значение правда/ложь varb : boolean = true
char Unicode var c : char = “c”[0];
String Представляет текст как последовательность символовUnicode var s : String = “Hello, World”

Дополнительные встроенные типы перечислены в справочнике ScriptingReference.

Преобразования

Стандартные типы могут быть преобразованы. Явные преобразования поддерживаются для ссылочных типов, но не для предопределённых типов значений. Будьте осторожны при преобразовании между предопределёнными типами, которые имеют различные минимальные и максимальные диапазоны. Например:

function Start()

{

var longValue : long = System.Int64.MaxValue;

var intValue : int = LongValue;

}

Вывод следующего сообщения в консоли: “OverflowException:NumberOverflow” происходит потому что целый тип не может содержать максимальное значение.

Типы массивов

В UnityScript есть два типа массивов – встроенные массивы и массивы класса. Массивы позволяют хранить несколько объектов в одной переменной. Вот пример того что можно сделать с массивом класса:

function Start(){

var arr = new Array();

//Добавляем один элемент

arr.Push(“Hello”);

//Выводим в консоль первый эллемент “Hello”

print(arr[0]);

//Изменяем размер массива

arr.length = 2;

//Присваиваем значение “World” следующему элементу

arr[1] = “World”;

//Проходим по массиву

for (var value : string in arr){

print(value);

}

}

Встроенные массивы очень быстры и эффективны, но они не могут быть изменены. Также их можно отредактировать в окне “Inspector”. Вот простой пример того как можно использовать встроенные массивы:

var values : float[];

function Start(){

//Проходим по массиву

for(var value in values){

print(value);

}

//Поскольку мы не можем изменить размер встроенных массивов

//Мы должны восстановить массив изменив его размер

values = new float[10];

//Присваиваем второй элемент

values[1]=5.0;

}

Также, встроенные массивы полезны при критичной производительности кода.

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

function Start(){

var array = new Array(Vector3(0,0,0),Vector3(0,0,1));

array.Push(Vector3(0,0,2));

array.Push(Vactor3(0,0,3));

//Копируем JS массивы во встроенный массив

var bultinArray : Vector3[] = array.ToBuiltin(Vector3);

//Назначаем встроенный массив в массив JS

var newarr = new Array(builtinArray);

//Newarr содержит те же элементы в виде массива

print(newarr);

}

Глава 2

Сценарии

Сценарий, написанный вами внутри одного или же нескольких файлов, как правило, имеет хотя бы одно определение классов, имеющих то же имя что и сам файл. Например, скрипт MyJavaScript.jsбудет содержать определение MyJavaScript класса. Обычно Unity автоматически создаёт класс с правильным именем, и всё что находится внутри этого файла будет членом этого класса.

Маркеры

Ключевые слова

Ключевое слово является идентификатором, как последовательность символов которая зарезервирована.

Ключевые слова Unity Script:

  • as
  • boolean
  • break
  • byte
  • case
  • char
  • class
  • continue
  • default
  • double
  • else
  • enum
  • false
  • float
  • for
  • if
  • in
  • int
  • long
  • new
  • null
  • private
  • protected
  • public
  • return
  • sbyte
  • static
  • string
  • super
  • switch
  • this
  • true
  • typeof
  • uint
  • ulong
  • ushort
  • virtual
  • void
  • while

Литералы

Литерал – запись в исходном коде, представляющая собой фиксированное значение.

Логические литералы

Есть два булевых(boolean) значения: правда(true) и ложь(false).

Тип boolean является логическим.

Целые литералы

Целые литералы используются для значений целого типа, таких как int, uint, long и ulong.

Десятичные литералы

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

1 2 3 4 5 6 7 8 9

Шестнадцатеричные целые литералы

Шестнадцатеричные целые литералы состоят из шестнадцатеричных цифр, где шестнадцатеричные цифры одно из следующих:

1 2 3 4 5 6 7 8 9 ABCDEFABCDEF

Реальные литералы

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

.Десятичные цифры

десятичные цифры

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

Строчные литералы

Строчные литералы составляют из нуля или больших символов, заключённых в двойные кавычки.

Строка имеет тип String.

Нулевой литерал

Ключевое слово null делает литерал нулевого типа.

Операторы и знаки пунктуации

Знаки пунктуации включают в себя: {} [] () . , : ;

Операторы включают в себя: + – * / % & | ^ ! = < > ? ++ && || << >> == =< >= => <=

Глава 3

Основные понятия

Функции

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

//Эта функция принимает в качестве исходных данных два номера, и добавляет их возвращая значения

function Add(number1 : int, number2 : int) : int

{

return number1+number2;

}

//Эта функция не имеет параметров и ничего не возвращает

function Start()

{

//Вызов функции, используя её имя и список параметров

var sum = Add(3,4);

}

Описание функций

  • functionStart – эта функция вызывается самим движком только один раз при старте, очень удобна для присвоения переменным разовых значений.
  • functionUpdate – данная функция вызывается движком каждый кадр. Она является самой быстрой, но довольно сильно влияет на оптимизацию.
  • functionLateUpdate – данная функция вызывается движком каждый кадр после выполнения всех остальных команд.
  • functionFixedUpdate – функция вызываемая движком через определённые промежутки времени. Её скорость не зависит от частоты кадров в секунду и именно поэтому в основном она применяется для работы с физикой в игре.

Основные операции

Большинство манипуляций с игровыми объектами осуществляется посредством командыTransform или же компонента Rigidbody. Они доступны в сценариях через переменные. Так что если вы хотите повернуть объект в сцене на пять градусов вокруг оси y, то в функции Update вы можете прописать следующее:

function Update(){

transform.Rotate(0,5,0);

}

Если же вы хотите переместить объект вперёд, то написать можно так:

function Update(){

transform.Translate(0,0,2);

}

Время

Класс Time содержит очень важную переменную deltaTime. Эта переменная содержит количество времени с момента последнего вызова функции Update или FixedUpdate (В зависимости от того в какой из функций она находится). Таким образом, можно изменить предыдущий пример, для того чтобы заставить вращаться объект с постоянной скоростью вне зависимости от частоты кадров. В данном случае скрипт будет выглядеть так:

function Update(){

transform.Rotate(0,5*Time.deltaTime,0);

}

Аналогичную операцию мы можем совершить и с перемещение объекта:

function Update(){

transform.Translate(0,0,2*Time.deltaTime);

}

Умножая на Time.deltaTime вы, по сути, даёте объекту команду: «Я хочу, чтобы этот объект перемещался со скоростью 10 метров в секунду, вместо 10 метров в кадре». Это хорошо не только потому, что игра будет работать в независимости от частоты кадров в секунду, но и потому, что единицы, используемые для движения легки в понимании (10 метров в секунду).

Другой пример, допустим вы хотите увеличить дальность света с течением времени:

function Update(){

light.range +=2,0*Time.deltaTime;

}

Доступ к другим компонентам

Компоненты назначаются на игровые объекты, и позже могут быть использованы вызовом через скрипт. Например, присоединение Render компонента к игровому объекту, будет визуализировать его на экране во время игры. А компонент Camera добавит к вашему игровому объекту камеру. Также, стоит отметить что все написанные вами скрипты позже могут быть присоединены к игровым объектам в качестве компонентов.

Любой компонент или скрипт присоединяемый к объекту, может быть доступен черезGetComponent.

transform.Translate(0,1,0);

//Эквивалентно

GetComponent(Transform).Translate(0,1,0);

Итак, давайте попробуем найти сценарий, или встроенный компонент, используя GetComponent. Обратите внимание, для этого сценария нам понадобятся готовый скрипт “OtherScript” содержащий функцию DoSomething. Также стоит отметить то, что скрипт “OtherScript” должен быть назначен на тот же игровой объект, что и следующий сценарий:

function Update(){

var otherScript : OtherScript = Get Component(OtherScript);

otherScript.DoSomething();

}

Доступ к объектам

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

В дальнейших примерах, мы предполагаем наличие OtherScript.js как стандартного компонента игрового объекта.

1. Назначение ссылочных типов переменных при помощи инспектора

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

//Перемещение объекта с использованием переменной Target

var target : Transform;

function Update(){

target.Translate(0,1,0);

}

Вы также можете выставить ссылочный тип на другие объекты инспектора. Ниже вы можете видеть пример скрипта, имеющего ссылочный тип под любой сторонний JS скрипт.

//Устанавливаем FooDoSomething на переменную Target принемаемую в инспекторе

var target : OtherScript;

function Update(){

//Устанавливаем значение переменной Foo в ссылочный тип переменной Target

target.foo = 2;

target.DoSomething(“Hello”);

}

2. Расположение объектов в окне иерархии

Вы можете находить дочерние и родительские объекты через компонент Transform:

//Найти дочерний объект “Hand”

transform.Find(“Hand”).Translate(0,1,0);

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

//Найти дочерний объект под названием “Hand”

//В компоненте OtherScript назначенный на этот объект, меняем значение переменной Foo на 2

.transform.Find(“Hand”).GetComponent(OtherScript).foo = 2;

.transform.Find(“Hand”).GetComponent(OtherScript).DoSomething(“Hello”);

.transform.Find(“Hand”).rigidbody.AddForce(0,10,0);

Вы можете использовать цикл для всех дочерних объектов:

//Переместить все дочерние объекты на 10 единиц вверх

for ( var child : Transform in transform) {

child.Translate(0, 10, 0);

}

3. Имена и теги

Вы можете искать игровые объекты с определенными тегами использованием GameObject.FindWithTag и GameObject.FindGameObjectsWithTag . Используйте GameObject.Find для поиска любого объекта по имени.

function Start () {

// По имени

var go = GameObject.Find ( “SomeGuy” );

go.transform.Translate(0, 1, 0);

// По тегу

var player = GameObject.FindWithTag ( “Player” );

player.transform.Translate(0, 1, 0);

}

Вы также можете использовать и GetComponent для того чтобы найти какие-либо компоненты назначенные на найденный объект:

function Start () {

// По имени

var go = GameObject.Find ( “SomeGuy” );

go.GetComponent(OtherScript).DoSomething();

// По тегу

var player = GameObject.FindWithTag ( “Player” );

player.GetComponent(OtherScript).DoSomething();

}

4. Принятие в качестве параметра

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

Например:

function OnTriggerStay ( other : Collider ) {

if (other.rigidbody)

other.rigidbody.AddForce(0, 2, 0);

}

Или мы можем добраться до любого компонента:

function OnTriggerStay ( other : Collider ) {

/ / Если есть другие коллайдеры OtherScript задействуется

/ / Вызываем DoSomething в нем.

if (other.GetComponent(OtherScript))

other.GetComponent(OtherScript).DoSomething();

}

5. Все скрипты одного типа

Найти любой объект одного класса, или скрипта, можно используя команду Object.FindObjectsOfType или найти первый объект одного типа при помощи Object.FindObjectOfType.

function Start () {

// Find the OtherScript which is attached to any game object in the scene.

var other : OtherScript = FindObjectOfType(OtherScript);

other.DoSomething();

}

Векторы

Unity использует Vector3, для определения всех 3D векторов. Вы также можете использовать Vector3 функцию для инициализации всех компонентов одновременно.

var aPosition = Vector3 (1, 1, 1);

Vector3 также определяет некоторые общие значения, как константы.

var direction = Vector3.up ; // аналогично Vector3 (0, 1, 0);

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

SomeVector.Normalize ();

А операции с использованием нескольких векторов осуществляется с помощью Vector3 функции класса:

var oneVector : Vector3 = Vector3 (0,0,0);

var otherVector : Vector3 = Vector3 (1,1,1);

var theDistance = Vector3.Distance (oneVector, otherVector);

Важно отметить, что вы должны написать Vector3 перед именем функции JavaScript.

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

Переменные

Переменные доступны через окно “Inspector” внутри Unity. Любое значение, сохранённое в переменной, также автоматически сохраняются в проекте.

var memberVariable = 0.0;

Пример переменной приведённой выше будет отображаться в виде числового свойства и может быть отредактирован в окне “Inspector”

Если вы установите какой-либо тип переменной (например, Transform, Rigidbody, Collider , любое имя сценария и т.д.), то вы можете установить их, перетаскивая игровыt объектов на значение в окне “Inspector”.

var enemy : Transform ;

function Update () {

if ( Vector3.Distance ( enemy.position, transform.position ) < 10 )

print( “I sense the enemy is near!” );

}

Вы также можете создавать специальные private переменные, редактирование которых через окно “Inspector” будет недоступно, и изменить их значения можно будет лишь изменив значения переменных в самом скрипте.

private var lastCollider : Collider ;

function OnCollisionEnter (collisionInfo : Collision ) {

lastCollider = collisionInfo.collider;

}

Глобальные переменные

Вы также можете создать глобальные переменные с помощью ключевого слова static.

Давайте создадим глобальную переменную someGlobal:

/ / Создаём глобальную переменную someGobal

static var someGlobal = 5;

/ / Вы можете получить доступ к этой переменной как изнутри сценария так и через окно “Inspector”

print(someGlobal);

someGlobal = 1;

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

TheScriptName.someGlobal;

//Также мы можем изменить её значение

TheScriptName.someGlobal = 10;

Сопрограммы

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

private var state = 0;

function Update () {

if (state == 0) {

/ / Делаем шаг 0

state = 1;

return ;

}

if (state == 1) {

/ / Делаем шаг 1

state = 2;

return ;

}

// …

}

Часто более удобно использовать yield. yield – это специальный вид return, который гарантирует, что функция будет продолжать выполнение со строки после yield при следующем вызове.

while(true) {

// делаем шаг 0

yield; // ждем один кадр

// делаем шаг 1

yield; // ждем один кадр

// …

}

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

// Делаем что-то

yield WaitForSeconds(5.0); // ждем 5 секунд

// делаем что-то еще…

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

Этот пример вызывает Do, но продолжит выполнение после вызова немедленно.

Do ();

print (“This is printed immediately”);

function Do () {

print(“Do now”);

yield WaitForSeconds (2);

print(“Do 2 seconds later”);

}

Этот пример вызывает Do и ждет пока она не закончит выполнение, прежде чем продолжить свое выполнение.

// сцепка корутин

yield StartCoroutine(“Do”);

print(“Also after 2 seconds”);

print (“This is after the Do coroutine has finished execution”);

function Do () {

print(“Do now”);

yield WaitForSeconds (2);

print(“Do 2 seconds later”);

}

Любой обработчик событий также может быть корутиной.

Заметим, что Вы не можете использовать yield напрямую из Update или FixedUpdate, но Вы можете использовать StartCoroutine для запуска функции, которая может это делать.

Оптимизация

1. Используйте статическую типизацию.

Когда Вы пишете на JavaScript наиболее важная оптимизация – использование статической типизации вместо динамической. Unity использует технику, называемую “type inference” для автоматического конвертирования JavaScript конструкций в статически типизированный код без необходимости Вам проделывать эту работу.

var foo = 5;

В примере выше foo будет автоматически преобразована в целый тип. Т.о. Unity может использовать множество оптимизаций времени компиляции, без дорогостоящего поиска типа переменной. Это одна из причин почему реализация JavaScript в Unity в среднем в 20 раз быстрее чем другие реализации.

Единственная проблема, что иногда не все может быть преобразовано таким образом, что возвращает Unity к динамической типизации. Из-за динамической типизации писать код на JavaScript проще, однако это же делает код медленнее.

Давайте посмотрим на некоторые примеры.

function Start () {

var foo = GetComponent(MyScript);

foo.DoSomething();

}

Здесь foo будет динамически типизировано, т.о. вызов функции DoSomething будет намного более долгим, чем необходимо – из-за того, что тип foo неизвестен нужно выяснить поддерживается ли он функцией DoSomething и если да – вызвать ее.

function Start () {

var foo : MyScript = GetComponent(MyScript);

foo.DoSomething();

}

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

2. Используйте #pragma strict

Теперь проблема, что Вы можете не заметить, что используете динамическую типизацию. #pragma strict – Ваше спасение! Просто добавьте #pragma strict на самый верх скрипта, и Unity запретит динамическую типизацию в этом скрипте, силой заставляя Вас использовать статическую. Когда при компиляции тип не может быть определен, Unity выведет ошибку компиляции. Так в этом случае, foo произведет ошибку при компиляции:

#pragma strict

function Start () {

var foo : MyScript = GetComponent(MyScript) as MyScript;

foo.DoSomething();

}

3. Кешируйте найденные компоненты.

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

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

Просто замените это:

function Update () {

transform.Translate(0, 0, 5);

}

на:

private var myTransform : Transform;

function Awake () {

myTransform = transform;

}

function Update () {

myTransform.Translate(0, 0, 5);

}

Последний код будет работать намного быстрее, т.к. Unity не нужно искать компонент transform в игровом объекте каждый кадр. Это же применимо для компонентов – скриптов, где Вы используете GetComponent вместо transform или других свойств.

4. Используйте встроенные масивы.

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

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

private var positions : Vector3[];

function Awake () {

positions = new Vector3[100];

for (var i = 0; i < 100; i++)

positions[i] = Vector3.zero;

}

5. Не вызывайте функцию, если Вам это не нужно.

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

var target : Transform;

function Update () {

// Early out if the player is too far away.

if (Vector3.Distance(transform.position, target.position) > 100)

return;

// perform real work work…

}

Это не очень хорошее решение, т.к. Unity вызывает функцию update и Вы производите работу каждый кадр. Лучшее решение – отключить поведение пока игрок не приблизится. Есть 3 пути сделать это:

1. Использование OnBecameVisible и OnBecameInvisible. Вызовы этих функций завязаны с системой рендеринга. Когда любая камера может увидеть объект, вызывается OnBecameVisible, когда объект выходит из области видимости всех камер вызывается OnBecameInvisible. В некоторых случаях это очень полезно. Но для AI это не стоит использовать, т.к. враги перестанут действовать, когда Вы отвернете от них камеру.

function OnBecameVisible () {

enabled = true;

}

function OnBecameInvisible () {

enabled = false;

}

2. Использование триггеров. Простой сферический триггер может чудесно работать в данном случае. Выполучитевызовы OnTriggerEnter/Exit привходе/выходевколайдер

function OnTriggerEnter (c : Collider) {

if (c.CompareTag(“Player”))

enabled = true;

}

function OnTriggerExit (c : Collider) {

if (c.CompareTag(“Player”))

enabled = false;

}

3. Использование Корутин. Проблема с Unity в том, что она вызывается каждый кадр. Вполне возможно, проверка расстояния до игрока может быть выполнена только каждые 5 секунд. Это позволит сэкономить много времени затраченного процессором на расчеты.

Использование C# и Boo

Помимо синтаксиса, есть некоторые различия при написании сценариев в C # или Boo. Наиболее примечательными являются:

1. Носледование от MonoBehaviour

Все скрипты поведения должны наследоваться от MonoBehaviour (напрямую или через другие скрипты). Это делается автоматически в Javascript, но должно быть указано прямо в C# или Boo. ЕслиВысоздаетеВашскриптв Unity черезAsset -> Create -> C Sharp/Boo Script menu, the created template will already contain the necessary definition.

public class NewBehaviourScript : MonoBehaviour {…} // C#

class NewBehaviourScript (MonoBehaviour): … # Boo

2.Использованиефункций Awake или Start дляинициализации.

Все что Вы помещаете вне любой функции в Javascript, Вам нужно поместить внутрь функцийAwake или Start в C# или Boo.

Различие между Awake и Start заключается в том, что Awake вызывается, когда загружается сцена, а Start iвызывается непосредственно перед первым вызовом Update или FixedUpdate . Все функции Awake вызываются перед любым вызовом функции Start .

3. Имя класса должно соответствовать имени файла.

В Javascript имя класса неявно устанавливается совпадающим с именем файла (минус расширение файла). В C# и Boo. это нужно делать вручную.

4. Корутины имеют иной синтаксис в C#.

В C# корутины имеют тип возвращаемого значения – IEnumerator и Вам нужно использовать yield return … ; вместо yield … ;

using System.Collections;

using UnityEngine;

public class NewBehaviourScript : MonoBehaviour {

// C# coroutine

IEnumerator SomeCoroutine () {

// Wait for one frame

yield return 0;

// Wait for two seconds

yield return new WaitForSeconds (2);

}

}

5. Не используйте пространства имен.

На данный момент Unity не поддерживает размещение Ваших скриптов в пространствах имен. Это требование будет отменено в будущих версиях.

6. Только публичные поля сериализуются и показываются в инспекторе.

Приватные и защищенные поля показываются только в Debug Mode. Свойства не сериализуются и не показываются в инспекторе.

7. Избегайте использования конструкторов или инициализации переменных.

Никогда не используйте конструкторов или инициализации переменных в скриптах производных от MonoBehaviour. Вместо этого используйте функции Awake или Start для этих целей. Юнити автоматически вызывает конструктор, даже в режиме редактора. Это обычно происходит непосредственно после компиляции скрипта для установки всех переменных в значение по умолчанию. Мало того, что это может привести к вызову конструктора непредвиденное число раз, но он также может быть вызван и для префабов или неактивных игровых объектов.

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

Используйте конструктор только если вы наследуетесь от ScriptableObject.

В случае использования шаблона singleton использование конструктора может иметь серьезные последствия и привести к казалось бы случайным исключениям.

Так что если Вы хотите реализовать шаблон singleton НЕ используйте конструктор. Используйте вместо него Awake. На самом деле нет причин иметь хоть какой-то код в конструкторе класса унаследованного от MonoBehaviour.

Компиляция

Unity компилирует все скрипты в файлы dll.NET. .dll копмилируются в нативный код в рантайм.

Это приводит к чрезвычайно быстрому выполнению скриптов – приблизительно в 20 раз быстрее чем традиционный javascript и на 50% медленнее чем нативный C++ код. Unity нужно некоторое время для компиляции всех Ваших скриптов когда Вы сохраняете их. Вы можете видеть что Unity еще компилирует их по маленькому вращающемуся прогрессбару в нижнем правом углу главного окна Unity.

Компиляция скриптов происходит в 4 шага:

1. Все скрипты в “Standard Assets”, “Pro Standard Assets” или “Plugins” компилируются первыми.

Скрипты в этих папках не могут напрямую обращаться к скриптам вне этих папок.

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

2. Всескриптыв “Standard Assets/Editor”, “Pro Standard Assets/Editor” или “Plugins/Editor”компилируютсядалее.

Если Вы хотите использовать пространство имен UnityEditor тогда поместите Ваши скрипты в эти папки. Например для добавления меню или создания пользовательского мастера Вам нужно поместить Ваши скрипты в эти папки.

Эти скрипты могут иметь прямой доступ к скриптам из предыдущей группы.

3. Все другие скрипты вне “Editor” компилируются далее.

Все скрипты не в папках выше или не в папке “Editor” компилируются далее.

Все скрипты, которые компилируются на этом шаге имеют доступ ко всем скрипта в первой группе (“Standard Assets”, “Pro Standard Assets” or “Plugins”). Это позволяет Вам взаимодействовать между различными языками программирования. Например, если вы хотите создать Javascript, который использует C# скрипт: поместите C# в папку “Standard Assets” а Javascript вне этой папки. Javascript теперь сможет напрямую обращаться к C# скрипту.

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

4. Все скрипты в папке “Editor” компилируются последними.

Если Вы хотите использовать пространство имен UnityEditor тогда поместите Ваши скрипты в эту папку. Например для добавления меню или создания пользовательского мастера Вам нужно поместить Ваши скрипты в эту папку.

Эти скрипты имеют доступ ко всем скриптам из всех предыдущих групп.

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

Условная компиляция Conditional compilation against the Unity version.

Начиная с версии Unity 2.6 были добавлены определения препроцессора C#. Эти определения указывают используемую версию Unity и предназначены для условной компиляции с учетом специфических особенностей данной версии. Например:

// Указываем версию с учётом ревизии

#if UNITY_2_6_0

// Используем особенности, присущие версии 2.6.0

#endif

// Указываем версию без учета ревизии

#if UNITY_2_6

// Используем особенности версии 2.6.x

#endif

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

Совместимость Unity и Mono

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

Generic функции

Некоторые функции в этом мануале (например, различные функции GetComponent) перечислены с вариантом, который имеет букву T или имя типа в угловых скобках после имени функции:

function FuncName.<T>(): T;

Такие функции известны как “generic” функции. Значимым для скриптинга является что вы получите указанный тип параметра и/или возвращаемого значения когда вы вызываете эти функции. В JavaScript это может быть использовано, чтобы обойти ограничения динамической типизации:

// Тип правильно определяется, т.к. он определен в теле функции.

var obj = GetComponent.<Rigidbody>();

В C#, это может сохранить множество нажатий клавиш и преобразований типов:

Rigidbody rb = go.GetComponent<Rigidbody>();

// …сравните с:

Rigidbody rb = (Rigidbody) go.GetComponent(typeof(Rigidbody));

Любая функция, которая имеет generic вариант указанный в мануале позволяет использовать этот специальный синтаксис вызова.

Source: Blog

921