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

Мягкая посадка: парашют и зиплайн

Падая с высоты, жми пробел — раскроется парашют (забудешь — раскроется сам). Следы в воздухе от рук и ног. Зиплайн: E у верхней точки троса — и съезжаешь вниз.

Структура

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

ServerStorage
├─ Parachute (Model) — MainPart (PrimaryPart) + купол; Anchored и CanCollide выкл., Massless
└─ FallTrail (Trail) — след в воздухе
ReplicatedStorage
└─ ParachuteEvent (RemoteEvent)
ServerScriptService
└─ ParachuteServer (Script)
StarterPlayer → StarterCharacterScripts
└─ ParachuteLocal (LocalScript) — Animation — поза падения (ID можно не вставлять)
Workspace
└─ Zip (Model)
   ├─ Start (Part) — наверху; Attachment, Beam, ProximityPrompt → ZipScript
   └─ End (Part) — внизу; Attachment

Шаги

  1. Парашют: модель из купола и стропы, главная деталь «MainPart» = PrimaryPart, остальное приварено к ней. Anchored и CanCollide выкл., Massless вкл. — ServerStorage. Там же Trail «FallTrail» (Lifetime 0.3, белый).
  2. Поза падения: Avatar → Rig Builder, Animation Editor, один ключевой кадр, Looping, Publish — ID в Animation внутри ParachuteLocal (по желанию).
  3. ParachuteLocal следит за состоянием Freefall. Падаешь дольше секунды — сервер добавляет следы. Пробел — парашют; ниже 60 studs раскроется сам. Приземлился — всё убирается.
  4. ParachuteServer вешает парашют сваркой над головой и добавляет LinearVelocity по одной вертикальной оси: падение 20 studs/с, а по сторонам игрок рулит сам. В методичке BodyVelocity устарел и луч к земле летел не туда.
  5. Зиплайн: две детали «Start» (наверху, например на вышке) и «End» (внизу) в модели «Zip». В каждой — Attachment. В Start — Beam: Attachment0 — из Start, Attachment1 — из End, FaceCamera вкл.
  6. Beam — лента между двумя Attachment: Color, Width0/Width1, Texture, CurveSize — изгиб, Segments — плавность.
  7. В Start — ProximityPrompt (ActionText «Спуститься») со скриптом ZipScript. Скорость спуска одинаковая на любом тросе, персонаж смотрит вдоль троса, ехать могут несколько игроков сразу.

Скрипты

ParachuteLocal

Где: StarterPlayer → StarterCharacterScripts · LocalScript
Зачем: Падение, пробел, авто.

-- клавиатура
local UIS = game:GetService("UserInputService")
-- события
local RS = game:GetService("ReplicatedStorage")
-- каждый кадр
local RunService = game:GetService("RunService")
-- персонаж
local character = script.Parent
-- Humanoid
local humanoid = character:WaitForChild("Humanoid")
-- центр
local root = character:WaitForChild("HumanoidRootPart")
-- просим сервер: следы, парашют, приземлились
local event = RS:WaitForChild("ParachuteEvent")
-- поза падения (ID можно не вставлять)
local anim = script:WaitForChild("Animation")
-- загружаем, если ID есть
local track = anim.AnimationId ~= "" and humanoid:WaitForChild("Animator"):LoadAnimation(anim) or nil
-- ниже этой высоты парашют раскроется сам
local AUTO_HEIGHT = 60
-- ниже — открывать поздно
local MIN_HEIGHT = 20
-- когда начали падать
local fallStart = nil
-- парашют уже открыт
local opened = false
-- следы уже включены
local trails = false
-- настройки луча
local params = RaycastParams.new()
-- исключаем…
params.FilterType = Enum.RaycastFilterType.Exclude
-- …самого себя
params.FilterDescendantsInstances = {character}

-- высота над землёй
local function Height()
    -- луч строго вниз (в методичке летел не туда)
    local result = workspace:Raycast(root.Position, Vector3.new(0, -2000, 0), params)
    -- до земли или «очень высоко»
    return result and root.Position.Y - result.Position.Y or math.huge
-- конец функции
end
-- раскрыть парашют
local function Open()
    -- уже
    if opened then return end
    -- раскрыт
    opened = true
    -- сервер вешает парашют и замедляет падение
    event:FireServer("Open")
-- конец функции
end

-- состояние персонажа сменилось
humanoid.StateChanged:Connect(function(_, new)
    -- начал падать
    if new == Enum.HumanoidStateType.Freefall then
        -- запоминаем время
        fallStart = os.clock()
    -- падение закончилось
    elseif fallStart then
        -- не падаем
        fallStart = nil
        -- поза падения выключается
        if track then track:Stop() end
        -- убрать парашют и следы
        if opened or trails then event:FireServer("Landed") end
        -- к следующему падению всё сначала
        opened, trails = false, false
    -- конец проверки
    end
-- конец функции
end)
-- нажали клавишу
UIS.InputBegan:Connect(function(input, processed)
    -- пробел во время падения
    if processed or input.KeyCode ~= Enum.KeyCode.Space or not fallStart then return end
    -- падаем давно и ещё высоко
    if os.clock() - fallStart > 0.7 and Height() > MIN_HEIGHT then Open() end
-- конец функции
end)
-- каждый кадр
RunService.Heartbeat:Connect(function()
    -- обычный прыжок короче секунды — не трогаем
    if not fallStart or os.clock() - fallStart < 1 then return end
    -- падаем долго — значит с высоты
    if not trails then
        -- один раз
        trails = true
        -- поза падения
        if track then track:Play() end
        -- сервер добавит следы-Trail на руки и ноги
        event:FireServer("Fall")
    -- конец проверки
    end
    -- забыл раскрыть — раскроется сам
    if not opened and Height() < AUTO_HEIGHT then Open() end
-- конец функции
end)

ParachuteServer

Где: ServerScriptService · Script
Зачем: Парашют и следы.

-- события
local RS = game:GetService("ReplicatedStorage")
-- образцы
local SS = game:GetService("ServerStorage")
-- просьбы клиента
local event = RS:WaitForChild("ParachuteEvent")
-- модель парашюта (PrimaryPart — MainPart)
local parachuteSample = SS:WaitForChild("Parachute")
-- след в воздухе
local trailSample = SS:WaitForChild("FallTrail")
-- скорость спуска на парашюте
local FALL_SPEED = 20
-- где рисуем следы (R15)
local TRAIL_PARTS = {"LeftHand", "RightHand", "LeftFoot", "RightFoot"}

-- удалить всё с этим именем
local function Remove(character, name)
    -- все части персонажа
    for _, item in ipairs(character:GetDescendants()) do
        -- нашли — удаляем
        if item.Name == name then item:Destroy() end
    -- конец перебора
    end
-- конец функции
end

-- клиент попросил
event.OnServerEvent:Connect(function(player, action)
    -- персонаж
    local character = player.Character
    -- Humanoid
    local humanoid = character and character:FindFirstChildOfClass("Humanoid")
    -- центр
    local root = character and character:FindFirstChild("HumanoidRootPart")
    -- погиб
    if not humanoid or not root or humanoid.Health <= 0 then return end
    -- долгое падение — следы
    if action == "Fall" then
        -- старые убираем
        Remove(character, "FallTrail")
        -- руки и ноги
        for _, partName in ipairs(TRAIL_PARTS) do
            -- часть тела
            local part = character:FindFirstChild(partName)
            -- есть (у R6 таких нет)
            if part then
                -- начало следа
                local a0 = Instance.new("Attachment")
                -- имя — чтобы потом удалить
                a0.Name = "FallTrail"
                -- с одной стороны
                a0.Position = Vector3.new(0.4, 0, 0)
                -- в часть тела
                a0.Parent = part
                -- конец следа
                local a1 = a0:Clone()
                -- с другой стороны
                a1.Position = Vector3.new(-0.4, 0, 0)
                -- в часть тела
                a1.Parent = part
                -- след
                local trail = trailSample:Clone()
                -- имя
                trail.Name = "FallTrail"
                -- между двумя точками
                trail.Attachment0 = a0
                -- получается лента
                trail.Attachment1 = a1
                -- в часть тела
                trail.Parent = part
            -- конец проверки
            end
        -- конец перебора
        end
    -- раскрыть парашют
    elseif action == "Open" then
        -- уже раскрыт
        if character:FindFirstChild("Parachute") then return end
        -- туловище R15 или R6
        local torso = character:FindFirstChild("UpperTorso") or character:FindFirstChild("Torso")
        -- нет туловища
        if not torso then return end
        -- парашют
        local chute = parachuteSample:Clone()
        -- имя
        chute.Name = "Parachute"
        -- над головой
        chute:PivotTo(torso.CFrame * CFrame.new(0, 6, 0))
        -- приварим к туловищу
        local weld = Instance.new("WeldConstraint")
        -- туловище
        weld.Part0 = torso
        -- MainPart парашюта
        weld.Part1 = chute.PrimaryPart
        -- в парашют
        weld.Parent = chute.PrimaryPart
        -- в персонажа
        chute.Parent = character
        -- точка для силы
        local attachment = Instance.new("Attachment")
        -- имя
        attachment.Name = "ParachuteForce"
        -- в центр
        attachment.Parent = root
        -- держит скорость падения
        local slow = Instance.new("LinearVelocity")
        -- имя
        slow.Name = "ParachuteForce"
        -- за центр
        slow.Attachment0 = attachment
        -- только по одной оси…
        slow.VelocityConstraintMode = Enum.VelocityConstraintMode.Line
        -- …вертикальной: по сторонам игрок управляет сам
        slow.LineDirection = Vector3.new(0, 1, 0)
        -- вниз 20 studs/с
        slow.LineVelocity = -FALL_SPEED
        -- достаточно, чтобы затормозить
        slow.MaxForce = 100000
        -- в центр
        slow.Parent = root
    -- приземлился
    elseif action == "Landed" then
        -- парашют
        Remove(character, "Parachute")
        -- торможение
        Remove(character, "ParachuteForce")
        -- следы
        Remove(character, "FallTrail")
    -- конец проверки
    end
-- конец функции
end)

ZipScript

Где: Workspace → Zip → Start → ProximityPrompt · Script
Зачем: Спуск по тросу.

-- каждый кадр
local RunService = game:GetService("RunService")
-- ProximityPrompt «Спуститься»
local prompt = script.Parent
-- верхняя точка троса
local zipStart = prompt.Parent
-- нижняя точка
local zipEnd = zipStart.Parent:WaitForChild("End")
-- скорость спуска, studs/с
local SPEED = 40
-- персонаж висит под тросом
local OFFSET = Vector3.new(0, -3.5, 0)
-- игрок → true: кто едет
local riding = {}

-- нажал E
prompt.Triggered:Connect(function(player)
    -- персонаж
    local character = player.Character
    -- Humanoid
    local humanoid = character and character:FindFirstChildOfClass("Humanoid")
    -- центр
    local root = character and character:FindFirstChild("HumanoidRootPart")
    -- уже едет или погиб
    if riding[player] or not humanoid or not root or humanoid.Health <= 0 then return end
    -- едет (другие тоже могут — кнопка не блокируется)
    riding[player] = true
    -- откуда
    local from = zipStart.Position + OFFSET
    -- куда
    local to = zipEnd.Position + OFFSET
    -- смотрит вдоль троса, а не боком
    local facing = CFrame.lookAt(from, Vector3.new(to.X, from.Y, to.Z)).Rotation
    -- время спуска — одинаковая скорость на любом тросе
    local duration = (to - from).Magnitude / SPEED
    -- едет по тросу, а не падает
    root.Anchored = true
    -- сколько едем
    local t = 0
    -- пока не доехали
    while t < duration and root.Parent and humanoid.Health > 0 do
        -- прошёл кадр
        t += RunService.Heartbeat:Wait()
        -- точка на тросе — от начала к концу
        root.CFrame = facing + from:Lerp(to, math.min(t / duration, 1))
    -- конец спуска
    end
    -- отпускаем
    if root.Parent then root.Anchored = false end
    -- приехал
    riding[player] = nil
-- конец функции
end)

Проверка

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