Изучая физику, вы обнаружите, что любое перемещение объектов осуществляется при помощи комбинации различных сил. Например, если вы пытаетесь толкнуть коробку, на нее действуют следующие силы:
- Сила толчка, которую вы прикладываете, чтобы толкнуть коробку;
- Сила гравитация , которая прижимает коробку к полу, соответственно мешает силе толчка;
- Сила трения о землю, которая направлена в противоположную сторону силе толчка и тоже мешает ей.
Если вы хотите смоделировать движение и столкновения, вам нужно использовать все математические уравнения для расчета точек соприкосновения и сил, действующих на объекты. Но это достаточно сложно и требует отличных знаний математики и физики. Но, поскольку законы физики одинаковы, можно обобщить этот код и использовать его во всех играх. Это как раз то, что делает Физическая Система Unity. В Unity есть встроенная физическая система, которая может вычислять для вас формулы движения и столкновения объектов.
Чтобы избежать выполнения дорогостоящих математических операций над каждым объектом в нашей игре, Unity выполняет только те вычисления для тех игровых объектов, к которым прикреплен компонент Rigidbody2D.
Начнем с добавления компонента Rigidbody2D для персонажа Ruby. Выберите в окне иерархии объект персонажа, в инспекторе нажмите кнопку «Add Component» и выберите в появившемся списке компонент «Rigidbody2D». Обратите внимание, что есть еще компонент «Rigidbody», который используется для 3D-игр, но он в этом случае выдаст ошибку.
Для удобства поиска нужного компонента в строке поиска можно ввести первые буквы названия этого компонента:
Попробуйте запустить игру и увидите, что персонаж Ruby просто упадет вниз! Это происходит, потому что Rigidbody2D применяет гравитацию к игровому объекту. А сила гравитации по умолчанию направлена всегда вниз, то есть вниз по оси Y. Но в данном случае игра имеет вид сверху,
а значит отрицательное направление оси Y не указывает на «низ» игрового мира. Персонаж должен свободно перемещаться по осям X и Y.
Поэтому нам нужно просто отключить силу гравитации для персонажа. В инспекторе Ruby найдите компонент Rigidbody2D и установите свойство Gravity Scale значение 0:
Запустите игру и проверьте, что Ruby перестал падать вниз.
Теперь, когда игровой объект персонажа известен Физической Системе (благодаря Rigidbody2D), нужно сообщить физической системе, какая его часть является «твердой» и будет участвовать во взаимодействиях с другими объектами. Например, волосы или развевающийся плащ персонажа можно исключить из твердой части, поскольку в реальном мире они вам явно не помешают пройти через препятствия.
Выделение "твердой" части объектов делается через коллайдеры. Коллайдеры — это простые формы, такие как квадраты или круги, которые физическая система принимает за приблизительную форму игрового объекта для выполнения расчетов столкновений.
Добавьте Ruby новый компонент «Box Collider 2D», нажав кнопку «Add Component» в инспекторе:
Вокруг Ruby на сцене появится зеленый контур. Это форма коллайдера и теперь это форма Ruby для физической системы. Но, обратите внимание, что контур чуть больше, чем сам персонаж, поскольку по умолчанию форма коллайдера задается по размерам спрайта. Давайте подкорректируем размеры коллайдера. Поскольку игра имеет вид сверху, то "твердой" частью персонажа будет только его нижняя половина, только она будет взаимодействовать с препятствиями. Это особенность формы коллайдеров именно для игр такого стиля.
Чтобы откорректировать форму коллайдера, в инспекторе в компоненте «Box Collider 2D» нажмите кнопку правее поля «Edit Collider». У коллайдера на сцене появятся маленькие зеленые квадратики. Нужно, удерживая левую кнопку мыши, перетащить их в нужное положение. Есть одна особенность - если мышкой не попасть по этому квадратику, то режим редактирования коллайдера завершится и квадратики пропадут. Тогда нужно опять нажать кнопку «Edit Collider» и продолжить редактирование:
Настройте коллайдер персонажа вот таким образом:
Аналогично добавьте коллайдер для префаба металлического куба. Это можно сделать двумя способами. Мы сделаем вторым способом, но первый стандартный мы уже разбирали - откройте префаб куба и именно в нем добавьте компонент «Box Collider 2D». Таким образом коллайдеры появятся у всех кубов на сцене:
Но давайте добавим коллайдер по другому. Для этого выберите любой куб на сцене и добавьте ему компонент «Box Collider 2D»:
Отредактируйте размер коллайдера следующим образом:
Вы внесли изменение только в один объект. Это называется переопределением, и позволяет вам вносить изменения в единственный экземпляр префаба на сцене. Переопределение всегда будет иметь приоритет над значением префаба! Таким образом сейчас на сцене только один куб с коллайдером, а остальные без коллайдера.
Однако в этом случае нам необходимо, чтобы эта модификация была внесена в сам префаб, поскольку коллайдер должен быть у всех кубов на сцене. В инспекторе у объектов, созданных из префабов, появляются дополнительные кнопки управления, при помощи которых можно перейти к самому префабу, а также внести в него изменения:
Кликните по раскрывающемуся меню «Overrides». Появится диалоговое окно со списком компонентов, которые были изменены по сравнению с оригинальным префабом. Нажмите «Apply All», чтобы изменения этого экземпляра были применены к префабу:
Проверьте, что все кубы на сцене теперь имеют компонент «Box Collider 2D».
Добавлять Rigidbody2D для металлического куба не нужно, так как его не нужно перемещать с помощью физики. Ему просто нужен коллайдер, чтобы игровые объекты, у которых есть компонент физического тела, взаимодействовали с ним.
Запустите игру и попробуйте провести Ruby рядом с кубом. К сожалению, правильного движения мы опять не получим - персонаж дрожит и вращается:
Во-первых, давайте уберем вращение. Для этого нужно запретить физической системе Unity вращать игровой объект персонажа. Это опять же особенность игр такого стиля, поскольку в реальной жизни, если мяч запустить вдоль стены, то мяч в итоге помимо движения вперед начнет немного закручиваться вокруг своей оси.
У компонента Rigidbody2D есть настройка для того, чтобы запретить вращение объекта. В инспекторе в компоненте Rigidbody2D разверните свойство Constrains и установите галочку Freeze Rotation Z:
Таким образом мы запретим вращение (Rotation) нашего персонажа.
Дрожание персонажа происходит из-за того, что физическая система для расчетов движения и столкновений использует упрощенную копию сцены, которая содержит только коллайдеры. Эта физическая сцена упрощает вычисления для физической системы и делает это таким образом:
- перемещает копию объекта в физической сцене всякий раз, когда этот объект с компонентом физического тела перемещается на основной сцене;
- применяет к копии объекта в физической сцене необходимые силы и вычисляет столкновения с другими коллайдерами;
- перемещает объект с компонентом физического тела на основной сцене в новую позицию, рассчитанную в физической сцене.
В данном случае это приводит к следующим событиям:
- Вы перемещаете персонажа во время обновления кадра;
- Физическая система перемещает свою копию в эту новую позицию;
- Физическая система определяет, что коллайдер персонажа попал внутрь другого коллайдера, в данном случае коробки, и перемещает его назад, так чтобы он вышел за пределы коробки;
- Физическая система синхронизирует положение персонажа на игровой сцене с этой новой позицией.
Вы постоянно двигаете Ruby внутрь коробки, а физическая система двигает ее обратно. Борьба между тем, что вы говорите в коде своего скрипта, и тем, что делает физическая система, вызывает дрожание.
Давайте обратимся к скрипту персонажа. Он выглядит сейчас следующим образом:
Сейчас проблема в том, что мы изменяем позицию игрового объекта через компонент Transform, который никак не связан с физической системой Unity! Поэтому наш код, минуя расчеты физической системы Unity, просто перемещает персонажа в новую позицию. При этом ему абсолютно неважно есть ли в этой новой позиции какое-то препятствие в виде другого коллайдера или нет.
Если мы добавляем игровому объекту компонент физического тела, то ВСЕГДА перемещать в коде мы ДОЛЖНЫ ТОЛЬКО само физическое тело! Иначе в игре постоянно будут ошибке в виде такого дрожания или перемещения персонажей внутрь стен. Если в коде для перемещения мы будем использовать не компонент Transform, а компонент Rigidbody2D, то физическая система автоматически произведет расчет при выполнении кода и сможет остановить движение до того, как Ruby попадет внутрь коробки. Таким образом дрожание пропадет.
Каким образом перемещать в коде физическое тело....
Поскольку все игровые объекты имеют компонент Transform по умолчанию, то в скрипте обратиться к этому компоненту мы можем сразу через переменную transform.position.
Но компонент Rigidbody2D мы добавляли игровому объекту вручную, поэтому в скрипте невозможно обратиться к этому компоненту напрямую через встроенную переменную. Для этого нам нужно определить свою переменную типа Rigidbody2D:
Rigidbody2D rb;
А затем связать ее с компонентом игрового объекта:
rb = GetComponent<Rigidbody2D>();
Определение переменной мы напишем в самом начале, чтобы использовать ее можно было в любом методе. А связь с компонентом сделаем в методе Start, который вызывается один раз в самом начале игры. Таким образом скрипт будет иметь вид:
Теперь позицию объекта мы будем брать не у компонента Transform, а у компонента Rigidbody:
Vector2 position = rb.position;
И изменять позицию тоже нужно не у компонента Transform, а у компонента Rigidbody с помощью метода MovePosition:
rb.MovePosition(position);
Скрипт будет иметь вид:
В дополнение к этому, система физики обновляется с другой скоростью, чем игра. За перемещения и столкновения отвечает физический движок, а за отрисовку графики - графический движок. Они работают независимо друг от друга.
Обновление графики вызывается каждый раз, когда графический движок вычисляет новое изображение. Но проблема в том, что это обновление вызывается с неопределенной скоростью. Это может быть 20 изображений в секунду на медленном компьютере или 3000 на очень быстром. При готовности графического движка обновить изображение вызывается метод Update, в комментарии к которому как раз и написано, что этот метод вызывается каждый КАДР.
Но физические вычисления не должны зависеть от обновления графики, они должны быть стабильными и обновляться через равные промежутки времени (например, каждые 16 мс). В Unity существует функция под названием FixedUpdate, которая создана именно для этого и вызывается через равные промежутки времени. Именно ее необходимо использовать, когда вы хотите напрямую воздействовать на физические компоненты или объекты, такие как Rigidbody. Обратите внимание, что метод Update будет вызываться намного чаще, чем метод FixedUpdate.
Но нельзя просто перенести код из метода Update в метод FixedUpdate, так как пользовательский ввод зависит от изображения, то есть от вызова метода Update, поэтому возникнет вероятность того, что пользовательский ввод будет пропущен.
Чтобы объединить пользовательский ввод и перемещение физического тела нужно добавить в класс скрипта две переменные дробного типа float:
float horizontal;
float vertical;
Сохранить горизонтальное и вертикальное пользовательское перемещение в методе Update:
horizontal = Input.GetAxis("Horizontal");
vertical = Input.GetAxis("Vertical");
А затем использовать эти переменные для перемещения Rigidbody2D в методе FixedUpdate.
Скрипт будет выглядеть так:
Сохраните скрипт и запустите игру. Перемещение должно быть теперь правильным с учетом физических сил и коллайдеров:
Выйдите из режима игры и сохраните изменения в проекте!
Самостоятельное задание - добавьте коллайдеры на деревья, здание и другие объекты, которые должны быть препятствием для Ruby.