Roblox · RPG · урок 9

Прокачка

Уровень делает героя сильнее: оружие светится на 5-м и горит на 10-м, урон и навыки растут с уровнем, лучник с 3-го уровня умеет красться.

Структура

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

ServerStorage
├─ Weapons → Sword → Blade, Bow → Handle, Staff → Handle (Part)
│  ├─ Level5 (ParticleEmitter) — Enabled выкл.
│  └─ Level10 (Fire) — Enabled выкл.
└─ Classes → Archer (Folder)
   └─ Sneak (LocalScript) — Animation — поза (Movement)
ServerScriptService
└─ UpgradeSystem (Script)
Workspace
└─ ExpStone (Part) — камень опыта для проверки
   └─ LvlTest (Script)

Шаги

  1. Эффекты: в Blade меча (и в Handle лука и посоха) — ParticleEmitter «Level5» и Fire «Level10», у обоих Enabled выкл.
  2. UpgradeSystem включает эффекты по уровню у оружия в рюкзаке, в руках и в StarterGear — после смерти оружие тоже светится.
  3. Камень опыта «ExpStone» со скриптом «LvlTest»: касание — +200 опыта. Удобно проверять.
  4. Урон теперь зависит от уровня: меч (10 + уровень × 3) ± 20 %, стрела 15 + уровень × 3, шар 10 + уровень × 3. В методичке math.random(0.3, 2) — random с дробями не работает, урон выходил 0 или 1.
  5. Навыки: кувырок 40 + уровень × 5 (до 90), прыжок 50 + уровень × 5 (до 120), телепорт 35 + уровень × 5 (до 100). В методичке кувырок на 1-м уровне был медленнее ходьбы.
  6. Sneak — новый навык лучника: держи F — крадётся вдвое медленнее, открывается с 3-го уровня.
  7. Замени скрипты оружия и навыков на версии ниже.

Скрипты

UpgradeSystem

Где: ServerScriptService · Script
Зачем: Эффекты оружия по уровню.

-- игроки
local Players = game:GetService("Players")
-- оружие → деталь с эффектами
local EFFECT_PART = {Sword = "Blade", Bow = "Handle", Staff = "Handle"}

-- включить эффекты по уровню у всего оружия игрока
local function Apply(player)
    -- уровень
    local lvl = player:FindFirstChild("Lvl")
    -- ещё не создан
    if not lvl then return end
    -- рюкзак, руки и запас на следующую жизнь
    for _, holder in ipairs({player:FindFirstChildOfClass("Backpack"), player.Character, player:FindFirstChild("StarterGear")}) do
        -- есть
        if holder then
            -- каждый предмет
            for _, tool in ipairs(holder:GetChildren()) do
                -- деталь с эффектами
                local part = tool:IsA("Tool") and EFFECT_PART[tool.Name] and tool:FindFirstChild(EFFECT_PART[tool.Name])
                -- это классовое оружие
                if part then
                    -- эффект 5-го уровня (искры)
                    local level5 = part:FindFirstChild("Level5")
                    -- эффект 10-го уровня (огонь)
                    local level10 = part:FindFirstChild("Level10")
                    -- с 5 по 9 уровень
                    if level5 then level5.Enabled = lvl.Value >= 5 and lvl.Value < 10 end
                    -- с 10-го
                    if level10 then level10.Enabled = lvl.Value >= 10 end
                -- конец проверки
                end
            -- конец перебора
            end
        -- конец проверки
        end
    -- конец перебора
    end
-- конец функции
end

-- игрок зашёл
local function Setup(player)
    -- новый уровень — новые эффекты
    player:WaitForChild("Lvl").Changed:Connect(function() Apply(player) end)
    -- в рюкзаке или StarterGear появился предмет
    player.DescendantAdded:Connect(function(obj)
        -- новое оружие сразу с эффектами
        if obj:IsA("Tool") then Apply(player) end
    -- конец функции
    end)
    -- сразу
    Apply(player)
-- конец функции
end
-- новые игроки
Players.PlayerAdded:Connect(Setup)
-- кто зашёл раньше скрипта
for _, player in ipairs(Players:GetPlayers()) do task.spawn(Setup, player) end

LvlTest

Где: Workspace → ExpStone · Script
Зачем: +200 опыта за касание.

-- игроки
local Players = game:GetService("Players")
-- камень опыта
local part = script.Parent
-- сколько опыта за касание
local EXP = 200
-- кто недавно получал
local cooldown = {}

-- касание
part.Touched:Connect(function(hit)
    -- игрок?
    local player = Players:GetPlayerFromCharacter(hit.Parent)
    -- не игрок или недавно было
    if not player or cooldown[player] or not player:FindFirstChild("Exp") then return end
    -- пауза
    cooldown[player] = true
    -- опыт
    player.Exp.Value += EXP
    -- секунда
    task.wait(1)
    -- можно снова
    cooldown[player] = nil
-- конец функции
end)

SwordScript

Где: ServerStorage → Weapons → Sword · Script
Зачем: Урон от уровня.

-- игроки
local Players = game:GetService("Players")
-- удаляет метки через время
local Debris = game:GetService("Debris")
-- меч
local tool = script.Parent
-- невидимая коробка вокруг меча — ею и бьём
local handle = tool:WaitForChild("Handle")
-- анимация удара
local anim = script:WaitForChild("Animation")
-- звук взмаха
local swingSound = script:WaitForChild("Swing")
-- звук попадания
local hitSound = script:WaitForChild("Hit")
-- «Ты не мечник»
local notGui = tool:WaitForChild("YouAreNotGui")
-- чей это меч
local CLASS = "Swordsman"
-- длина взмаха
local COOLDOWN = 0.6
-- идёт взмах
local swinging = false
-- кого уже ударили этим взмахом
local hitList = {}

-- клик с мечом в руках
tool.Activated:Connect(function()
    -- хозяин
    local player = Players:GetPlayerFromCharacter(tool.Parent)
    -- его Humanoid
    local humanoid = tool.Parent:FindFirstChildOfClass("Humanoid")
    -- нет хозяина, погиб или взмах идёт
    if not player or not humanoid or humanoid.Health <= 0 or swinging then return end
    -- чужой класс
    if player:GetAttribute("Class") ~= CLASS then
        -- надпись ещё не показана
        if notGui.Parent == tool then
            -- «Ты не …» на экран
            notGui.Parent = player.PlayerGui
            -- 2 секунды
            task.wait(2)
            -- прячем обратно в оружие
            notGui.Parent = tool
        -- конец проверки
        end
        -- не бьём
        return
    -- конец проверки
    end
    -- начали
    swinging = true
    -- новый взмах — список пуст
    table.clear(hitList)
    -- анимация удара
    if anim.AnimationId ~= "" then humanoid:FindFirstChildOfClass("Animator"):LoadAnimation(anim):Play() end
    -- вжух
    swingSound:Play()
    -- ждём
    task.wait(COOLDOWN)
    -- можно снова
    swinging = false
-- конец функции
end)

-- меч коснулся чего-то
handle.Touched:Connect(function(hit)
    -- бьём только во время взмаха
    if not swinging then return end
    -- хозяин
    local player = Players:GetPlayerFromCharacter(tool.Parent)
    -- чья деталь
    local model = hit:FindFirstAncestorOfClass("Model")
    -- её Humanoid
    local target = model and model:FindFirstChildOfClass("Humanoid")
    -- не цель, сам себя или уже ударили
    if not player or not target or model == tool.Parent or target.Health <= 0 or hitList[target] then return end
    -- игроков не бьём — только мобов
    if Players:GetPlayerFromCharacter(model) then return end
    -- этим взмахом больше не бьём
    hitList[target] = true
    -- прошлая метка «кто ударил»
    local old = target:FindFirstChild("creator")
    -- убираем
    if old then old:Destroy() end
    -- новая метка
    local tag = Instance.new("ObjectValue")
    -- стандартное имя метки в Roblox
    tag.Name = "creator"
    -- кто ударил — получит опыт за победу
    tag.Value = player
    -- в Humanoid цели
    tag.Parent = target
    -- через 3 секунды метка исчезнет
    Debris:AddItem(tag, 3)
    -- уровень хозяина
    local lvl = player.Lvl.Value
    -- урон растёт с уровнем, ±20 % случайности
    local damage = math.floor((10 + lvl * 3) * (0.8 + math.random() * 0.4))
    -- бьём
    target:TakeDamage(damage)
    -- звук попадания
    hitSound:Play()
-- конец функции
end)

BowServer

Где: ServerStorage → Weapons → Bow · Script
Зачем: Урон от уровня.

-- игроки
local Players = game:GetService("Players")
-- удаляет стрелы
local Debris = game:GetService("Debris")
-- лук
local tool = script.Parent
-- сам лук
local handle = tool:WaitForChild("Handle")
-- стрела на тетиве
local arrow = tool:WaitForChild("Arrow")
-- выстрел от клиента
local bowEvent = tool:WaitForChild("Bow")
-- звук выстрела
local fireSound = handle:WaitForChild("Fire")
-- звук попадания
local hitSound = handle:WaitForChild("Hit")
-- анимация выстрела (Action)
local anim = script:WaitForChild("Animation")
-- кто может стрелять
local CLASS = "Archer"
-- перезарядка
local COOLDOWN = 1.5
-- скорость стрелы
local SPEED = 200
-- перезаряжаемся
local busy = false

-- клиент просит выстрелить
bowEvent.OnServerEvent:Connect(function(player, target)
    -- кто держит
    local character = tool.Parent
    -- его Humanoid
    local humanoid = character and character:FindFirstChildOfClass("Humanoid")
    -- лук не в его руках или мусор вместо точки
    if busy or Players:GetPlayerFromCharacter(character) ~= player or typeof(target) ~= "Vector3" then return end
    -- погиб или не лучник
    if not humanoid or humanoid.Health <= 0 or player:GetAttribute("Class") ~= CLASS then return end
    -- перезарядка
    busy = true
    -- натягиваем тетиву
    if anim.AnimationId ~= "" then humanoid:FindFirstChildOfClass("Animator"):LoadAnimation(anim):Play() end
    -- момент выстрела
    task.wait(0.3)
    -- звук
    fireSound:Play()
    -- летящая стрела
    local shot = arrow:Clone()
    -- всё внутри копии
    for _, obj in ipairs(shot:GetChildren()) do
        -- сварка с луком — иначе стрела тянет лук за собой
        if obj:IsA("WeldConstraint") or obj:IsA("JointInstance") then obj:Destroy() end
    -- конец перебора
    end
    -- летит
    shot.Anchored = false
    -- не отскакивает
    shot.CanCollide = false
    -- у стрелы есть вес — падает дугой
    shot.Massless = false
    -- у лука, остриём к цели
    shot.CFrame = CFrame.new(handle.Position, target)
    -- в мир
    shot.Parent = workspace
    -- полёт считает сервер
    shot:SetNetworkOwner(nil)
    -- вылетает
    shot.AssemblyLinearVelocity = shot.CFrame.LookVector * SPEED
    -- на тетиве стрелы нет
    arrow.Transparency = 1
    -- стрела ещё не попала
    local done = false
    -- стрела коснулась
    shot.Touched:Connect(function(hit)
        -- уже попала или задела стрелка
        if done or hit:IsDescendantOf(character) then return end
        -- чья деталь
        local model = hit:FindFirstAncestorOfClass("Model")
        -- Humanoid цели
        local target = model and model:FindFirstChildOfClass("Humanoid")
        -- живой моб
        if target and target.Health > 0 and not Players:GetPlayerFromCharacter(model) then
            -- попала
            done = true
            -- прошлая метка «кто ударил»
            local old = target:FindFirstChild("creator")
            -- убираем
            if old then old:Destroy() end
            -- новая метка
            local tag = Instance.new("ObjectValue")
            -- стандартное имя метки в Roblox
            tag.Name = "creator"
            -- кто ударил — получит опыт за победу
            tag.Value = player
            -- в Humanoid цели
            tag.Parent = target
            -- через 3 секунды метка исчезнет
            Debris:AddItem(tag, 3)
            -- урон растёт с уровнем
            target:TakeDamage(15 + player.Lvl.Value * 3)
            -- звук попадания
            hitSound:Play()
            -- стрела исчезает
            shot:Destroy()
        -- врезалась в стену или землю
        elseif hit.CanCollide then
            -- дальше не летит
            done = true
            -- торчит, где попала
            shot.Anchored = true
        -- конец проверки
        end
    -- конец функции
    end)
    -- через 5 секунд стрела исчезнет
    Debris:AddItem(shot, 5)
    -- перезарядка
    task.wait(COOLDOWN)
    -- новая стрела на тетиве
    arrow.Transparency = 0
    -- можно снова
    busy = false
-- конец функции
end)

FireBallServer

Где: ServerStorage → Weapons → Staff · Script
Зачем: Урон от уровня.

-- игроки
local Players = game:GetService("Players")
-- общее хранилище
local RS = game:GetService("ReplicatedStorage")
-- удаляет эффекты
local Debris = game:GetService("Debris")
-- посох
local tool = script.Parent
-- откуда вылетает шар
local spell = tool:WaitForChild("SpellCast")
-- звук заклинания
local fireSound = tool:WaitForChild("Handle"):WaitForChild("Fire")
-- заклинание от клиента
local fireballEvent = tool:WaitForChild("FireBall")
-- образцы эффектов
local effects = RS:WaitForChild("MageEffects")
-- огненный шар
local fireballSample = effects:WaitForChild("FireBall")
-- взрыв (в методичке по ошибке брался FireBall)
local explosionSample = effects:WaitForChild("Explosion")
-- кто колдует
local CLASS = "Mage"
-- скорость шара
local SPEED = 80
-- перезарядка (проверка на сервере)
local COOLDOWN = 1
-- время прошлого заклинания
local lastCast = 0

-- клиент колдует
fireballEvent.OnServerEvent:Connect(function(player, target)
    -- кто держит посох
    local character = tool.Parent
    -- посох не у него или он не маг
    if Players:GetPlayerFromCharacter(character) ~= player or player:GetAttribute("Class") ~= CLASS then return end
    -- мусор вместо точки или слишком часто
    if typeof(target) ~= "Vector3" or os.clock() - lastCast < COOLDOWN then return end
    -- запоминаем
    lastCast = os.clock()
    -- звук
    fireSound:Play()
    -- новый шар
    local ball = fireballSample:Clone()
    -- летит
    ball.Anchored = false
    -- сквозь своих
    ball.CanCollide = false
    -- у конца посоха, смотрит на цель
    ball.CFrame = CFrame.new(spell.Position, target)
    -- в мир
    ball.Parent = workspace
    -- полёт считает сервер
    ball:SetNetworkOwner(nil)
    -- точка для силы
    local attachment = Instance.new("Attachment", ball)
    -- держит скорость
    local move = Instance.new("LinearVelocity")
    -- за центр шара
    move.Attachment0 = attachment
    -- без предела — летит прямо, без падения
    move.MaxForce = math.huge
    -- к цели
    move.VectorVelocity = ball.CFrame.LookVector * SPEED
    -- в шар
    move.Parent = ball
    -- ещё не взорвался
    local done = false
    -- шар коснулся
    ball.Touched:Connect(function(hit)
        -- уже взорвался или задел мага
        if done or hit:IsDescendantOf(character) then return end
        -- чья деталь
        local model = hit:FindFirstAncestorOfClass("Model")
        -- Humanoid цели
        local target = model and model:FindFirstChildOfClass("Humanoid")
        -- сквозь эффекты и невидимые зоны
        if not target and not hit.CanCollide then return end
        -- взрываемся
        done = true
        -- взрыв
        local boom = explosionSample:Clone()
        -- на месте
        boom.Anchored = true
        -- не мешает
        boom.CanCollide = false
        -- где шар
        boom.Position = ball.Position
        -- показываем
        boom.Parent = workspace
        -- секунду
        Debris:AddItem(boom, 1)
        -- шара больше нет
        ball:Destroy()
        -- попали в моба
        if target and target.Health > 0 and not Players:GetPlayerFromCharacter(model) then
            -- прошлая метка «кто ударил»
            local old = target:FindFirstChild("creator")
            -- убираем
            if old then old:Destroy() end
            -- новая метка
            local tag = Instance.new("ObjectValue")
            -- стандартное имя метки в Roblox
            tag.Name = "creator"
            -- кто ударил — получит опыт за победу
            tag.Value = player
            -- в Humanoid цели
            tag.Parent = target
            -- через 3 секунды метка исчезнет
            Debris:AddItem(tag, 3)
            -- урон растёт с уровнем
            target:TakeDamage(10 + player.Lvl.Value * 3)
            -- обычная скорость цели (запоминаем один раз)
            local base = target:GetAttribute("BaseSpeed") or target.WalkSpeed
            -- храним в самой цели
            target:SetAttribute("BaseSpeed", base)
            -- замедление
            target.WalkSpeed = base * 0.3
            -- через 3 секунды
            task.delay(3, function()
                -- её собственная скорость, а не 16
                if target.Parent then target.WalkSpeed = base end
            -- конец функции
            end)
        -- конец проверки
        end
    -- конец функции
    end)
    -- промах — через 4 секунды исчезнет
    Debris:AddItem(ball, 4)
-- конец функции
end)

Dash

Где: ServerStorage → Classes → Swordsman · LocalScript
Зачем: Кувырок от уровня.

-- клавиатура
local UIS = game:GetService("UserInputService")
-- игроки
local Players = game:GetService("Players")
-- персонаж: Dash → Swordsman → персонаж
local character = script.Parent.Parent
-- Humanoid
local humanoid = character:WaitForChild("Humanoid")
-- анимация кувырка
local anim = script:WaitForChild("Animation")
-- навык класса — на E
local KEY = Enum.KeyCode.E
-- перезарядка
local COOLDOWN = 3
-- уровень игрока
local lvl = Players.LocalPlayer:WaitForChild("Lvl")
-- можно использовать
local ready = true

-- нажали клавишу
UIS.InputBegan:Connect(function(input, processed)
    -- не та клавиша, перезарядка или погиб
    if processed or input.KeyCode ~= KEY or not ready or humanoid.Health <= 0 then return end
    -- перезарядка началась
    ready = false
    -- кувырок
    if anim.AnimationId ~= "" then humanoid:FindFirstChildOfClass("Animator"):LoadAnimation(anim):Play() end
    -- скорость до кувырка (вдруг на игроке нимб)
    local old = humanoid.WalkSpeed
    -- чем выше уровень, тем быстрее; не больше 90
    humanoid.WalkSpeed = math.min(40 + lvl.Value * 5, 90)
    -- полсекунды
    task.wait(0.5)
    -- прежняя скорость
    humanoid.WalkSpeed = old
    -- перезарядка
    task.wait(COOLDOWN)
    -- можно снова
    ready = true
-- конец функции
end)

Jump

Где: ServerStorage → Classes → Archer · LocalScript
Зачем: Прыжок от уровня.

-- клавиатура
local UIS = game:GetService("UserInputService")
-- игроки
local Players = game:GetService("Players")
-- удаляет толчок
local Debris = game:GetService("Debris")
-- персонаж
local character = script.Parent.Parent
-- Humanoid
local humanoid = character:WaitForChild("Humanoid")
-- центр
local root = character:WaitForChild("HumanoidRootPart")
-- анимация прыжка
local anim = script:WaitForChild("Animation")
-- навык класса — на E
local KEY = Enum.KeyCode.E
-- перезарядка
local COOLDOWN = 5
-- уровень игрока
local lvl = Players.LocalPlayer:WaitForChild("Lvl")
-- можно прыгать
local ready = true

-- нажали клавишу
UIS.InputBegan:Connect(function(input, processed)
    -- не та клавиша, перезарядка или погиб
    if processed or input.KeyCode ~= KEY or not ready or humanoid.Health <= 0 then return end
    -- перезарядка
    ready = false
    -- анимация
    if anim.AnimationId ~= "" then humanoid:FindFirstChildOfClass("Animator"):LoadAnimation(anim):Play() end
    -- точка приложения силы
    local attachment = root:FindFirstChild("RootAttachment") or Instance.new("Attachment", root)
    -- толчок
    local push = Instance.new("LinearVelocity")
    -- за центр
    push.Attachment0 = attachment
    -- только вдоль одной линии
    push.VelocityConstraintMode = Enum.VelocityConstraintMode.Line
    -- вверх — по сторонам можно двигаться
    push.LineDirection = Vector3.new(0, 1, 0)
    -- выше с каждым уровнем, не больше 120
    push.LineVelocity = math.min(50 + lvl.Value * 5, 120)
    -- сильный
    push.MaxForce = 100000
    -- своего персонажа игрок двигает сам
    push.Parent = root
    -- толкает 0.3 секунды
    Debris:AddItem(push, 0.3)
    -- перезарядка
    task.wait(COOLDOWN)
    -- можно снова
    ready = true
-- конец функции
end)

Teleportation

Где: ServerStorage → Classes → Mage · LocalScript
Зачем: Дальность от уровня.

-- клавиатура
local UIS = game:GetService("UserInputService")
-- общее хранилище
local RS = game:GetService("ReplicatedStorage")
-- каждый кадр
local RunService = game:GetService("RunService")
-- игроки
local Players = game:GetService("Players")
-- персонаж
local character = script.Parent.Parent
-- центр
local root = character:WaitForChild("HumanoidRootPart")
-- куда указывает курсор
local mouse = Players.LocalPlayer:GetMouse()
-- своя копия маркера — образец не трогаем
local marker = RS:WaitForChild("MageEffects"):WaitForChild("TeleportLocation"):Clone()
-- не падает
marker.Anchored = true
-- сквозь
marker.CanCollide = false
-- курсор его не замечает
marker.CanQuery = false
-- и mouse.Hit тоже
mouse.TargetFilter = marker
-- держать E — выбор места, отпустить — телепорт
local KEY = Enum.KeyCode.E
-- перезарядка
local COOLDOWN = 3
-- уровень игрока
local lvl = Players.LocalPlayer:WaitForChild("Lvl")
-- дальше с каждым уровнем, не больше 100
local function Range() return math.min(35 + lvl.Value * 5, 100) end
-- выбираем место
local aiming = false
-- можно телепортироваться
local ready = true

-- место достаточно близко?
local function InRange()
    -- Magnitude — длина вектора
    return (mouse.Hit.Position - root.Position).Magnitude < Range()
-- конец функции
end

-- нажали
UIS.InputBegan:Connect(function(input, processed)
    -- не E или перезарядка
    if processed or input.KeyCode ~= KEY or not ready or aiming then return end
    -- выбираем место
    aiming = true
    -- маркер виден только этому игроку
    marker.Parent = workspace
-- конец функции
end)
-- каждый кадр
RunService.RenderStepped:Connect(function()
    -- не выбираем
    if not aiming then return end
    -- маркер под курсором
    marker.Position = mouse.Hit.Position
    -- голубой — можно, красный — далеко
    marker.Color = InRange() and Color3.fromRGB(0, 255, 255) or Color3.fromRGB(255, 0, 0)
-- конец функции
end)
-- отпустили
UIS.InputEnded:Connect(function(input)
    -- не E или не выбирали
    if input.KeyCode ~= KEY or not aiming then return end
    -- выбор закончен
    aiming = false
    -- прячем маркер
    marker.Parent = nil
    -- далеко — не телепортируемся
    if not InRange() then return end
    -- перезарядка
    ready = false
    -- переносимся, смотрим в ту же сторону
    character:PivotTo(root.CFrame.Rotation + mouse.Hit.Position + Vector3.new(0, 3, 0))
    -- ждём
    task.wait(COOLDOWN)
    -- можно снова
    ready = true
-- конец функции
end)

Sneak

Где: ServerStorage → Classes → Archer · LocalScript
Зачем: Красться с 3-го уровня.

-- клавиатура
local UIS = game:GetService("UserInputService")
-- игроки
local Players = game:GetService("Players")
-- персонаж
local character = script.Parent.Parent
-- Humanoid
local humanoid = character:WaitForChild("Humanoid")
-- поза «крадётся» (Movement)
local anim = script:WaitForChild("Animation")
-- уровень игрока
local lvl = Players.LocalPlayer:WaitForChild("Lvl")
-- держать F — красться
local KEY = Enum.KeyCode.F
-- навык открывается с 3-го уровня
local NEED_LEVEL = 3
-- дорожка позы
local track = nil
-- скорость до того, как начали красться
local old = nil

-- нажали
UIS.InputBegan:Connect(function(input, processed)
    -- не F, рано или уже крадёмся
    if processed or input.KeyCode ~= KEY or lvl.Value < NEED_LEVEL or old then return end
    -- запоминаем скорость
    old = humanoid.WalkSpeed
    -- вдвое медленнее
    humanoid.WalkSpeed = old / 2
    -- поза задана
    if anim.AnimationId ~= "" then
        -- загружаем
        track = humanoid:FindFirstChildOfClass("Animator"):LoadAnimation(anim)
        -- крадёмся
        track:Play()
    -- конец проверки
    end
-- конец функции
end)
-- отпустили
UIS.InputEnded:Connect(function(input)
    -- не F или не крались
    if input.KeyCode ~= KEY or not old then return end
    -- прежняя скорость
    humanoid.WalkSpeed = old
    -- больше не крадёмся
    old = nil
    -- обычная поза
    if track then track:Stop() end
-- конец функции
end)

Проверка

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