Жаропонижающие средства для детей назначаются педиатром. Но бывают ситуации неотложной помощи при лихорадке, когда ребенку нужно дать лекарство немедленно. Тогда родители берут на себя ответственность и применяют жаропонижающие препараты. Что разрешено давать детям грудного возраста? Чем можно сбить температуру у детей постарше? Какие лекарства самые безопасные?
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
- Применяется только в случае, если движения вашего АТТ кажутся вам странными или неуклюжими и т.п.:
- None: Интерполяция не применяется
- Interpolate: По-сравнению с трансформацией предыдущего кадра (фрейма ) , следующий будет сглажен.
- Extrapolate: Трансформация текущего кадра сглаживается, по-сравнению с оценочной (примерной) трансформацией следующего.
- Freeze Rotation - Запрещает любое вращение, как скриптовое, так и при столкновениях. Однако, вращение можно будет выполнять функцией transform.Rotate()
- Collision Detection
- Используется для того, чтобы быстро-движущиеся объекты не проходили сквозь другие объекты, не находя Collision
"ов (специальная "сетка" на объектах, которой они сталкиваются друг с другом и с игроком).
- Discrete: Значение по-умолчанию для того, чтобы наш объект "замечал" все другие объекты, с которыми может столкнуться.
- Continuous: Используйте Discrete Сollision с динамическими объектами столкновения (у которых имеется АТТ ), а Continuous Сollision для статических MeshCollider "ов (без АТТ ). Режим Continuous Dynamic использует Continuous Сollision для одного конкретного АТТ . Остальные АТТ будут использовать режим Discrete . (Это сильно скажется на нагрузке физического движок, просто оставьте Discrete , если не возникает проблем со столкновением быстрых объектов)
- 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 , я, пожалуй, закончу. Однако, есть пара подсказок, специально для тех, кто до сюда долистал:)
- Стандартный размер куба в Unity равен 1 метру , поэтому, проверять размер ваших моделей по нему очень удобно. Чтобы создать куб, выберите в меню GameObject - Create Other - Cube
- Относительный показатель Mass определяет, как два объекта будут взаимодействовать друг с другом.
- Mass не влияет на скорость падения с высоты, для этих целей используйте Drag .
- Чем выше значения Drag , тем больше весит предмет. стандартные значения варьируются от 0.001 (твердый кусок металла) до 10 (перышко).
- Если вам нужно изменять объект как с помощью скриптов, так и с помощью физики, добавьте к нему АТТ с параметром 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 - Подвеска пытается достигнуть указанной точки, используя различные силы.
- Spring:// Пытается достигнуть указанной точки (позиции). Чем выше параметр, тем быстрее она достигается.
- Damper:// Смягчает, замедляет скорость движения подвески. Чем выше значение, тем медленнее двигается амортизатор.
- 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* - Применяется только в случае, если движения вашего АТТ кажутся вам странными или неуклюжими и т.п.:
- None: Интерполяция не применяется
- Interpolate: По-сравнению с трансформацией предыдущего кадра (*фрейма*) , следующий будет сглажен.
- Extrapolate: Трансформация текущего кадра сглаживается, по-сравнению с оценочной (примерной) трансформацией следующего.
- Freeze Rotation - Запрещает любое вращение, как скриптовое, так и при столкновениях. Однако, вращение можно будет выполнять функцией //transform.Rotate()
- Collision Detection - Используется для того, чтобы быстро-движущиеся объекты не проходили сквозь другие объекты, не находя Collision "ов (специальная "сетка" на объектах, которой они сталкиваются друг с другом и с игроком).
- Discrete: Значение по-умолчанию для того, чтобы наш объект "замечал" все другие объекты, с которыми может столкнуться.
- Continuous: Используйте Discrete Сollision с динамическими объектами столкновения (у которых имеется *АТТ*), а Continuous Сollision для статических MeshCollider "ов (без *АТТ*). Режим Continuous Dynamic использует Continuous Сollision для одного конкретного *АТТ*. Остальные *АТТ* будут использовать режим _Discrete_. (Это сильно скажется на нагрузке физического движок, просто оставьте _Discrete_, если не возникает проблем со столкновением быстрых объектов)
- 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*, я, пожалуй, закончу. Однако, есть пара подсказок, специально для тех, кто до сюда долистал:)
- Стандартный размер куба в *Unity* равен 1 метру , поэтому, проверять размер ваших моделей по нему очень удобно. Чтобы создать куб, выберите в меню GameObject - Create Other - Cube
- Относительный показатель *Mass* определяет, как два объекта будут взаимодействовать друг с другом.
- *Mass* не влияет на скорость падения с высоты, для этих целей используйте *Drag*.
- Чем выше значения *Drag*, тем больше весит предмет. стандартные значения варьируются от 0.001 (твердый кусок металла) до 10 (перышко).
- Если вам нужно изменять объект как с помощью скриптов, так и с помощью физики, добавьте к нему *АТТ* с параметром *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
- Подвеска пытается достигнуть указанной точки, используя различные силы.
- Spring: Пытается достигнуть указанной точки (позиции). Чем выше параметр, тем быстрее она достигается.
- Damper: Смягчает, замедляет скорость движения подвески. Чем выше значение, тем медленнее двигается амортизатор.
- 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 знака после запятой. Проблема в том, что игра у нас бесконечная и, если мы достаточно долго будем лететь, начнутся различного рода безумные баги, вплоть до телепортации сквозь стены. Есть несколько подходов к решению этой проблемы:В нашем случае, единственный допустимый вариант - третий, который и был реализован. О реализации - чуть позже.
Первый - игнорирование - абсолютно недопустим. Создание робота, который сможет вечно играть в нашу игру - интересная (и весьма простая) задача, которую кто-нибудь решит. Да и обычных корейских игроков недооценивать не стоит - самолетик быстрый, уровень генерируется непредсказуемо. И если до прохождений сквозь стены лететь и лететь, то куда более точная стрельба начнет очевидно подглючивать уже через 5 минут полета.
Второй - телепортация игрока и всего мира - ставит мобильные устройства на колени, в некоторых случаях - где-то на полсекунды. Это очень заметно, а потому - недопустимо. Но это вполне приемлемый вариант для простеньких бесконечных игр для ПК.
Проблема 2: Генерация уровня
Есть несколько основных подходов к строительству endless runner"ов:
Конечно же, мы выбрали самый сложный вариант. В его сердце находится весьма сложная машина состояний, которая выполняет по ним случайные переходы. Но в рамках данной статьи интересен не механизм, а процесс генерации уровня и его организация, с учетом выбранной точки отсчета.
Структура уровня
Летим мы в пещере, она имеет пол и потолок - пару блоков, элементарных строительных единиц. Блоки объединяются в сегменты, которые бесшовно стыкуются друг с другом. Сегменты, как единое целое, вращаются вокруг самолета и двигаются по его вектору скорости, создавая иллюзию полета. Если сегмент выходит из поля зрения камеры - он очищается от блоков, пристыковывается к последнему сегменту уровня и заполняется новыми блоками, согласно указаниям генератора. Совокупность таких сегментов - и есть уровень.
Опытные 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, вы сможете быстро склонировать нашу игру, разрушив мечты, жизни и веру трех бедных студентов в человечество. Я надеюсь, эта статья сэкономит вам много времени в будущем и поможет найти не совсем очевидные нарушения законов производительной физики в своих проектах.
Читайте документацию и экспериментируйте, даже если пользуетесь простыми и интуитивно понятными инструментами.