Программист, Разработчик игр.
0,0
рейтинг
30 января 2015 в 09:03

Разработка → Руководство к Car Tutorial (Unity3d) часть 3 из 3 tutorial

Часть 1
Часть 2

Прежде чем начнем, хочу предложить скрипты переписанные на C#. т.к половина скриптов на JS я решил переписать их под C# (мне C# роднее). Скачать

Так же идет активный «разбор» проекта для начинающих «Project: Survival shooter», видеоуроки представлены в следующем Плэйлисте, дополнительные видеоуроки к Survival shooter. Лично выражаю благодарность этому каналу, за их не легкий труд.

p.s в конце этого руководства есть видео обзор в котором на примерах идет сравнение двух реализованных методов управления автомобилем.

Итак, приступим к изучению нашего руководства.

Часть 3: Под капотом


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

Сейчас речь пойдет о изучении работы скрипта Car.

• Дважды щелкните по скрипту Car.js, чтобы открыть его в редакторе кода.

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

Так мы предлагаем вам взглянуть на него, начиная от точек «входа» изучения скрипта и следуя дальше по руководству. В этом случае эти точки входа будут Start (), Update () и FixedUpdate () функции.

Каждая из этих «основных» функций, вызывает другие функции. Поэтому, когда мы начнем с функцией Start (), мы увидим, что функция впервые вызвала функцию SetupWheelColliders (). Найдите эту функцию в коде и изучите то, что она делает, и затем вернитесь к функции Start () и перейдите к следующей функции SetupCenterOfMass (). Изучая этот код вы поймете как функционирует автомобиль. В дальнейшем мы будем рассматривать все эти функции. Мы не будем объяснять каждую строку кода, но мы будем изучать по порядку все функции.

Какие вещи надо знать?


Работать в Unity легко во многих отношениях, благодаря таким вещам: встроенные компоненты, редактор, возможности D&D. Настройка автомобиля это половина работы — Unity заботится о импортировании моделей, компонентов столкновения (collision), рендеринга (rendering) и физики (physics) можно добавить на объект с помощью нажатия на кнопку «добавить компоненты» (add the Components).

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

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

Rigidbody
Wheel Colliders
• Расчеты и формулы, которые мы составляем, а так же их порядок.

Думайте об этом так:
  • При добавлении Rigidbody в нашу модель автомобиля, у нас есть способ контролировать его физические способности. Мы можем сделать это путем расчета силы, которые двигают ее вперед, которые замедляют его.
  • При добавлении колес коллайдера (Wheel Colliders) мы получаем контроль над тем, где автомобиль сталкивается с дорогой.

Start () — Установка


Это там где мы делаем инициализацию необходимую для автомобиля. Функция Start () выполняется только один раз, в начале скрипта, перед функций Update. Поэтому Start () часто используется для установки начальных значений необходимых компонентам в коде.

SetupWheelColliders()


У нас есть четыре колеса прикрепленные к нашей машине, и мы поставили их в FrontWheels и RearWheels массивы в инспекторе. В этой функции мы создаем настоящие коллайдеры (colliders), создавая возможность взаимодействия колес с поверхностью и автомобилем. Мы начинаем с функции SetupWheelFrictionCurve ().

SetupWheelFrictionCurve()


В SetupWheelFrictionCurve () мы просто создаем новый WheelFrictionCurve и присвоим ему значения, которое мы считаем целесообразным для нашего автомобиля. WheelFrictionCurve использует WheelColliders для описания свойств трения шин колес. Если вы хотите изучить процесс создания автомобиля в Unity поподробнее, построенный с помощью WheelColliders, тогда прочитайте документацию.

SetupWheel()


После настройки кривой (curve), мы вернулись к скрипту SetupWheelColliders () , теперь мы готовы создать коллайдеры (colliders) и объекты колес (Wheel). Это делается с помощью вызова функции SetupWheel () для каждого нашего колеса. Если вы посмотрите на эту функцию, вы увидите, что она имеет два параметра: Transform и boolean, и возвращает объект колеса (Wheel). Это нужно нам для того, чтобы менять расположение колёс, а так же чтобы указать колесо относится к передней части автомобиля или нет. Затем функция создает и возвращает объект колесо (Wheel), которое мы вкладываем в массив wheels содержащий все наши колеса для остальной части скрипта:

for (var t : Transform in frontWheels) {
   wheels[wheelCount] = SetupWheel(t, true);
   wheelCount++;
}

В данном цикле мы создаем новый игровой объект, и вызываем функцию SetupWheel (), в качестве первого аргумента передаем координаты объекта, в качестве второго параметра указываем TRUE либо FALSE, если TRUE — то созданное колесо будет передним, если FALSE то задним. Затем мы добавляем компонент WheelCollider к этому игровому объекту. Мы устанавливаем свойства WheelCollider из переменных подвески (suspension), которые мы обсуждали, когда настраивали автомобиль (диапазон подвески, пружина подвески и амортизатор) (suspension range, spring and damper).

Необходимые свойства: коллайдер (collider) мы уже создали, WheelFrictionCurve создается в функции WheelFrictionCurve(), графика для колеса (объект DiscBrake мы перетащили в инспекторе, когда мы создавали автомобиль) и графика для шин (который является дочерним по отношению к DiscBrake).

Мы установим радиус колеса автоматически, в зависимости от размера шины:

wheel.collider.radius = wheel.tireGraphic.renderer.bounds.size.y / 2;

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

Кроме того, мы делаем маленький прием для переднего колеса, создавая дополнительный игровой объект, который мы устанавливаем между кузовом автомобиля и колесом. Это Steer Column (фронтальный столб), который мы будем использовать позже для вращения колеса при повороте, В конечном итоге мы создаем колесо, которое возвращаем в массив колёс «Wheel» и когда мы обработали все колёса мы возвращаемся в функцию Start().

SetupCenterOfMass()


Это следующая функция, которую мы рассмотрим. Это очень маленькая функция, которая установит центр массы в Rigidbody к CenterOfMass, которую мы создали ранее. Если центр массы не был установлен, Rigidbody будет использовать центр массы по умолчанию, который будет рассчитывать Unity автоматически. Тогда мы преобразуем максимальную скорость введенную в инспекторе с помощью небольшой полезной функции:

topSpeed = Convert_Miles_Per_Hour_To_Meters_Per_Second(topSpeed);

Функция просто умножает переменную TopSpeed на число 0,44704, которая переводит его в метры в секунду. Это установка, так что мы можете ввести желаемую скорость в инспекторе в милях/час. При вычислении физики, мы работаем с м/с. У нас также есть небольшая функция, которая делает обратное вычисление, что очень полезно, если вы хотите вывести скорость автомобиля в милях/час.

SetupGears()


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

SetupSkidmarks()


Эта функция находит игровой объект Skidmark на сцене и сохраняет ссылку на него, используя ParticleEmitter создает дым. Код для skidmarks не рассматривается в этом руководстве, но это не должно остановливать вас от того, что бы открыть скрипт и исследовать его по своему усмотрению. В конце Start () мы присваиваем значения Х нашего массива dragMultiplier в переменную:

initialDragMultiplierX = dragMultiplier.x;

Он сохраняется, потому что мы изменяем X переменную dragMultiplier, когда мы используем ручной тормоз (handbrake), а затем нужно вернуться к исходному значению снова, когда мы не используем ручной тормоз.

Для установки начальных значений используется функция Start(). Для регулярных изменений этих значений используется функция Update().

Update()


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

GetInput()


Первое что мы делаем в каждом кадре это разные действия с клавиатуры с помощью вызова функции GetInput (). Первые две линии считываются с вертикальных (vertical) и горизонтальных (horizontal) осей и сохраняются в переменных throttle и steer :

throttle = Input.GetAxis(“Vertical”);
steer = Input.GetAxis(“Horizontal”);

Вертикальные (vertical) и горизонтальные (horizontal) оси могут быть установлены в Unity Input Manager (Edit -> Project Settings -> Input). По умолчанию вертикальная (vertical) ось устанавливается на клавиши «W», «стрелка вверх» для движения вперед и клавиши «S», «стрелка вниз» для движения назад и значение, которое мы используем здесь сохраниться в переменной throttle. горизонтальная (horizontal) ось устанавливается как клавиши «A» и «стрелкой влево» для поворота влево, а так же клавишами «D» и «стрелкой вправо» для поворота вправо.

CheckHandbrake()


После изучения GetInput для управления автомобилем, мы вызываем функцию CheckHandbrake (). Это специфичная функция, которая проверяет, нажата ли клавиша Space (Пробел) или нет, и применяет определенную логику соответственно:

Когда мы сначала нажали Space (пробел), мы устанавливаем переменную handbrake в true, это запускает механизм ручного тормоза и изменяет значение dragMultiplier.x (создает вибрацию торможение на дороге, напоминающее настоящий ручной тормоз).

Когда Space (пробел) не нажат, код будет выполнен другой, до тех пор пока клавиша не будет нажата. Это опять-таки означает, что код ручного тормоза не будет работать только в том случае, когда пользователь впервые отпускает Space (пробел), потому что мы устанавливаем переменную (handbrake) ручного тормоза в false внутри блока. Функция StopHandbraking () будет активирована:

StartCoroutine(StopHandbraking(Mathf.Min(5, Time.time - handbrakeTime)));


StopHandbraking()


StopHandbraking () принимает входную переменную определяющее число секунд, которые придется потратить на возвращение dragMultiplier.x обратно в это исходное состояние. Это значение должно быть не менее 5 для таймера ручного тормоза (handbraking), который мы только что запустили. Затем функция отсчитывает заданное количество секунд, после чего устанавливает значение переменной dragMultiplier.x по умолчанию, это создает движение автомобиля снова нормальным.

Check_If_Car_Is_Flipped()


Вернемся в функцию Update() мы теперь рассмотрим функцию Check_If_Car_Is_Flipped() для проверки перевернулась ли машина. Внутри этой функции мы проверим перевернутую машину. Это совершенно справедливо для автомобиля, который будет перевернут или повернулся например на экстремальных поворотах, если мы попали в аварию или делаем какие то трюки, но мы хотим исключить возможность переворота автомобиля. Поэтому мы проверяем, если машина перевернулась под определенным углом, под которым автомобиль не на ходу больше, и если это так, мы прибавляем с момента последнего кадра к переменной resetTimer. Если это значение в конечном счете сводится к превышению значения, которое мы установили для resetTime (5 секунд по умолчанию), мы вызываем функцию FlipCar(). Если автомобиль под углом с которым можно ехать, мы устанавливаем таймер обратно к нулю.

FlipCar()


В FlipCar() мы получаем машину обратно на колесах и устанавливаем его скорость на 0, так что мы можем начать движение снова с этого места.

UpdateWheelGraphics()


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

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

w.wheelGraphic.localPosition = wheel.transform.up * (wheelRadius + wheel.transform.InverseTransformPoint(wh.point).y);

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

w.wheelVelo = rigidbody.GetPointVelocity(wh.point);
w.groundSpeed = w.wheelGraphic.InverseTransformDirection(w.wheelVelo);

Если колесо в настоящее время не касается земли, то мы устанавливаем положение колеса на основе его координат, диапазона подвески и самой подвески «колес родителей».

UpdateGear()


Последняя функция вызывающаяся в функции Update () является UpdateGear (), которая вычисляет текущую «передачу» автомобиля, на основе установленных значений в SetupGears() и текущей скорости. В последнем разделе руководства мы должны рассмотреть остальную часть основного цикла, а именно — физические расчеты, которые происходят внутри функции FixedUpdate ().

FixedUpdate() — Вся наша физика


Когда имеешь дело с физикой, важно, чтобы расчеты и действия были под строгим контролем, чтобы результат оказался хорошим. FixedUpdate() создан для этой цели. Это гарантирует исполнение кода с фиксированным временным интервалом. Частота вызова функции FixedUpdate (): «Она может быть вызвана несколько раз в кадре, если частота кадров низкая; или может быть вызвана через несколько кадров, если частота кадров высокая. Все расчеты и обновления физики вызываются непосредственно перед FixedUpdate () ». У нас есть ряд функций, выполняемых внутри FixedUpdate (), и все они касаются вычисления и применения силы к автомобилю.

UpdateDrag()


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

После relativeDrag (Относительного сопротивления) и масштабируемого dragMultiplier (сопротивление множителей) которые мы уже рассмотрели, приняли во внимание, что автомобиля в профиль по виду очень отличается от фронтальных, боковых и сверху видов.

Если мы к ручному тормозу применяем дополнительные силы к боковым и фронтальным значением сопротивления, на основе того, насколько быстро машина едет. Обратите внимание, как мы используем скалярное произведение между скоростью и направлением спереди автомобиля для расчета дополнительного сопротивления. Это уравнение приводит к дополнительному сопротивлению спереди автомобиля, когда автомобиль едет вперед не сворачивая (торможение более быстрое) и более медленное торможение на заносах и поворотах. Для значения X сопротивления, то же самое: для автомобиля во время скольжения боком. После мы постепенно увеличиваем значение сопротивления X, чтобы замедлить автомобиль вместо того, чтобы позволить ему скользить по дороге всегда.

Если мы не будем использовать ручной тормоз, мы только обновим значение X:

drag.x *= topSpeed / relativeVelocity.magnitude;

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

В конце функции мы применяем силы к RigidBody:

rigidbody.AddForce(transform.TransformDirection(drag) * rigidbody.mass * Time.deltaTime);

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

UpdateFriction()


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

w.collider.sidewaysFriction = wfc;
w.collider.forwardFriction = wfc;

Мы выполняем одно действие — изменяем значение трения автомобиля на основании текущей скорости движения автомобиля и направления движения автомобиля (нормальной езды — ForwardFriction, и на основе заноса автомобиля — «езды боком» sidewaysfriction).

CalculateEnginePower()


Расчет мощности двигателя, который мы позже используем для применения силы к RigidBody относительно прост, но имеет несколько «причуд».

• Если мы не дросселируем, мы просто уменьшим мощность двигателя, тем самым замедлив автомобиль.
• Если мы дросселируем в том же направлении, в котором автомобиль сейчас движется (мы проверяем это с помощью функции HaveSameSign ()) и вычисляем значение, которое добавим в силу двигателя. То, что мы видим может показаться немного странным: мы рассчитываем норму силы, которая является произведением текущей мощности двигателя разделенную на максимальную мощность двигателя (выдавая результат между 0 и 1), а затем умножаем в 2 раза. Результат будет между 0 (когда мы стоим на месте или очень медленно едим) и 2 (когда мы едим на максимальной мощности). Затем мы вызываем вспомогательную функцию EvaluateNormPower (). Эта функция смотрит на переданное значение и возвращает число между 1 и 0, если норма мощности составляет от 0 до 1. Если норма мощности будет составлять от 1 до 2 функция вернет значение между 0 и 1. Удивлены? Число используемое в формуле, которое добавляет силу к двигателю:

currentEnginePower += Time.deltaTime * 200 * EvaluateNormPower(normPower);

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

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

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

CalculateState()


Это небольшая функция, которую мы рассмотрим сейчас, потому что мы должны знать, какие колеса автомобиля находятся на земле. Она делает эту проверку очень просто:
• Мы устанавливаем переменные canDrive и canSteer в False по умолчанию.
• Затем мы проверяем каждое колесо в массиве Wheels, что бы проверить какое колесо касается земли, а какое нет:
if(w.collider.isGrounded)

Если колесо находится на земле, мы проверяем какой это тип колеса. Если это фронтальное колесо, то canDrive устанавливается в True. Если это заднее колесо, то steerWheel устанавливается True. Эта функция сделала свое дело, если хотя бы одно колесо (заднее колесо) касается земли, то мы можем управлять своим автомобилем. Если хотя бы одно колесо (переднее колесо) касается земли, мы можем поворачивать.

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

ApplyThrottle()


Эта функция сработает, если функция CalculateState () установит переменную canDrive в True (это означает, что по крайней мере одно приводное колесо на дороге). Если мы можем управлять, мы начнем со сравнения переменной throttle (которая является нажатием клавиши с клавиатуры) и переменной relativeVelocity.z в которой значение скорости автомобиля. Если эти значения имеют одинаковый знак — определяемый в функции HaveSameSign() — это означает, что мы дросселируем в том же направлении в котором автомобиль едет и в этом случае мы добавляем дроссельную силу к RigidBody:

throttleForce = Mathf.Sign(throttle) * currentEnginePower * rigidbody.mass;

Если значение throttle отрицательное (пользователь нажимает кнопку тормоза), знак будет -1 и мы рассчитаем отрицательную throttleForce (Силу дросселя), которую мы добавляем к машине, мы также знаем, что дроссельная сила имеет отрицательную скорость. Поэтому мы будем дросселировать назад быстрее. Противоположный эффект, когда пользователь нажимает кнопку «газа». Тогда мы добавляем «положительный» throttleForce к автомобилю, который уже едет вперед.

Если relativeVelocity.z и throttle имеют разные знаки, то это должно означать, что мы добавим дроссельную силу в противоположном направлении от направления в котором сейчас едет автомобиль. Иначе говоря автомобиль тормозит или замедляется. Мы делаем это, установив переменную Brakeforce исходя из массы автомобиля и силе первой передаче двигателя:

brakeForce = Mathf.Sign(throttle) * engineForceValues[0] * rigidbody.mass;

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

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

rigidbody.AddForce(transform.forward * Time.deltaTime * (throttleForce + brakeForce));

ApplySteering()


Если вы не создаете игру типа дрэг-рейсинг, где вы пытаетесь установить мировой рекорд скорости на прямом участке, то управление с помощью руля также важно, как дросселирование, так что давайте изучим эту функцию. Мы не применяем какую-либо дроссельную силу покуда приводные колеса (drive wheels) не касались земли, и то же самое для этой функции, где мы не можем управлять автомобилем покуда управляемые колеса (steer wheels) не касаются земли.

В начале функции вычисляем значение переменной с именем turnRadius, на основе входных данных. Уравнение делает увеличение значения turnRadius, когда вы поворачиваете в любую из сторон. Мы рассчитываем значение minMaxTurn, вызвав функцию EvaluateSpeedToTurn ().

EvaluateSpeedToTurn()


Эта функция возвращает значение поворота (turn) в зависимости от того, как быстро машина едет, подробнее описано это во второй главе нашего руководства. Если машина едет быстро, это значение будет ближе к minimumTurn, что делает труднее поворот автомобиля вовремя быстрого движения. Вернемся в функцию ApplySteering (), там turnSpeed относится непосредственно к вычислению turnRadius в автомобиле. Чем больше радиус, тем меньше угол поворота, потому что окружность поворота больше.

Мы поворачиваем автомобиль по формуле:

transform.RotateAround( transform.position + transform.right * turnRadius * steer, transform.up, turnSpeed * Mathf.Rad2Deg * Time.deltaTime * steer );

Функция RotateAround () поворачивает вокруг преобразованной оси в заданной точке и принимает угол, который представляет собой сумму поворотов (turns).

• Точка поворота находится точно посередине автомобиля, когда мы не поворачиваем автомобиль вообще. Когда мы начинаем нажимать клавишу управления, точка удаляется из автомобиля, в сторону в которую мы поворачиваем. Помните, что переменная Steer была извлечена из горизонтальной оси, которая является отрицательной, когда мы повернем налево и положительной, когда мы повернем направо. TurnRadius будет расти всё больше и больше, если мы будем поворачивать в одну сторону. Когда TurnRadius умножаем на вектор transform.right получим точку, которая базируется в центре автомобиля и перемещенная в сторону в которую поворачиваем, как показано на следующих изображениях:

image

• Оси вращаются вокруг оси Y (up), это означает, что мы поворачиваем автомобиль в плоскости X — мы повернем автомобиль к линии, показанной на изображении.
• Угол поворота рассчитывается на основе turnSpeed умноженного на Steer, чтобы повернуть влево / вправо.

Теперь рассмотрим изнутри:

if(initialDragMultiplierX > dragMultiplier.x)

Как мы помним в функции ручного тормоза (handbraking) мы проверяли касается ли хотя бы одно передние колесо земли или нет.

Если автомобиль не стоит на месте, то мы проверяем автомобиль в настоящее время поворачивает или нет, глядя на значения angularVelocity.y в RigidBody. Если это значение равно нулю или очень маленькое, мы не поворачиваем или поворачиваем на очень мало градусов, а затем применяем случайное значение в ту сторону в которую совершается поворот. Это будет имитировать шаткость автомобиля, во время использования ручного тормоза.

Если значение не очень низкое, то вместо того, чтобы применять действительное значение angularVelocity.y в сторону поворота. Во время поворота влево значение будет -1, при повороте на право значение будет 1.

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

frontWheels[0].localPosition + frontWheels[1].localPosition) * 0.5

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

Теперь круг замкнулся и функции Update () и LateUpdate () будут работать вместе. Надеюсь, вам понравилось изучать создание автомобиля, играя с его переменными и заглянув внутрь кода с нами.

Реальная физическая модель


В панели проекта (Project view) вы найдете папку с именем ~ AlternatePhysicsModel. Эта папка содержит некоторые скрипты и примеры Prefabs для имитации реалистичной физики автомобилей в Unity. Симуляция представленная здесь не использует колесных коллайдеров (wheel colliders) Unity, но вместо этого реализуем собственные коллайдеры колёс (wheel collider) в скрипте основанном на Physics.Raycast. Затем использует скрипт Pacejka «магической формулы/Magic Formula» — на основе модели шин для расчета колесной силы, которые применим к автомобилю это даст лучшие результаты.

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

Входящие в комплект Prefabs


Папка содержит пять автомобилей в Prefabs и тормозные следы (skidmarks) в Prefabs. Чтобы попробовать их, просто перетащите один из автомобилей и тормозные следы (Skidmarks Prefab) на сцену (разумеется, Prefabs skidmarks вероятно уже в сцене). Теперь вы должны быть в состоянии ездить по сцене с автомобилем с помощью клавиш со стрелками.

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

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

Входящие в комплект скрипты


AerodynamicResistance.cs: Этот скрипт, должен быть добавлен к каждому автомобилю, чтобы вычислить аэродинамическое трение автомобиля.

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

CarController.cs: скрипт для обработки управления автомобилем. Этот скрипт необходим для каждого автомобиля. Вы можете редактировать этот скрипт, если вы хотите изменить управление автомобилем или реализовать ИИ. Также устанавливает некоторые характеристики корпуса автомобилей, таких как центр тяжести и инерции.

Drivetrain.cs: двигатель и трансмиссия автомобиля. Этот скрипт содержит коробку передач и двигатель. Один из скриптов необходимый в автомобиле.

Skidmarks.cs: Глобальный менеджер тормозных следов (Skidmarks). Добавьте Skidmark Prefab на сцену, который использует этот класс, чтобы визуализировать и управлять тормозными следами (Skidmarks) на всех автомобилях.

SoundController.cs: Простой класс, чтобы проигрывать звуки автомобиля и другие звуки. Этот скрипт должен быть добавлен на автомобиль.

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

Wheel.cs: Этот скрипт имитирует модели шин и подвески колес, и действует в качестве замены в Unity встроенного компонента Wheel Collider.

Wing.cs: Добавьте один скрипт или более одного, если вы хотите имитировать прижимную силу аэродинамики для вашего автомобиля.

Анализ и заключение от переводчика


Изучил 2 метода управления автомобилем, первый метод оказался легче в управлении и в понятии скриптов, но в нем есть свои недостатки:
1) ездит в гору, не спускается с горы под углом 70 градусов даже с гравитацией.
2) при езде вперед и если резко дать задний ход и отпустить клавишу, то машина резко поедет вперед.
3) машина «летает» на не ровных дорога и приземляется не реалистично.
4) даже если в Rigidbody вручную включить Drag и Angular Drag (по сути сопротивление вовремя взаимодействия физических моделей) все равно будет ехать в крутую гору)).
Второй метод реально более реалистичен и лишен этих недостатков. (~ AlternatePhysicsModel).
Вот собственно видео: Lambordgine — метод 2, Стандартная машина в примере метод 1.



Вывод: для тех, кто создает «Футуристические гонки» изучаем метод 1. Для тех, кто создает гонки более реалистичные — метод 2.
Павел @patch1
карма
9,0
рейтинг 0,0
Программист, Разработчик игр.
Реклама помогает поддерживать и развивать наши сервисы

Подробнее
Реклама

Самое читаемое Разработка

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

  • 0
    А можно полный обновленный проект добавить?
    • –2
      Обновленный проект представлен в части 1ой этого руководства, это перевод оригинального руководства скрипты оригинальные на JS, я перевел их на C# для тех кто не знает JS. вы можете скачать C# скрипты и добавить их в свой проект, скрипты нечем не отличаются от JS визуально — все те же настройки, которые оглашались в 1ой и во 2ой части руководства. Если вам интересен готовый проект, запустите сцену CompleteScene.
    • 0
      Обновил 1ую часть руководства, вместе с архивом для игры. в нём включены скрипты C#, но настраивать их надо самому.

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