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)
Где: 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Где: 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)Где: 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)Где: 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)Где: 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)Где: 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)Где: 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)Где: 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)Где: 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) и убедись: