Roblox · Гонки · урок 2

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

Своя машина: кузов, четыре колеса на пружинах, задний привод и руль. Садишься по E, едешь на WASD, выходишь прыжком — персонаж оказывается рядом с машиной.

Структура

Синим — что добавляем на этом уроке.

Workspace
└─ Car (Model) — PrimaryPart = Body
   ├─ Body (Part) — кузов; масса 80–90 кг через Density
   │  └─ AttachmentFL, AttachmentFR, AttachmentBL, AttachmentBR (Attachment) — места крепления колёс, Orientation Z = 90
   ├─ WheelFL, WheelFR, WheelBL, WheelBR (Part) — цилиндр-колесо; масса ≈ 60 кг
   │  ├─ Attachment (Attachment)
   │  ├─ CylindricalConstraint (CylindricalConstraint) — у задних — AngularActuatorType Motor
   │  ├─ SpringConstraint (SpringConstraint) — амортизатор
   │  └─ Decal (Decal) — диск колеса
   └─ VehicleSeat (VehicleSeat) — MaxSpeed, Torque, TurnSpeed
      ├─ WeldConstraint (WeldConstraint) — Part0 — сиденье, Part1 — Body
      ├─ ProximityPrompt (ProximityPrompt) — «Сесть»
      ├─ Attachment (Attachment) — точка выхода — сбоку от машины
      └─ Prompt (Script)
ServerScriptService
└─ Network (Script)
StarterPlayer
└─ StarterCharacterScripts
   └─ CarController (LocalScript)

Шаги

  1. Toolbox: найди кузов машины без колёс. Положи кузов в новую модель Car, деталь кузова назови Body.
  2. Выдели модель Car → Properties → PrimaryPart → выбери Body: это главная деталь машины.
  3. Body → CustomPhysicalProperties — галочка; подбери Density, чтобы масса (Mass) была 80–90 кг.
  4. Колесо: в Car «+» → Part, Shape — Cylinder, размер колеса. Назови WheelFL (переднее левое). Внутрь — Decal с картинкой диска.
  5. В Body «+» → Attachment, назови AttachmentFL и передвинь туда, где крепится колесо. Orientation — 0, 0, 90.
  6. В колесо «+» → Attachment (не двигай). Ещё «+» → CylindricalConstraint: Attachment0 — AttachmentFL, Attachment1 — Attachment колеса; InclinationAngle — 90; LimitsEnabled — вкл., LowerLimit −1, UpperLimit 1 (ход подвески).
  7. Model → Collision Groups → Table View: Add Group «Car» (добавь Body), Add Group «Wheel» (добавь колёса), сними галочку на пересечении Car и Wheel — колёса не будут биться о кузов.
  8. В колесо «+» → SpringConstraint: Attachment0 — AttachmentFL, Attachment1 — Attachment колеса. Stiffness ≈ 5000, Damping ≈ 300, FreeLength ≈ 2 — подбери, чтобы машина стояла ровно и мягко покачивалась.
  9. Колесо: CustomPhysicalProperties — масса ≈ 60 кг. Поставь колесо на место.
  10. Выдели одновременно AttachmentFL и колесо → Ctrl+D: соединения сами переключатся на копии. Передвинь копию, переименуй в WheelFR и AttachmentFR. Так же WheelBL / AttachmentBL и WheelBR / AttachmentBR. Диск на внешнюю сторону — меняй Face у Decal, а не поворачивай колесо.
  11. Мотор — задние колёса: у CylindricalConstraint колёс WheelBL и WheelBR AngularActuatorType — Motor, MotorMaxTorque — 2000.
  12. В Car «+» → VehicleSeat. В него «+» → WeldConstraint: Part0 — VehicleSeat, Part1 — Body. Настрой сиденье: MaxSpeed ≈ 60 (предельная скорость), Torque ≈ 8000 (разгон), TurnSpeed ≈ 30 (угол поворота колёс в градусах).
  13. ServerScriptService → «+» → Script «Network», StarterPlayer → StarterCharacterScripts → «+» → LocalScript «CarController» — тексты из «Скриптов».
  14. Сначала проверь езду с сиденьем сверху кузова. Потом опусти сиденье внутрь, по центру, и выключи у него CanCollide, CanTouch и CanQuery.
  15. В сиденье «+» → ProximityPrompt (ActionText «Сесть», HoldDuration 0) и «+» → Attachment — передвинь сбоку от машины, сюда игрок выйдет. «+» → Script «Prompt».

Скрипты

Network

Где: ServerScriptService · Script
Зачем: Когда игрок садится за руль, передаёт его компьютеру физику машины.

-- игрок зашёл в игру
game.Players.PlayerAdded:Connect(function(player)
    -- у игрока появился персонаж
    player.CharacterAdded:Connect(function(character)
        -- персонаж сел или встал
        character.Humanoid.Seated:Connect(function(active, seatPart)
            -- встал — ничего не делаем
            if seatPart == nil then return end
            -- сел не за руль машины
            if seatPart.Name ~= "VehicleSeat" then return end
            -- физику машины считает компьютер водителя — управление без задержек
            seatPart:SetNetworkOwner(player)
        -- конец обработчика
        end)
    -- конец обработчика
    end)
-- конец обработчика
end)

CarController

Где: StarterPlayer → StarterCharacterScripts · LocalScript
Зачем: Читает руль и газ сиденья и крутит колёса; при выходе ставит персонажа рядом с машиной.

-- персонаж игрока (скрипт лежит в StarterCharacterScripts)
local character = script.Parent
-- плавные изменения
local TS = game:GetService("TweenService")
-- поворот колёс за полсекунды
local tweenInfo = TweenInfo.new(0.5)
-- подписка на руль и газ, пока сидим в машине
local connection = nil
-- точка, куда переносим при выходе
local exitAttach = nil

-- сел или встал
character.Humanoid.Seated:Connect(function(active, seatPart)
    -- была старая подписка
    if connection ~= nil then
        -- отключаем
        connection:Disconnect()
        -- забываем
        connection = nil
    -- конец проверки
    end
    -- встал с сиденья
    if seatPart == nil then
        -- вышли из машины
        if exitAttach ~= nil then
            -- ставим персонажа рядом с машиной
            character:PivotTo(exitAttach.WorldCFrame)
            -- забываем точку
            exitAttach = nil
        -- конец проверки
        end
        -- дальше — только для тех, кто сел
        return
    -- конец проверки
    end
    -- сел не за руль
    if seatPart.Name ~= "VehicleSeat" then return end
    -- точка выхода внутри сиденья
    exitAttach = seatPart:FindFirstChildOfClass("Attachment")
    -- модель машины
    local car = seatPart.Parent
    -- крепление переднего левого колеса
    local attachFL = car.PrimaryPart.AttachmentFL
    -- крепление переднего правого колеса
    local attachFR = car.PrimaryPart.AttachmentFR
    -- мотор заднего левого колеса
    local cylindricalBL = car.WheelBL.CylindricalConstraint
    -- мотор заднего правого колеса
    local cylindricalBR = car.WheelBR.CylindricalConstraint
    -- скорость вращения колеса = скорость ÷ радиус
    local maxAngVelocity = seatPart.MaxSpeed / (car.WheelBR.Size.Y / 2)
    -- у сиденья поменялось свойство
    connection = seatPart.Changed:Connect(function(property)
        -- руль: A/D или стрелки
        if property == "SteerFloat" then
            -- угол поворота передних колёс
            local orientation = Vector3.new(0, -seatPart.SteerFloat * seatPart.TurnSpeed, 90)
            -- плавно поворачиваем левое
            TS:Create(attachFL, tweenInfo, {Orientation = orientation}):Play()
            -- и правое
            TS:Create(attachFR, tweenInfo, {Orientation = orientation}):Play()
        -- газ: W/S
        elseif property == "ThrottleFloat" then
            -- сила мотора при газе
            local torque = math.abs(seatPart.ThrottleFloat) * seatPart.Torque
            -- отпустили газ — небольшая сила, машина плавно тормозит
            if torque == 0 then torque = 2000 end
            -- сила левого мотора
            cylindricalBL.MotorMaxTorque = torque
            -- сила правого мотора
            cylindricalBR.MotorMaxTorque = torque
            -- вперёд, назад или стоп
            local angVelocity = math.sign(seatPart.ThrottleFloat) * maxAngVelocity
            -- крутим левое колесо
            cylindricalBL.AngularVelocity = angVelocity
            -- крутим правое колесо
            cylindricalBR.AngularVelocity = angVelocity
        -- конец проверки
        end
    -- конец обработчика
    end)
-- конец обработчика
end)

Prompt

Где: Workspace → Car → VehicleSeat · Script
Зачем: Сажает игрока за руль по E и прячет надпись, пока место занято.

-- сиденье водителя
local seatPart = script.Parent
-- надпись «Сесть» (клавиша E)
local prompt = seatPart.ProximityPrompt

-- кто-то сел или вышел
seatPart:GetPropertyChangedSignal("Occupant"):Connect(function()
    -- надпись видна, только когда место свободно
    prompt.Enabled = seatPart.Occupant == nil
-- конец обработчика
end)

-- игрок нажал E
prompt.Triggered:Connect(function(player)
    -- его персонаж
    local humanoid = player.Character and player.Character:FindFirstChildOfClass("Humanoid")
    -- персонаж есть
    if humanoid then
        -- сажаем за руль
        seatPart:Sit(humanoid)
    -- конец проверки
    end
-- конец обработчика
end)

Проверка

Запусти Play (или Run) и убедись: