Как недавно оказалось, что столь интересной задачи как создание транспорта, я в личном блоге не отразил.

Автомобили тема интересная и надо будет её сюда перенести. Чем и займусь…
Актуальность — Сентябрь 2023
Поступила мне задачка — сделать гонки на картингах. Но есть проблема, последний раз я занимался созданием авто года два-три назад. Много воды утекло и… теперь авто создаются совсем по другому.
Посмотрев разные гайды и видосы, пришёл к не утешительному выводу — они морально устарели и не соответствуют текущим реалиям. Единственное, что почти сразу удалось адаптировать, с небольшими изменениями, это данное видео 5. Человек хорошо рассказывает, разбирает большинство возникающих проблем. Т.е. разжёвывает что и почему делается. На основе данного видео и будет этот гайд.
Сперва, сделаем почти так же, возьмём из тулбокса модель авто у которой цельный корпус, но можно оторвать колёса. Или просто найдите мэш авто без колёс. На худой конец — обычный парт нужного размера тоже сойдёт.

Как выяснилось, я взял проблемную модель. на скриншоте можно видеть что стало, после того как я его разместил в позиции 0,0,0 с ориентацией 0,0,0. Так получилось, потому что в 3Д редакторе его забыли сориентировать перед экспортом.
С другой стороны, так даже лучше. Вероятно будет больше проблем, которые можно будет озвучить.
Второе действо — надо удалить из него камеру и все скрипты и то что к ним привязано. В данном уроке будет рассматриваться только базовое управление авто, потому удалим так же и всё что касается озвучки и управления.

Немного посмотрев на оставшееся, я пришёл к выводу, что нам ещё можно немного поколдовать над моделью, чтобы осталось минимум деталей.
В итоге у нас остались модели корпуса и колёс. Заодно и проверю на модельках вместо партов.

Задаём PrimaryPart для модели авто на MESH корпуса и ориентируем авто по оси Z. Собственно ориентация не особо важна, это просто чтобы было легче копировать.

Переименую колёса чтобы не было пробелов и было легче писать и можно сказать, что наша моделька подготовлена к оживлению.
Но запустив игру на исполнение, обнаружился нежданчик. И даже два.

- У машины отключены столкновения
- Размер авто не рассчитан на размер RTHRO персонажа.
Так что растягиваем модель и включаем у всех мешей CanCollide:

Берём наши колёса и выдвигаем их в стороны. Сейчас мы будем их крепить к модели.

Нам нужно создать аттачи на корпусе. Но как выяснилось это не возможно на модели. Так что переименовываем наш PrimaryPart и задаём в нём четыре Attachment и их так же переименовываем под наши колёса.

Тут нам и пригодится то, что у нас машина отцентрирована по нулям. Располагаем аттачи чуть выше места крепления колёс.
FL — Front Left — перед лево
BR — Back Right — зад право
А чтобы их было видно, то на вкладке Model нужно включить Constraint Details

На этом шаге выяснилось что у моделей колёс уже установлен PrimaryPart. Но вот только в одном колесе меши имели собственное название. Так что изначально переименовываем их в одинаковый набор. И только потом в указанный PrimaryPart (у меня это Tire) добавляем ещё аттачи. Но вот их то как раз переименовывать не будем. И менять их положение тоже не будем, пусть будут в центре модели. Если понадобится подвинуть колёса, мы это сделает предыдущими аттачами.

Ну что же. Пришла пора их соединить. Для этого добавляем CylindricalConstraint в пару к новым аттачам. И в нём указываем первым аттачем тот что в шасси, а вторым тот что тут же находится.

Запускаем и… ничего не происходит. Потому что модель закреплена. Отключаем все Ancored. Можно просто выделить модель и нажать Anchor.

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

То же делаем и с остальными осыпающимися частями. Предварительно можно поднять модель повыше и заякорить шасси.
Проделав всё это и запустив игру видим интересную картинку:

Из неё следует вывод — надо сделать чтобы колёса и шасси не сталкивались друг с другом. Потому что это ещё и будет мешать рулить!
Опять же на вкладке Model открываем Collision Group, переводим в Table View и добавляем 2 группы — Car и Wheel. После отключаем их столкновение между собой. Ну и добавляем Body в Car, а Wheel в Wheel. Целиком моделями — выделяем нужную модель и жмём плюсик у названия группы.

После запуска видим, что колёса болтаются, и даже куда-то двигаются… Только не туда, куда нам надо.

Для начала сменим направление движения, повернув главные аттачи на 90 градусов.

Теперь ограничим разбег, установив границы от -2 до -1. Это будет играль роль… амортизаторов.

Теперь у нас колёса не падают и никуда не улетают. Но… крутятся не по той оси.

Поигравшись с ориентацией аттачей, таки получил нужный результат. Колёса стоят как надо и крутятся тоже куда надо.
AttachmentFL = 0,90,90
Attachment = 0,0,90
Так что пока что проставил эти значения во все нужные аттачи. Теперь можно убрать якорь с шасси и запустив игру убедиться что всё выглядит как надо. И даже катится, если суметь подтолкнуть.

Можно бы переходить к скриптам. И начнём с банального — нам надо сесть за руль! Поэтому добавим сиденью промпт, который за это и будет отвечать.

local seat = script.Parent -- указатель на сиденье водителя -- сигнал что кто-то сел или встал с сиденья seat:GetPropertyChangedSignal("Occupant"):Connect(function() if seat.Occupant == nil then seat.ProximityPrompt.Enabled = true else seat.ProximityPrompt.Enabled = false end end) -- активация промпта seat.ProximityPrompt.Triggered:Connect(function(player) seat:Sit(player.Character.Humanoid) end)
И настроем сам промпт, чтобы он соответствовал нашим запросам:

Запустив мы ничего не увидим, потому как забыли прицепить сиденья к корпусу. Делаем для них Weld и…вот мы уже и в кабине!


Добавим Attachment в кресло и вытащим его за пределы авто. Это будет место, куда мы будем выпрыгивать из машины.
У кресла водителя нас будут интересовать лишь несколько значений.
MaxSpeed — максимальная скорость.
Torque — крутящий момент
TurnSpeed — угловая скорость
Для примера выставляем указанные параметры:

Steer и SteerFloat — это усилие для поворота руля
Throtle и ThrotleFloat — это усиление для изменения скорости (газ/тормоз)
Добавляем в модель скрипт.
-- для ровного поворота руля local TS = game:GetService("TweenService") local tweenInfo = TweenInfo.new(0.4) -- рулевые направляющие local BR = script.Parent.Body.Chassee.AttachmentBR local BL = script.Parent.Body.Chassee.AttachmentBL local FR = script.Parent.Body.Chassee.AttachmentFR local FL = script.Parent.Body.Chassee.AttachmentFL -- колёса local WBR = script.Parent.Wheels.BR.Tire.CylindricalConstraint local WFR = script.Parent.Wheels.FR.Tire.CylindricalConstraint local WBL = script.Parent.Wheels.BL.Tire.CylindricalConstraint local WFL = script.Parent.Wheels.FL.Tire.CylindricalConstraint -- сиденье local seat = script.Parent.DriveSeat -- кто сидит local ocupant = nil -- выход local exit = nil local maxAngular = seat.MaxSpeed / (WBR.Parent.Size.Y / 2) connect = seat.Changed:Connect(function(param) -- print(seat.Occupant) if param == "Occupant" then if seat.Occupant ~= nil then ocupant = seat.Occupant exit = seat:FindFirstChildOfClass("Attachment") if ocupant then -- передача преимущества управления сетью игроку local player = game.Players:GetPlayerFromCharacter(ocupant.Parent) if player then seat:SetNetworkOwner(player) end else seat:SetNetworkOwnershipAuto() end end if seat.Occupant == nil and ocupant ~= nil and ocupant.Parent ~= nil then if exit == nil then return end seat:SetNetworkOwnershipAuto() -- отмена приоритета local character = ocupant.Parent -- task.wait() -- дать возможность отцепиться персу от сиденья character:PivotTo(exit.WorldCFrame) -- выкинуть перса из машины exit = nil return end end -- поворот if param == "SteerFloat" then -- ориентация от FL local orient = Vector3.new(0, -seat.SteerFloat * seat.TurnSpeed +90, 90) TS:Create(FL, tweenInfo, {Orientation = orient}):Play() TS:Create(FR, tweenInfo, {Orientation = orient}):Play() end -- газ if param == "ThrottleFloat" then local torque = math.abs(seat.ThrottleFloat) * seat.Torque if torque == 0 then torque = 2000 end -- ведущий мост задний local angular = math.sign(seat.ThrottleFloat) * maxAngular WBL.MotorMaxTorque = torque WBR.MotorMaxTorque = torque WBL.AngularVelocity = angular WBR.AngularVelocity = angular end end)
Всё хорошо, вроде бы. Руль поворачивается, но машина не едет… Я просто забыл добавить движитель…

Немножко можно поднастроить физику. Активируем CustomFisicalProperties, раскроем:
Density — коэффициент веса. Поставим там значение поменьше и увидим что Mass внизу уменьшился.

На колёсах можно изменить жёсткость сцепления Elasticity.
Запускаем и катаемся!

Ещё много чего можно сделать… Например добавить ресоры, озвучку, освещение, приборы, первую помощь от переворотов, посадку пассажиров и т.д. и т.п.
А раз принцип понятен — едем в рассвет!

https://www.youtube.com/watch?v=JgBhFG4TILQ&list=PLGGdAUTrNbI9rMOBX9yzc_jot2oHxPwc6&index=14
Thanks for making this so clear and easy to follow.