Информационный портал по безопасности. Пишем арканоид на Unity

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

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

Rigidbody

Что это такое?

За функцией Rigidbody скрывается Абсолютно Твердое Тело (АТТ ). Если объяснять грубо и понятно, то АТТ в физике и механике - это идеальное твердое тело, которое под воздействием силы не может менять свои свойства, но может (под ее воздействием) перемещаться в 3х измерениях (вниз, вверх, вперед и т.д., т.е. в наших X-Y-Z осях), а также вращаться в 3х измерениях (опять же по осям X-Y-Z).

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

Какое применение мы можем найти этой функции?

К примеру, для создания автомобиля, кроме Rigidbody нам понадобятся 4 Wheel Collider "а и код (скрипт ) , применяющий силовое воздействия к колесам, в зависимости от нажатых клавиш.

Настраиваемые характеристики

  • Mass - Масса нашего объекта в килограммах . Рекомендуется не ставить значения массы в 100 раз больше или меньше масс других АТТ .
  • Drag - Насколько тело подвержено сопротивлению воздуха, когда оно движется под воздействием сил. При значении 0 сопротивления нет, а бесконечное значение мгновенно остановит наш объект.
  • Angular Drag - Насколько тело подвержено сопротивлению воздуха, когда оно вращается под воздействием сил. При значении 0 сопротивления нет, а бесконечное значение мгновенно прекратит вращение нашего объекта.
  • Use Gravity - При включении, объект становится подвержен влиянию гравитации.
  • Is Kinematic - При включении, объект становится не подвержен влиянию физического движка и может изменяться только его функцией Transform . Это может быть полезно для создания движущихся платформ, к примеру.
  • Interpolate - Применяется только в случае, если движения вашего АТТ кажутся вам странными или неуклюжими и т.п.:
    1. None: Интерполяция не применяется
    2. Interpolate: По-сравнению с трансформацией предыдущего кадра (фрейма ) , следующий будет сглажен.
    3. Extrapolate: Трансформация текущего кадра сглаживается, по-сравнению с оценочной (примерной) трансформацией следующего.
  • Freeze Rotation - Запрещает любое вращение, как скриптовое, так и при столкновениях. Однако, вращение можно будет выполнять функцией transform.Rotate()
  • Collision Detection - Используется для того, чтобы быстро-движущиеся объекты не проходили сквозь другие объекты, не находя Collision "ов (специальная "сетка" на объектах, которой они сталкиваются друг с другом и с игроком).
    1. Discrete: Значение по-умолчанию для того, чтобы наш объект "замечал" все другие объекты, с которыми может столкнуться.
    2. Continuous: Используйте Discrete Сollision с динамическими объектами столкновения (у которых имеется АТТ ), а Continuous Сollision для статических MeshCollider "ов (без АТТ ). Режим Continuous Dynamic использует Continuous Сollision для одного конкретного АТТ . Остальные АТТ будут использовать режим Discrete . (Это сильно скажется на нагрузке физического движок, просто оставьте Discrete , если не возникает проблем со столкновением быстрых объектов)
    3. Continuous Dynamic: Используется для объектов в режиме Continuous или Continuous Dynamic Collision . Continuous Сollision также будет использоваться для статических MeshCollider "ов (без АТТ ). Для всех остальных используется режим Discrete . Используется для быстро движущихся объектов.

Как мы можем использовать эту функцию?

Базовые знания.

Чтобы использовать АТТ , нам нужен уже созданный игровой объект (GameObject ), кликнув на нем, мы проходим в меню по следующему пути: Components - Physics - Rigidbody . Все, АТТ добавлено! :)
Теперь объект подвержен гравитации, к нему можно применять силы с помощью скриптов, но для того, чтобы объект вел себя именно так, как вам нужно, следует добавить Collider или Joint .

Код правит миром.

В скрипте манипулировать нашим объектом теперь мы будем с помощью функций AddForce() и AddTorque() .
Так как я в Unity применяю JavaScript , мои примеры будут с ним, ссылки на другие примеры скриптинга (на C# или Boo ) вы найдете ниже, в пункте Дополнительная информаия по АТТ .

» Rigidbody.AddForce

// Rigidbody.AddForce использует 2 типа формул, как и многие другие функции, связанные с перемещениями в пространстве. // 1 тип: function AddForce (force: Vector3, mode: ForceMode = ForceMode.Force) : void // Сила, подбрасывающая объект вверх, относительно глобальной системы координат. function FixedUpdate () { rigidbody.AddForce (Vector3.up * 10); } // Используется Vector3 - встроенная функция Unity, которая, в принципе, аналогична стандартной системе координат. // 2 тип: function AddForce (x: float, y: float, z: float, mode: ForceMode = ForceMode.Force) : void // То же самое, но тут используется X-Y-Z система координат. function FixedUpdate () { rigidbody.AddForce (0, 10, 0); }

» Rigidbody.AddTorque

// Функция раскручивает объект вокруг заданной оси. // 1 тип: function AddTorque (torque: Vector3, mode: ForceMode = ForceMode.Force) : void // Раскручивает АТТ вокруг глобальной оси Y. function FixedUpdate () { rigidbody.AddTorque (Vector3.up * 10); } // 2 тип: function AddTorque (x: float, y: float, z: float, mode: ForceMode = ForceMode.Force) : void // Делает то же самое, но снова в другой системе измерения. function FixedUpdate () { rigidbody.AddTorque (0, 10, 0); }

АТТ взаимодействует с объектами.

Для правильной работы наших АТТ их нужно снабдить Collider "ами (или Collision "ами, как вам будет угодно ^.^).
Подробно о коллайдерах читайте ниже.

Размер имеет значение!

Соблюдайте размеры вашего объекта, ведь они гораздо более значимы даже массы АТТ . Если ваш объект движется неправильно, висит в воздухе или не сталкивается, попробуйте настроить его величину (не АТТ , а самого объекта). При импортировании модели из 3D редактора ее размеры сохраняются, так что будьте внимательны на стадии моделирования и соблюдайте размеры всех моделей.

Дополнительная информация по АТТ

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

  1. Стандартный размер куба в Unity равен 1 метру , поэтому, проверять размер ваших моделей по нему очень удобно. Чтобы создать куб, выберите в меню GameObject - Create Other - Cube
  2. Относительный показатель Mass определяет, как два объекта будут взаимодействовать друг с другом.
  3. Mass не влияет на скорость падения с высоты, для этих целей используйте Drag .
  4. Чем выше значения Drag , тем больше весит предмет. стандартные значения варьируются от 0.001 (твердый кусок металла) до 10 (перышко).
  5. Если вам нужно изменять объект как с помощью скриптов, так и с помощью физики, добавьте к нему АТТ с параметром Kinematic .

Colliders

Что это такое?

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

Виды коллайдеров.

Коллайдеры добавляются через меню Component - Physics . Есть несколько видов:

  • Box Collider - в форме куба.
  • Sphere Collider - в форме сферы.
  • Capsule Collider - в форме капсулы.
  • Mesh Collider - автоматически создает коллайдер по форме сетки объекта, не может сталкиваться с другими коллайдерами этого же типа. В основном используется для статических объектов, например, окружение гоночной трассы.
  • Wheel Collider - используется для колес, очень полезная вещь.
  • Compound Collider - комбинации примитивов, которые вместе действуют как один. Чтобы создать такой сложный коллайдер нужно к нашему базовому коллайдеру добавить дочерние объекты, а к ним уже привязать по примитиву. Таким образом, к примеру, очень удобно делаются простенькие коллайдеры для машин.

Настраиваемые характеристики

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

  • Куб
    • Material - Показывает, как коллайдер взаимодействует с остальными объектами, при этом присваивая физический материал, например, металл, лед и т.п.
    • Is Trigger - Если параметр включен, то на объект воздействует скрипт, а не физика.
    • Size - Размер коллайдера по осям X-Y-Z.
    • Center - Положение коллайдера, относительно локальных координат объекта.
  • Сфера
    • Radius - Радиус сферы, заменяет параметр Size .
    • Остальные параметры без изменений.
  • Капсула (параметры заменяют размер)
    • Radius - Толщина капсулы.
    • Height - Высота цилиндрической части коллайдера (без скругленных оснований).
    • Direction - Направление коллайдера, относительно локальных координат объекта.
  • Mesh Collider (параметры заменяют размер)
    • Mesh - Выбор нужного меша для создания коллайдера.
    • Smooth Sphere Collisions - Включение этой функции сглаживает поверхность коллайдера. Использовать следует на гладких поверхностях, к примеру, наклонный ландшафт без лишней углоатости, по которому должны скатываться сферы.
    • Convex - При включении позволяет нашему коллайдеру сталкиваться с другими такими же. Convex Mesh Collider "ы ограничены до 255 трианглов .
  • Wheel Collider (параметры заменяют размер)
  • Radius - Радиус колеса.
  • Suspension Distance - Максимальная дистания увеличения подвески колеса. Подвеска всегда увеличивается вниз по локальной оси Y .
  • Suspension Spring - Подвеска пытается достигнуть указанной точки, используя различные силы.
  1. Spring:// Пытается достигнуть указанной точки (позиции). Чем выше параметр, тем быстрее она достигается.
  2. Damper:// Смягчает, замедляет скорость движения подвески. Чем выше значение, тем медленнее двигается амортизатор.
  3. Target Position:// Полный "путь", который может "пройти" подвеска. 0 означает полностью расправленный амортизатор, а 1 - полностью сжатый. Значением по-умолчанию является 0, что соответствует обычной автомобильной подвеске..
  • Mass - Масса колеса.
  • Forward/Sideways Friction - Параметры трения при простом качении колеса и при качении боком (такое бывает в заносах или при дрифте).


Мне нравится +15 - 0

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

Rigidbody
=
= Что это такое? =
За функцией *Rigidbody* скрывается Абсолютно Твердое Тело (*АТТ*). Если объяснять грубо и понятно, то *АТТ* в физике и механике - это идеальное твердое тело, которое под воздействием силы не может менять свои свойства, но может (под ее воздействием) перемещаться в 3х измерениях (вниз, вверх, вперед и т.д., т.е. в наших X-Y-Z осях), а также вращаться в 3х измерениях (опять же по осям X-Y-Z).

В *Unity*, как и в других игровых движках (опять же называю их именно "игровыми" движками грубо), *Rigidbody* используется для различных объектов, с которыми мы можем взаимодействовать, толкая, пиная и т.п. Подобные объекты под нашим влиянием будут далее под воздействием гравитации кататься, передвигаться и сталкиваться с другими предметами.

Какое применение мы можем найти этой функции? =
К примеру, для создания автомобиля, кроме *Rigidbody* нам понадобятся 4 Wheel Collider "а и *код* (*скрипт*) , применяющий силовое воздействия к колесам, в зависимости от нажатых клавиш.

  • *Mass* - Масса нашего объекта в килограммах . Рекомендуется не ставить значения массы в 100 раз больше или меньше масс других *АТТ*.
  • *Drag* - Насколько тело подвержено сопротивлению воздуха, когда оно движется под воздействием сил. При значении *0* сопротивления нет, а бесконечное значение мгновенно остановит наш объект.
  • Angular Drag - Насколько тело подвержено сопротивлению воздуха, когда оно вращается под воздействием сил. При значении *0* сопротивления нет, а бесконечное значение мгновенно прекратит вращение нашего объекта.
  • Use Gravity - При включении, объект становится подвержен влиянию гравитации.
  • Is Kinematic - При включении, объект становится не подвержен влиянию физического движка и может изменяться только его функцией *Transform*. Это может быть полезно для создания движущихся платформ, к примеру.
  • *Interpolate* - Применяется только в случае, если движения вашего АТТ кажутся вам странными или неуклюжими и т.п.:
  1. None: Интерполяция не применяется
  2. Interpolate: По-сравнению с трансформацией предыдущего кадра (*фрейма*) , следующий будет сглажен.
  3. Extrapolate: Трансформация текущего кадра сглаживается, по-сравнению с оценочной (примерной) трансформацией следующего.
  • Freeze Rotation - Запрещает любое вращение, как скриптовое, так и при столкновениях. Однако, вращение можно будет выполнять функцией //transform.Rotate()
  • Collision Detection - Используется для того, чтобы быстро-движущиеся объекты не проходили сквозь другие объекты, не находя Collision "ов (специальная "сетка" на объектах, которой они сталкиваются друг с другом и с игроком).
  1. Discrete: Значение по-умолчанию для того, чтобы наш объект "замечал" все другие объекты, с которыми может столкнуться.
  2. Continuous: Используйте Discrete Сollision с динамическими объектами столкновения (у которых имеется *АТТ*), а Continuous Сollision для статических MeshCollider "ов (без *АТТ*). Режим Continuous Dynamic использует Continuous Сollision для одного конкретного *АТТ*. Остальные *АТТ* будут использовать режим _Discrete_. (Это сильно скажется на нагрузке физического движок, просто оставьте _Discrete_, если не возникает проблем со столкновением быстрых объектов)
  3. Continuous Dynamic: Используется для объектов в режиме _Continuous_ или Continuous Dynamic Collision . Continuous Сollision также будет использоваться для статических MeshCollider "ов (без *АТТ*). Для всех остальных используется режим _Discrete_. Используется для быстро движущихся объектов.

Как мы можем использовать эту функцию? =
= Базовые знания.
Чтобы использовать *АТТ*, нам нужен уже созданный игровой объект (*GameObject*), кликнув на нем, мы проходим в меню по следующему пути: Components - Physics - Rigidbody . Все, *АТТ* добавлено! :)
Теперь объект подвержен гравитации, к нему можно применять силы с помощью скриптов, но для того, чтобы объект вел себя именно так, как вам нужно, следует добавить *Collider* или *Joint*.

Код правит миром.
В скрипте манипулировать нашим объектом теперь мы будем с помощью функций AddForce() и AddTorque() .
Так как я в *Unity* применяю *JavaScript*, мои примеры будут с ним, ссылки на другие примеры скриптинга (на C# или *Boo*) вы найдете ниже, в пункте Дополнительная информаия по АТТ .

Rigidbody.AddForce

// Rigidbody.AddForce использует 2 типа формул, как и многие другие функции, связанные с перемещениями в пространстве. // 1 тип: function AddForce (force: Vector3, mode: ForceMode = ForceMode.Force) : void // Сила, подбрасывающая объект вверх, относительно глобальной системы координат. function FixedUpdate () { rigidbody.AddForce (Vector3.up * 10); } // Используется Vector3 - встроенная функция Unity, которая, в принципе, аналогична стандартной системе координат. // 2 тип: function AddForce (x: float, y: float, z: float, mode: ForceMode = ForceMode.Force) : void // То же самое, но тут используется X-Y-Z система координат. function FixedUpdate () { rigidbody.AddForce (0, 10, 0); }

Rigidbody.AddTorque

// Функция раскручивает объект вокруг заданной оси. // 1 тип: function AddTorque (torque: Vector3, mode: ForceMode = ForceMode.Force) : void // Раскручивает АТТ вокруг глобальной оси Y. function FixedUpdate () { rigidbody.AddTorque (Vector3.up * 10); } // 2 тип: function AddTorque (x: float, y: float, z: float, mode: ForceMode = ForceMode.Force) : void // Делает то же самое, но снова в другой системе измерения. function FixedUpdate () { rigidbody.AddTorque (0, 10, 0); }

АТТ взаимодействует с объектами.
Для правильной работы наших *АТТ* их нужно снабдить Collider "ами (или Collision "ами, как вам будет угодно ^.^).
Подробно о коллайдерах читайте ниже.


Размер имеет значение!
Соблюдайте размеры вашего объекта, ведь они гораздо более значимы даже массы *АТТ*. Если ваш объект движется неправильно, висит в воздухе или не сталкивается, попробуйте настроить его величину (не *АТТ*, а самого объекта). При импортировании модели из 3D редактора ее размеры сохраняются, так что будьте внимательны на стадии моделирования и соблюдайте размеры всех моделей.

Дополнительная информация по АТТ =
На этом, описывать *АТТ* или *Rigidbody*, я, пожалуй, закончу. Однако, есть пара подсказок, специально для тех, кто до сюда долистал:)

  1. Стандартный размер куба в *Unity* равен 1 метру , поэтому, проверять размер ваших моделей по нему очень удобно. Чтобы создать куб, выберите в меню GameObject - Create Other - Cube
  2. Относительный показатель *Mass* определяет, как два объекта будут взаимодействовать друг с другом.
  3. *Mass* не влияет на скорость падения с высоты, для этих целей используйте *Drag*.
  4. Чем выше значения *Drag*, тем больше весит предмет. стандартные значения варьируются от 0.001 (твердый кусок металла) до 10 (перышко).
  5. Если вам нужно изменять объект как с помощью скриптов, так и с помощью физики, добавьте к нему *АТТ* с параметром *Kinematic*.

Посмотреть скриптовые примеры воздействия внешних сил на объект с функцией *АТТ* можно по следующим ссылкам:
*AddForce*
*AddTorque*

Для изменения скриптового примера кликните на тексте с названием языка программирования!

Colliders
=
= Что это такое? =
В предыдущем разделе мы рассмотрели принцип работы *Rigidbody* и упомянули так называемые *коллайдеры*. *Коллайдер* для нас - вспомогательный объект в виде сетки простой примитивной или, наоборот, сложной формы, который находится вокруг нашей модели или части модели и взаимодействует с другими объектами, если те тоже окружены коллайдерами.
Чтобы наглядно объяснить знатокам редактора мира *Warcraft 3*, представьте себе импортированную нами модель, которой мы в редакторе дудадов не присвоили текстуры путей - это будет наш объект; а роль коллайдеров тут будут играть блокираторы пути вокруг модели. Естественно, это довольно грубое сравнение, ведь в *Unity* они гораздо более функциональны. Что-ж, рассмотрим поподробнее.

Виды коллайдеров. =
Коллайдеры добавляются через меню Component - Physics . Есть несколько видов:

  • Box Collider - в форме куба.
  • Sphere Collider - в форме сферы.
  • Capsule Collider - в форме капсулы.
  • Mesh Collider - автоматически создает коллайдер по форме сетки объекта, не может сталкиваться с другими коллайдерами этого же типа. В основном используется для статических объектов, например, окружение гоночной трассы.
  • Wheel Collider - используется для колес, очень полезная вещь.
  • Compound Collider - комбинации примитивов, которые вместе действуют как один. Чтобы создать такой сложный коллайдер нужно к нашему базовому коллайдеру добавить дочерние объекты, а к ним уже привязать по примитиву. Таким образом, к примеру, очень удобно делаются простенькие коллайдеры для машин.


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

  • *Куб*

* *Material* - Показывает, как коллайдер взаимодействует с остальными объектами, при этом присваивая физический материал, например, металл, лед и т.п.
* Is Trigger - Если параметр включен, то на объект воздействует скрипт, а не физика.
* *Size* - Размер коллайдера по осям X-Y-Z.
* *Center* - Положение коллайдера, относительно локальных координат объекта.

  • *Сфера*

* *Radius* - Радиус сферы, заменяет параметр *Size*.
* Остальные параметры без изменений.

  • *Капсула* (параметры заменяют размер)

* *Radius* - Толщина капсулы.
* *Height* - Высота цилиндрической части коллайдера (без скругленных оснований).
* *Direction* - Направление коллайдера, относительно локальных координат объекта.


  • Mesh Collider (параметры заменяют размер)

* *Mesh* - Выбор нужного меша для создания коллайдера.
* Smooth Sphere Collisions - Включение этой функции сглаживает поверхность коллайдера. Использовать следует на гладких поверхностях, к примеру, наклонный ландшафт без лишней углоатости, по которому должны скатываться сферы.
* *Convex* - При включении позволяет нашему коллайдеру сталкиваться с другими такими же. Convex Mesh Collider "ы ограничены до 255 трианглов .

  • Wheel Collider (параметры заменяют размер)

* *Radius* - Радиус колеса.
* Suspension Distance - Максимальная дистания увеличения подвески колеса. Подвеска всегда увеличивается вниз по локальной оси *Y*.
* Suspension Spring - Подвеска пытается достигнуть указанной точки, используя различные силы.

  1. Spring: Пытается достигнуть указанной точки (позиции). Чем выше параметр, тем быстрее она достигается.
  2. Damper: Смягчает, замедляет скорость движения подвески. Чем выше значение, тем медленнее двигается амортизатор.
  3. Target Position: Полный "путь", который может "пройти" подвеска. *0* означает полностью расправленный амортизатор, а *1* - полностью сжатый. Значением по-умолчанию является 0, что соответствует обычной автомобильной подвеске..

* *Mass* - Масса колеса.
* Forward/Sideways Friction - Параметры трения при простом качении колеса и при качении боком (такое бывает в заносах или при дрифте).

Посмотрело: 734


Мой странный творческий путь занес меня в разработку игр. Благодаря отличной студенческой программе от IT-компании, название которой СостоИт из одной Греческой МАленькой буквы, сотрудничающей с нашим университетом, удалось собрать команду, родить документацию и наладить Agile разработку игры под присмотром высококлассного QA-инженера (здравствуйте, Анна!)

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

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

Игра

Пару слов о том, как она сделана.
Сделана с помощью Blender и пары скриптов на питоне. На время съемки, в углу экрана находились 16 квадратиков, цвет которых кодировал 32 бита числа с плавающей запятой - вращение телефона в данный момент времени. R, G - данные, B - четность. 0 - 0, 255 - 1. Снятое на компьютере видео разбивалось на кадры с помощью ffmpeg, каждому кадру рендера в соответствие ставился расшифрованный угол. Такой формат позволил пережить любое сжатие в процессе съемки и поборол тот факт, что все программы имеют несколько разные представления о течении времени. В реальности игра играется так же как и на рендере.


Самолетик летит по бесконечной и непредсказуемой пещере, в которой есть бонусы, всякие монетки и враги, в которых можно стрелять самонаводящимися ракетами. Врезался в стену - сразу проиграл.
Отличительная особенность игры в том, что уровень прибит к горизонту и управление в ней гироскопическое, причем, абсолютное. Наклонил телефон на 45 градусов - самолетик полетел под углом 45 градусов. Нужно сделать мертвую петлю - придется крутить планшет. Никакой чувствительности нет, только хардкор.
Выделим две основные и очевидные проблемы для разработчика:
Проблема 1: Бесконечность
Unity хранит и обрабатывает координаты объектов в виде обычных 32-битных float, имеющих точность где-то до 6 знака после запятой. Проблема в том, что игра у нас бесконечная и, если мы достаточно долго будем лететь, начнутся различного рода безумные баги, вплоть до телепортации сквозь стены. Есть несколько подходов к решению этой проблемы:
  • Игнорирование. В Minecraft, например, ошибки округления лишь сделали игру интереснее, породив .

  • Телепортация в (0;0;0) при слишком сильном удалении самолетика от начала координат.

  • Смена точки отсчета. Движется не самолет, а уровень вокруг него.
  • В нашем случае, единственный допустимый вариант - третий, который и был реализован. О реализации - чуть позже.
    Первый - игнорирование - абсолютно недопустим. Создание робота, который сможет вечно играть в нашу игру - интересная (и весьма простая) задача, которую кто-нибудь решит. Да и обычных корейских игроков недооценивать не стоит - самолетик быстрый, уровень генерируется непредсказуемо. И если до прохождений сквозь стены лететь и лететь, то куда более точная стрельба начнет очевидно подглючивать уже через 5 минут полета.
    Второй - телепортация игрока и всего мира - ставит мобильные устройства на колени, в некоторых случаях - где-то на полсекунды. Это очень заметно, а потому - недопустимо. Но это вполне приемлемый вариант для простеньких бесконечных игр для ПК.

    Проблема 2: Генерация уровня

    Есть несколько основных подходов к строительству endless runner"ов:
  • Использование готовых сегментов уровня, которые стыкуются случайным образом. Так сделано, например, в Subway Surfers. Это просто реализовать, но игрок к этому быстро привыкает и знает, к чему готовиться, что скучно.

  • Уровень - просто прямая, на которой случайным образом расставляются препятствия. Так сделано в Joypack Joyride и Temple Run. В нашем случае, это сильно ограничило бы количество маневров.

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

    Структура уровня

    Летим мы в пещере, она имеет пол и потолок - пару блоков, элементарных строительных единиц. Блоки объединяются в сегменты, которые бесшовно стыкуются друг с другом. Сегменты, как единое целое, вращаются вокруг самолета и двигаются по его вектору скорости, создавая иллюзию полета. Если сегмент выходит из поля зрения камеры - он очищается от блоков, пристыковывается к последнему сегменту уровня и заполняется новыми блоками, согласно указаниям генератора. Совокупность таких сегментов - и есть уровень.
    Опытные Unity-разработчики могли вполне оправданно поморщиться, прикинув объем работ и все возможные подводные камни. Но на словах все просто, а опыта разработки у меня не было…

    Основные Законы Физики в Unity

    За месяц разработки, экспериментов и чтения документации, я выделил три основных закона физики в Unity. Их можно нарушать, но плата за нарушение - производительность. Движок никак не будет предупреждать вас о допущенной ошибке, а без профайлера вы можете никогда о них и не узнать. Несоблюдение этих законов может замедлить вашу игру в десятки раз. Как я понял, нарушение любого закона приводит к тому, что физический движок помечает коллайдер-нарушитель как некорректный и пересоздает его на объекте, с последующим пересчетом физики:
    1. Коллайдеры не должны двигаться, вращаться, включатьсявыключаться и менять размер.
    Как только вы добавили коллайдер на объект - забудьте про какое-либо воздействие на него или объекты, в которых он содержится. Обычный коллайдер - исключительно статический объект. Дерево, например, может быть с одним коллайдером. Если дерево может упасть на игрока - дерево будет падать вместе с производительностью. Если это дерево растет из волшебного питательного облака, которое коллайдера не имеет, но может перемещаться - это будет сопровождаться падением производительности.
    2. Если объект движется или вращается - он должен быть твердым телом т.е. иметь компонент Rigidbody.
    Про это написано в документации, да. Которую не обязательно вдумчиво читать, чтобы начать делать игру, потому Unity очень прост и интуитивно понятен.
    Rigidbody меняют отношение физического движка к объекту. На него начинают воздействовать внешние силы, он может иметь линейную и угловую скорости, а самое главное - твердое тело может двигаться и вращаться средствами физического движка, не вызывая полный пересчет физики.
    Существует два типа твердых тел - обычные и кинематические. Обычные тела взаимодействуют друг с другом и обычными коллайдерами - одно тело не может пройти сквозь другое. Кинематические тела следуют упрощенным правилам симуляции - на них не воздействуют никакие внешние силы, гравитация - в том числе. Они свободно могут проходить через что угодно.
    Если объекты не жалко отдать под контроль физического движка - используйте обычные твердые тела. Например, если вам нужно красиво скатить камни со скалы. Если ваши скрипты или аниматоры управляют объектом напрямую - используйте кинематические тела, так вам не придется постоянно бороться с движком и случайными столкновениями объектов. Например, если у вас анимированный персонаж или управляемая ракета, взрывающаяся при контакте с чем-то.
    3. Если объект является твердым телом - двигаться и вращаться он должен через методы твердого тела.
    Забудьте про прямое обращение к Transform"у объекта сразу же после добавления к нему коллайдера. Отныне и навсегда, Transform - ваш враг и убийца производительности. Перед тем как написать transform.position =… или transform.eulerAngles = ..., произнесите фразу «я сейчас абсолютно четко понимаю, что делаю, меня устраивают те тормоза, которые будут вызваны этой строкой». Не забывайте про иерархические связи: если вы, вдруг, сдвинете объект, содержащий твердые тела - произойдет пересчет физики.
    Есть три уровня управления твердым телом:
    - Самый высокий и, следовательно, естественный, уровень - через силы. Это методы AddForce и AddTorque. Физический движок учтет массу тела и правильно посчитает результирующую скорость. Все взаимодействия тел происходят на этом уровне.
    - Средний уровень - изменение скоростей. Это свойства velocity и angularVelocity. На их основе вычисляются силы, влияющие на тела при их взаимодействии, а также, очевидно, их положения в следующий момент времени. Если у твердого тела очень маленькая скорость - оно «засыпает», для экономии ресурсов.
    - Самый низкий уровень - непосредственно координаты объекта и его ориентация в пространстве. Это методы MovePosition и MoveRotation. На следующей итерации вычисления физики (это важно, поскольку каждый последующий вызов метода в рамках одного кадра заменяет вызов предыдущего) они выполняют телепортацию объекта в новое положение, после которой он живет как раньше. В нашей игре используется именно этот уровень, и только он, потому что он предоставляет полный контроль над объектом.

    Что остается за бортом? Включениевыключение объекта и масштаб. Я не знаю, есть ли способ изменить размер объекта, не смущая движок. Вполне возможно, что нет. Выключение объекта проходит безболезненно, а включение… да, вызывает пересчет физики, в окрестностях включенного объекта. Поэтому старайтесь не включать одновременно слишком много объектов, растяните этот процесс во времени, чтобы пользователь не заметил.

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

    Есть еще одна особенность Unity, не относящаяся к физике, но достойная упоминания: динамическое создание и удаление объектов через методы Instantiate/Destroy - БЕЗУМНО медленный процесс. Я боюсь себе даже представить, что там происходит под капотом во время создания объекта. Если вам нужно создавать и удалять что-то динамически - используйте фабрики и заправляйте их нужными объектами во время загрузки игры. Instantiate должен вызываться в крайнем случае - если у фабрики вдруг закончились свободные объекты, а про Destroy забудьте навсегда - все созданное должно использоваться повторно.

    Применение законов на практике

    (в этом разделе находится ход рассуждений при создании игры и ее особенности)

    Уровень, очевидно, должен вращаться и двигаться.
    Облегчим себе жизнь навечно, разместив ось вращения уровня - самолетик - в начале координат. Теперь мы сможем вычислять расстояние от точки до него, вычисляя длину вектора координат точки. Мелочь, а приятно.
    Совместное движение объектов легко реализуется через иерархию объектов в Unity, потому что дети являются частью родителя. Например, описанная структура уровня логично реализуется следующим образом:
    - Ось вращения
    - - Уровень
    - - - Сегмент 1
    - - - - Блок 1 (Collider)
    - - - - ...
    - - - - Блок N
    - - - Сегмент 2 ...
    - - - Сегмент 3 ...
    - - - Сегмент 4 ...
    (Можно даже обойтись без объекта уровня)

    Скрипт на оси получает данные с гироскопа и выставляет ей соответствующий угол… И нарушает сразу множество правил, потому что вращение передастся по иерархии на коллайдеры, что сведет физический движок с ума. Придется делать ось твердым телом и вращать ее через соответствующий метод. Но что с движением уровня? Очевидно, что ось вращения и объект уровня перемещаться не будут, каждый сегмент нужно двигать персонально, иначе мы сталкиваемся с проблемой бесконечности. Значит, твердыми телами должны быть сегменты. Но у нас уже есть твердое тело выше в иерархии и твердое тело не может быть частью твердого тела. Логичная и элегантная иерархия не подходит, все придется делать руками - и вращение, и перемещение, без использования объекта для оси вращения. Будьте готовы к такому, если у вас уникальные геймплейные фичи.

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

    У нас есть враги, которые отталкивают самолет в стену, надеясь убить. Есть щит, который отталкивает самолет от стен, помогая выжить. Реализовано это тривиально - есть вектор смещения, который каждый кадр прибавляется к координатам каждого сегмента и сбрасывается после этого. Любой желающий пнуть самолетик, через специальный метод, может оставить вектор своего пинка, который прибавится к этому вектору смещения.

    В конечном итоге, настоящие координаты сегмента, каждый кадр, вычисляются центром управления движением уровня как-то так:
    Vector3 position = segment.CachedRigidbody.position; Vector3 deltaPos = Time.deltaTime * Vector3.left * settings.Speed; segment.truePosition = Quaternion.Euler(0, 0, deltaAngle) * (position + deltaPos + movementOffset);
    После всех вычислений и костылей, необходимых для работы точной стыковки сегментов при регенерации, segment.truePosition отправляется в метод MovePosition твердого тела сегмента.

    Выводы

    Насколько все это быстро работает? На старых флагманах - Nexus 5 и LG G2 - игра летает на 60 FPS, с еле заметной просадкой во время включения новых коллайдеров во время генерации сегмента (это неизбежно и никак не обходится) и выдвигания червяков из земли (можно нагородить какой-то ад, чтобы это обойти, но сейчас там осознанное нарушение третьего закона). 40 стабильных FPS выдает любое устройство с гироскопом, которое нам попадалось. Без знания и учета всех законов, производительность была, мягко сказать, неудовлетворительной и телефоны перегревались. Настолько, что я думал написать свой простенький специализированный движок для 2д-физики. К счастью, физика в Unity оказалось достаточно гибкой, чтобы все проблемы можно было обойти и создать уникальную игру, достаточно было лишь пары недель экспериментов.

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

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



    Поддержите проект — поделитесь ссылкой, спасибо!
    Читайте также
    Игра петушиные бои правила Игра петушиные бои правила Мод для майнкрафт 1.7 10 смотреть рецепты. Рецепты крафтинга предметов в Minecraft. Оружие в Minecraft Мод для майнкрафт 1.7 10 смотреть рецепты. Рецепты крафтинга предметов в Minecraft. Оружие в Minecraft Шиллинг и стерлинг - происхождение слов Шиллинг и стерлинг - происхождение слов