Погружение в скрипты игрового движка Unity3d, ч.1

Доброго времени суток, уважаемый читатель! На Хабре неоднократно публиковались статьи о разработке игр с использованием замечательного движка Unity3d. Большинство этих статей были посвящены вполне определенным задачам, я же хотел сделать общий экскурс в данный движок. Данная часть будет посвящена наиболее часто используемым скриптовым методам и объектам, которые используются мной в процессе разработки на данном движке. Примеры я буду приводить на JavaScript, как на наиболее близком мне языке.

Игровой объект


Создание нового игрового объекта с именем MyObject.
var myObject = new GameObject("MyObject");
Созданный объект будет доступен по ссылку myObject.

Поиск объекта по его имени.
var myObject = GameObject.Find("MyObject");


Теги можно использовать для помечания группы объектов со сходными свойствами, либо использующиеся в единой сцене.
Поиск объекта по тегу, возвращает единственный объект:
var myObject = GameObject.FindWithTag("MyTag");


Возвращает список всех объектов с указанным тегом:
var objectList = GameObject.FindGameObjectsWithTag("MyTag");


Проверка на наличие у объекта требуемого тега. Возвращает true, если у указанного объекта имеется тег MyTag:
var isCompare = GameObject.CompareTag("MyTag");


Уничтожение объекта:
Destroy(myObject);


Уничтожение объекта через минуту, после его создания:
Destroy(myObject, 60);


Возвращает компонент component, привязанный к объекту GameObject, либо null, если объект не содержит данного компонента. Может использоваться, например, для доступа к другим скриптам, привязанным к объекту.
var objectComponent = GameObject.GetComponent(component);


Возвращает все имеющиеся у объекта компоненты типа componentType.
var objectComponents = GameObject.GetComponents(componentType);


Привязать компонент myComponent к объекту GameObject и получить ссылку на него.
var component = GameObject.AddComponent(myComponent);


Положение игрового объекта



Свойство transform объекта GameObject содержит в себе данные о положении объекта в игровом мире.

Возвращает глобальные координаты объекта в игровом мире. Возвращаемая величина имеет тип Vector3, который представляет из себя список из 3 координат — x, y и z:

var position = GameObject.transform.position; 
var x = position.x;


Переместить объект в точку 0, 10, 0 игрового мира.
GameObject.transform.position = Vector3(0, 10, 0);


Тоже самое, что и в случае глобальных координат, но с локальными. Локальные координаты расситываются относительно родительского объекта. В случае отсутствия родительского объекта локальные координаты совпадают с глобальными:
var localPosition = GameObject.transform.localPosition; 
var x = localPosition.x;


Поворот объекта в углах Эйлера. Метод также возвращает координаты в виде объекта Vector3:
var eulerAngle = GameObject.transform.eulerAngles;


Тоже самое, что и предыдущий пример, но поворот объекта рассчитывается относительно родительского объекта:
var localEulerAngle = GameObject.transform.localEulerAngles;


Текущий угол поворота объекта, основанный на кватернионах. Возвращает объект типа Quaternion.
var quaternionAngle = GameObject.transform.rotation;


Текущий поворот объекта, основанный на кватернионах, но относительно родительского объекта:
var localQuaternionAngle = GameObject.transform.localRotation;


Сброс угла поворота объекта:
GameObject.transform.rotation = Quaternion.identity;
GameObject.transform.localRotation = Quaternion.identity;


Вращаем наш объект в указанную сторону со скоростью 1 градус в секунду. Принимает в качестве координат объект типа Vector3. Метод deltaTime объекта Time содержит время в секундах, затраченное на выполнение предыдущего кадра:
GameObject.transform.Rotate(Vector3.left * Time.deltaTime);


Тоже самое, что и предыдущий пример, но вращение объекта относительно координат родителя:
GameObject.transform.localRotate(Vector3.left * Time.deltaTime);


Перемещаем наш объект в указанном направлении со скоростью 1 юнит в секунду. Также принимает в качестве координат объект класса Vector3:
GameObject.transform.Translate(Vector3.up * Time.deltaTime);


Физические свойства игрового объекта


Метод rigidbody объекта GameObject хранит в себе его физические свойства. Прежде, чем использовать метод rigidbody, его необходимо добавить к игровому объекту.

Получаем/задаем вектор скорости объекта:
var velocity = GameObject.rigidbody.velocity;
GameObject.rigidbody.velocity = Vector3(0, 1, 0);


Сила противодействия объекта. Может использоваться для замедления скорости, в среде с отсутствующей силой трения. Наиболее часто используется для замедления падающих объектов, например при создании парашюта. Принимает в качестве параметра целое число:
GameObject.rigidbody.drag = 100;


Задание массы объекту. Рекомендуется использовать массу в пределах от 0.1 до 10. Использование слишком больших значений может привести к непредсказуемым результатам при расчете физики:
GameObject.rigidbody.mass = 5;


Влияние на объект гравитации. Принимает в качестве параметра булево значение. Позволяет отключить влияние гравитации на отдельные объекты:
GameObject.rigidbody.useGravity = false;


Влияние физики на игровой объект. Позволяет отключить частично, либо полностью влияние физических законов на объект:
GameObject.rigidbody.isKinematic = true;


Запрет на вращение объекта. Наиболее часто используется, когда необходимо сохранить определенный угол поворота даже после столкновения с другими объектами:
GameObject.rigidbody.freezeRotation = true;


Указание координат точки центра массы объекта. Применяет координаты в виде уже знакомого нам объекта Vector3.
GameObject.rigidbody.centerOfMass = Vector3(1, 0, 0);


Использовать ли для объекта обнаружение столкновений с другими объектами. Можно выключить, тогда ваш объект будет игнорировать любые столкновения:
GameObject.rigidbody.detectCollisions = false;


Режим определения столкновений между объектами. Можно указать несколько разных режимов:
CollisionDetectionMode.ContinuousDynamic для быстро движущихся объектов;
CollisionDetectionMode.Continuous для столкновений с быстро движущимися объектами;
CollisionDetectionMode.Discrete (по умолчанию) для обычных столкновений;
В случае отсутствия проблем с определением столкновений рекомендуется использовать свойство по умолчанию.

Задать плотность объекта:
GameObject.rigidbody.SetDensity(1.5);


Применить импульс к объекту с указанным вектором. В результате применения импульса объект придет в движение пропорционально силе импульса.
GameObject.rigidbody.AddForce(5, 0, 0);


Применить импульс к объекту с вектором в его (объекта) системы координат:
GameObject.rigidbody.AddRelativeForce(0, 0, 5);


Добавить объекту крутящий момент. Применение данного метода заставит объект вращаться вокруг своего центра масс GameObject.rigidbody.centerOfMass.
GameObject.rigidbody.AddTorque(0, 1, 0);


Тоже самое, что и предыдущий пример, но относительно координат объекта:
GameObject.rigidbody.AddRelativeTorque (1, 0, 0);


Применение импульса к объекту из внешней указанной точки. Заставляет объект двигаться и вращаться одновременно. Может использоваться, например, для симуляции попадания в объект пули. Первый параметр указывает вектор направления силы, второй параметр — исходную точку направления силы.
GameObject.rigidbody.AddForceAtPosition(Vector3(0, 5, 7), Bomb.transform.position);


Для полноценной симуляции объемных взрывов в Unity3D есть отдельный метод. Первый параметр метода позволяет указать мощность импульса, второй параметр — точку, из которой исходит импульс, третий параметр — радиус распространения импульса, четвертый параметр — модификатор сжатия сферы распространения силы, пятый, необязательный, параметр указывает тип используемого импульса:
GameObject.rigidbody.AddExplosionForce(power, explosionPos, radius, 2.0);


Заставить объект «уснуть», и запретить дальнейший расчет физических показателей для него:
GameObject.rigidbody.Sleep();


Проверить «заснул» ли объект:
GameObject.rigidbody.IsSleeping();


«Разбудить» объект для возможности дальнейшего применения влияния физики на него:
GameObject.rigidbody.WakeUp();


Трассировка лучей


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

Создаем объект класса RaycastHit, который содержит информацию об объекте, с которым столкнулся луч:
var hit : RaycastHit;


Отправляем луч длиной в 50 юнитов из позиции rayPosition в направлении rayVector, и заносим объект, с которым столкнулся луч в переменную hit:
Physics.Raycast(rayPosition, rayVector.forward, hit, 50);


Получаем дистанцию до объекта, с которым столкнулся луч. Дистанция не может быть больше, чем протяженность луча:
var distance = hit.distance;


Иногда бывает необходимо получить имя объекта, с которым произошло столкновение луча. Наиболее простой способ это сделать:
var objectName = hit.collider.gameObject.name;


Для получения тега объекта используем следующий способ:
var Tag = hit.collider.tag;


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

UPD. вторая часть (скриптовые события).

Комментарии 34

    0
    Неплохая выборка для тех кто не стал читать документацию. В основном новичкам будет полезно.
    • НЛО прилетело и опубликовало эту надпись здесь
      • НЛО прилетело и опубликовало эту надпись здесь
          +1
          ИМХО — от статьи больше вреда чем пользы. Например, за Find(name) можно увольнять, если оно больше чем раз.
            +4
            Спасибо. Статья в первую очередь позиционируется как справочник, а не самоучитель. Просто хотел, чтобы человек, впервые взявшийся за данный движок, знал о подобных возможностях. Если у Вас есть предложения, то я всегда готов к конструктивной критике.
              0
              объясните, пожалуйста, подробнее. Я, к примеру, реализовываю синглтон вот так:
              public class FileDownloader : MonoBehaviour
              {
              private static FileDownloader instance = null;

              public static FileDownloader current {
              get { return instance; }
              }

              // Use this for initialization
              void Awake ()
              {
              if (FileDownloader.instance == null) {
              FileDownloader.instance = FindObjectOfType (typeof(FileDownloader)) as FileDownloader;
              }
              queue = new Queue ();
              isReady = false;
              }


              Это плохо? Чем?
                0
                блин, не до конца скопипастил. Но вы поняли идею, да?
                  0
                  ну, последнюю неделю, я провел работая над тем что бы игра закажчика на айфоне 3гс выдавала больше 20 фпс. Чесно признаться — лучше вообще ничего не искать. Совсем. Сделайте себе референс, перетащите в редакторе. Тем более я сказал, «больше чем раз». У вас оно раз, не каждый кадр, а при старте/возвращении. Но я все еще не понял зачем. У вас же и так статический FileDownloader, зачем его еще искать?
                    0
                    в смысле куда логичнее было бы сделать инициализацию лениво в самом файл даунлоадере, не?
                    public class FileDownloader
                    {
                    private static FileDownloader _instance = null;
                    public static FileDownloader instance
                    {get { return _instance??(_instance = new FileDownloader()); } }
                    }

                  +2
                  Тема оптимизации вообще заслуживает отдельной статьи. В данной замечательной статье рассматриваются некоторые вопросы оптимизации, в одноименной главе. В частности, цитирую:
                  2. Без необходимости лучше не использовать Find\GetComponent и похожие методы — по мере возможности лучше сохранить ссылку на компонент один раз при запуске скрипта.
                +1
                unity3d.com/support/documentation/ScriptReference/Input-mousePosition.html
                там есть пример, который делает то что вы хотите
                if (Input.GetButtonDown("Fire1")) {
                Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
                if (Physics.Raycast(ray))
                Instantiate(particle, transform.position, transform.rotation) as GameObject;

                }
                • НЛО прилетело и опубликовало эту надпись здесь
                    0
                    нет, не Вам =)
                  +1
                  Я руководствовался оффициальной документацией, в частности этим описанием. В принципе на практике я никогда не испытывал проблем с указанием больших значений, однако раз это указано в Script Reference, я решил упомянуть об этом и в статье.
                  0
                  возможно оффтопик, но хотел бы спросить.
                  можно ли модифицировать один обьект другим обьектом с использованием булевой опреации?

                  грубо говоря есть квадрат и окружность (пересекающаяся), надо получить обьект иллиюстрирующий булевую операцию «И»
                  тоесть оставить только ту область где два обьекта пересекаются?

                  +1
                  Ничего нового для себя не нашёл, но новичкам правда может пригодится.

                  Автору всё равно спасибо
                    +1
                    Ну вот, а я как раз подобную статью пишу в данный момент. Даже не знаю, стоит ли постить теперь…

                    По статье — сухо, ну какой мне толк от того, что я знаю, как получить кватернион объекта, если я не понимаю, что это такое и как они работают (статья на вики не одержит ответа на этот вопрос). В Scripting Refefence и то подробнее все расписано.
                      +2
                      Лично мое мнение — полезной информации никогда не бывает много. Поэтому писать статью Вам определенно стоит.
                      Возможно описано сухо, но я постарался аггрегировать наиболее употребляемые мной лично методы в работе, надеюсь кому-то пригодится как справочник. Изначально была идея сделать «живые» примеры использования для большинства описанных методов, но статья выходила слишком объемный. Решил оставить в таком виде, как вводной экскурс. Если получится, то в ближайшее время планировал сделать более подробные статьи по каждому разделу с примерами и описанием.
                        0
                        Да просто обсасывать одну и ту же тему, ИМХО, моветон. Тем более, с отрывом в пару дней :)

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

                        Про объем можете и не говорить, я в январе этого года написал тут коротенький туториал для новичков, а когда второй стал делать быстро сдулся, потому что пытался объяснить в одном уроке от и до каждый аспект… А сейчас вот решил продолжить это дело, только уже постепенно матчасть объяснять, а не сразу практические задачи решать…
                    • НЛО прилетело и опубликовало эту надпись здесь
                        +1
                        Просто на JavaScript пишу несколько дольше, да и изначально писать под Unity3d начал на нем. Конечно переучиваться никогда не поздно, но на данный момент меня все устраивает.
                        +2
                        Очень познавательно для новичков и для тех кто не читает документацию, но я бы на вашем месте раскрыл все таки нюансы производительности при использовании той или иной функции например Find(name) как писали выше, категорически нельзя пихать в Update(), или например:

                        Добавить объекту крутящий момент. Применение данного метода заставит объект вращаться вокруг своей оси в указанную сторону:

                        GameObject.rigidbody.AddTorque(0, 1, 0);


                        Маленькая поправка: он будет вращаться всетаки вокруг своего центра тяжести rigidbody.centerOfMass.
                          0
                          Спасибо, поправил.
                          0
                          У меня была одна проблема, которую до сих пор не решил:
                          Есть объект не правильной формы, скажем в виде буквы «П», для инспектора в Юнити он представляется как правильный прямоугольный объект, как программно определить все его реальные стороны, то есть внутренние стороны «П», с внешними все понятно, это не сложно, а вот внутренние, или скажем изначально объект в Максе был повернут и сохранен, в Юнити его видно как прямоугольный, но у него уже есть угол наклона, тоже определить его реальные стороны так и не вышло.

                          Некто не сталкивался с такой проблемой и не знает как ее решить?
                            0
                            Я не совсем понял о чем речь (точнее совсем не понял),
                            скриншот помог бы!
                              0
                              Давайте перефразирую, есть фигура в виде ромба, для инспектора он прямоугольник, как реально найти стороны этой фигуры?
                                0
                                Это такой очень тонкий троллинг? )

                                1. Что значит фраза «для инспектора в юнити он представляется»?
                                2. Что значит фраза «как программно определить все его реальные сторон»/«как реально найти его стороны»?

                                Нужен скриншот, или лучше чуточку более внятное описание и можно простой проект-пример.

                                Спасибо!
                                  0
                                  Я конечно могу ошибаться, но мне кажется, что у Вас при импорте задействуется дефолтный боксовый коллайдер. То есть после импорта модели Вам следует повесить на нее коллайдер с той же геометрией, что и сама модель. То есть навесить в качестве коллайдера эту же модель. Немного субурно, конечно, но думаю смысл ясен :)
                              +1
                              Неплохо было бы добавить, что обращение к rigidbody объекта требует, чтобы у этого объекта был компонент Rigidbody (добавить либо через редактор, либо через код), а то будет возникать NullReferenceException
                                0
                                Спасибо, обновил.

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

                              Самое читаемое