Перейти к основному содержимому

Unity3D

· 7 мин. чтения

Unity3D это игровой движок 3D-игр в основном стрелялок и аркад, но при большой сноровке можно реализовать и RPG, RTS, квесты, 2D-игры, встраиваемые в браузер, iPhone и с использованием сети. Движок понимает множество разные форматы файлов

  • 3д модели и анимация из Maya, Cinema 4D, 3ds Max, Cheetah3D, Modo, Lightwave, Blender. Форматы 3ds, fbx и dae
  • практически все растровые изображения для маск и текстур
  • звуковые mp3, wav форматы для эффектов и фона

Профессиональная версия имеет более расширенные возможности по работе с динамическим освещением, сетевыми возможностями. Но даже без наличия блокбастерных игр о которых все бы слышали, unity успешно продвигается в плане комьюнити и «народного движка» и вполне возможно что с бесплатными плагинами можно будет написать игру ничем не уступающую платным.

IDE

Графическая оболочка разработчика (IDE) состоит из 4 основных панелей

  1. Графическое поле, переключающееся / вид игры (Game & Scene)
  2. Иерархия объектов (Hierarchy)
  3. Инспектор объекта (Inspector)
  4. Библиотека доступных ресурсов (Assets)

По используемости самое важное место это Инспектор, потому что именно в нём видны внутренности объекта (GameObject)

Создание карты

Карта (Terrain) создаётся очень просто из меню и по умолчанию уже есть. Курсором как в фотошопе можно изменять высоту рельефа (в том числе используя Shift). Более продвинутые господа придумали как импортировать карты из реального мира с помощью google earth, openTDD, делая так называемые heightmaps. Встроенный импорт работает только с raw форматами, надо использовать хак. Недавно вышел генератор карт

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

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

Свои скрипты добавляются очень легко перетаскиванием в инспектор и публичные переменные сразу переводятся в названия с пробелами и значением по умолчанию. Для игрового объекта добавленные компоненты и с точки зрения ООП являются инкапсулированными объектами, хотя и играют в большинстве своём роль свойств (т.е. прилагательных - движущийся, крутящийся, выбираемый и тп.). По синтаксису скрипты пишутся на Javascript, C# или Boo скриптах со своими прибамбасами - глобальными объектами Unity, некоторыми своими ключевыми словами..


Взаимодействие объектов

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

Поскольку объекты можно вкладывать друг в друга как dom-блоки, то искать по иерархии объекты можно несколькими путями. Жалко что ребята изобретали велосипед, вместо того что-бы взять существующие аналоги из html/js

  • Сразу писать название компонента скажем myWorkerGuy.someColorParam

  • Поиск по иерархии - transform.Find("MyWorkerGuy") - аналог html'ного getElementById

  • По тєгам, через GameObject.FindWithTag ("Worker")

Компонент объекта доступен через - GetComponent("Moveable").

Ещё одно мощное решение - вместо поиска объекта с проверкой наличия метода и затем вызова его, можно просто послать сообщение - gameObject.SendMessage("BegForFood",true), причём метод вызывается у всех компонентов.

Каждый компонент наследует класс Monobehaviour (в Javascript синтаксисе объявлять не нужно) и именно с ним приходится работать описывая логику и расширяя наследуемые методы которые сами вызываются когда движок решит это нужным, например:

  • Invoke - вызов третьего метода, в том числе периодический, аналог call_user_method в php + cron

  • Awake - своего рода конструктор, вызывающийся когда все объекты уже существуют.

  • Start - конструктор второго рода, вызывается после Awake

  • Update - вызывается каждый фрейм. Сюда идёт основная логика.

  • FixedUpdate - вызывается каждый n-фрейм, полезен для вычислений твёрдых тел

  • LateUpdate - вызов после всех Update вызовов, полезен для камеры

  • OnGui - сюда идёт показ кнопок, цифр для управления

Реальные нужды

После того как вы придумаете историю, надо продумать основные технические моменты. К этому моменту вы знаете что есть объекты и их характеристики (компоненты), но из чего состоит процесс игры?

Рождение (Spawn) юнитов и врагов. Объект создаётся с указанием, где и с каким вращательным моментом. Как оказывается нарожать много умных юнитов с поддержкой всех фич (в частности физики) очень нагружает процессор.

GameObject go = Instantiate (building,hit.point,Quaternion.identity) as GameObject;

Управление камерой. Существуют уже заранее сделанные компоненты-скрипты на всякие случаи жизни, которые учитывают в том числе столкновение камеры со стеной и тп. Вот часть кода для изометрической камеры (добавьте остальные направления сами)

if (Input.GetKey("up") && transform.position.z < terrain.terrainData.size.z) {
transform.position += linearSpeed * Vector3.forward * Time.deltaTime;
}

if(Input.GetAxis("Mouse ScrollWheel") > 0 && transform.position.y > 1){
transform.position -= linearSpeed * Vector3.up * Time.deltaTime;
}

Выделение юнитов мышкой. Достаточно сложная задача - в кратце на камеру вешается обработчик нажатий и передвижения курсора. Независимо создаётся singleton-объект для хранения всех юнитов. При нажатии и передвижении курсора происходит проекция прямоугольника с маской ландшафта. Задача не из лёгких, потому что обрабатывать надо и deselect, учитывать клики для передвижения и атаки, рождение юнитов и тп. Возможно singleton не самое хорошее решение и лучше это сделать тэгами.

mouseButton1UpPoint = screenPosition;
var hit : RaycastHit;
ray = Camera.main.ScreenPointToRay (screenPosition);
if ( Physics.Raycast (ray, hit, raycastLength, terrainLayerMask) ){
selectionPointEnd = hit.point;
UnitManager.GetInstance().SelectUnitsInArea(selectionPointStart, selectionPointEnd);
}

Поиск пути (Pathfinding) и передвижение юнита кликами мышки очень алгоритмически увлекательная задача. К счастью существует отличное решение Арона Гранберга. Алгоритм A* интересно в теории, но в редакторе можно и запутаться. Основная идея в том что юниты имеют дискретную сетку клеток для расчёта передвижения. 

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

GetComponent("Seeker").StartPath (transform.position,destinationPoint);

Seeker расчитает путь по которому можно добраться до цели (обычно это ломанная кривая из 3х отрезков) и дальше уже своими силами надо реализовывать передвижение по этим точкам.

Движение юнитов. Пожалуй самое сложное, поскольку если юниты обладают физикой то это усложняет задачу. Надо просто понимать что нельзя сказать юниту x=x+1 через фиксированные участки времени. По идее надо использовать rigidbody.AddForce и учитывать ситуации OnCollision. Ещё одна проблема - повороты объекта. Они далеко не простые

transform.rotation = Quaternion.Slerp (transform.rotation, Quaternion.LookRotation(direction), rotationSpeed * Time.deltaTime);

Минусы и сложности

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

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

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

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

Глупый GUI. Во времена когда HTML5 реализуется всеми браузерами, тут приходится писать очень странный код, напоминающий layout в Java Swing (sic!) и порой вычитывать недокументированные особенности на форуме (например что при декларации кнопки его onclick event сразу внутри скобок, а вот как получить onmouseover и другие события или изменить её дизайн - та ещё тайна).

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

Дебаг. Из-за того что код размыт на объекты-компоненты очень сложно организовывать нормально проект и как следствие сложно дебагить, даже несмотря на консоль, подсветку и возможность поменять значения на лету. Сложно и оттого что Scite который по умолчанию редактирует скрипты не знает никаких подсказок о пространстве переменных Unity, не умеет ничего подсказать, нельзя перепрыгнуть в нужное место как то привычно в других языка и IDE.

Расширения

Unity позволяет не только писать скрипты под существующие объекты, но и изменять саму IDE добавляя новые панели управления и меню, то движок можно расширить своими мини-фреймворками

  • Explosion- многопараметрические взрывы (цвет, взрывная волна, дым, размеры)
  • Locomotion - движение людей и зверюшек c вызовом нужной анимации при соответсвующих вводах с клавиатуры
  • Skydome - динамическое небо
  • Last Bastion Games - диалоги, recorder, прототип RTS и гонок

Обязательно гляньте:

  • LearnMeSilly - набор основных уроков по работе с редактором, объектами

  • Blender - бесплатный редактор. Можно в нём создать модели и экспортировать в 3ds

  • Turbosquid - коллекции 3д моделей (в т.ч. платные)

  • Unity3D Answers - своего рода база проблем и решений