Создание игры на Unity небольшой командой: особенности технологии. Unity3d. Начало работы, практические советы. Рецензия

Наткнулся на англоязычную статью 2012-го года, от немецкого автора, фрилансера-геймдевелопера, разработчика инструментов для Юнити — Herman Tulleken. В статье, сведены 50 советов по работе в Юнити. Советы основаны на опыте работы автора в проектах, с командами от 3 до 20 человек. Автор предупреждает, что не все из них, могут быть применены в каждом проекте; многие советы — дело вкуса. От себя добавлю, что Юнити эти 3 года не стоял на месте, и возможно некоторые советы, могут быть уже не актуальны. Перевода я не нашел. Подумал, а почему бы мне ее не перевести ее, для своего сайта.

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

Процесс

1. Избегайте разветвленных ассетов. Всегда, должна быть только одна версия, любого ассета. Если вам абсолютно необходимо ответвить новую версию префаба, сцены или сетки, следуйте процессу, это сделает ясным, какая версия является правильной. «Неправильная» версия должна иметь броское имя, например, использовать двойное подчеркивание префикс: __MainScene_Backup . Ветвление префабов требует определенного процесса, чтобы сделать его безопасным (см в разделе Префабы).

2. Каждый член команды, должен иметь второй экземпляр проекта для тестирования, если вы используете систему контроля версий. Все изменения должны делаться, и тестироваться в нём, и затем вносится в «чистовую» версию. Никто не должен делать какие-либо изменения в их «чистовых» экземплярах. Это особенно полезно, чтобы отловить недостающие ассеты.

3. Рассмотрите возможность использования внешних инструментов для редактирования уровня. Юнити не лучший инструмент для этого. Например, мы использовали TuDee для создания уровней на основе 3D-тайлов, где есть такие полезные функции, как: привязка к сетке, 2D вид, быстрый выбор тайлов. Простое инстанцирование префабов из XML-файлов.

4. Рассмотрите сохранение уровней в XML, а не в сценах. Это прекрасный метод:

  • это избавляет от необходимости настраивать каждую сцену;
  • это делает загрузку намного быстрее (если большинство объектов являются общими для всех сцен).
  • это делает легче объединение сцен.
  • это делает более легким отслеживание данных уровня.

Вы по-прежнему сможете использовать Unity в качестве редактора уровня (хотя и не должны). Вам нужно будет написать код для сериализации и десериализации данных, для загрузки уровня в редакторе и во время выполнения, а также для сохранения уровня в редакторе. Вам также может понадобиться, имитировать ID систему Unity, для поддержания ссылок между объектами.

5. Рассмотрите написание пользовательского инспектора. Написать пользовательский инспектор довольно просто, но система Unity имеет много недостатков:

  • Он не позволяет воспользоваться преимуществами наследования.
  • Он не позволяет определить компоненты инспектора на уровне поля, только на уровне класса. Например, если каждый игровой объект, имеет поле типа SomeCoolType , который вы хотите отобразить в инспекторе иначе, вы должны написать инспектор для каждого класса.

Вы можете решить эти вопросы, повторной реализацией системы инспекторов. Используя несколько приемов рефлексии, это не так сложно, как кажется. Детали, представлены в конце статьи.

Организация сцены

6. Используйте именованные пустые объекты, как папки. Тщательно организовывайте свои сцены, чтоб облегчить поиск объектов в них.

7. Помещайте при редактировании свои префабы и папки (пустышки) в нулевые координаты (0, 0, 0). Если префаб/пустышка редактируется не для позиционирования, он должен быть в нуле. Таким образом, уменьшается вероятность возникновения проблем с локальными и мировыми системами координат, и код как правило проще.

8. Минимизируйте использование смещений для компонентов GUI. Смещения должны использоваться для компоновки компонентов, только относительно их родительского контейнера; они не должны полагаться на позиционирование своих прародителей. Смещения не должны отменять друг друга, чтобы правильно отображаться. Это в основном для предотвращения такого рода вещей:

Родительский контейнер произвольно размещен в (100, -50). Ребенок, предназначенный для позиционирования в точке (10, 10), помещен в (90, 60) [относительно родительского].

Эта ошибка распространена, когда контейнер невидим, или не имеет визуального представления вообще.

9. Размещайте вашу землю на y=0. Это облегчает задачу размещение объектов на земле.

10. Делайте игру, готовую к запуску в каждой сцене. Это значительно сокращает этап тестирования. Для этого нужно сделать 2 вещи:

Во-первых, предоставить в сцене все данные, требующиеся от предшествующих сцен, если они не доступны.

Во-вторых, создайте объекты, которые должны сохраняться между загрузками сцены, следующим образом:

myObject = FindMyObjectInScene();

if (myObjet == null)
{
myObject = SpawnMyObject();
}

Арт

11. Размещайте пивот поинты (точки вращения) в основании объектов и персонажей, а не в центре. Это упрощает установку объектов, точно на поверхность. Так же это позволяет работать с 3d, как с 2d в игровой логике, AI, и даже в физике, когда это необходимо.

12. Ориентируйте все ваши объекты, включая персонажей, «лицом» вдоль одной и той же оси (положительное или отрицательное направление оси Z). Многие алгоритмы упрощаются, когда все объекты лицом направлены в одном направлении. Тут имеются ввиду, локальные оси самого объекта, а не мировые.

13. Позаботьтесь о правильном масштабе своих ассетов, до импорта в движок. При масштабе (1, 1, 1) они должны иметь задуманные размеры. Для сравнения, можно использовать ссылку на объект куба в юнити, находящийся в стандартных ассетах. Выберите подходящее соотношения к мировым единицам, и придерживайтесь его.

14. Чтобы, было легче размещать и ориентировать GUI и партиклы, используйте two-poly plane (плоскость из двух треугольников). Сориентируйте плоскость лицом по оси Z (см. пункт 12) и вкладывайте в нее GUI или партиклы.

15. Создайте и применяйте тестовые арты:

  • Меченые квадраты для скайбоксов.
  • Текстуру с сеткой.
  • Различные «плоские» цвета для шейдерного тестирования: белый; черный; 50% серого; красный; зеленый; синий; пурпурный; желтый; голубой.
  • Градиенты для шейдерного тестирования: от черного к белом; от красного к зеленому; от красного к синему; от зеленого к синему.
  • Текстуру «чекер» (черно-белые квадраты, по типу шахматной доски).
  • Сглаженные и жесткие карты нормалей.
  • Готовое и настроенное освещение, для быстрого создания тестовых сцен.

Префабы

16. Используйте префабы абсолютно для всего. Единственные объекты, которые не должны быть префабом, это папки. Даже уникальные объекты, которые будут использованы только раз, должны быть префабом. Это облегчает внесение изменений в сцены, не трогая сами сцены. (Дополнительным преимуществом является то, что это делает построение атласа спрайтов надежнее, при использовании EZGUI).

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

  • вносить изменения для каждого типа в одном месте;
  • вносить изменения без изменения сцены.

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

18. Связывайте префабы с префабами; не связывайте экземпляры с экземплярами. Ссылки на префаб поддерживаются, при помещении префаба в сцене; ссылки на экземпляры нет. Ссылки на префабы, когда это возможно, упрощают настройку сцены, и уменьшают необходимость ее изменения.

19. Насколько это, возможно, устанавливайте связи между экземплярами автоматически. Если вам необходимо установить связь между экземплярами, делайте это программно (в коде). Например, префаб игрока может зарегистрировать себя в GameManager , когда он стартует. Или GameManager может сам найти экземпляр префаба игрока, когда он стартует.

Не ставьте меши, в корень префаба, если собираетесь добавлять другие скрипты. Когда вы делаете префаб из меша, поместите его в пустой объект, сделав, таким образом, корнем пустышку, и уже на нее вешайте скрипты. Так намного проще заменить меш другим мешем, не потеряв каких-то значений, которые вы установили в инспекторе.

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

20. Используйте безопасный процесс ветвления префабов. Объясним на примере префаба Player .

Делать опасные изменения в префабе, нужно следующим образом:

  • Скопировать префаб Player .
  • Переименовать дубликат на __Player_Backup .
  • Сделайте изменения в префабе Player .
  • Если все работает, удалить __Player_Backup .

Не называйте дубликат Player_New , и не вносите в него изменения!

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

Разработчик 1:

  • Скопировать префаб Player .
  • Переименовать его в __Player_WithNewFeature или __Player_ForDev .
  • Сделать изменения в дубликате, закоммитить / передать 2-му разработчику.

Разработчик 2:

  • Внести изменения в новый префаб.
  • Скопировать префаб Player , и назвать его __Player_Backup .
  • Перетащить экземпляр __Player_WithNewFeature на сцену.
  • Перетащите экземпляр на оригинальный префаб Player .
  • Если все работает, удалить __Player_Backup и __Player_WithNewFeature .

Расширения и MonoBehaviourBase

21. Расширьте MonoBehaviour своим поведением, и получайте все ваши компоненты от него. Это позволяет реализовать некоторую общую функциональность, такие как типобезопасного вызова (Invoke), и более сложные вызовы (например, случайный, и т.д.).

22. Определите безопасные методы для методов Invoke, StartCoroutine и Instantiate. Определите делегат Task, и используйте его, для определения методов, которые не зависят от имени. Например:

public void Invoke(Task task, float time)
{
Invoke(task.Method.Name, time);
}

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

Реализации ниже используют TypeOf , вместо обобщенных версий этих функций. Обобщенные версии не работают с интерфейсами. Методы ниже, обертывают TypeOf-версии в обобщенные методы.

//Defined in the common base class for all mono behaviours
public I GetInterfaceComponent() where I: class
{
return GetComponent(typeof(I)) as I;
}

Public static List FindObjectsOfInterface() where I: class
{
MonoBehaviour monoBehaviours = FindObjectsOfType();
List list = new List();

Foreach(MonoBehaviour behaviour in monoBehaviours)
{
I component = behaviour.GetComponent(typeof(I)) as I;

If(component != null)
{
list.Add(component);
}
}

Return list;
}

24. Используйте расширения, чтобы сделать синтаксис более удобным. Например:

public static class CSTransform
{
public static void SetX(this Transform transform, float x)
{
Vector3 newPosition =
new Vector3(x, transform.position.y, transform.position.z);

Transform.position = newPosition;
}
...
}

25. Используйте безопасную альтернативу GetComponent. Иногда внедрение зависимостей компонентов (через RequiredComponent ), может быть болезненным. Например, это затрудняет изменение компонентов в инспекторе (даже, если они имеют тот же базовый тип). В качестве альтернативы, может быть использовано, следующее расширение GameObject , для вывода ошибки, если компонент не найден.

public static T GetSafeComponent(this GameObject obj) where T: MonoBehaviour
{
T component = obj.GetComponent();

If(component == null)
{
Debug.LogError("Expected to find component of type "
+ typeof(T) + " but found none", obj);
}

Return component;
}

Идиомы

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

  • Некоторые идиомы не сочетаются.
  • Использование одной и той же идиомы, помогает всей команде понять, что происходит. Это делает структуру и код легче для понимания. Уменьшает вероятность совершить ошибку.

Примеры групп идиом:

  • Сопрограммы vs конечного автомата.
  • Вложенные префабы vs связанные префабы vs «Бог»-префабы.
  • Стратегии разделения данных.
  • Способы использования спрайтов для состояний в 2D-игр.
  • Структура префаба.
  • Стратегии спауна (spawning).
  • Способы поиска объектов: по типу vs по имени vs по тегу vs по слою vs ссылке.
  • Способы группировки объектов: по типу vs по имени vs по тегу vs по слою vs массиву или ссылкам.
  • Поиск групп объектов vs self-registration.
  • Контроль порядка выполнения (использовать установленный порядок исполнения Unity vs логику yield vs Awake/Start и Update/Late Update vs ручные методы vs в любом порядке).
  • Выделение объектов / позиции / целей с помощью мыши в игре: менеджер выделения vs локальный self-management.
  • Хранение данных между изменениями сцены: через PlayerPrefs, или объекты, которые не уничтожаются при загрузке новой сцены.
  • Способы комбинирования (смешивания, добавления и наслаивание) анимации.

(*vs — против)

Время

27. Держите свой собственный класс времени, чтобы сделать паузу легче. Оберните Time.DeltaTime и Time.TimeSinceLevelLoad для учета пауз и временной шкалы. Использование его требует дисциплины, но облегчит работу с разными таймерами (например, анимация интерфейса и игровая анимация).

Спаун объектов

28. Не позволяйте порождаемым объектам загромождать иерархию, когда игра запущена. Установите для них родителя — объект сцены, для облегчения поиска объектов, при запущенной игре. Вы можете воспользоваться пустым объектом игры, или даже единичным объектом без поведения, чтобы легко получить доступ из кода. Вызовите этот объект DynamicObjects .

Дизайн классов

29. Используйте Синглтоны для удобства. Класс ниже, сделает синглтоном автоматически, любой класс производный от него:

public class Singleton : MonoBehaviour where T: MonoBehaviour
{
protected static T instance;

/**
Returns the instance of this singleton.
*/
public static T Instance
{
get
{
if(instance == null)
{
instance = (T) FindObjectOfType(typeof(T));

If (instance == null)
{
Debug.LogError("An instance of " + typeof(T) +
" is needed in the scene, but there is none.");
}
}

Return instance;
}
}
}

Синглтоны полезны, для различных менеджеров, таких как ParticleManager , или AudioManager , или GUIManager .

  • Избегайте использования синглтонов для уникальных экземпляров префабов, которые не менеджеры (например, экземпляр Игрока). Не соблюдение этого принципа, усложняет иерархию наследований, и делает некоторые изменения сложнее. Вместо этого, держите ссылки на такие экземпляры в вашем GameManager (или другом подходящем «Бог»-классе;-)).
  • Определите статические свойства и методы для публичных переменных и методов, которые часто используются за пределами класса. Это позволит вам писать GameManager.Player вместо GameManager.Instance.player .

30. Для компонентов, никогда не делайте переменные общедоступными, если они не должны изменяться через инспектор. Иначе, ее может «покрутить» дизайнер, не зная, что переменная делает. В редких случаях, это неизбежно. В таком случае, используйте два, или даже четыре подчеркивания в качестве префикса, в имени переменной, чтобы отпугнуть «крутильщиков»:

public float __aVariable;

31. Отделяйте интерфейс от логики игры. Это, по сути паттерн MVC.

Каждый контроллер ввода, должен лишь давать команды соответствующим компонентам, чтобы дать им знать, что контроллер был вызван. Например, в логике контроллера, можно решать, какие команды давать, основываясь на состоянии игрока. Но это плохо (например, это приведет к дублированию логики, если добавлено несколько контроллеров). Вместо этого, объект Игрока должен быть уведомлен о намерениях двигаться вперед, а затем на основе текущего состояния (замедлен или оглушен, например) установить скорость и обновить направление игрока. Контроллеры должны делать только то, что относится к их собственному состоянию (контроллер не меняет состояние, когда игрок изменяет состояние, поэтому контроллер вообще не должен знать о состоянии игрока). Другим примером является смена оружия игроком. Правильный способ сделать это в Player , с помощью метода SwitchWeapon(Weapon newWeapon) , который можно вызвать из GUI. GUI не должен манипулировать трансформациями, родителями и т.д..

Любой компонент интерфейса, должен только поддерживать данные, и выполнять обработку, связанную с его собственным состоянием. Например, отображение карты, на основе расчетов движения игрока. Тем не менее, это данные состояния игры, и не принадлежат к GUI. GUI должен лишь отображать данные состояния игры, хранить их нужно в другом месте. Картографические данные должны быть сохранены в другом месте (в GameManager , например).

Объекты геймплея не должны знать практически ничего о GUI. Единственным исключением является режим паузы, которая может контролироваться глобально посредством Time.timeScale (что не является хорошей идеей). Геймплейные объекты должны знать, если игра приостановлена. И это всё. Поэтому, не должно быть ссылок, на GUI-компоненты из объектов геймплея.

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

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

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

  • сохранение состояния игры
  • отладку состояния игры

Один из способов сделать это, определение класса SaveData для каждого класса игровой логики:


PlayerSaveData
{
public float health; //public for serialisation, not exposed in inspector
}

Player
{
//... bookkeeping variables (переменные промежуточных данных)

//Don’t expose state in inspector. State is not tweakable.
private PlayerSaveData playerSaveData;
}

33. Отделяйте конфигурацию специализаций.

Рассмотрим на примере двух врагов с одинаковыми мешами, но с разными настройками экземпляров (например, разные сила и скорость). Есть разные пути разделить данные. Вот один из тех, что я предпочитаю, особенно когда объекты порождены, или игра сохранена. (Настройки объектов не являются состоянием, это конфигурационные данные, так что они не должны сохраняться. Когда объект загружен, или порожден, конфигурационные данные, загружаются отдельно автоматически.)

  • Определите шаблонный класс для каждого класса игровой логики. Для экземпляра врага, мы определим класс EnemyTemplate . Где будут храниться, все отличительные характеристики.
  • В классе игровой логики, определите переменную типа нашего шаблона.
  • Сделайте префаб врага, и два шаблонных префаба WeakEnemyTemplate и StrongEnemyTemplate .
  • При загрузке и порождении объектов, задайте переменной шаблона нужный шаблон.

Этот метод может стать довольно сложным (а иногда, излишне сложным, так что будьте осторожны!).

Например, лучше будет использование полиморфизма обобщения, мы можем определить класс, так:

public class BaseTemplate
{
...
}

Public class ActorTemplate: BaseTemplate
{
...
}

Public class Entity where EntityTemplateType: BaseTemplate
{
EntityTemplateType template;
...
}

Public class Actor: Entity
{
...
}

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

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

public void SelectWeapon(int index)
{
currentWeaponIndex = index;
Player.SwitchWeapon(weapons);
}

Public void Shoot()
{
Fire(bullets);
FireParticles(particles);
}

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


public class Weapon
{
public GameObject prefab;
public ParticleSystem particles;
public Bullet bullet;
}

Код выглядит аккуратнее, а главное, так труднее совершить ошибку в настройке данных в инспекторе.

36. Избегайте использования в массивах структур. Например, игрок может иметь три типа атак. Каждая использует текущее оружие, но генерирует разные пули и другое поведение. Вы можете захотеть поместить пули в массиве, и воспользоваться таким видом логики:

public void FireAttack()
{
/// behaviour
Fire(bullets);
}

Public void IceAttack()
{
/// behaviour
Fire(bullets);
}

Public void WindAttack()
{
/// behaviour
Fire(bullets);
}

Перечисления могут сделать ваш код лучше …

public void WindAttack()
{
/// behaviour
Fire(bullets);
}

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


public class Bullets
{
public Bullet FireBullet;
public Bullet IceBullet;
public Bullet WindBullet;
}

Здесь предполагается, что нет других данных для огня, льда и воздуха.

37. Группируйте данные в сериализуемых классах, чтобы сделать параметры в инспекторе упорядоченнее. Некоторые объекты могут иметь десятки параметров. Это может стать кошмаром, при попытке найти правильную переменную в инспекторе. Чтобы упростить это дело, выполните следующие действия:

  • Определите отдельные классы для групп переменных. Сделайте их публичными и сериализуемыми.
  • В основном классе, определите публичные переменные каждого типа, как указанно ранее.
  • Не инициализируйте эти переменные в Awake или Start ; так как они сериализуемые, Unity позаботится об этом.
  • Вы можете указать значения по умолчанию, до присвоения значений в определении.

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


public class MovementProperties //Not a MonoBehaviour!
{
public float movementSpeed;
public float turnSpeed = 1; //default provided
}

Public class HealthProperties //Not a MonoBehaviour!
{
public float maxHealth;
public float regenerationRate;
}

Public class Player: MonoBehaviour
{
public MovementProperties movementProeprties;
public HealthPorperties healthProeprties;
}

Текст

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

39. Если вы планируете локализовать отдельно все строки в одном месте. Есть много способов сделать это. Один из способов заключается в определении класса Text , с публичным полем для каждой строки, и заданным значением по умолчанию на английском, например. Другой языковой подкласс, повторно инициализирует поля языковым эквивалентом.

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

Тестирование и отладка

42. Реализуйте свой собственный FPS-счетчика. Да. Никто не знает, что на самом деле измеряет FPS-счетчик Unity, но это не скорость кадров. Реализуйте собственный, так чтобы FPS соответствовал интуиции и визуальному контролю.

43. Реализуйте сочетаний клавиш, для снятия снимков экрана. Многие ошибки являются визуальными, и гораздо проще, сообщить о них, когда можно сделать скриншот. Идеальная система должна содержать счетчик в PlayerPrefs, чтобы последовательные скриншоты не перезаписывались. Скриншоты должны быть сохранены за пределами папки проекта, чтобы избежать случайного коммита в репозиторий.

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

45. Реализуйте опции отладки для упрощения тестирования. Некоторые примеры:

  • Разблокировать все предметы (айтемы).
  • Отключить врагов.
  • Отключить GUI.
  • Сделать игрока непобедимым.
  • Отключить весь геймплей.

46. Для команд, которые достаточно малы, сделайте префаб с опциями отладки, для каждого члена команды. Положите идентификатор пользователя в файл, который не коммитится, и читается, когда игра запускается. И вот, почему:

  • Члены команды не закоммитят свои опции отладки по случайности, и никого не затронут.
  • Изменение опций отладки не изменит сцену.

47. Держите сцену со всеми элементами геймплея. Например, сцена со всеми врагами, все объекты, с которыми можно взаимодействовать, и т.д. Это делает легкой проверку функциональности, без необходимости играть слишком долго.

48. Определите константы для отладки сочетания клавиш, и держите их в одном месте. Клавиши отладки не нормально (не удобно) обрабатываются в едином месте, как и остальные части игрового ввода. Чтобы избежать конфликтов между сочетаниями клавиш, определите константы в одном месте. Альтернативой является, обрабатывать все клавиши в одном месте, независимо от того, является функция отладочной, или нет. (Недостатком является то, что этому классу могут понадобиться дополнительные ссылки на объекты).

Документация

49. Документируйте ваши настойки. Большинство документации должно быть в коде, но некоторые вещи должны быть задокументированы вне кода. Документация, повышает эффективность (если она актуальная).

Документируйте следующее:

  • Использование слоя (для коллизий, Culling, и Raycasting — по сути то, что должно быть на слое).
  • Использование тэгов.
  • Глубина GUI для слоев (что должно отображаться, поверх чего).
  • Настройка сцены.
  • Идиома предпочтений.
  • Структуры префабов
  • Слои анимации.

Стандарт именования и структура папок

50. Следуйте документированному соглашению об именовании и структурах папок. Следование ему облегчает поиск вещей, и понимание, что это за вещь.

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

Общие принципы именования.
  • Называйте вещи своими именами. Птица должна называться Bird .
  • Выбирайте имена, легко произносимые и запоминающиеся. Если вы делаете игру о Майя, не называйте свой уровень QuetzalcoatisReturn .
  • Будьте последовательными. Выбрав имя, придерживайтесь его.
  • Используйте стиль PascalCase, например: ComplicatedVerySpecificObject . Не используйте пробелы, подчеркивания или дефисы, с одним исключением (см. «Именования различных аспектов одного и того же»).
  • Не используйте номера версий, или слова для обозначения стадии прогресса (WIP, финал).
  • Не используйте аббревиатуры: DVamp@W должно быть DarkVampire@Walk .
  • Используйте терминологию из дизайн документации: если в документе анимация смерти называется Die, то надо использовать имя DarkVampire@Die , а не DarkVampire@Death .
  • Держите наиболее специфический дескриптор слева: DarkVampire , вместо VampireDark ; PauseButton , вместо ButtonPaused . Легче, например, найти кнопку паузы в инспекторе, когда не все кнопки, начинаются со слова «Button». [Многие люди предпочитают делать наоборот, потому что, это делает группировки, визуально более очевидными. Имена существуют не для группировок, для этого есть папки. Имена для различения объектов, одного и того же типа, и они должны помогать делать это быстро.]
  • Некоторые имена образуют последовательность. Используйте номера в этих названиях, например, PathNode0 , PathNode1 . Всегда начинайте с 0, а не 1.
  • Не используйте цифры для вещей, которые не образуют последовательность. Например, Bird0 , Bird1 , Bird2 , должно быть Flamingo , Eagle , Swallow .
  • Ставьте префикс временным объектам в виде двойного подчеркивания __Player_Backup .
Именования различных аспектов одного и того же

Используйте подчеркивание между основным именем, и тем, что описывает «Аспект». Например:

  • Состояние GUI-кнопок: EnterButton_Active, EnterButton_Inactive.
  • Текстуры: DarkVampire_Diffuse, DarkVampire_Normalmap.
  • Скайбоксы: JungleSky_Top, JungleSky_North.
  • LOD-группы: DarkVampire_LOD0, DarkVampire_LOD1.

Не используйте, это соглашение просто, для различия типов предметов, например, Rock_Small , Rock_Large , должно быть SmallRock , LargeRock .

Структура

Организация ваших сцен, папки проекта и папки скриптов, должны следовать аналогичной схеме.

Структура папок:

Materials
GUI
Effects
Meshes
Actors
DarkVampire
LightVampire
...
Structures
Buildings
...
Props
Plants
...
...
Plugins
Prefabs
Actors
Items
...
Resources
Actors
Items
...
Scenes
GUI
Levels
TestScenes
Scripts
Textures
GUI
Effects
...

Структура сцены:

Cameras
Dynamic Objects
Gameplay
Actors
Items
...
GUI
HUD
PauseMenu
...
Management
Lights
World
Ground
Props
Structure
...

Структура папки скриптов:

ThirdParty
...
MyGenericScripts
Debug
Extensions
Framework
Graphics
IO
Math
...
MyGameScripts
Debug
Gameplay
Actors
Items
...
Framework
Graphics
GUI
...

Как переопределить отрисовку инспектора.

1. Определите базовый класс для всех ваших редакторов:

BaseEditor : Editor
where T: MonoBehaviour
{
override public void OnInspectorGUI()
{
T data = (T) target;

GUIContent label = new GUIContent();
label.text = "Properties"; //

DrawDefaultInspectors(label, data);

If(GUI.changed)
{
EditorUtility.SetDirty(target);
}
}
}

2. Используйте рефлексию и рекурсию, чтобы составить компоненты:

public static void DrawDefaultInspectors(GUIContent label, T target)
where T: new()
{
EditorGUILayout.Separator();
Type type = typeof(T);
FieldInfo fields = type.GetFields();
EditorGUI.indentLevel++;

Foreach(FieldInfo field in fields)
{
if(field.IsPublic)
{
if(field.FieldType == typeof(int))
{
field.SetValue(target, EditorGUILayout.IntField(
MakeLabel(field), (int) field.GetValue(target)));
}
else if(field.FieldType == typeof(float))
{
field.SetValue(target, EditorGUILayout.FloatField(
MakeLabel(field), (float) field.GetValue(target)));
}

///etc. for other primitive types

Else if(field.FieldType.IsClass)
{
Type parmTypes = new Type{ field.FieldType};

String methodName = "DrawDefaultInspectors";

MethodInfo drawMethod =
typeof(CSEditorGUILayout).GetMethod(methodName);

If(drawMethod == null)
{
Debug.LogError("No method found: " + methodName);
}

Bool foldOut = true;

DrawMethod.MakeGenericMethod(parmTypes).Invoke(null,
new object
{
MakeLabel(field),
field.GetValue(target)
});
}
else
{
Debug.LogError(
"DrawDefaultInspectors does not support fields of type " +
field.FieldType);
}
}
}

EditorGUI.indentLevel--;
}

Описанный выше метод использует вспомогательный метод:

private static GUIContent MakeLabel(FieldInfo field)
{
GUIContent guiContent = new GUIContent();
guiContent.text = field.Name.SplitCamelCase();
object descriptions =
field.GetCustomAttributes(typeof(DescriptionAttribute), true);

If(descriptions.Length > 0)
{
//just use the first one.
guiContent.tooltip =
(descriptions as DescriptionAttribute).Description;
}

Return guiContent;
}

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

3. Определите новые пользовательские редакторы.

К сожалению, вы все равно должны определить класс, для каждого MonoBehaviour. К счастью, эти определения могут быть пустыми, вся фактическая работа делается в базовом классе.


public class MyClassEditor: BaseEditor
{}

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

Перевод Максим Саликов .

Разработчик детского мобильного паззла Fold the Adventure Алексей Галкин написал для ЦП колонку о том, на что следует обратить внимание при разработке мобильной игры на платформе Unity: как выбрать правильные ассеты из Asset Store, в каком сервисе хранить данные о прогрессе пользователей и где взять звуки для игры.

В закладки

Начало

Чтобы сделать хорошую игру, нужна хорошая идея. Но даже имея идею, создать на её основе достойную игру совсем непросто. Помимо банальной удачи, существует огромное множество вещей, с которыми приходится иметь дело. Бесконечное число решений необходимо принять в весьма скромное время. Ведь время - это деньги, а бюджет проекта имеет тенденцию исчезать до того, как сделано хоть что-то достойное демонстрации (не говоря уже о выпуске).

Эта история о том, как наша небольшая инди-команда создавала игру Fold the Adventure («Сложи приключение») на основе оригинальной идеи. Поскольку игра выпущена, мы можем перевести дух и окинуть взглядом три месяца, потраченные на разработку. Несмотря на наличие многолетнего опыта в игровой индустрии, создание Fold the Adventure заставило нас изрядно попотеть ввиду непредсказуемых поворотов событий и весьма ограниченных ресурсов. Добро пожаловать в наше приключение!

Выбор движка: Unity

Сказать по правде, движок нам выбирать не пришлось. Мы сразу взяли Unity и не пожалели об этом. Нашей первостепенной задачей было создание сравнительно небольшой игры и запуск её на максимальном количестве платформ. При этом хотелось избежать возни с программированием на Objective C и Java. С Unity нам это удалось. И хотя всё было не так просто и гладко, как предполагалось, движком мы остались довольны.

По нашему мнению, Unity - это лучший игровой движок для инди-разработки в настоящий момент. Его программная архитектура хорошо продумана, редактор сделан добротно и становится лучше с каждой версией. Начать пользоваться Unity легко, но опытным разработчикам он может показаться поначалу контринтуитивным. Тем не менее, и новички, и профессионалы, скорее всего, столкнутся с одними и теми же подводными камнями.

  • Необходимо с самого начала потратить время на изучение базовой архитектуры Unity, включая игровые объекты, скриптуемые объекты, сцены, префабы, ассеты, методы-события и порядок их вызова, сопрограммы, измерение времени, обработку ввода, сериализацию. Unity может показаться лёгким в использовании движком, но без понимания его основ можно провести бесконечные часы за отладкой и рефакторингом.
  • С самого начала сформулируйте правила структуризации, а также именования ассетов и игровых объектов. Даже небольшая игра имеет тенденцию превращаться в хаос без должной организации. Существует много статей, посвящённых данному вопросу. Можно также ориентироваться на проекты, публикуемые разработчиками в Unity Assets Store.
  • Программируйте как можно меньше. Вместо этого активно используйте WYSIWYG (What You See Is What You Get - свойство прикладных программ, в которых содержание отображается в процессе редактирования и выглядит максимально близко похожим на конечную продукцию - ред.) возможности редактора Unity. Благодаря лёгкой расширяемости, редактор Unity позволяет превратить разрабатываемую игру в удобный конструктор, которым смогут пользоваться даже те, кто не умеет программировать. А это существенно упрощает и ускоряет создание игрового контента.
  • Избегайте использования привычных практик, которые плохо сочетаются с идеологией Unity. Какой бы заманчивой не была возможность размещения всех объектов игры в рамках одной сцены или сохранение параметров игровой механики в XML-файлах, подобный подход существенно осложнит жизнь на более поздних этапах разработки.
  • Будьте аккуратны и внимательны при использовании систем управления версиями. Unity имеет тенденцию непредсказуемо менять файлы. Например, внесение изменений в префаб влечёт за собой модификацию всех файлов сцен, в которых он используется, но только после их последующего сохранения. Всегда используйте force text в качестве режима сериализации ассетов и внимательно следите за файлами, которые заливаете на сервер, чтобы не уничтожить результат работы коллег.
  • Тестируйте игру на максимально возможном количестве платформ. При этом не забывайте, что настройки можно определить для каждой из платформ в отдельности. Без подобного тестирования, вы, как и мы, можете столкнуться с тем, что ваша игра ведёт себя по-разному в web player-е под Windows и OS X.

Ассеты: создавать или покупать

Если у вас небольшая команда при сильно ограниченных времени и бюджете, то создать большое количество качественных игровых ассетов с нуля практически невозможно. Приходится усмирять грандиозные амбиции и минимизировать количество необходимого. Но даже при условии минимизации, нужно оставить что-то, на что игроку было бы приятно посмотреть.

Если ассеты для игры могут быть сделаны силами команды - одной проблемой меньше (cкорее всего). Но есть ещё фаза прототипирования, а для неё также нужны ассеты, которые с большой долей вероятности будут выброшены. Поэтому практически каждой команде требуются ассеты, которые она не может или не хочет делать. Вопрос в том, где их взять.

Первым и самым очевидным ответом для нас, как для Unity разработчиков, был Unity Assets Store . Какой бы заманчивой ни казалась эта возможность, она влечёт за собой и ряд сложностей:

  • Ассеты в Unity Assets Store доступны всем без исключения. Однако никто не хочет, чтобы их игра была похожа на другие. По крайней мере, мы этого очень не хотели.
  • Практически невозможно купить один пак ассетов, который бы удовлетворил все нужды проекта, и возникает проблема стилистической совместимости. Для Fold the Adventure мы купили несколько паков, из которых использовали меньше половины.
  • Несмотря на то, что общее количество ассетов в Unity Assets Store довольно велико, найти среди них что-то подходящее для конкретных нужд бывает весьма непросто. При этом качество зачастую оставляет желать лучшего. Мы потратили часы на поиски, при этом не всегда имея возможность оценить качество пака без его покупки.

Вторым вариантом был аутсорсинг ассетов. Безусловно, этот путь отнимает больше времени и денег, чем просто покупка чего-то из Unity Assets Store. Однако, при удачном раскладе, получаемые ассеты уникальны и более качественны.

Существовала также возможность покупки ассетов из источников помимо Unity Assets Store. Однако здесь возникала проблема совместимости с Unity, и мы решили воздержаться от таких экспериментов.

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

Вот несколько простых правил, к которым мы пришли в ходе поиска и покупки ассетов в Unity Assets Store

  • Чтобы избежать стилистических расхождений, желательно покупать ассеты одного типа у одного автора. Это не мешает купить модели у одной студии, а партиклы у другой, но при этом нужно следить за совместимостью стилей.
  • Избегайте использования ассетов в том виде, в котором они были куплены. Достаточно внести небольшие изменения (например, немного перерисовать текстуры, реструкурировать партиклы) или использовать ассеты нестандартным образом.
  • Если вы планируете выпуск игры на мобильных платформах, убедитесь, что покупаемые ассеты оптимизированы под них.

Музыка и звуки заслуживают отдельного внимания. Они редко создаются инди-командой самостоятельно (если у вас есть композитор, то вам либо очень повезло, либо вы уже не инди). К счастью, существует большое количество сервисов, поставляющих royalty-free музыку и звуки, включая Unity Assets Store. И это совсем недорого.

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

Где хранить данные: Parse

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

В поисках лучшего решения мы обнаружили Parse . Parse - это кросс-платформенный сервис BaaS (Backend as a Service - платформа типа «бэкенд как сервис», предоставляет облачную серверную инфраструктуру для всех типов приложений - ред.), который позволяет приложению сохранять данные в облаке, поддерживает авторизацию пользователей, серверные функции, push-оповещения и аналитику. Это не исчерпывающий список функциональности, но он даёт общее представление о том, что есть Parse.

Одной из причин, по которым мы выбрали Parsе, была его интеграция с Facebook (первая версия Fold the Adventure создавалась именно для Facebook). И, несмотря на то, что позднее фокус разработки был смещён на мобильные платформы, мы продолжили использовать Parse.

Ещё одной приятной особенностью Parse является его ценовая политика. Поначалу она кажется немного странной, но, после размышлений и расчётов, оказывается более чем удачной. По существу, вы ограничены количеством запросов в секунду. Хорошая новость в том, что 30 запросов в секунду даются бесплатно. Может показаться, что это совсем немного, но на деле этого достаточно для поддержки тысяч пользователей. При условии, что вы используете эти запросы разумно.

В Parse вы делаете всё через запросы. Для получения и изменения полей данных, создания отношения между таблицами, формирования выборки - для всего нужен запрос. На первый взгляд, количество запросов должно быть довольно большим, но, на деле, большую часть элементарных операций можно объединить в один запрос с помощью метода ParseObject.SaveAllAsync. Кроме того, Parse выбросит исключение, если предел запросов в секунду превышен. Но ничего не мешает вам подождать некоторое время и выполнить запрос повторно. И хотя игра в таком случае может показаться пользователю «подвисшей», эта проблема легко обходится с помощью внесения небольших изменений в пользовательский интерфейс и логику сохранения и чтения данных.

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

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

Если есть необходимость поддерживать офлайн-режим и обновлять данные в Parse при наличии сетевого соединения, то вам придётся написать для этого небольшую отдельную систему. Мы использовали для этого класс PlayerPrefs. Система сохраняет данные локально и заливает их в Parse, как только обнаруживает наличие подключения к интернету.

Стационарные платформы против мобильных: компромиссы

Несмотря на постепенное сближение в течение последних лет, стационарные и мобильные платформы по-прежнему сильно отличаются друг от друга. Это становится очевидным при попытке заставить «красивые» шейдеры работать приемлемо (не говоря уже о том, чтобы заставить их работать быстро) на всех устройствах, на которых предполагается запуск игры. Unity существенно облегчает этот процесс, но, к сожалению, не решает всех проблем.

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

  • Если вы планируете запуск на мобильных платформах и используете тени, то ограничьтесь режимом forward lighting. Несмотря на запекание освещения и сокращения числа объектов, отбрасывающих тени, мы не смогли добиться приемлемой производительности в режиме deferred lighting. Возможно, мы недостаточно старались, но форумы Unity, по большей части, солидарны с нами в этом вопросе.
  • Минимизируйте количество draw call-ов. Большое количество полигонов в моделях не скажется так сильно на производительности, как дополнительные draw call-ы. Мы планировали большую оптимизацию, направленную на сокращение количества draw call-ов, чтобы лучше поддерживать старые модели мобильных устройств, но, к сожалению, не смогли сделать её в отведённые жёсткие сроки.
  • Не злоупотребляйте текстурными выборками в шейдерах. Это может привести к существенному падению производительности. В нашей игре мы были вынуждены использовать несколько специальных шейдеров вместо одного универсального - именно по этой причине.

Помимо различий в производительности и аппаратных ограничений, существует ещё одно важное отличие между стационарными и мобильными платформами. И это отличие - режим ввода. Игра, созданная для управления с помощью клавиатуры и мыши, плохо переносится на мультитач и акселерометр. Мы убедились в этом на своём горьком опыте. Прежде всего, в Unity разделена обработка мыши и мультитача. А потому было необходимо создать систему, унифицирующую этот аспект. Для этих целей мы использовали систему ввода из состава NGUI , которая, после небольших доработок, показала себя весьма хорошо. Она также позволила нам решить проблему распределения ввода между пользовательским интерфейсом и игровым управлением, которая доставляла нам некоторые неприятности на тот момент.

Пользовательский интерфейс в целом потребовал ряда модификаций для корректной поддержки мобильных устройств. Например, вместо прокручивания колёсика мыши и удерживания кнопок пришлось ввести мультитач-жесты. Некоторые из модификаций можно было потенциально упросить с помощью готовых решений из Unity Assets Store. Но в нашем случае это был простой pinch, а потому мы решили написать его за час с нуля, вместо того, чтобы тратить дни на подключение и отладку системы, которая «делает всё и даже больше».

Наибольшее количество проблем вызвало внутриигровое управление. Мы начали с традиционного набора ASWD + мышь для управления персонажем и камерой, планируя использовать экранный джойстик на мобильных устройствах. Но всё получилось не совсем так, как мы ожидали: игра стала практически неуправляемой. Нам пришлось срочно менять внутриигровое управление, при этом внося изменения даже в игровую механику. Методом проб и ошибок мы остановились на point-n-click управлении, которое на мобильных устройствах воспринимается интуитивно.

Локализация: чем проще, тем лучше

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

Локализовывать необходимо всё, что имеет отношение к человеческому языку. То есть тексты, речь, надписи на текстурах - всё это должно быть локализовано. Такой процесс очень быстро может стать весьма трудоёмким и дорогим. Поэтому крайне важно свести количество локализуемого контента к минимуму.

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

Но минимизация локализуемого контента - это только начало. Следующий этап - подготовка самой игры к локализации. Плохая новость состоит в том, что Unity (на момент написания данной статьи) не имеет встроенных механизмов для этих целей. И хотя существует целый ряд специализированных решений в Unity Assets Store (например, l2 Localization), мы решили использовать систему локализации, идущую в составе NGUI.

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

Написать

Тестируем свежий 2D-kit от Unity и подробно описываем процесс создания нашего первого платформера

Отправить

В середине февраля создатели Unity выпустили 2D-kit — необычное приложение, созданное для всех начинающих игроделов. С его помощью любой желающий может собрать платформер, не утруждаясь долгим написанием кода. Программный код, модели и анимации подготовили разработчики, а вам остается только вникнуть в манипуляции с ними и создать ту игру, о которой вы всегда мечтали (если это, конечно, двухмерный платформер). Мы протестировали 2D-kit, создали собственный уровень и на его примере рассказываем, как быстро освоится с движком и выпустить первую игру.

С чего начать

Если вы ни разу не запускали Unity или, по каким-то причинам, слышите о нем впервые, кратко поясним основы интерфейса. Все окна и вкладки вы можете свободно передвигать в любое удобное для вас место, но изначально они расположены следующим образом: в левой части находится столбик иерархии, который показывает все объекты, находящиеся в сцене; сама сцена располагается в центре, а справа от нее окно инспектора показывает свойства выделенного объекта. Внизу вы увидите меню проекта и материалы, которые ему присвоены. Теперь разберемся с тем, чем отличается 2D-kit от обычного запуска Unity.

В стартовом меню движка у вас будет выбор: начать новый проект или перейти во вкладку обучения, а там запустить загруженный 2D-kit. Выбрав второй вариант, вы увидите вместо пустого полотна материалы игры и сможете творить буквально с двух кликов. Сперва необходимо создать сцену: найдите сверху Kit Tools и выберете вкладку Create New Scene .

Сцена создана, в иерархии сразу выстроилось множество непонятных объектов, но главное — появилась героиня, которая будет представлять игрока в будущем. Управление ею настроено заранее, так что об этом беспокоиться не стоит. Пока важно понять, что набор строчек под заголовком GameUtilities в иерархии должен перемещаться вместе с главной героиней, чтобы на старте не возникло проблем с управлением. Вот мы и подобрались к самому насущному: как создавать уровни?

В меню Window есть вкладка The Palette — это окно кисти, которое позволяет рисовать фундамент. Чтобы перейти к выбору нужной текстуры нажмите на TilesetRockWaterBlockers и выберете TilesetGamekit . Теперь у вас есть два вида кисти: трава и камень, имитирующий инопланетные постройки. Совмещать их, к сожалению, нельзя, так что заранее планируйте уровень в одном стиле или маскируйте шов подручными объектами, вроде кислотного озера.

Создание окружения

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

В первую очередь рекомендуем заглянуть в Interactables — это самый простой вид предметов, которые очень легко располагать и настраивать. Например, там есть «DestructableColumn» — колонна, разрушаемая при ударе жезлом. Мы поставили ее при входе в пещеру. Чтобы добавить ее в свой уровень просто перетащите колонну из папки на сцену. Таким же образом добавляются и другие предметы, такие как «MovingPlatform».

Вы, наверное, заметили, что при добавлении объектов правое окно инспектора сразу заполняется непонятными настройками, а рядом с движущейся платформой еще и путь какой-то красный отметился. Не беспокойтесь, сейчас мы с ними быстро разберемся. На самом деле, большинство иконок, ползунков и цифр вам не пригодится на первом этапе. Главное - заметить кнопочку Add Node в настройках платформы — она добавляет новую точку в пути движения островка. Все точки можно передвигать стрелочками по оси координат. Проблема в том, что изначально платформа двигается по принципу «туда-сюда», а если вы формируете квадрат, то вы, естественно, хотите, чтобы островок двигался по кругу. Для этого в подменю Moving Platform (Script) , там же, где находится Add Node , вам нужно выбрать надпись BACK_FORTH , что и означает «туда-сюда», и сменить ее на LOOP , что означает «по кругу».

Теперь у вас есть движущиеся платформы, как в каком-нибудь Mario . Более того, вы можете их вертеть и увеличивать, выбрав в левом верхнем углу нужный маркер. Чтобы вместе с платформой перемещался какой-нибудь предмет, например, шипы, вам необходимо перетащить его прямиком на платформу в списке иерархии. Уже только на основе этого формируется неплохая игра, но мы пойдем еще дальше.

Теперь добавим в нашу сцену нечто посложнее - злющего NPC. Враги находятся в папке Enemies , которая лежит там же, в Prefabs . Неважно, кого вы кинете на уровень: синего или розового монстра, внимательно присмотритесь к его настройкам. Самая первая - Transform — изменяет положение и размер объекта. Вторая - Sprite Renderer — позволяет его отзеркалить в разных координатах; для этого поставьте галочку рядом с X или Y . И уж совсем далеко внизу находится Enemy Behaviour , которая управляет поведением монстра. Важнейшие строчки в этом окне - это View Fov и View Direction . Первая определяет область зрения врага, а вторая ее вращает. Вы также сможете отрегулировать дистанцию взора в строчке View Distance , просто указав нужное число.

Интерактивные объекты

На этом этапе вы способны создать собственный уровень со скрытыми пещерами, летающими островами и разными типами врагов, и все это без единой строчки кода. Пришла пора усложнить собранную сцену более комплексными, связанными друг с другом, объектами. Мы установим дверь, которая будет открываться благодаря кнопке, расположенной в полу или на стене. Для этого нам понадобится дверь, лежащая в уже знакомой нам папке Interactables , но с ней мы ничего делать не будем. Основная работа пойдет над кнопкой PressurePad , которая и должна открывать путь.

Ее расположение может быть любым; нам важна настройка Pressure Pad (Script) , которая кроется глубоко внизу инспектора. В ней есть маленькое окошко On Pressed () с плюсиком внизу. Этот плюс добавляет реакцию чего бы то ни было в сцене на нажатие кнопки. Поскольку нам надо, чтобы открывалась дверь, то именно дверь и нужно перетащить из окна иерархии в строку под надписью Runtime Only .

Мы связали между собой кнопку и дверь, но пока не определили, какой будет эта связь. Для того чтобы назначить ее нужно зайти в подменю No Function и в том списке, который выпадет при наведении на Animator , выбрать Play (string) . Вуаля! Опробуйте результат, просто нажав на кнопку Play , расположенную над сценой.

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

Связываются точки перехода еще проще, чем дверь с кнопкой. Они находятся в папке Scene Control , которая, как и все прочие папки с объектами, лежит в Prefabs . Там нам понадобится объект TransitionStart , который является точкой перехода. Если вы хотите создать телепорт внутри сцены, то вам понадобится две таких точки, одна из которых обязательно должна быть переименована в TransitionEnd (делается это в верхней строке инспектора).

Наберитесь терпения, потому что сейчас придется плотно поработать с настройками точки отправления, которые называются Transition Point (Script) . Первое поле говорит нам о том, какой объект будет переноситься. Поэтому из иерархии в него нужно перетащить героиню (Ellen ).

Вторая строка отвечает за тип перехода: внутри зоны или вне - это как перелет внутри страны или за границу. Если вы выбрали внутреннее путешествие, то перетащите в новое поле TransitionEnd — так вы укажите куда произойдет переход. Обычно перемещение происходит автоматически, поэтому следующей строкой стоит надпись On Trigger Enter , но вы можете изменить это по своему желанию, как это сделали мы у корабля. Не забудьте также перетащить Ellen в настройки TransitionEnd , иначе чуда не свершится.

Если вы переходите в другую локацию, то вам сперва нужно выбрать ее, а затем уже точку перехода, потому что на уровне их может быть несколько, и все они помечаются буквами в алфавитном порядке. Кстати, местом назначения в другом уровне служит объект под названием TransitionDestination , который находится там же, в Scene Control . Не перепутайте!

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

Первый секрет заключен в портале, сверкающем в самом начале локации. Мы не станем раскрывать что внутри, но вы и сами можете разгадать это, если, конечно, попадете в него.

Второй секрет — это оформление. Деревья, трава, столбы, статуи и другие декоративные украшения располагаются в папке Sprites , которая лежит внутри папки Art. В этом нет ничего тайного, но вы можете пополнять библиотеку из магазина Unity или самостоятельно рисуя в Photoshop . Они могут как спрятаться за другими объектами, так и перекрывать их — за это отвечает цифра в строке Order in Layer .

Третий секрет — музыка. Он совсем простой: кидаете свой саундтрек в папку Music , а затем перетаскиваете его в настройки BackgroundMusicPlayer .

И последний секрет — смерть. Если вы строите открытые локации, в которых игрок может запросто упасть со скалы, вам необходимо подготовить для него штраф, а иначе он продолжит бесконечно парить. Лучше всего для этого подходит невидимая смерть. Создайте пустой объект и прикрепите к нему Damager (Script) . Растянув его по всей карте, вы получите ту самую невидимую смерть, от которой игрок будет гибнуть, падая с высоты.

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

Скачать движок можно по этой ссылке , а найти к нему более подробную инструкцию на английском языке — по этой . Удачного девелопмента!

Надеемся, что наш гайд был вам полезен и сподвиг на создание собственных шедевров!

Поэтому открываю этим постом цикл туториалов, освещающих основные моменты работы с объектом (цикл неопределённой пока длительности - если кому окажется полезным продолжу).

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

Let"s get it started

Предстартовая подготовка
Для начала качаем и устанваливаем собственно сам Unity на офсайте, выбрав лицензию с ценником Free . Можно скачать и тридцатидневный триал UnityPro, это на ваш вкус.

При первом запуске перед вами предстанет окошко Project Wizard"а (у вас там будет пусто):

Для общего ознакомления можете открыть прилагавшийся в комплекте BootcampDemo, который в винде ложится, как ни странно, в «DocumentsAndSettings\AllUsers\Документы\Unity Projects\Bootcamp Demo».
Для продолжения же нашего обучения переходим на закладку Create New Project и отмечаем галочками наборы стандартных игровых объектов и скриптов, которые нам понадобятся. Понадобятся нам

  • Character Controller
  • Particles
  • Physic materials
  • Scripts
  • Skyboxes
  • Terrain Assets
  • Tree Creator
А можно отметить все, что там только можно отметить галочками. Запас карман не жмёт.

Выбираем папку назначения, жмём Create и ожидаем, пока небыстрый процесс импорта завершится. В конце перед нами предстанет пустое поле для экспериментов:

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

Иерархия объектов на сцене - это список всех объектов на текущем уровне, показывающий заодно отношения Parent-Child.

Инспектор объектов показывает компоненты и их свойства выделенного в данный момент объекта - модели, текстуры, префаба.

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

В главном окне редактирования мы пользуемся всеми прелестями драг"н"дропа для расставления объектов по уровням.

Для начала посмотрим, что у нас уже есть в дефолтной сцене. Негусто, правда? Объект с говорящим названием Main Camera, по нажатию на название которого в иерарахии мы увидим конус viewport"а и маленькое окошко с видом из камеры.

Сразу расскажу основные контролсы окна редактирования:

  • средняя мыши - передвижение камеры вправо-влево (по внутренней плоскости xy)
  • левый клик - понятное дело, выделение объекта (хотя тяжело бывает переключаться между Blender"ом и юнити:))
  • правый клик - вращение «головой»
  • F - центрирование на выделенном объекте
  • перетаскивание объекта с зажатым ctrl - перетаскивание с шагом в 1 единицу координат
Кстати о координатах - разработчики советуют принимать 1 юнит игрового пространства за 1 реальный метр, и лучше этого придерживаться (хотя бы чтобы с физикой меньше возиться).
Земля обетованная
Но возвращаясь к нашим баранам, создадим поверхность, по которой будем ходить. Моделей у нас нет пока, поэтому можно создать просто большую плоскость… Но мы создадим землю, то бишь Terrain - он покрасивше голой плоскости будет. Для этого в меню выбираем Terrain->Create Terrain. Вуаля!

Опять-таки, не очень впечатляет. Для начала, сменим размер земной тверди через Terrain->Set Resolution. По умолчанию параметры Length и Width равны 2000, т.е. 2 км на 2 км. Для тестовых побегушек нам столько не надо, потому пишем в эти поля 500 и 500 - более чем достаточно.

Теперь назначим земле текстуру. Для этого выделяем террейн (в главном окне или в иерархии), и наблюдаем доступные свойства в инспекторе объектов. Там мы увидим инструменты для редактирования террейнов (стандартные - поднять\опустить, сгладить, и т.д.). Можете сразу начать рисовать нужный вам ландшафт, но в данный момент нас интересует кнопка с кисточкой Paint the terrain texture. А на закладке, которую она открывает, кнопка Edit textures, в меню которой жмём Add Texture:

Откроется окошко с параметрами будущей текстуры. Находим среди них один со значением «None (Texture 2d)» и кликаем на шарик с точкой справа от этих слов. Откроется выбиралка текстуры из уже добавленных в ресурсы проекта. К слову, чтобы добавить текстуру в проект, достаточно её просто скопировать в папку проекта - юнити все сам подхватит. Импорт всего остального происходит анналогично а удаление ресурса из проекта означает физическое удаление с диска.

Выбираем нужную текстуру, например Grass(Hill). Можно добавить ещё несколько текстур, например Cliff (Layered Rock), и, пользуясь кистью, выбираемой чуть выше, раскрасить по своему усмотрению. У меня после предыдущих манипуляций получилось вот так:

Если мы в данный момент нажмем на кнопку «Play» вверху экрана, то вы увидите, скорее всего, кусок нашей плохо освещённой земли на голубом фоне. Но так ведь неинтересно, поэтому прежде чем продолжить, нам надо добавить на сцену освещение, симулирующее солнечное, и заменить голубой фон небом. Свет добавляется через главное меню, GameObject->Create other->Directional light. Затем, следуя картинке, нажимаем кнопку которая меняет стрелки-хелперы вокруг выделенного объекта на оси вращения. Тягая за эти оси мы, как ни странно, вращаем осветительный прибор так, чтобы земля покрасивше подсвечивалась.

Чтобы вместо голубого полотна над головой было небо, надо указать текстуру скайбокса (skybox - «небесная коробка», куб, на который изнутри натянута текстура, как правило - панорамная фотография неба, сделанная таким образом, что изнутри кажется, будто ты окружен шаром с этой текстурой. Именно так в большинстве игр и рисуется небо). Для этого заходим в меню Edit->Render Settings, находим свойство «Skybox material» и, как мы это делали с текстурой земли, жмем на кружок с точкой справа от него. Нам покажут материалы, имеющиеся в проекте (чтобы не ходить в дебри терминологии, будем считать что материал это текстура и то, как она будет отображаться - будет она прозрачной, или будет блестеть, и т.д.). Выбирайте любой со словом skybox, например sunny2 skybox. Отлично, теперь наш полигон выглядит поживее.

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

Вообще-то, в комплект Юнити входят два игровых объекта, дающих готовое, уже собранное решение для камеры и управления. Но я о них вам пока не скажу, чтобы был стимул собрать своими руками. :)

Для начала, создадим объект для игрока. Вернее, чтобы не плодить лишние скрипты, переименуем «Main Camera» в «player». Теперь, выделив камеру, в меню выберем Component->Physics->Character Controller. Character controller - это компонент, который обрабатывает положение объекта (вернее, персонажа) в пространстве: его движение, повороты, падения, прыжки и столкновения с другими объектами. Все это можно делать напрямую, без помощи этой обертки вокруг стандартных функций перемещения объектов и проверки коллизий, но тогда код реализации передвижения вырастает в разы, что плохо для первого знакомства с движком. А так, в общем-то, можно на первом уроке и не кодить:)

<Лирическое отступление>
В качестве лирического отступления стоит рассказать о компонентах.

Любой предмет, который мы видим на сцене - это экземпляр класса с говорящим названием GameObject. Он имеет некоторое количество т.н. компонентов - в свою очередь, являющихся экземплярами своих классов. Каждый компонент выполняет некую утилитарную функцию. Так, у каждого GameObject должен быть компонент Transform, который занимается тем, что хранит текущие координаты, угол разворота и размеры объекта в трехмерном пространстве. Ну и заодно содержит методы для выполнения действий над положением объекта в этом пространстве: пермещение, развороты и т.д., т.е. когда мы в редакторе тянем объект за стрелки-управляторы, мы меняем координаты его трансформа.

А вот например компонент RigidBody занимается тем, что обрабатывает физическое поведение объекта: всё, что мы видим, когда бочка от выстрела падает на бок и катится - результат работы RigidBody. Который, кстати, не сможет правильно работать, если не назначить объекту компонент типа Collider, который хранит в себе трехмерную модель, по которой определяются столкновения объекта. Эта модель не отображается, но именно она, а не та что мы видим на экране во время игры, проверяется на столкновения с окружающим миром.

Вокруг камеры мы теперь видим зелёные линии, образующие купсулу, по которой рассчитываются столкновения с игроком - его, так сказать, тело. Камера находится в середине этой капсулы, но нам же не нужны глаза на поясе (хотя ситуации бывают разные:)), поэтому надо передвинуть камеру в район предполагаемой головы персонажа. Но эта капсула - часть объекта с единым Transform, а потому подвинуть её отдельно от камеры не получится. К счастью, если в инспекторе объекта мы глянем на свойства, доступные для Character Controller, то увидим там свойство Center, с параметрами x, y, z. То есть мы можем сдвинуть центр капсулы относительно центра объекта. Ставим y = -0.8 и получаем нормальную высоту глаз.

Если мы теперь поставим нашего player"а над поверхностью земли и включим Play, наша камера всё ещё будет стоять на месте. Так происходит потому, что Character Controller лишь обрабатывает поступающие к нему управляющие команды, сама они инициативы не проявляет. Чтобы разъяснить ей, что делать, драг-н-дропнем на player"a скрипт, который лежит в инспекторе префабов в папке Standart Assets\Character Controllers\Sources\Scripts и зовётся «CharacterMotor». Его задача - используя методы Character Controller осуществлять основную рутину передвижения: реализовывать действие гравитации, прыжки, вычислять текущую скорость и направление движения. Запуск уровня теперь заставит камеру игрока падать на землю - уже что-то.

Но мы всё еще стоим на месте: ведь у нас нет скрипта, который перехватывал нажатия клавиш и сообщал о них в CharacterMotor. Этим занимается лежащий в той же папке скрипт FPSInputController. Кидаем его на игрока, запускаем и ура - кнопки W, S, A, D теперь позволяют ходить, а пробел - прыгать! Но ходим-то мы всё как-то в одну сторону, вправо-влево да приставным шагом. Чтобы вертеть головой и идти куда глаза глядят, понадобится ещё один скрипт, оттуда же: MouseLook. Кидаем, запускаем - и получаем полноценное управление.

Теперь можно поиграться с переменными. Character Cоntroller установил вместе с собой Character Motor - в его-то свойствах и хранятся такие параметры, как Gravity, Max Forward Speed, Jumping Base Height и всякие другие. Вот они, все плюсы Инспектора - все основные свойства классов всегда на виду, даже в код лезть не надо.

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

Стрельба по изредка движущимся мишеням
Бегать по собственноручно сделанным холмам, любовно раскрашенным аж тремя текстурами, конечно, весело. Но вскоре надоедает. Поэтому мы выберем в меню GameObject->Create other->Cube, разместим этот куб в воздухе рядом с игроком, в свойствах Transform у него укажем следующие параметры Scale (x, y, z): 3, 3, 3; и запустим уровень, чтобы посмотреть, что этот куб будет делать. Делать он не будет ровным счетом ничего, если честно. Но стоит, выделив этот куб, назначить ему, Component->Physics->Rigidbody, при следующем запуске мы увидим совсем другую картину. Куб падает, крутится, почти как настоящий, правда как картонный - масса его по умолчанию 1 кг, что для таких размеров маловато, но мы её пока трогать не будем.

Выделив куб, нажмём Ctrl+D (дублирование объекта), и с зажатым Ctrl потянем куб вверх до тех пор, пока кубы не будут расположены один над другим. Теперь у нас два совершенно одинаковых куба ровно друг над другом. Повторим эту процедуру кубов до 15, и посмотрим на падение Вавилонской башни.

Далее, создаем новый куб, но не назначаем ему Rigidbody. Из него мы сделаем ровную поверхность для удобства стрельбы. Задаем ему Scale: 150, 20, 150; и располагаем где угодно - главное разместить на нем нашу башню и игрока. У меня получилось так:

Теперь создадим оружие и патроны. Патронами будет служить префаб, который мы создадим в инспекторе префабов нажав на кнопку «Create» вверху инспектора, и выбрав там собственно Prefab. Новый префаб создастся в той папке, что была открыта в тот момент в инспекторе. Он будет подсвечен серым, что символизирует отсутствие у него компонентов. В главном меню жмём GameObject->Create other->Sphere, находим в иерархии сцены эту Sphere, вешаем на неё Rigidbody. Но для пущей красоты зайдем в свойства этого Rigidbody, что мы только что повесили, и изменим парамтер Mass на 5.

После этого перетаскиваем сферу из списка в иерархии на созданный нами префаб. Префаб становится голубым, а вместе с ним синеет и имя сферы на сцене - это означает, что она является клоном префаба. Переименовываем префаб из дефолтного имени в, допустим, prefab_bullet. Для красоты. Пуля готова!

Следующим шагом будет создание оружия, вернее - скрипта, стреляющего по клику мыши этой самой сферой. Рядом с префабом создаем файл JavaScript"а: Create->JavaScript. Называем его, например, player (ну, чтобы не путаться). Даблкликом по нему откроется встроенный скрипторедактор, в котором мы заменяем содержимое файла вот этим (разбирать пока что не будем):

Public var bulletImpulse = 300;
public var shootSpeed = 1;
public var bullet: GameObject;

Public var lastShotTime: float;

Function Start() {
lastShotTime = 0;
}

Function Update () {

If (Input.GetKey(KeyCode.Mouse0)) {
if (Time.time>(lastShotTime + shootSpeed)) {
var bull_clone: GameObject;
bull_clone = Instantiate(bullet, transform.position, transform.rotation);
Physics.IgnoreCollision(bull_clone.collider, collider);
bull_clone.rigidbody.AddForce(transform.forward*bulletImpulse, ForceMode.Impulse);
lastShotTime = Time.time;

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

Если, конечно, НЛО не вмешалось в мои мысли и я не пропустил какой-то важный шаг.

Надеюсь, что я достаточно подробно осветил то, что надо было осветить, и не зацикливался на неважных мелочах. Пусть этот туториал и носит ознакомительный характер, хотелось бы верить, что он кому-то окажется реально полезен. Жду рациональной критики и пожеланий на следующий выпуск. Спасибо за внимание!

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

Обзор приложения

В этом уроке вы узнаете, как создать создать мобильную 2D игру в Unity. В качестве языка программирования будет использован C#.

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

В этом проекте вы узнаете следующие аспекты разработки в Unity:

  • создание 2D проекта в Unity
  • ознакомление с интерфейсом Unity
  • создание префаба
  • добавление скриптов к игровым объектам
  • обработка столкновений
  • использование таймеров
  • работа со звуком

Создание проекта в Unity

Загрузите Unity и выберите New Project в меню File меню, чтобы открыть новый диалог проекта. Выберите каталог для вашего проекта и установить настройки по умолчанию для 2D.Open

Настройки

На следующем этапе вы познакомитесь с интерфейсом Unity. Создайте проект на мобильных разработок, щелкнув на Build Settings в меню File и выбрав нужную вам платформу для вашей будующей игры.

В Unity можно создавать игры под iOS, Android, BlackBerry и Windows Phone 8. Поскольку мы собираемся создать 2D игры, первое, что мы должны сделать после выбора платформы, - это выбрать размер картинок, которые мы будем использовать в игре:

  • iPad без Retina: 1024px x 768px
  • iPad с Retina: 2048px x 1536px
  • 3.5" iPhone/iPod Touch без Retina: 320px x 480px
  • 3.5" iPhone/iPod с Retina: 960px x 640px
  • 4" iPhone/iPod Touch: 1136px x 640px

Поскольку Android является открытой платформой, существует множество устройств различными разрешениями экрана и плотности пикселей. Некоторые из наиболее распространенных из них перечислены ниже:

  • Asus Nexus 7 Tablet: 800px x 1280px, 216 ppi
  • Motorola Droid X: 854px x 480px, 228 ppi
  • Samsung Galaxy SIII: 720px x 1280px, 306 ppi

Для Widows Phone и BlackBerry:

  • Blackberry Z10 : 720px x 1280px, 355 ppi
  • Nokia Lumia 520 : 400px x 800px, 233 ppi
  • Nokia Lumia 1520 : 1080px x 1920px, 367 ppi

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


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

Интерфейс Unity

Не забудьте нажать кнопку 2D в панели Scene . Вы также можете изменить разрешение, которое используется для отображения сцены, на панели Game .

Игровой интерфейс


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

Языки программирования в Unity

Вы можете использовать один из трех языков программирования, которые поддерживает Unity: C# , UnityScript , который по синтаксису похож на JavaScript и Boo . Каждый язык имеет свои плюсы и минусы и только от вас зависит на каком языке создавать свою игру. Мне больше нравится C#, так что его и будем использовать.

Если вы хотите программировать на другом языке, то обратитесь к Unity"s Script Reference для изучения.

Прежде чем мы начнем кодирование, нам нужно добавить наши ассеты в проект Unity. Есть несколько способов сделать это:

  • выбрать Import New Asset из меню Assets
  • добавить детали в папку assets в проектеt
  • перетащить ассеты в окно проекта

После завершения этого шага, вы должны увидеть ассеты вашего проекта в папке Assets в панели Project .

Создание сцены

Мы готовы создать сцену нашей игры путем перетаскивания объектов в панель Hierarchy (Иерархия) или Scene (Сцена).

Фон

Перетащите фон (файл gameBg.png) в панель Hierarchy панели. Затем он должен появиться в панели Scene .

Поскольку вкладка Scene (Сцена) установлена в режим 2D, вы можете выбрать Основную камеру (Main Camera ) в панели Иерархия, при этом вы увидете то, что камера будет отображать. Вы также можете увидеть это переключившись из вкладки Сцена во вкладку Game (Игра). Чтобы вся сцена видна, измените параметр Size (Размер) для Основной камеры на 1.6 в панели Inspector (Инспектор).


Летающая тарелка

Корабль в нашей игре является статическим элементом и игрок с ним никак не взаимодействует. Расположим его в центре сцены, для этого просто ппертащите летательный аппарат из папки Assets, находящейся в панели Project , на вкладку Scene . Точно также как и с НЛО перенесем на сцену сарайчик для наших коровок и расположим его так, как показано на скриншоте:


Коллайдер сарая

Чтобы убедиться, что сарай реагирует на столкновение с коровой когда она пытается войти внутрь – нужно добавить один компонент, а точнее Box Collider 2D .

Выберите сарай в сцене и во в вкладке Inspector нажмите на кнопку Add Component (добавить компонент). Из списка компонентов выберите раздел Physics 2D , а в нем Box Collider 2D . Убедитесь в том, что напротив параметра Is Trigger стоит галочка. Если ее нет - поставьте.


Мы хотим, чтобы наш триггер срабатывал, корова попадала на дверь сарая, поэтому мы должны сделать коллайдер поменьше. Перейдите на вкладку Inspector и измените значения коллайдера напротив Size (высота и ширина коллайдера) и Center (координаты центра коллайдера) так, чтобы он располагался поближе к двери сарая. У меня это получилось примерно так:


Скрипт для обработки столкновений

Настало время написать наш первый код. Нам нужен скрипт, который заставлял бы приложение реагировать на столкновение всякий раз, когда корова входит в сарай. Выберите сарай и нажмите на кнопку Add Component в панели Inspector . Выберите New Script назовите его OnCollision . Незабудьте поменять язык на C#.

Откройте вновь созданный файл и добавьте следующий фрагмент кода:

Using UnityEngine; using System.Collections; public class OnCollision: MonoBehaviour { void OnTriggerEnter2D(Collider2D other) { if (other.gameObject.name == "cow(Clone)") { /* Проиграть крик о помощи */ audio.Play(); /* Уничтожим корову */ Destroy(other.gameObject); } } }

Сниппет реагирует на столкновение между объектами, к которым привязан скрипт, сараем и объектом под именем cow(Clone) - один из экземпляров коровы, префаб которой мы создадим позже. Когда происходит столкновение, проигрывается звук и объект корова разрушается.

Добавляем звук

Для воспроизведения звука, когда корова попадает в сарай, нужно для начала прикрепить этот звук к сараю. Щелкните мышью на сарае во вкладке Hierarchy или на самой сцене (вкладка Scene ), нажмите кнопку Add Component во вкладке Inspector и выберите в разделе Audio пункт Audio Source Audio Source . Теперь нам нужно связать с ним наш звуковой файл. Щелкните на кружочке справа от пункта Audio Clip и выберите звук barn . Снимите галочку напротив Play on Awake

Вы можете увеличить размер значков в пользовательском интерфейсе Unity (Gizmos), нажав на Gizmos в вкладке Scene и отрегулировав положение ползунка.

Космический луч

Перетащите изображение с космическим лучем из вкладки Assets на сцену и добавьте коллайдер к нему (как это делать вы уже знаете). Это необходимо для обнаружения столкновения луча с нерасторопными коровамии. Убедитесь, что переключатель Is Trigger включен.

Скрипт луча

Создайте новый скрипт, повторяя шаги, проделанные ранее. Назовите скрипт Bullet и напишите в нем следующий код:

Using UnityEngine; using System.Collections; public class Bullet: MonoBehaviour { public AudioClip cowSound; // Используется для инициализации void Start() { renderer.enabled = false; /* Делает объект невидимым */ } // Обновлять каждый кадр void Update() { /* Обработка нажатия на правую кнопку мыши или касание экрана */ if (Input.GetButton("Fire1")) { renderer.enabled = true; /* Делает объект видимым */ /* Проиграть звук выстрела из корабля лучем */ audio.Play(); } if (renderer.enabled == true) { transform.position += Vector3.down * (Time.deltaTime * 2); } /* Check for out of bounds */ if (this.transform.position.y

Здесь много кода, но он вовсе не сложный. Давайте посмотрим, что происходит. Сначала мы создаем экземпляр AudioClip под названием cowSound , который мы будем использовать для хранения аудиофайла. Это еще один вариант проигрывания звука, если вы не хотите привязывать к объекту два аудиокомпонента. Мы объявили переменную как публичную, поэтому мы можем получить к ней доступ из вкладки Inspector . Нажмите на маленькую точку справа от cowSound и выберите аудиофайл cowSound .

Затем мы сделали луч невидим, отключив его визуализацию. Мы используем один и тот же объект, чтобы сэкономить ресурсы. Этот момент является очень важным для оптимизации под маломощные устройства.

Мы обнаруживаем касания экрана, делаем луч видимым и воспроизводим звук луча (см. ниже). Когла наш луч становится видимым, он начинает двигаться вниз чтобы попасть в корову.

Также мы предусмотрели проверку не оказался ли наш луч за границами сцены. Если это так, то мы возвращаем его на место и наше НЛО пожет стрелять снова (проверьте значения х и у луча в Инспекторе).

Последняя часть проверяет коснулся ли луч коровы. Если это произойдет, то проигрывается звук и корова исчезает. Затем луч снова делается невидимыми, перемещается в исходное положение и НЛО готово к стрельбе снова.

Добавляем звук для луча


Чтобы добавить звуковое сопровождение к нашему космическому лучу необходимо выбрать во вкладке Hierarchy или Scene наш лучик и нажать на кнопку Add Component во вкладке Inspector . Выберите Audio Source из раздела Audio . Снимите галочку с Play on Awake и нажмите маленькую точку справа от Audio Clip , чтобы выбрать звуковой файл по имени bizzed .

Добавляем корову

Перетащите нашу буренку из панели Assets на вкладку Сцена так, как показано на рисунке ниже:


Rigid Body 2D

Чтобы столкновение было зарегистрировано, вы должны проассоциировать компонент RigidBody2D хотя бы с одним из участвующих в нем объектов. Поскольку корова может столкнуться и с сараем, и с лучом, лучше всего добавить компонент к корове. Выберите нашу Буренку и нажмите на кнопку Add Component (Добавить компонент) во вкладке Inspector (Инспектор). Выберите пункт Physics 2D , а в нем RigidBody2D . Мы только что добавили компонент RigidBody2D . Установите в нем параметр Gravity Scale равным нулю:


Коллайдер коровы

Мы также должны привязать к корове коллайдер (как это делать вы уже знаете), чтобы определять столкновения с сараем и лучом. Не забудьте поставить галочку в чекбоксе Is Trigger в Inspector .

Скрипт движения коровы

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

Using UnityEngine; using System.Collections; public class MoveCow: MonoBehaviour { public Vector3 moveSpeed; public float spawnTime = 2f; public float spawnDelay = 2f; // Используется для инициализации void Start() { moveSpeed = Vector3.left * Time.deltaTime; InvokeRepeating("ChangeSpeed", spawnDelay, spawnTime); } void ChangeSpeed() { moveSpeed = new Vector3(Random.Range(-1, -2), 0, 0) * 0.05f; } // Обновлять каждый кадр void Update() { transform.position += moveSpeed; } }

Класс MoveCow анимирует корову во время ее движения на экране с помощью переменной moveSpeed . Метод InvokeRepeating изменяет скорость коровы, заставляя ее пускаться галопом начиная с того момента, когда она достигает центра сцены. Это делает игру более сложной.

Создаем префаб для коровки

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

"Префаб – это GameObject для многократного использования, который хранится в Project View . Префаб может быть вставлен в любое количество сцен, несколько раз. Когда вы добавляете префаб в сцену, вы создаете его экземпляр. Все экземпляры префаба связаны с оригинальным префабом и, по сути, являются его клонами. Независимо от того, сколько копий вы создали для своего проекта, любые изменения, коснувшиеся оригинального префаба, будут применены ко всем его экземплярам".

Для преобразования коровы в префаб, перетащите корову из вкладки Иерархия в панель Assets . В результате название в Hierarchy станет синим.

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

Скрипт Spawner

Скрипт Spawner отвечает за отображение коров. Откройте MonoDevelop или ваш любимый C#-редактор и создайте новый скрипт:

Using UnityEngine; using System.Collections; public class Spawner: MonoBehaviour { public float spawnTime = 2f; public float spawnDelay = 2f; public GameObject cow; // Используется для инициализации void Start() { InvokeRepeating("Spawn", spawnDelay, spawnTime); } void Spawn() { /* Создание экземпляра коровы */ GameObject clone = Instantiate(cow, transform.position, transform.rotation) as GameObject; } }

Мы используем метод InvokeRepeating для клонирования коров с применением значений, установленных для spawnTime и spawnDelay . GameObject cow установлен на значение public и создан с использованием Inspector . Нажмите на маленькую точку справа и выберите префаб коровы.

Чтобы создать несколько экземпляров префаба коровы, используйте график коровы, который мы добавили к сцене несколько минут назад. Выделите ее и удалите ее компоненты. Затем добавьте скрипт Spawner.

Тестирование

Настало время протестировать нашу игру. Нажмите на Command + P для запуска игры в Unity. Если все работает как надо, вы готовы к заключительному шагу.

Настройки плеера

После успешного тестирования, можно сосредоточиться на настройках плеера. Выберите Build Settings из меню меню File и нажмите на кнопку Player Settings (настройки плеера). Теперь можно настроить параметры для вашего приложения, например Иконку приложения (Icon) или Картинку-заставку (Splash Image), которая показывается при запуске приложения. Используйте графические редакторы чтобы создать красивый значок для вашей игры. Unity покажет в разделе Иконки (Icon) вам необходимые размеры, которые зависят от вашего приложения.


Публикация

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

Xcode

Если вы делаете игры для iOS, вам понадобится Xcode, чтобы скомпилировать конечный двоичный код для приложения. Откройте проект Xcode и выберите Build из меню Product .

Заключение

В этом уроке мы узнали о новых 2D возможностях Unity, обнаружение столкновений и другие аспекты разработки игр под Unity.

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




Top