Roblox · Королевская битва · урок 4

Вертолёт

Большую карту долго бегать — вертолёт: E/Q — вверх/вниз, W/S — вперёд/назад, A/D — поворот, пробел — выйти. Лопасти крутятся мотором HingeConstraint.

Структура

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

Workspace
└─ Helicopter (Model) — все детали Anchored выкл., приварены к Body
   ├─ Body (Part) — корпус (PrimaryPart)
   ├─ VehicleSeat (VehicleSeat) — приварен к Body, смотрит вперёд
   ├─ MainBlades (Part) — Attachment → HingeConstraint (Motor) → Body
   ├─ BackBlades (Part) — так же, ось вбок
   └─ HelicopterServer (Script)
StarterPlayer → StarterCharacterScripts
└─ HelicopterLocal (LocalScript)

Шаги

  1. Модель: сделай сам или возьми из Toolbox (удали из неё все скрипты). Все детали Anchored выкл. и сварены с корпусом «Body» (WeldConstraint), кроме лопастей. Лопасти CanCollide выкл., Massless вкл.
  2. Лопасти: отодвинь их от корпуса. Model → Constraints → HingeConstraint: сначала кликни лопасти, потом корпус. Верни лопасти на место. В HingeConstraint: ActuatorType = Motor, MotorMaxTorque = очень большое число (1000000000), AngularVelocity = 0 — пока стоят.
  3. VehicleSeat: свёрнутый вперёд, приварен к корпусу. Модель назови «Helicopter» — по имени HelicopterLocal понимает, что сел именно в вертолёт.
  4. HelicopterServer: когда пилот сел — включает LinearVelocity (скорость) и AlignOrientation (поворот), раскручивает лопасти и отдаёт физику компьютеру пилота (SetNetworkOwner) — без задержек.
  5. HelicopterLocal: ContextActionService привязывает клавиши к действиям (BindAction) только пока сидишь в вертолёте и отвязывает (UnbindAction), когда вышел. Каждый кадр задаёт скорость и поворот.
  6. В методичке скрипт управления был перепутан, а BodyPosition тянул вертолёт к точке и он «плыл» — исправлено.

Скрипты

HelicopterServer

Где: Workspace → Helicopter · Script
Зачем: Пилот, силы, лопасти.

-- игроки
local Players = game:GetService("Players")
-- модель Helicopter
local heli = script.Parent
-- кресло пилота
local seat = heli:WaitForChild("VehicleSeat")
-- моторы лопастей
local blades = {heli:WaitForChild("MainBlades"):WaitForChild("HingeConstraint"), heli:WaitForChild("BackBlades"):WaitForChild("HingeConstraint")}
-- скорость вращения лопастей
local BLADE_SPEED = 25

-- точка, к которой приложены силы
local attachment = Instance.new("Attachment")
-- имя
attachment.Name = "FlyAttachment"
-- в кресло
attachment.Parent = seat
-- держит скорость вертолёта
local move = Instance.new("LinearVelocity")
-- имя — его ищет HelicopterLocal
move.Name = "Move"
-- за кресло
move.Attachment0 = attachment
-- сильнее гравитации и веса
move.MaxForce = 10000000
-- висит на месте
move.VectorVelocity = Vector3.zero
-- пока пилота нет — выключено
move.Enabled = false
-- в кресло
move.Parent = seat
-- держит поворот: не кувыркается
local turn = Instance.new("AlignOrientation")
-- имя — его ищет HelicopterLocal
turn.Name = "Turn"
-- к заданному направлению
turn.Mode = Enum.OrientationAlignmentMode.OneAttachment
-- за кресло
turn.Attachment0 = attachment
-- сильно
turn.MaxTorque = 10000000
-- поворачивает плавно
turn.Responsiveness = 15
-- пока пилота нет — выключено
turn.Enabled = false
-- в кресло
turn.Parent = seat

-- сел или встал
seat:GetPropertyChangedSignal("Occupant"):Connect(function()
    -- кто сидит
    local humanoid = seat.Occupant
    -- игрок
    local player = humanoid and Players:GetPlayerFromCharacter(humanoid.Parent)
    -- пилот сел
    if player then
        -- куда смотрит вертолёт
        local _, yaw = seat.CFrame:ToOrientation()
        -- держим ровно в эту сторону
        turn.CFrame = CFrame.Angles(0, yaw, 0)
        -- висим
        move.VectorVelocity = Vector3.zero
        -- силы включены
        move.Enabled = true
        -- поворот включён
        turn.Enabled = true
        -- лопасти крутятся
        for _, hinge in ipairs(blades) do hinge.AngularVelocity = BLADE_SPEED end
        -- физику вертолёта считает компьютер пилота — без задержек
        seat:SetNetworkOwner(player)
    -- пилот вышел
    else
        -- силы выключены — вертолёт падает и садится
        move.Enabled = false
        -- поворот выключен
        turn.Enabled = false
        -- лопасти останавливаются
        for _, hinge in ipairs(blades) do hinge.AngularVelocity = 0 end
        -- физику снова считает сервер
        seat:SetNetworkOwner(nil)
    -- конец проверки
    end
-- конец функции
end)

HelicopterLocal

Где: StarterPlayer → StarterCharacterScripts · LocalScript
Зачем: Управление.

-- привязка клавиш к действиям
local CAS = game:GetService("ContextActionService")
-- каждый кадр
local RunService = game:GetService("RunService")
-- игроки
local Players = game:GetService("Players")
-- этот игрок
local player = Players.LocalPlayer
-- Humanoid персонажа
local humanoid = script.Parent:WaitForChild("Humanoid")
-- скорость вперёд-назад
local SPEED = 45
-- скорость вверх-вниз
local UP_SPEED = 18
-- поворот, радиан в секунду
local TURN_SPEED = 1.5
-- действие → клавиша
local ACTIONS = {
    -- вверх, вниз
    HeliUp = Enum.KeyCode.E, HeliDown = Enum.KeyCode.Q,
    -- вперёд, назад
    HeliForward = Enum.KeyCode.W, HeliBack = Enum.KeyCode.S,
    -- поворот
    HeliLeft = Enum.KeyCode.A, HeliRight = Enum.KeyCode.D,
-- конец таблицы
}
-- какие действия сейчас зажаты
local held = {}
-- кресло вертолёта, если сидим
local seat = nil
-- куда повёрнут вертолёт
local yaw = 0
-- дальность камеры до посадки
local oldZoom = player.CameraMaxZoomDistance

-- нажали или отпустили клавишу действия
local function OnAction(name, state)
    -- зажата?
    held[name] = state == Enum.UserInputState.Begin
    -- клавиша больше никуда не идёт
    return Enum.ContextActionResult.Sink
-- конец функции
end
-- включить или выключить управление
local function Bind(on)
    -- каждое действие
    for name, key in pairs(ACTIONS) do
        -- привязать / отвязать (false — без кнопки на телефоне)
        if on then CAS:BindAction(name, OnAction, false, key) else CAS:UnbindAction(name) end
    -- конец перебора
    end
    -- ничего не зажато
    table.clear(held)
-- конец функции
end
-- 1, -1 или 0 по двум клавишам
local function Axis(plus, minus)
    -- обе зажаты — 0
    return (held[plus] and 1 or 0) - (held[minus] and 1 or 0)
-- конец функции
end

-- сел или встал
humanoid.Seated:Connect(function(active, seatPart)
    -- сел в вертолёт
    if active and seatPart and seatPart:IsA("VehicleSeat") and seatPart.Parent.Name == "Helicopter" then
        -- запоминаем
        seat = seatPart
        -- куда смотрит
        local _, y = seat.CFrame:ToOrientation()
        -- начальный поворот
        yaw = y
        -- запоминаем камеру
        oldZoom = player.CameraMaxZoomDistance
        -- камеру можно отдалить — видно вертолёт
        player.CameraMaxZoomDistance = 60
        -- управление
        Bind(true)
    -- вышел (пробел) или погиб
    elseif seat then
        -- не в вертолёте
        seat = nil
        -- камера как была
        player.CameraMaxZoomDistance = oldZoom
        -- клавиши свободны
        Bind(false)
    -- конец проверки
    end
-- конец функции
end)

-- каждый кадр
RunService.RenderStepped:Connect(function(dt)
    -- не летим
    if not seat then return end
    -- скорость (создаёт HelicopterServer)
    local move = seat:FindFirstChild("Move")
    -- поворот
    local turn = seat:FindFirstChild("Turn")
    -- вертолёт без скрипта
    if not move or not turn then return end
    -- D — вправо, A — влево
    yaw -= Axis("HeliRight", "HeliLeft") * TURN_SPEED * dt
    -- направление вертолёта
    local facing = CFrame.Angles(0, yaw, 0)
    -- поворачиваем
    turn.CFrame = facing
    -- вперёд-назад и вверх-вниз
    move.VectorVelocity = facing.LookVector * Axis("HeliForward", "HeliBack") * SPEED + Vector3.new(0, Axis("HeliUp", "HeliDown") * UP_SPEED, 0)
-- конец функции
end)

Проверка

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