Unity3d, в помощь начинающим

  • Tutorial

Эта статья предназначена для тех пользователей unity3d, что уже хорошо знакомы с самим движком, но ещё не обладают достаточной собственной базой знаний для того, чтобы писать без дополнительного сёрфинга по интернету, с целью поиска возникающих иногда фундаментальных вопросов. Чтобы сократить некоторым время на ресёч, расскажу несколько важных фишек, которые необходимо знать каждому unity программисту. Если у Вас возникают вопросы: как сделать чтобы у Вас не тормозило на чём-то послабее iPad 3, или Вы не знаете как удобно работать со спрайтами, как заставить музыку не прерываться при загрузке, или как обойти максимальный допустимый размер под android (50 мегабайт) и так далее, возможно Вы найдёте ответ в этой статье.

Статья затрагивает лишь проблемы мобильной разработки (IOS, Android). Все примеры только на C#.

Автор статьи не претендует на абсолютную категоричность и правильность предложенных решений.



Основы основ


1) Обязательно почитайте рекомендации от разработчиков Unity, как писать оптимальные с точки зрения производительности скрипты.

2) Лучше всего создать класс, прямой наследник от MonoBehaviour, который реализует кеширование transform и остальных подобных свойств MonoBehavior, все основные скрипты наследовать от него:
Код с примером кэширования
public class Qwant : MonoBehaviour {
    private Transform _qwantTransform = null;
    public Transform qwantTransform {
        get {
            if (_qwantTransform == null) {
                _qwantTransform = transform;
            }
            return _qwantTransform;
        }
        protected set {
            _qwantTransform = value;
        }
    }
}


3) Используйте для хранения глобальных параметров своей игры статический класс со статическими методами и полями (если конечно данные не должны быть сохранены при закрытии игры, тогда пользуйте PlayerPrefs) но такой объект должен быть всего один, не увлекайтесь. Храните в нем только такие глобальные вещи, как текущее состояние игры (игра на паузе; мы в меню; cписок доступных пёрхейзов с их ценами, полученный с сервера и т.п.)

4) Всегда используйте (даже если ваша игра вся в 2D) две камеры одну для игровых объектов, другую для GUI, указывайте у GUI-элементов необходимый слой (слой GUI камеры и будет Вам счастье) Конечно это не касается случаев меню, где и одной камеры предостаточно.
Скриншот с GUI камерой


5) Если Вы хотите, чтобы ваш GUI под iPad выглядел не размытым а так же на всех устройствах одинаковым, Вы не хотите его ресайзить в зависимости от размеров экрана, сделайте для GUI камеры size — 1,59. Спокойно верстайте всё под размер iPad2, на других устройствах всё будет отмасштабировано само.

6) Если необходимо перетащить несколько объектов в список другого объекта, и не хочется перетаскивать их по одному, можно зафиксировать объект, отображаемый в инспекторе, нажав на замочек в правом верхнем углу (на предыдущей картинке его тоже можно заметить).

Работа с 2d спрайтами (плагин tk2d)


В Unity3d для работы с 2d спрайтами нет практически никаких инструментов. Создать текстурную карту без использования плагинов Вы не сможете, да и писать спрайтовую анимацию Вам придется самостоятельно (в 4 unity это дело немного поправили). Для работы со спрайтами лучше всего использовать плагин tk2d. Плагин на момент написания статьи стоил 65$, и, на мой взгляд, отрабатывает их на все 100%. Плагин позволяет:
  • Создавать текстурные карты различного размера (при этом позволяет разрезать исходные текстуры так, что они влезут);
  • создавать спрайтовые анимации;
  • работать с текстурным шрифтом;
  • tk2dCamera? (Ни разу не пользовался, но разработчики плагина рекомендуют, наверно действительно стоящая вещь!);
  • снабжён достаточно толковой документацией и туториалами.

За достаточно долгое и плотное использование плагина не было замечено ни одного бага.

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

Пример: PlaneResizer.cs

Графический текст


Какая же игра без красивого шрифта? Для создания красивого графического текста лучше всего использовать Glyph Designer, работает только под Mac, платная, но зато удобная. Имеется как минимум два бесплатных аналога, работающие под Windows, но по-настоящему красивый текст в них Вы не получите.

Программы по созданию графических шрифтов создадут xml и текстурную карту, используйте их в tk2d или в бесплатном EZ GUI.

Pool объектов


Одна из проблем, с которой можно столкнуться при написании игры – это большая дороговизна операции создания объекта, т. е. дороговизна операции Instantiate. Для решения этой проблемы для начала можно воспользоваться CoRoutine’ом, но если на мобильном устройстве тормозит даже создание одного объекта, тогда необходимо использовать порождающий паттерн проектирования Объектный пулл.

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

Это единственный способ борьбы с лагами при написании игры с динамически генерируемым уровнем, например, при написании раннера.

Quality Levels


Мобильные устройства – очень разные по разрешению, но всегда хочется добиться, чтобы игра работала с хорошей производительностью на всех устройствах. Готовить несколько различных билдов для различных устройств (4 apk для android и 2 билда для iOS) абсолютно необязательно.

Сначала правильно настройте Quality уровни в игре (Edit->Project settings->Quality):

Скриншот с настройками Quality


В unity3d есть замечательная возможность переключать Quality прямо на этапе запуска приложения.

Таким образом: необходимо создать нужные нам Quality Levels, и написать небольшой скрипт, который можно повесить на пред загрузочную сцену. Этот скрипт будет переключать качество, в соответствии с любыми нашими условиями.

Например, можно менять качество текстур от Full Res до Quarter Res. Из всех функций и свойств нам могут понадобиться, только:
  • QualitySettings.names;
  • QualitySettings.SetQualityLevel (int).

Пример такого скрипта: CorrectQuality.cs

DontDestroyOnLoad объекты и бесконечная музыка


Если необходимо, чтобы главная тема приложения проигрывалась беспрерывно, даже при перезагрузке из одной сцену в другую необходимо использовать DontDestroyOnLoad (gameObject);

Т. е. объект с нашей музыкой не удаляется никогда. Важно учесть, чтобы таких наших неудаляемых объектов не получилось в результате несколько. Для этого целесообразно использовать паттерн программирования Singleton.

Таким же образом организуются и другие неудаляемые объекты.

Пример такого скрипта: Music.cs

TouchDispatcher


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

На мой личный взгляд очень удобно это организованно в cocos2d/x, за основу и была взята предложенная этим игровым движком концепция:

1) есть интерфейс, содержащий обработку 4 стандартных функций touch событий: TouchBegan, TouchMoved, TouchCancelled, TouchEnd. Если объект отлавливает touch события, он реализует этот интерфейс.
2) Есть класс TouchDispatcher – класс, управляющий и делегирующий события.

Для удобства тестирования необходимо, чтобы не было разницы между мышкой и пальцем, всё-таки компилировать под устройство – долго. Также надо учесть следующее:
  1. возможность использования multitouch
  2. захватывает(swallow) объект touch событие или нет
  3. приоритет объектов
  4. добавление объекта в список делегатов
  5. удаление объекта из списка делегатов

После написания такого простенького класса, работа с touch становится очень удобной, простой и ясной. Полученный скрипт вешается на GameObject, который помещается в корень сцены, например под именем SharedTouchDispatcher. Скрипт реализующий интерфейс делегата, ищет наш TouchDispatcher посредством GameObject.Find(“/SharedTouchDispatcher”).

Пример: TouchDispatcher.cs, TouchTargetedDelegate.cs

AccelerometerDispatcher


Всё что, написано выше для TouchDispather’а справедливо и для аккселерометра, с одним лишь исключением: аккселерометр у устройсва всегда только один, а значит, и код реализующего контроллера немного проще.

Есть только одна особенность, про которую нельзя забывать, при работе с акселерометром: если устройство перевёрнуто вверх ногами: PortraitUpsideDown или LandscapeRight, нельзя забывать, что меняются значения по оси x или y, соответственно. Мы получим обратно-ориентированное управление, не учтя это.

Пример: AccelerometerDispatcher.cs, AccelerometerTargetedDelegate.cs

Проверка попадания точки в concave полигон


Одна из самых распространённых задач компьютерной графики. Если коротко: точка находится внутри невыпуклого полигона, если луч, направленный в любую сторону пересекает края полигона нечётное количество раз.

Сам алгоритм можно легко найти в интернете.

Я рассматриваю эту задачу сейчас в контексте попадания touch в объект GUI (Конечно, можно всё делать проверкой попадания луча в collider, но, на мой взгляд, для 2D объектов лучше использовать предложенный ниже способ).
Для простых кнопок можно использовать обычные прямоугольники и touchZone.Contains(position);. Но если объекты – сложной формы, и перекрывают друг друга – этого будет недостаточно.

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

В результате мы получим координаты относительно центра нашего 2D объекта, что нам и нужно.

Полученную строку легко загнать в наш обрабатывающий скрипт, и распарсить на массив точек:

Код с примером распарсивания строки в массив точек
private void ParsePath(){
	//получили массив пути
	char []separator={',','\n'};
	string []numbers=pathTouch.Split(separator);
	Path= new Vector2[numbers.Length/2];
	for (int i=0;i+1<numbers.Length;i+=2){
		Path[i/2]=new Vector2(float.Parse(numbers[i],NumberStyles.Currency),float.Parse(numbers[i+1],NumberStyles.Currency));
	}
}


Таким образом, осталось только реализовать алгоритм поиска пересечения прямых.

Пример реализации: Crossing.cs

Также прилагается макет класса, реализующий интерфейс делегата touch событий, и проверяющий попадания пальца в обводку.

Пример: SpriteTouch.cs

Догрузка уровней из интернета WWW


Если у Вас возникла необходимость догрузить часть уровней из интернета после установки игры, то WWW.LoadFromCacheOrDownload – Ваш лучший друг. Но он работает только в Unity Pro.

Загружать можно только специально подготовленные сцены, создавать такие можно при помощи
BuildPipeline.BuildStreamedSceneAssetBundle. Честно говоря под Unity 3.5.2 довольно глючно работало под IOS, но я думаю с тех времён это дело поправили.

В документации Unity подробно и исчерпывающе описан весь процесс организации загрузки уровней из сети.

Заключение


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

Почти всё из описанного выше, было использовано здесь:

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

    –5
    Эммм… «Автор статьи не претендует на… правильность предложенных решений.»
    Зачем делать рекомендации другим людям, если сами вы некомпетентны?
      +8
      Думаю, автор готов принять любые обоснованные замечания, и просто напомнил, что не стоит воспринимать его советы как истину в последней инстанции…
      +5
      Хостить код на гугл докс — месье знает толк в извращениях.
        0
        Соглашусь 2D Toolkit — очень полезный плагин, за 65 баксов экономишь очень много времени.

        Баги есть (я лично нашёл несколько), но автор (Dinesh Kumar) правит быстро. Саппорт очень хорош, отвечает быстро (автор живёт в UK, то есть по их времени работает).

        Так же хочу отметить плагины Prime31 — тоже полезны, хотя ценник завышен.
          0
          Вопрос автору — а зачем 2 камеры в 2Д? Поясните 4-ый пункт, просто мы одной обошлись, не понятно зачем усложнять.

          Что касается PlayerPrefs — он тормозной на планшетах, вот есть быстрый вариант:
          www.previewlabs.com/writing-playerprefs-fast/

          перевод статьи тут:
          www.v4siliy.ru/2012/08/%D0%B1%D1%8B%D1%81%D1%82%D1%80%D0%B0%D1%8F-%D0%B7%D0%B0%D0%BF%D0%B8%D1%81%D1%8C-playerprefs/
            0
            Конечно, если сцена всегда влезает в камеру, и камера относительно сцены не двигается, а всегда находится, например в (0,0,0), можно обойтись и одной камерой. Можно обойтись одной камерой, и если всё двигается, а Вы все элементы меню повесили на какой-то один GameObject, но неудобно, про это всегда надо помнить, при написании скриптов, как-то связанных с GUI. Но потом, если Вы захотите, чтобы камера, например тряслась, а элементы меню нет, у Вас сложные выпадающие меню и так далее, то возможно будет плохо. Лучше, когда всё для GUI висит где-то в пространстве само по себе, GUI-камера, например в (0,0,0), и с миром игры GUI никак не связано.
            +2
            Замечания будут, причем их довольно много. Но основной смысл моего комментария - хватит писать собственные велосипеды и начать юзать готовые плагины/extensionы. Потому что это не только в разы ускоряет разработку, но и отдаляет от множества собственноручных костылей-подпорок.
            А теперь по пунктам:
            Основы: 1) да, нужно читать про оптимизацию, юнитеки плохого не напишут, но не нужно бросаться оптимизировать все и вся — юзаем профайлер. Все знают, что является root of all evil.
            2) не понял, зачем? В чем смысл? Нафига нужен кешер для трансформа, если трансформ все время будет меняться? Если просто хочется написать wrapper, то почему бы не сделать что-то типаhttp://pastebin.com/k7FFsV60. Такие врапперы уже вбиты в более-менее достойные плагины и юзаются по умолчанию.
            3) можно было проще: используйте синглтон.
            4) Как-то очевидно. Зачем тогда нужно несколько камер?
            5) А вот так вот не надо делать. Круто, если мы пишем только под iOS, там пропорции экрана не меняются, там все клево. А что делать с зоопарком андроидов? При портировании на андроиды схватим немало проблем.
            Чтобы все выглядело одинаково, мы применяем следующую технику: мы делаем текстуры заведомо больше, чем нужно, а потом скейлим вниз пропорционально, по высоте. То есть на более мелких экранах это будет выглядеть четко, не будет размытия и т.д. А чтоб не попасть на проблемы со слабыми девайсами, используем HD и SD текстурные карты, которые переключаем в рантайме. Также не забываем про привязку к краям экрана(Anchor, Placement, их по разному называют). И соответственно, нужно забыть про pixel-perfect. У кого есть более удобные методы, поделитесь плиз.
            по поводу шрифтов и вообще 2d: ни в коем случае не использовать стандартные средства. Пока в юнитях не появился человеческий gui, это неудобно, при этом это энергозатратно. А если используется много спрайтов, то прощай производительность мобильных игр. Используйте EZ GUI, NGUI. Мне лично больше понравился NGUI, просто потому, что он легче настраивается и многое у него работает корректно из коробки. В ЕZ мне пришлось много писать системных компонентов.

            Про пул объектов сказано все абсолютно верно. Отмечу, что нужно по максимуму избегать операций типа GameObject.Find, Shader.Find, GetComponent, SendMessage и производных от них. Это ОЧЕНЬ медленно. Намного лучше кешировать и обращаться напрямую.

            DontDestroyOnLoad нужно использовать с опаской, потому что
            а) его включают и забывают. А потом не понимают, куда теряются связи и прочие элементы. Обоснованное использование — это синглтоны.
            б) Объект должен быть нетяжеловесным. Оптимизировать производительность таких элементов невозможно, потому что он всегда есть и всегда нужен.

            Про TouchDispatcher, AccelerometerDispatcher и иже с ними: посмотрите, что есть на unity store! Зачем писать собственный велосипед, когда есть уже готовые, 99% рабочие компоненты? Тот же самый finger gestures стоит 10 баксов, а на разработке по-любому спасет несколько человеко-часов, а может быть и дней.

            Про проверку пересечений, 2D — смотрим выше про NGUI, EZGUI. Но если уж так хочется написать собственную систему, то тут несколько моментов:
            1) если объекты круглые или прямоугольные, попадание нужно проверять через Rectы или просто по пересечению окружностей.
            2) если у нас сложные объекты, то быстрее проверять коллайдерами. Проверено. Ну а избегать пересечения нужно с помощью z-глубины.

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

            Как-то длинно и сумбурно написал, извините. Прямо крик души.
              +2
              Кешировать transform надо потому, что когда вы пишите
              gameObject.transform
              Unity делает
              gameObject.GetComponent<Transform>()
                0
                Крик души, это очень хорошо, но Вы совершенно невнимательно прочитали статью, и привели очень много замечаний не соответсвующих её содержанию.
                Мне не понятно, как прочитав рекоммендации по оптимизации скриптов Вы не поняли, зачем кешировать transform и т.п.
                  0
                  Да, про трансформы затупил, согласен. Но другие замечания кажутся вполне в тему.При этом почти не затронуты другие, мне кажется, более ресурсоемкие и важные, вопросы оптимизации(к примеру, рендеринг и вообще 3d). Если Вам кажется, что это не так, давайте обсудим.

                  Просто считаю некоторые пункты поста вредными для прочтения и выполнения новичкам(про разные разрешения экранов, про polygon crossing и т.д.).
                  [captain_mode]Нулевым правилом для начинающих я бы отметил умение гуглить и находить наилучшее готовое решение, а не бросаться писать все руками.[/captain_mode]
                  Я помню, как после полугода писания на Unity у меня была мания написания собственных крутых штук под все и вся. Как только появлялась какая-то объемная задача, я уже начинал пилить свою универсальную систему велосипеда. Уже только после нескольких таких систем осознал, что намного лучше по времени разработки и производительности найти/купить уже готовое решение, чем делать самому. Даже сожалею, что никто меня не одергивал и не говорил, что так делать не надо.
                    0
                    На самом деле очень приятно, что Вы критикуете.
                    Сейчас прокомментирую Ваш ответ весь по пунктам:
                    3)Не совсем ясно, чем синглтон проще чем, статик класс, и потом или он уничтожится при смене сцены, или надо делать его DontDestroyOnLoad.
                    4)Это неочевидно.
                    5)Под iOS пропорции экрана ещё как меняются от 4x3 до 16x9. Предлагается верстать всё PixelPerfect для iPad, для всех же других разрешений всё отмасштабируется, как нужно, само. Конечно, расположение объектов надо привязывать к краям экрана. Резиновую вёрстку никто не отменял. Т.е. поясню ещё раз: всё верстаем под размер 1024x768 полученные элементы будут PixelPerfect на iPad, на других же экранах всё отмасштабируется, но положение элементов надо вычислять.
                    Очень много трудоёмких операций) Например ActiveRecursively() (в unity 3). А ещё распаковка музыки в память.

                    Насчёт 2д, смотря какие задачи.
                    Во-первых, Вы всё же невнимательно прочитали, и да, простые объекты хорошо проверять по кругу, или прямоугольнику.
                    Во-вторых. Интересно, а как вы добьётесь без контроллера захвата тач события(swallow) и зададите используя только z-index, чтобы объект спереди перехватывал событие раньше, того, что сзади? А ещё: коллайдер у объекта маленький, а мы хотим дополнительный масштаб для тач событий, чтобы можно было пальцем попасть?
                    Ну и моё личное мнение с 2д, всё таки, надо работать как с 2д. Да и физика с обработкой тач никак не связана.
                    Для простых задач и лучи с коллайдерами сгодятся.

                    Про www, действительно, было бы интересно почитать.
                      0
                      3) синглтон на то и синглтон, что у него ленивая инициализация и он всегда обязан существовать во время вызова. ну и синглтон все же отличается от статик класса
                      5) это я понял, даже так делал. пока не начал портировать все на android-ы. Во-первых, там размеры экранов могут быть совершенно разные, но самое фиговое — это разные пропорции. При вашем случае получается несколько проблем:
                      а) скейл вверх — портит картинку именно качество
                      б)пиксел-перфект тупо пропадет (из-за разных пропорций).

                      ща по поводу 2d: давайте определимся с тем, что нужно и как это делать.
                      1) 90% всех touchable элементов делается кругами или прямоугольниками — тут все просто.
                      2) 5 из 10% оставшихся элементов можно привести к кругам или прямоугольникам.
                      3) при остальных пяти процентах случаев(а это должно быть, реально тяжелый полигон, раз нельзя его привести к первым двум случаям) реально быстрее проверить через физику.
                      это во-первых, теор.часть. Во-вторых, Вас никто не заставляет писать это все руками, уже давно есть 2d фреймворки, где все уже давно сделано за Вас. К примеру, в NGUI проверка проходит через физику, если нужно сделать дополнительный рект, то просто увеличиваешь коллайдер.
                      В EZGUI же происходит сортировка по глубине.
                      И не надо писать собственный контроллер тачей, подписчиков событий.

                      Мне кажется, что наше обсуждение больше переходит в холливар. Предлагаю перейти в личку.
                        0
                        >Мне кажется, что наше обсуждение больше переходит в холливар. Предлагаю перейти в личку.
                        А зря, мы ведь читаем и учимся. :) Хорошо видеть две стороны одной проблемы. За то и люблю хабр, что комменты к статьям очень ценные бывают.
                  +1
                  посмотрите, что есть на unity store!

                  а как же спортивный интерес? ) бывает и самому хочется что-нибудь эдакое сделать
                  +1
                  Думаю стоит добавить немного информации про плагины от Prime31, для реализации таких вещей как внутренние покупки а также быстрой интеграции социальных функций/рекламы/gamecenter и тд. очень упрощают жизнь и экономят уйму времени.
                  Отличная документация, поддержка, регулярные обновления. Используем уже в 3ем проекте и очень довольны.
                  Правда цены дороговаты но того стоят.
                    0
                    Да, да без них никуда!
                    0
                    Какое-то слабо связанное множество советов.
                    Взяли что вспомнили и запихали в статью. Возможно, сами по себе они имеют смысл, но так как статья без цели, то и советы эти ни у кого в голове не отложатся.

                    И почему вдруг лишь tk2d? В Asset Store еще много хороших движков для 2d. И все они умеют все, что вы описали в статье.
                      0
                      Спасибо интересная статья! Небольшой комментарий, всё же от синглтонов я отказался бы, стоит использовать статические классы. Доступ к ним понятнее и храниться будет стеке, а не в куче. И ко всем господа, рекомендация Unity — страйтесь создавать значимые типы, а не классы.
                        0
                        Это касается в основном для хранилищ каких-либо значений, параметров и прочего (для моделей данных, DTO и т.п.).

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

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