Гравитация в Unity

Гравитация в Unity

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

В Unity для управления персонажем создан специальный упрощенный контроллер Character Controller, который представляет собой капсулу с заданной высотой и радиусом. Эта капсула позволяет персонажу перемещаться в пространстве, отслеживая столкновения с другими объектами, к которым присоединен какой-либо Collider. Благодаря этому наш персонаж не проваливается в стены и под пол. Но вся проблема в том, что о гравитации в данном случае приходится заботиться самому, т.к. Character Controller ничего о ней не знает. На то он и упрощенный компонент.

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

Имитация гравитации для Character Controller в Unity

Для компонента Character Controller нам придется имитировать воздействие гравитации в Unity. Для перемещения персонажа в Unity при помощи компонента Character Controller достаточно передать расстояние, на которое требуется передвинуть персонажа. Предположим, что под персонажем нет земли. Тогда гравитация начнет двигать его вниз. Иными словами, мы должны искусственно добавлять смещение вниз для любого персонажа, управляемого при помощи Character Controller в Unity. А так как Character Controller реализует обработку столкновений, то если у нас под ногами есть «твердая» поверхность, то под землю мы не провалимся! Вот как это выглядит на C# в Unity:


// Настройка скорости
public float Speed;

// Гравитация
public float Gravity

// Ссылка на Character Controller
private CharacterController characterController;

// Вектор перемещение
private Vector3 vMotion;

void Start()
{
    // Запоминаем ссылку на Character Controller
    characterController = GetComponent<CharacterController>();

    // Изначально вектор перемещения равен нулю
    vMotion = Vector3.zero;
}

void Update()
{
    // Рассчитываем смещение персонажа при нажатии на кнопки управления.
    // Вклад в перемещение будет давать скорость, которая задается в виде параметра.
    vMotion.x = Input.GetAxis("Horizontal") * Speed;
    vMotion.z = Input.GetAxis("Vertical") * Speed;

    // Тут имитируем гравитацию, добавляя перемещение по вертикальной оси вниз
    vMotion.y = -Gravity;

    // А теперь перемещаем персонажа на рассчитанный вектор
    characterController.Move(vMotion);
}


Из кода видно, что на каждом тике (кадре) мы добавляем персонажу вертикальное смещение под действием гравитации, равное -Gravity (потому как гравитация направлена вниз). Но в природе сила гравитации вызывает ускорение свободного падения, т.е. перемещение должно увеличиваться с каждым кадром. Для этого достаточно поменять одну строчку и записать:


    vMotion.y -= Gravity;


Тогда на каждом кадре сила гравитации будет расти.

Да, совсем забыл об одной важной мелочи — разная мощность компьютерного железа. У одного видеокарта выдает 60 FPS (кадров в секунду), у другого 100. Чтобы у каждого игра работала одинаково, в Unity введен поправочный коэффициент, на который нужно домножать все покадровые вычисления. Этот коэффициент содержится в Time.deltaTime. И перед применением метода Move контроллера персонажа, мы должны каждый компонент вектора перемещения домножить на этот самый deltaTime:


    vMotion.x *= Time.deltaTime;
    vMotion.y *= Time.deltaTime;
    vMotion.z *= Time.deltaTime;

    // А теперь перемещаем персонажа на рассчитанный вектор
    characterController.Move(vMotion);


Как сделать прыжки?

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

Для создания прыжков персонажа с Character Controller в Unity нам потребуется придать персонажу положительное смещение по вертикальной оси Y. Для этого добавим в скрипт на C# еще одну настройку — величину прыжка. Силой у меня назвать это значение язык не поворачивается, но это и не высота. Короче, что-то, что переместит нас вверх.


// "Величина" прыжка
public float JumpValue;

void Update()
{
    // ...

    // Перед применением метода Move добавим прыжок, если нажата клавиша "Jump"
    if( Input.GetButton("Jump") ){
        vMotion.y = JumpValue;
    }
    // Тут можно или совсем отключить гравитацию или не отключать,
    // но задать значение JumpValue изначально больше Gravity, чтобы
    // вертикальное смещение было положительным.

    /**
    else {
        vMotion.y -= Gravity
    }
    */

    // ...

    // А теперь перемещаем персонажа на рассчитанный вектор
    characterController.Move(vMotion);
}


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


        vMotion.y += JumpValue;


Тогда игрок взлетит в небо с ускорением на каждом кадре, как ракета!

Что же нужно для создания нормальных прыжков персонажа в Unity с использованием компонента Character Controller? А нужно для этого вводить понятие «импульса». На практике импульс реализуется в виде некоторой начальной величины, которая потом угасает со временем.

Я решил создать для себя относительно универсальный класс управления гравитацией. В нем гравитация в Unity будет задаваться произвольным вектором (не обязательно вдоль вертикальной оси Y и не обязательно вниз :). Вообще говоря, правильно, что создание гравитации для контроллера Character Controller в Unity отдали на откуп программистам. Это дает больше простора для фантазии. Благодаря этому появились такие интересные игрушки, как Inertia, Rochard, Cargo Commander (а с чего это я взял, что Cargo Commander сделан на Unity?) и многие другие.

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


    g = worldGravity - curImpulse - forceFields;


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

Для его реализации в универсальный класс управления гравитацией в Unity я добавил несколько методов:

        private Vector3 curImpulse;

	/**
	 * Добавление импульса, придающего ускорение
	 */
	public void AddImpulse(Vector3 impulseValue)
	{
		curImpulse += impulseValue;
	}
	
	/**
	 * Очистка импульса
	 */
	public void ClearImpulse()
	{
		// TODO: Вероятно, очищать нужно по каждой отельной оси
		
		curImpulse = Vector3.zero;
	}

	/**
	 * Уменьшаем значение импульса со временем
	 */
	private void UpdateImpulse()
	{
		// Постепенно гасим текущий импульс мировой гравитацией
		curImpulse -= worldGravity * Time.deltaTime;
		
		// До полного нуля по всем осям
		if(curImpulse.x < 0.0f) curImpulse.x = 0.0f;
		if(curImpulse.y < 0.0f) curImpulse.y = 0.0f;
		if(curImpulse.z < 0.0f) curImpulse.z = 0.0f;
	}



Метод UpdateImpulse() вызывается при каждом тике и постепенно гасит изначальный импульс силой мировой гравитации. Таким образом реализуется резкое ускорение прыжка и плавное затухание силы прыжка со временем с достижением вершины и плавным ускорением от вершины вниз назад к земле — все как в школьном курсе физики — движение тела по параболе. Только теперь до меня начинают доходить такие абстрактные понятия, как dV по dt (эти долбаные производные — ненавижу математику! А без нее никуда :)
  • 0
  • 17 декабря 2012, 14:30
  • dimanjy

Комментарии (12)

RSS свернуть / развернуть
+
0
Хорошее разъяснение, интересная тема, немного помогла, только один вопрос: curImpulse -= worldGravity * Time.deltaTime;

worldGravity — где то объявляется? Или она уже заранее прописана? Когда попытался реализовать код, у меня начало ругаться на неизвестную переменную.
avatar

Morigun

  • 29 марта 2013, 12:35
+
0
У меня в топике написано
В моем классе результирующая гравитация складывается из следующих компонентов:
* мировой гравитации, задаваемой в виде параметра
т.е. worldGravity — это внешнее настраиваемое через инспектор свойство. Объявляется, как публичное редактируемое свойство.
avatar

dimanjy

  • 29 марта 2013, 12:43
+
0
А тип объявляемого свойства float?
avatar

Morigun

  • 29 марта 2013, 12:48
+
0
Лучше, наверное, float. У меня float.
avatar

dimanjy

  • 29 марта 2013, 12:52
+
0
Но на Флоат ругается) Ладно в принципе часть понятна, но не до конца, как работать с импульсом.
avatar

Morigun

  • 29 марта 2013, 12:56
+
0
Или тоже Vector3? На float не работает и да, тогда заключительный вопрос, вызывать это все в методе update в условии jump?
avatar

Morigun

  • 29 марта 2013, 12:53
+
0
Простите! Конечно Vector3! Я совсем отдалился от темы :)

А вызывается эта формула в самом конце подсчета текущей гравитации. Не в Jump, а в самом конце метода Update(). В методе Update Вам надо учесть все возможные влияния на компоненты этой формулы. Сюда могут входить прыжки, реактивные ранцы, разорвавшиеся рядом снаряды, силовые поля и т.д. Каждое «влияние» изменяет один из компонентов конечной формулы, и только уже после пересчета всех этих компонентов производится окончательное суммирование.

В общем, все зависит от того, что вы в игре хотите реализовать :)
avatar

dimanjy

  • 29 марта 2013, 13:08
+
0
Хочу убрать эффект реактивного ранца и сделать простой прыжок Х) Но к сожалению он все так же летает… Как я понимаю, его надо к чему то привязать…
avatar

Morigun

  • 29 марта 2013, 13:15
+
0
Эффект реактивного ранца получается, когда при каждом вызове Update и проверке на нажатие прыжка к текущему вектору гравитации вновь и вновь прибавляется вектор тяги. Я в самом конце статьи рассказал про импульс — это как раз то, что нужно. Надо при каждом вызове Update проверять, не угас ли еще импульс последнего прыжка. Если он угас и равен нулю, то можно снова сделать кнопку прыжка активной и снова добавить импульс, а пока импульс действует, кнопка прыжка не реагирует на нажатие, а в это время импульс постепенно затухает в методе UpdateImpulse.
avatar

dimanjy

  • 01 апреля 2013, 11:57
+
0
Ладно, теперь я уже это не испробую Х) Дабы решил что изучить UDK будет продуктивнее и дешевле)
avatar

Morigun

  • 01 апреля 2013, 23:51
+
0
Ох, не советую!

Нет, изучить-то, конечно, неплохо бы, но в плане программирования Unity намного приятнее и понятнее, чем UDK, в котором очень много придется копаться в готовых UDK-шных классах, чтобы понять, как оно работает. А каждый из этих классов обычно имеет по 4 уровня наследования! В общем, сам Черт ногу сломит :)

Почитайте у меня в блоге по тэгу UDK. Я все прошлое лето с ним ковырялся и постов наделал даже больше, чем по Unity.
avatar

dimanjy

  • 02 апреля 2013, 10:05
+
0
Угу, обязательно посмотрю) хм, получается за пол месяца я ознакомился с UDK лучше чем с Unity 3D? Спасибо ОгаСоде Х) До скриптов я собственно пока только добрался, но я скажу, что мне UDK очень понравился, не столько скриптами, сколько остальным кол-вом возможностей! Кстати у вас случаем по камерам скриптов нету? А то сейчас вожусь с Камерой, хочу сделать вид от первого лица, но не просто такой, а чтобы он вращался на 50UU перед персонажем, чтобы как бы вокруг персонажа вращался по оси, но не вокруг самого себя, копаюсь уже второй день)
avatar

Morigun

  • 18 апреля 2013, 08:26

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