
Devlog. Часть 4 - Контроллер транспортных средств
Тачки и прокачка!
В недавнем обновлении EdenSpark мы добавили возможность создавать транспортные средства. У любого транспортного средства есть колёса и двигатель, и сегодня я кратко расскажу, как они работают и как их настраивать.
VehicleController в действии
В этом «Дневнике разработки» я буду использовать отличные 3D-модели с kenney.nl. Понравятся — загляните к ним на сайт, они бесплатны!
Колёса — это обман
Знаете ли вы, что в большинстве физических движков колёса машин на самом деле не являются цилиндрами? Каждое колесо выглядит как цилиндр, поэтому кажется очевидным, что его формой должен быть цилиндр. Но если вы действительно прикрепите вращающиеся цилиндры к машине, результат скорее всего вас не обрадует.
Основная проблема физики в играх заключается в том, что иногда она бывает нестабильной. Объекты могут проходить друг сквозь друга, могут угрожающе дрожать, большое количество коллизий может снижать производительность системы, а если вы неосторожны, вся сцена может просто развалиться. Вы наверняка видели такое в играх: объект застревает в земле, а затем взлетает в небо. В общем, чем сложнее сцена, тем выше вероятность её развала. По этой причине, если есть возможность упростить объект — надо это сделать. Применяйте простые выпуклые формы, используйте как можно меньше соединений, не нагружайте физику сложными процессами, которые легко заменить с помощью простых импульсов в коде или хорошей анимации. Всегда нужно стремиться сохранить стабильность физических вычислений.
Если делать колёса из цилиндров, они выглядят довольно стабильно... пока машина стоит на месте. Как только машина приходит в движение, цилиндр начинает быстро вращаться и создаёт огромное количество бесполезных контактов с поверхностью, может случайно что-то задеть и машина начнёт подпрыгивать. Цилиндр сложно настраивать — для серьёзной симуляции трудно получить чистую, управляемую настройку трения и пробуксовки шин.
Именно поэтому физические движки строят колёса иначе. Машина просто парит над землёй и удерживает зазор с поверхностью с помощью лучения (raycast), направленного строго вниз из позиции каждого колеса. Сами колёса — это не более чем чисто визуальный объект, торчащий из кузова машины!
Такой подход кажется неинтуитивным на первый взгляд, но он значительно упрощает физическую симуляцию, делает её настройку более гибкой. Во-первых, вы можете легко настраивать множество параметров — трение, подвеску и т.д. Во-вторых, в сцене меньше объектов — только кузов машины. Нет колеса, в которое мог бы заклинить другой объект. Нефизическое колесо никогда не отвалится от машины и не провалится сквозь дорогу.
Сравнение вращающихся цилиндрических колёс и колёс на основе рейкаста
Если вы умеете делать рейкаст, вы даже можете попробовать симулировать упрощённое колесо самостоятельно! Для каждого колеса применяем импульсы, чтобы оно оставалось в парении над дорогой. Код будет выглядеть примерно так:
let ray = Ray(wheel.worldPosition, DOWN)
trace(RayCast(ray = ray, maxDistance = HOVER_RAY_LENGTH, $(hit) {
// gravity compensation
var impulse = -gravity * vehicleBody.mass * dt
// suspension spring
impulse += UP * \
((HOVER_RIDE_HEIGHT - hit.distance) * HOVER_SUSPENSION_STRENGTH -
get_velocity_at(vehicleBody, ray.origin).y * HOVER_SUSPENSION_DAMPING) * dt
if (driveForward) {
impulse += FORWARD * HOVER_DRIVE_FORCE * dt
}
apply_impulse(vehicleBody, impulse, ray.origin)
}))Вот так выглядит транспортное средство с колёсами на основе рейкаста
Всё это, конечно, должно происходить внутри on_fixed_update(), а не on_update(). Добавьте визуальные модели колёс к машине — и она наверное даже поедет.
Но если цель в более реалистичном поведении транспортного средства, вам потребуются симуляции двигателя и трансмиссии, которые предоставляет компонент VehicleController.
add_component(VehicleController)
Поведение колёс уже встроено в VehicleController. В самом простом случае нужна всего одна строка:
// The most basic vehicle example
// Adds vehicle support for any rigid body
// Adds four default wheels at positions (-1,1), (1,1), (-1,-1), (1,-1)
newVehicle.add_component(VehicleController())
Так получим максимально простое транспортное средство.

Но если хотите более гибко настраивать колёса, попробуйте такой подход:
// Advanced vehicle example
newVehicle.add_component(new VehicleController())
// Setup wheels
get_component(newVehicle) $(var vc : VehicleController?) {
vc.wheels[0].position = float3(-1, 0, 1)
vc.wheels[0].maxSteeringAngle = deg_to_rad(30.)
vc.wheels[1].position = float3(1, 0, 1)
vc.wheels[1].maxSteeringAngle = deg_to_rad(30.)
vc.wheels[2].position = float3(-1, 0, -1)
vc.wheels[3].position = float3(1, 0, -1)
}
// Set at every frame:
get_component(newVehicle) $(var vc : VehicleController?) {
vc.gasInput = 1.0 // Full throttle!
vc.brakeInput = 0.0 // No brakes!
vc.steeringInput = 0.0 // 1 for the right, -1 for the left
}Полностью настроенное транспортное средство с отладкой
Двигатель, трансмиссия, дифференциал
Я не стану делать вид, что полностью разбираюсь в том, как работают двигатель и дифференциалы. Jolt, физический движок, который мы используем, уже сделал это за нас. Он основан на статье "Car Physics for Games" Марко Монстра. Также есть документация по компоненту VehicleController. В конце концов, мы просто разработчики игр, наша задача не в том, чтобы понять, как это работает, а в том, чтобы понять, как это настраивать. А здесь явно есть что настраивать!
Чем быстрее работает двигатель, тем выше его RPM (обороты в минуту). Двигатель вращает шестерню внутри трансмиссии, которая вращает шестерню внутри дифференциала, которая в свою очередь вращает колёса. Каждая передача увеличивает крутящий момент, но снижает скорость вращения.

Чтобы вычислить, какой крутящий момент приходит на колесо, нужно умножить крутящий момент двигателя на передаточные числа трансмиссии и дифференциала. На низких передачах это число может быть большим (например, 7, 10 или даже 15), а на высоких передачах оно может быть 1 или даже меньше (т.е. двигатель вращается медленнее колёс).
Обычно передаточные числа берутся непосредственно из технических характеристик реальной машины. Но если вы хотите смоделировать машину, которой не существует, вам нужно понять, как подбирать эти числа.
Чтобы подобрать передаточные числа для вашего транспорта: выберите первую передачу так, чтобы машина могла тронуться с места — даже с грузом, даже на подъёме. Выберите последнюю передачу на основе желаемой максимальной скорости. Затем распределите остальные передачи, используя геометрическую прогрессию — сохраните примерно одинаковое соотношение между соседними передачами, чтобы при переключении на повышение RPM всегда падал на один и тот же процент.
Можно упростить настройку передаточных чисел, установив axles.differentialRatio в 1.0. Тогда вам останется настроить только gearRatio — и ваша машина поедет! Но если вы хотите что-то менее «аркадное» и более реалистичное, у VehicleController есть множество других полезных настроек.
Настраиваем!
Существует множество других настроек, и да, некоторые из них выглядят несколько пугающе — но они действительно помогут с точной настройкой вашего транспортного средства. Например, torqueCurve — как крутящий момент изменяется в зависимости от оборотов. Или момент инерции двигателя (inertia). Многие из этих параметров можно оставить по умолчанию или взять непосредственно из технических характеристик реального транспортного средства, которое вы пытаетесь смоделировать. Не нужно бояться большого списка параметров — они применяются только для точной настройки и большинству пользователей попросту не нужны. Не знаете, что делает какой-то параметр? Да просто измените его и посмотрите, что произойдёт! Вероятно, ничего хорошего... но если серьёзно, у каждого параметра есть документация, читайте.
Моя любимая настройка из этих — maxPitchRollAngle, она не даёт машине перевернуться. Установите 45 градусов — и машина никогда не завалится на бок. Оставьте значение по умолчанию 180 градусов — и она сможет переворачиваться и крутиться в воздухе, если вы запустите её с хорошего трамплина. Установите ноль — и она вообще не будет наклоняться, хотя это может ухудшить её поведение в поворотах.
Справа — 45 градусов. Машина слева легко падает на бок при резком повороте.
Кроме того, если колёса вашей машины большие или выступают за пределы кузова (как на болиде «Формулы-1»), вам может понадобиться возможность добавить колёсам дополнительный коллайдер с помощью флага useCylinderShapes.
Обратите внимание, что когда флаг useCylinderShapes становится истинным, колёса перестают пересекаться с дорожным покрытием.
Мотоцикл
Когда я прикручивал поддержку транспортных средств, я заметил, что Jolt также поддерживает мотоциклы. Моя первая мысль была: почему бы не добавить их тоже?
Оказалось, что это не так уж сложно. Концептуально мотоцикл отличается от четырёхколёсного транспортного средства одним: он наклоняется набок при повороте. А это реализуется всего одной настройкой — leanMaxAngle, максимальному углу наклона транспортного средства. Обратите внимание, что это работает только если вы включите логику мотоцикла, установив enableMotorcycleLean=true. Но всё равно круто, правда?
Мотоцикл — это двухколёсное транспортное средство, которое наклоняется в поворотах.
(Внимательный читатель может заметить, что Jolt также поддерживает гусеничные транспортные средства. Что ж, ни на что не намекаю, ничего не могу сказать наверняка, никаких прогнозов дать не могу... *подмигиваю, подмигиваю*! В общем, следите за обновлениями!)