Roblox · RPG · урок 13
Итог курса на одной странице: полная структура RPG и финальные версии всех 28 скриптов. Удобно сверить свою игру или собрать её заново.
Синим — что добавляем на этом уроке.
Workspace ├─ MapFolder (Folder) — вся карта: Baseplate, SpawnLocation, деревня, тайники │ ├─ Chest, Coin (Model / Part) — Highlight, CanInteract, Coin, Sound │ ├─ Note (Part) — Highlight, CanInteract, Text │ ├─ Market (Model) — лавки; Торговец (Part): CanInteract, CanBuy │ ├─ ExpStone (Part) — LvlTest │ └─ Sound (Part) — музыка тайника ├─ SafePart (Part) — куда переносит свиток ├─ CameraPart (Part) — ракурс главного меню └─ Mobs (Folder) — Wolf: Humanoid, HealthGui → Lvl, Background → Bar (HealthBar), HealthValue; MobAI ReplicatedStorage ├─ ClassEvent, InteractEvent (RemoteEvent) ├─ BuyFunction (RemoteFunction) ├─ MageEffects (Folder) — FireBall, Explosion, TeleportLocation └─ Indicator (Part) — Num → Damage ServerStorage ├─ Classes (Folder) — Swordsman (Dash), Archer (ArcherCamera, Jump, Sneak), Mage (Teleportation) ├─ Weapons (Folder) │ ├─ Sword (Tool) — Handle, Blade (Level5, Level10), YouAreNotGui, SwordScript (Animation, Swing, Hit) │ ├─ Bow (Tool) — Handle (Fire, Hit, Level5, Level10), Arrow, Bow, YouAreNotGui, BowLocal, BowServer │ └─ Staff (Tool) — Handle (Fire, Level5, Level10), SpellCast, FireBall, YouAreNotGui, FireBallLocal, FireBallServer └─ Shop (Folder) — Swordsman, Archer, Mage → Potion, Scroll, Halo (у каждого Price) ServerScriptService └─ PlayerData, ClassServer, InteractServer, UpgradeSystem, MobManager, DamageIndicatorSystem, Buy (Script) StarterPlayer → StarterPlayerScripts └─ InteractSystem (LocalScript) StarterGui — все ScreenGui — ResetOnSpawn выкл. ├─ InteractGui (BillboardGui) — Object, Interact ├─ NoteGui (ScreenGui) — NoteFrame → NoteText, CloseButton ├─ ClassGui (ScreenGui) — Frame → Swordsman, Archer, Mage, ClassLocal ├─ ExpGui (ScreenGui) — ExpFrame → ExpBar, ExpText, LevelText, ExpLocal ├─ MiniMapGui (ScreenGui) — MapFrame (ViewportFrame) → PlayerArrow; MapScript ├─ ShopGui (ScreenGui) — Swordsman, Archer, Mage → Title, Items, Message, CloseButton └─ MainMenuGui (ScreenGui) — Background, BackFrame → RPG, Logo, PlayButton; MainMenu Players → игрок (во время игры) — leaderstats → Монеты; Lvl, Exp, RequiredExp; атрибут Class
Где: ServerScriptService · Script
Зачем: Монеты, уровень, опыт, сохранение.
-- игроки
local Players = game:GetService("Players")
-- сохранение
local DSS = game:GetService("DataStoreService")
-- хранилище RPG
local store = DSS:GetDataStore("RPGData")
-- у кого данные загрузились
local loaded = {}
-- создаёт контейнер со значением
local function Value(class, name, value, parent)
-- контейнер
local v = Instance.new(class)
-- имя
v.Name = name
-- значение
v.Value = value
-- на место
v.Parent = parent
-- отдаём
return v
-- конец функции
end
-- игрок зашёл
local function Setup(player)
-- таблица по Tab
local leaderstats = Instance.new("Folder")
-- имя строго такое
leaderstats.Name = "leaderstats"
-- в игрока
leaderstats.Parent = player
-- кошелёк
local coins = Value("IntValue", "Монеты", 0, leaderstats)
-- уровень
local lvl = Value("IntValue", "Lvl", 1, player)
-- опыт
local exp = Value("IntValue", "Exp", 0, player)
-- сколько опыта до следующего уровня
local required = Value("IntValue", "RequiredExp", 100, player)
-- загружаем сохранение
local ok, data = pcall(function() return store:GetAsync("rpg_" .. player.UserId) end)
-- загрузка удалась
if ok then
-- сохранение есть
if type(data) == "table" then
-- монеты
coins.Value = data.Coins or 0
-- уровень
lvl.Value = data.Lvl or 1
-- порог уровня
required.Value = lvl.Value * 100
-- опыт
exp.Value = data.Exp or 0
-- класс — ClassServer выдаст навыки и оружие
if data.Class then player:SetAttribute("Class", data.Class) end
-- конец проверки
end
-- можно сохранять
loaded[player] = true
-- конец проверки
end
-- опыт изменился
exp.Changed:Connect(function()
-- хватает на уровень (и даже на несколько)
while exp.Value >= required.Value do
-- лишний опыт не пропадает
exp.Value -= required.Value
-- новый уровень
lvl.Value += 1
-- следующий порог выше
required.Value = lvl.Value * 100
-- конец цикла
end
-- конец функции
end)
-- конец функции
end
-- сохранить игрока
local function Save(player)
-- не загрузились — не затираем сохранение нулями
if not loaded[player] then return end
-- что сохраняем
local data = {
-- монеты
Coins = player.leaderstats["Монеты"].Value,
-- уровень
Lvl = player.Lvl.Value,
-- опыт
Exp = player.Exp.Value,
-- класс
Class = player:GetAttribute("Class"),
-- конец таблицы
}
-- записываем
pcall(function() store:SetAsync("rpg_" .. player.UserId, data) end)
-- конец функции
end
-- новые игроки
Players.PlayerAdded:Connect(Setup)
-- кто зашёл раньше скрипта
for _, player in ipairs(Players:GetPlayers()) do task.spawn(Setup, player) end
-- игрок вышел
Players.PlayerRemoving:Connect(function(player)
-- сохраняем
Save(player)
-- забываем
loaded[player] = nil
-- конец функции
end)
-- сервер закрывается
game:BindToClose(function()
-- сохраняем всех
for _, player in ipairs(Players:GetPlayers()) do Save(player) end
-- конец функции
end)Где: ServerScriptService · Script
Зачем: Класс, навыки, оружие.
-- игроки
local Players = game:GetService("Players")
-- общее хранилище
local RS = game:GetService("ReplicatedStorage")
-- образцы классов и оружия
local SS = game:GetService("ServerStorage")
-- выбор класса
local classEvent = RS:WaitForChild("ClassEvent")
-- папки навыков: Swordsman, Archer, Mage
local classes = SS:WaitForChild("Classes")
-- классовое оружие
local weapons = SS:WaitForChild("Weapons")
-- класс → его оружие
local WEAPON = {Swordsman = "Sword", Archer = "Bow", Mage = "Staff"}
-- папка навыков в персонажа
local function GiveSkills(player)
-- класс игрока
local className = player:GetAttribute("Class")
-- персонаж
local character = player.Character
-- нет класса, персонажа или уже выдано
if not className or not character or character:FindFirstChild(className) then return end
-- папка класса
local folder = classes:FindFirstChild(className)
-- навыки (локальные скрипты) работают в персонаже
if folder then folder:Clone().Parent = character end
-- конец функции
end
-- классовое оружие
local function GiveWeapon(player)
-- образец
local weapon = weapons:FindFirstChild(WEAPON[player:GetAttribute("Class")] or "")
-- предметы, которые Roblox выдаёт при каждом появлении
local gear = player:FindFirstChild("StarterGear")
-- нет оружия или уже выдано
if not weapon or not gear or gear:FindFirstChild(weapon.Name) then return end
-- будет у игрока после каждой смерти
weapon:Clone().Parent = gear
-- рюкзак — прямо сейчас
local backpack = player:FindFirstChildOfClass("Backpack")
-- в рюкзак
if backpack then weapon:Clone().Parent = backpack end
-- конец функции
end
-- игрок выбрал класс
classEvent.OnServerEvent:Connect(function(player, className)
-- класс выбирается один раз
if player:GetAttribute("Class") then return end
-- нет такого класса
if typeof(className) ~= "string" or not classes:FindFirstChild(className) then return end
-- запоминаем — дальше сработает подписка ниже
player:SetAttribute("Class", className)
-- конец функции
end)
-- игрок зашёл
local function Setup(player)
-- класс выбран или загружен из сохранения
player:GetAttributeChangedSignal("Class"):Connect(function()
-- навыки
GiveSkills(player)
-- оружие
GiveWeapon(player)
-- конец функции
end)
-- каждое появление
player.CharacterAdded:Connect(function(character)
-- ждём, пока персонаж соберётся
character:WaitForChild("Humanoid")
-- навыки снова (старые удалились вместе с персонажем)
GiveSkills(player)
-- конец функции
end)
-- класс загрузился раньше, чем мы подписались
if player:GetAttribute("Class") then
-- навыки
GiveSkills(player)
-- оружие
GiveWeapon(player)
-- конец проверки
end
-- конец функции
end
-- новые игроки
Players.PlayerAdded:Connect(Setup)
-- кто зашёл раньше скрипта
for _, player in ipairs(Players:GetPlayers()) do Setup(player) endГде: ServerScriptService · Script
Зачем: Монеты и сундуки.
-- общее хранилище
local RS = game:GetService("ReplicatedStorage")
-- клик игрока по монете или сундуку
local interactEvent = RS:WaitForChild("InteractEvent")
-- дальше — не засчитываем (защита от читов)
local MAX_DISTANCE = 32
-- через сколько секунд награда вернётся
local RESPAWN = 60
-- спрятать или показать монету/сундук
local function SetVisible(item, visible)
-- всё внутри
local list = item:GetDescendants()
-- и сам объект
table.insert(list, item)
-- каждый
for _, obj in ipairs(list) do
-- деталь
if obj:IsA("BasePart") then
-- запоминаем обычную прозрачность
if obj:GetAttribute("BaseTransparency") == nil then obj:SetAttribute("BaseTransparency", obj.Transparency) end
-- видна или нет
obj.Transparency = visible and obj:GetAttribute("BaseTransparency") or 1
-- подсветка
elseif obj:IsA("Highlight") then
-- вместе с объектом
obj.Enabled = visible
-- конец проверки
end
-- конец перебора
end
-- конец функции
end
-- игрок кликнул
interactEvent.OnServerEvent:Connect(function(player, item)
-- пришло не то — игнорируем
if typeof(item) ~= "Instance" or not item:IsDescendantOf(workspace) then return end
-- метка доступности
local can = item:FindFirstChild("CanInteract")
-- сколько монет внутри
local coin = item:FindFirstChild("Coin")
-- уже взято или это не награда
if not can or not can.Value or not coin then return end
-- где объект
local position = item:IsA("Model") and item:GetPivot().Position or item.Position
-- далеко — не засчитываем
if player:DistanceFromCharacter(position) > MAX_DISTANCE then return end
-- больше не взять
can.Value = false
-- монеты в кошелёк
player.leaderstats["Монеты"].Value += coin.Value
-- звук монеты
local sound = item:FindFirstChild("Sound")
-- звон
if sound then sound:Play() end
-- исчезает
SetVisible(item, false)
-- ждём
task.wait(RESPAWN)
-- появляется снова
SetVisible(item, true)
-- можно взять снова
can.Value = true
-- конец функции
end)Где: 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Где: ServerScriptService · Script
Зачем: Уровни мобов, награда, возрождение.
-- все мобы
local mobs = workspace:WaitForChild("Mobs")
-- через сколько секунд моб вернётся
local RESPAWN = 15
-- следим за одним мобом
local function Setup(mob)
-- копия целого — для возрождения
local sample = mob:Clone()
-- где стоял
local spot = mob:GetPivot()
-- Humanoid
local humanoid = mob:WaitForChild("Humanoid")
-- уровень моба
local level = mob:FindFirstChild("Level") or Instance.new("IntValue")
-- имя — его читают HealthBar и MobAI
level.Name = "Level"
-- случайный уровень
level.Value = math.random(1, 5)
-- в моба
level.Parent = mob
-- здоровье растёт с уровнем
humanoid.MaxHealth = 50 * level.Value
-- полное
humanoid.Health = humanoid.MaxHealth
-- моб побеждён
humanoid.Died:Connect(function()
-- метка от оружия
local tag = humanoid:FindFirstChild("creator")
-- кто победил
local player = tag and tag.Value
-- игрок ещё в игре
if player and player.Parent and player:FindFirstChild("Exp") then
-- опыт
player.Exp.Value += 40 * level.Value
-- монеты
player.leaderstats["Монеты"].Value += 5 * level.Value
-- конец проверки
end
-- ждём
task.wait(RESPAWN)
-- убираем тело
mob:Destroy()
-- новый моб
local new = sample:Clone()
-- на старое место
new:PivotTo(spot)
-- в игру
new.Parent = mobs
-- следим и за ним
Setup(new)
-- конец функции
end)
-- конец функции
end
-- все мобы
for _, mob in ipairs(mobs:GetChildren()) do Setup(mob) endГде: ServerScriptService · Script
Зачем: Цифры урона.
-- образец индикатора
local RS = game:GetService("ReplicatedStorage")
-- удаляет индикаторы
local Debris = game:GetService("Debris")
-- кубик с BillboardGui Num → Damage
local template = RS:WaitForChild("Indicator")
-- следим за здоровьем одного персонажа или моба
local function Watch(humanoid)
-- здоровье до изменения
local last = humanoid.Health
-- изменилось
humanoid.HealthChanged:Connect(function(health)
-- сколько потерял
local damage = math.floor(last - health + 0.5)
-- запоминаем
last = health
-- где он
local root = humanoid.Parent and humanoid.Parent:FindFirstChild("HumanoidRootPart")
-- это лечение или нет центра
if damage <= 0 or not root then return end
-- новый индикатор
local indicator = template:Clone()
-- рядом, чуть в стороне
indicator.CFrame = CFrame.new(root.Position + Vector3.new(math.random(-2, 2), 2 + math.random(0, 2), math.random(-2, 2)))
-- текст урона
indicator.Num.Damage.Text = "-" .. damage
-- показываем
indicator.Parent = workspace
-- 0.7 секунды
Debris:AddItem(indicator, 0.7)
-- конец функции
end)
-- конец функции
end
-- все, кто уже есть
for _, obj in ipairs(workspace:GetDescendants()) do
-- следим
if obj:IsA("Humanoid") then Watch(obj) end
-- конец перебора
end
-- новые: игроки, возродившиеся мобы
workspace.DescendantAdded:Connect(function(obj)
-- следим и за ними
if obj:IsA("Humanoid") then Watch(obj) end
-- конец функции
end)Где: ServerScriptService · Script
Зачем: Покупки у торговцев.
-- общее хранилище
local RS = game:GetService("ReplicatedStorage")
-- товары — клиент их не видит и не подменит
local SS = game:GetService("ServerStorage")
-- покупка
local buyFunction = RS:WaitForChild("BuyFunction")
-- папки товаров по классам
local shop = SS:WaitForChild("Shop")
-- игрок нажал «купить»
buyFunction.OnServerInvoke = function(player, className, itemName)
-- пришёл мусор
if typeof(className) ~= "string" or typeof(itemName) ~= "string" then return false, "Ошибка" end
-- чужой торговец
if player:GetAttribute("Class") ~= className then return false, "Этот торговец не для твоего класса" end
-- товары класса
local folder = shop:FindFirstChild(className)
-- товар
local item = folder and folder:FindFirstChild(itemName)
-- цена
local price = item and item:FindFirstChild("Price")
-- нет
if not price then return false, "Нет такого товара" end
-- кошелёк
local coins = player.leaderstats["Монеты"]
-- денег мало
if coins.Value < price.Value then return false, "Не хватает монет: нужно " .. price.Value end
-- копия товара
local copy = item:Clone()
-- нимб — надеваем
if copy:IsA("Accessory") then
-- персонаж
local humanoid = player.Character and player.Character:FindFirstChildOfClass("Humanoid")
-- нет персонажа — денег не берём
if not humanoid then return false, "Персонаж не найден" end
-- надели
humanoid:AddAccessory(copy)
-- инструмент
else
-- в рюкзак (а не на землю, как в методичке)
copy.Parent = player.Backpack
-- конец проверки
end
-- списываем
coins.Value -= price.Value
-- ответ
return true, "Куплено: " .. itemName
-- конец функции
endГде: StarterPlayer → StarterPlayerScripts · LocalScript
Зачем: Взаимодействие и магазин.
-- игроки
local Players = game:GetService("Players")
-- общее хранилище
local RS = game:GetService("ReplicatedStorage")
-- каждый кадр
local RunService = game:GetService("RunService")
-- этот игрок
local player = Players.LocalPlayer
-- мышь: mouse.Target — деталь под курсором
local mouse = player:GetMouse()
-- интерфейс игрока
local playerGui = player:WaitForChild("PlayerGui")
-- надпись над объектом (BillboardGui)
local interactGui = playerGui:WaitForChild("InteractGui")
-- окно записки
local noteFrame = playerGui:WaitForChild("NoteGui"):WaitForChild("NoteFrame")
-- просим сервер: «взять монету/сундук»
local interactEvent = RS:WaitForChild("InteractEvent")
-- дальше 30 studs не взаимодействуем
local MAX_DISTANCE = 30
-- окна торговцев
local shopGui = playerGui:WaitForChild("ShopGui")
-- покупка с ответом сервера
local buyFunction = RS:WaitForChild("BuyFunction")
-- объект с CanInteract под курсором (деталь или её модель)
local function Target()
-- деталь под курсором
local obj = mouse.Target
-- поднимаемся к родителям
while obj and obj ~= workspace do
-- метка «с этим можно взаимодействовать»
local can = obj:FindFirstChild("CanInteract")
-- нашли, и он сейчас доступен
if can and can.Value then return obj end
-- выше
obj = obj.Parent
-- конец цикла
end
-- ничего нет
return nil
-- конец функции
end
-- где объект
local function PositionOf(item)
-- у модели — центр
if item:IsA("Model") then return item:GetPivot().Position end
-- у детали — её позиция
return item.Position
-- конец функции
end
-- что написать под именем
local function ActionText(item)
-- записка
if item:FindFirstChild("Text") then return "ЛКМ — прочитать" end
-- торговец
if item:FindFirstChild("CanBuy") then return "ЛКМ — торговать" end
-- монета или сундук
return "ЛКМ — взять"
-- конец функции
end
-- каждый кадр
RunService.RenderStepped:Connect(function()
-- что под курсором
local item = Target()
-- есть и не слишком далеко
if item and player:DistanceFromCharacter(PositionOf(item)) < MAX_DISTANCE * 2 then
-- надпись над объектом
interactGui.Adornee = item:IsA("Model") and (item.PrimaryPart or item:FindFirstChildWhichIsA("BasePart")) or item
-- имя объекта
interactGui.Object.Text = item.Name
-- что можно сделать
interactGui.Interact.Text = ActionText(item)
-- показываем
interactGui.Enabled = true
-- ничего нет
else
-- отвязываем
interactGui.Adornee = nil
-- прячем
interactGui.Enabled = false
-- конец проверки
end
-- конец функции
end)
-- окно торговца
local function OpenShop(className)
-- окно для этого класса
local frame = shopGui:FindFirstChild(className)
-- такого окна нет
if not frame then return end
-- торговец не для нашего класса
if player:GetAttribute("Class") ~= className then
-- его ответ
noteFrame.NoteText.Text = "Торговец: я работаю только с классом " .. className
-- показываем
noteFrame.Visible = true
-- окно не открываем
return
-- конец проверки
end
-- без старых сообщений
frame.Message.Text = ""
-- открываем
frame.Visible = true
-- конец функции
end
-- каждое окно торговца — подписываемся один раз
for _, frame in ipairs(shopGui:GetChildren()) do
-- только окна
if frame:IsA("Frame") then
-- «Закрыть»
frame.CloseButton.MouseButton1Click:Connect(function() frame.Visible = false end)
-- кнопки товаров
for _, button in ipairs(frame.Items:GetChildren()) do
-- только кнопки
if button:IsA("TextButton") then
-- нажали
button.MouseButton1Click:Connect(function()
-- класс и товар — серверу, ждём ответ
local _, text = buyFunction:InvokeServer(frame.Name, button.Name)
-- «Куплено» или «Не хватает монет»
frame.Message.Text = text
-- конец функции
end)
-- конец проверки
end
-- конец перебора
end
-- конец проверки
end
-- конец перебора
end
-- клик левой кнопкой
mouse.Button1Down:Connect(function()
-- что под курсором
local item = Target()
-- ничего или далеко
if not item or player:DistanceFromCharacter(PositionOf(item)) > MAX_DISTANCE then return end
-- это записка?
local text = item:FindFirstChild("Text")
-- да
if text then
-- текст записки
noteFrame.NoteText.Text = text.Value
-- показываем окно
noteFrame.Visible = true
-- шелест бумаги
local sound = item:FindFirstChild("Sound")
-- играем у себя
if sound then sound:Play() end
-- это торговец
elseif item:FindFirstChild("CanBuy") then
-- окно его класса
OpenShop(item.CanBuy.Value)
-- монета или сундук
else
-- деньги начисляет только сервер
interactEvent:FireServer(item)
-- конец проверки
end
-- конец функции
end)
-- «Закрыть» записку — подписка одна
noteFrame:WaitForChild("CloseButton").MouseButton1Click:Connect(function()
-- прячем
noteFrame.Visible = false
-- конец функции
end)Где: StarterGui → MiniMapGui · LocalScript
Зачем: Мини-карта.
-- игроки
local Players = game:GetService("Players")
-- каждый кадр
local RunService = game:GetService("RunService")
-- этот игрок
local player = Players.LocalPlayer
-- всё, что видно на мини-карте
local mapFolder = workspace:WaitForChild("MapFolder")
-- ViewportFrame — окно с 3D-копией карты
local mapFrame = script.Parent:WaitForChild("MapFrame")
-- стрелка игрока
local arrow = mapFrame:WaitForChild("PlayerArrow")
-- с какой высоты смотрит камера карты
local HEIGHT = 2500
-- своя камера для окна карты
local camera = Instance.new("Camera")
-- узкий угол — почти без перспективы, как план
camera.FieldOfView = 1
-- лежит в самом окне
camera.Parent = mapFrame
-- окно смотрит через неё
mapFrame.CurrentCamera = camera
-- каждый объект карты
for _, obj in ipairs(mapFolder:GetChildren()) do
-- копия (некоторые объекты копироваться не умеют)
local ok, copy = pcall(function() return obj:Clone() end)
-- получилось
if ok and copy then
-- всё внутри копии
for _, inner in ipairs(copy:GetDescendants()) do
-- скрипты убираем — иначе локальные заработают
if inner:IsA("LuaSourceContainer") then inner:Destroy() end
-- конец перебора
end
-- в окно карты
copy.Parent = mapFrame
-- конец проверки
end
-- конец перебора
end
-- каждый кадр
RunService.RenderStepped:Connect(function()
-- персонаж (после смерти — новый)
local character = player.Character
-- его центр
local root = character and character:FindFirstChild("HumanoidRootPart")
-- персонажа ещё нет
if not root then return end
-- сверху вниз; «верх» карты — север
camera.CFrame = CFrame.lookAt(root.Position + Vector3.new(0, HEIGHT, 0), root.Position, Vector3.new(0, 0, -1))
-- стрелка поворачивается вместе с персонажем
arrow.Rotation = -root.Orientation.Y
-- конец функции
end)Где: StarterGui → ExpGui → ExpFrame · LocalScript
Зачем: Полоса опыта.
-- игроки
local Players = game:GetService("Players")
-- этот игрок
local player = Players.LocalPlayer
-- опыт
local exp = player:WaitForChild("Exp")
-- уровень
local lvl = player:WaitForChild("Lvl")
-- порог уровня
local required = player:WaitForChild("RequiredExp")
-- ExpFrame — подложка полосы
local frame = script.Parent
-- полоса опыта
local bar = frame:WaitForChild("ExpBar")
-- «40 / 100»
local expText = frame:WaitForChild("ExpText")
-- «Ур. 2»
local lvlText = frame:WaitForChild("LevelText")
-- обновить полосу и надписи
local function Update()
-- доля до следующего уровня
local k = math.clamp(exp.Value / required.Value, 0, 1)
-- плавно меняем длину
bar:TweenSize(UDim2.fromScale(k, 1), Enum.EasingDirection.Out, Enum.EasingStyle.Quad, 0.3, true)
-- числа
expText.Text = exp.Value .. " / " .. required.Value
-- уровень
lvlText.Text = "Ур. " .. lvl.Value
-- конец функции
end
-- опыт изменился
exp.Changed:Connect(Update)
-- новый уровень
lvl.Changed:Connect(Update)
-- новый порог
required.Changed:Connect(Update)
-- сразу при входе
Update()Где: StarterGui → ClassGui → Frame · LocalScript
Зачем: Выбор класса.
-- игроки
local Players = game:GetService("Players")
-- общее хранилище
local RS = game:GetService("ReplicatedStorage")
-- этот игрок
local player = Players.LocalPlayer
-- окно выбора класса
local frame = script.Parent
-- сообщить серверу выбор
local classEvent = RS:WaitForChild("ClassEvent")
-- показать окно, только если класса ещё нет
local function Update()
-- класс выбран (или загружен) — окно не нужно
frame.Visible = player:GetAttribute("Class") == nil
-- конец функции
end
-- все объекты окна
for _, button in ipairs(frame:GetChildren()) do
-- кнопки классов
if button:IsA("TextButton") then
-- нажали
button.MouseButton1Click:Connect(function()
-- прячем окно
frame.Visible = false
-- имя кнопки = имя класса
classEvent:FireServer(button.Name)
-- конец функции
end)
-- конец проверки
end
-- конец перебора
end
-- класс появился — окно прячется
player:GetAttributeChangedSignal("Class"):Connect(Update)
-- сразу
Update()Где: StarterGui → MainMenuGui · LocalScript
Зачем: Главное меню.
-- игроки
local Players = game:GetService("Players")
-- каждый кадр
local RunService = game:GetService("RunService")
-- этот игрок
local player = Players.LocalPlayer
-- камера
local camera = workspace.CurrentCamera
-- MainMenuGui
local gui = script.Parent
-- «Играть»
local playButton = gui:WaitForChild("BackFrame"):WaitForChild("PlayButton")
-- откуда смотрит камера меню
local cameraPart = workspace:WaitForChild("CameraPart")
-- управление персонажем
local controls = require(player:WaitForChild("PlayerScripts"):WaitForChild("PlayerModule")):GetControls()
-- в меню персонаж не ходит
controls:Disable()
-- каждый кадр, пока меню
local follow = RunService.RenderStepped:Connect(function()
-- камерой управляет скрипт
camera.CameraType = Enum.CameraType.Scriptable
-- красивый вид на карту
camera.CFrame = cameraPart.CFrame
-- конец функции
end)
-- «Играть»
playButton.MouseButton1Click:Connect(function()
-- камера свободна
follow:Disconnect()
-- обычная камера за персонажем
camera.CameraType = Enum.CameraType.Custom
-- можно ходить
controls:Enable()
-- меню прячется (ResetOnSpawn выкл. — после смерти не вернётся)
gui.Enabled = false
-- конец функции
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 · LocalScript
Зачем: Лук: поза и клик.
-- игроки
local Players = game:GetService("Players")
-- лук
local tool = script.Parent
-- сигнал «выстрел» на сервер
local bowEvent = tool:WaitForChild("Bow")
-- «Ты не лучник»
local notGui = tool:WaitForChild("YouAreNotGui")
-- поза с луком (Movement, зациклена)
local holdAnim = script:WaitForChild("Animation")
-- этот игрок
local player = Players.LocalPlayer
-- куда целимся
local mouse = player:GetMouse()
-- чей это лук
local CLASS = "Archer"
-- дорожка позы
local holdTrack = nil
-- взяли лук
local function OnEquipped()
-- Humanoid
local humanoid = tool.Parent:FindFirstChildOfClass("Humanoid")
-- анимации
local animator = humanoid and humanoid:FindFirstChildOfClass("Animator")
-- поза задана
if animator and holdAnim.AnimationId ~= "" then
-- загружаем
holdTrack = animator:LoadAnimation(holdAnim)
-- по кругу
holdTrack.Looped = true
-- держим лук
holdTrack:Play()
-- конец проверки
end
-- конец функции
end
-- каждый раз, когда берут
tool.Equipped:Connect(OnEquipped)
-- подобрали с земли — уже в руках
if tool.Parent and tool.Parent:FindFirstChildOfClass("Humanoid") then OnEquipped() end
-- убрали лук
tool.Unequipped:Connect(function()
-- обычная поза
if holdTrack then holdTrack:Stop() end
-- дорожки нет
holdTrack = nil
-- конец функции
end)
-- клик — подписка одна, не внутри Equipped
tool.Activated:Connect(function()
-- чужой класс
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
-- стреляем туда, где центр экрана
bowEvent:FireServer(mouse.Hit.Position)
-- конец функции
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 · LocalScript
Зачем: Посох: выпад.
-- игроки
local Players = game:GetService("Players")
-- посох
local tool = script.Parent
-- сигнал «заклинание» на сервер
local fireballEvent = tool:WaitForChild("FireBall")
-- «Ты не маг»
local notGui = tool:WaitForChild("YouAreNotGui")
-- выпад посохом; кадр Release — момент выстрела
local anim = script:WaitForChild("Animation")
-- этот игрок
local player = Players.LocalPlayer
-- куда целимся
local mouse = player:GetMouse()
-- чей посох
local CLASS = "Mage"
-- перезарядка
local COOLDOWN = 1
-- дорожка анимации
local track = nil
-- колдуем
local busy = false
-- взяли посох
local function OnEquipped()
-- Humanoid
local humanoid = tool.Parent:FindFirstChildOfClass("Humanoid")
-- анимации
local animator = humanoid and humanoid:FindFirstChildOfClass("Animator")
-- готовим выпад
if animator and anim.AnimationId ~= "" then track = animator:LoadAnimation(anim) end
-- конец функции
end
-- каждый раз, когда берут
tool.Equipped:Connect(OnEquipped)
-- уже в руках
if tool.Parent and tool.Parent:FindFirstChildOfClass("Humanoid") then OnEquipped() end
-- заклинание
local function Cast()
-- колдуем
busy = true
-- шар ещё не вылетел
local fired = false
-- выпустить шар
local function Fire()
-- только один раз
if fired then return end
-- вылетел
fired = true
-- серверу — куда лететь
fireballEvent:FireServer(mouse.Hit.Position)
-- конец функции
end
-- анимация есть
if track then
-- анимация дошла до именованного кадра
local connection = track.KeyframeReached:Connect(function(name)
-- кадр выстрела
if name == "Release" then Fire() end
-- конец функции
end)
-- выпад
track:Play()
-- ждём кадр Release
task.wait(0.6)
-- подписка только на этот выпад
connection:Disconnect()
-- конец проверки
end
-- кадра не было или анимации нет — стреляем сейчас
Fire()
-- перезарядка
task.wait(COOLDOWN)
-- можно снова
busy = false
-- конец функции
end
-- клик с посохом
tool.Activated:Connect(function()
-- ещё колдуем
if busy 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
-- колдуем
Cast()
-- конец функции
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 RunService = game:GetService("RunService")
-- персонаж: скрипт → Archer → персонаж
local character = script.Parent.Parent
-- Humanoid
local humanoid = character:WaitForChild("Humanoid")
-- центр
local root = character:WaitForChild("HumanoidRootPart")
-- камера
local camera = workspace.CurrentCamera
-- сейчас лук в руках?
local aiming = false
-- каждый кадр
RunService.RenderStepped:Connect(function()
-- что в руках
local tool = character:FindFirstChildOfClass("Tool")
-- лук
if tool and tool.Name == "Bow" then
-- целимся
aiming = true
-- курсор в центре экрана — камера крутится мышью
UIS.MouseBehavior = Enum.MouseBehavior.LockCenter
-- камера чуть правее — вид из-за плеча
humanoid.CameraOffset = Vector3.new(2, 0, 0)
-- персонаж не разворачивается сам
humanoid.AutoRotate = false
-- куда смотрит камера
local look = camera.CFrame.LookVector
-- то же направление по горизонтали
local flat = Vector3.new(look.X, 0, look.Z)
-- камера не смотрит строго вверх или вниз
if flat.Magnitude > 0.01 then
-- персонаж смотрит туда же — не стреляет себе за спину
root.CFrame = CFrame.new(root.Position, root.Position + flat)
-- конец проверки
end
-- лук убрали
elseif aiming then
-- больше не целимся
aiming = false
-- обычный курсор
UIS.MouseBehavior = Enum.MouseBehavior.Default
-- камера на место
humanoid.CameraOffset = Vector3.zero
-- персонаж снова поворачивается сам
humanoid.AutoRotate = true
-- конец проверки
end
-- конец функции
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 → Archer · LocalScript
Зачем: Красться.
-- клавиатура
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)Где: 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)Где: Workspace → Mobs → Wolf → HealthGui → Background → Bar · Script
Зачем: Полоса здоровья моба.
-- полоса здоровья
local bar = script.Parent
-- HealthGui: Bar → Background → HealthGui
local gui = bar.Parent.Parent
-- «120 / 150»
local healthText = bar.Parent:WaitForChild("HealthValue")
-- «Ур. 3»
local lvlText = gui:WaitForChild("Lvl")
-- модель моба
local mob = gui.Parent
-- его здоровье
local humanoid = mob:WaitForChild("Humanoid")
-- уровень моба (ставит MobManager)
local level = mob:WaitForChild("Level")
-- обновить полоску
local function Update()
-- длина = доля здоровья
bar.Size = UDim2.fromScale(math.clamp(humanoid.Health / humanoid.MaxHealth, 0, 1), 1)
-- числа
healthText.Text = math.ceil(humanoid.Health) .. " / " .. humanoid.MaxHealth
-- уровень
lvlText.Text = "Ур. " .. level.Value
-- конец функции
end
-- урон
humanoid.HealthChanged:Connect(Update)
-- новый максимум
humanoid:GetPropertyChangedSignal("MaxHealth"):Connect(Update)
-- новый уровень
level.Changed:Connect(Update)
-- сразу
Update()Где: Workspace → Mobs → Wolf · Script
Зачем: Поведение моба.
-- игроки
local Players = game:GetService("Players")
-- моб
local mob = script.Parent
-- Humanoid
local humanoid = mob:WaitForChild("Humanoid")
-- центр
local root = mob:WaitForChild("HumanoidRootPart")
-- уровень — от него урон
local level = mob:WaitForChild("Level")
-- Animator
local animator = humanoid:FindFirstChildOfClass("Animator") or Instance.new("Animator", humanoid)
-- замечает игрока ближе 30 studs
local SIGHT = 30
-- кусает ближе 5 studs
local ATTACK_RANGE = 5
-- секунд между укусами
local COOLDOWN = 1.2
-- ходьба
local walk = Instance.new("Animation")
-- стандартная R15
walk.AnimationId = "rbxassetid://507777826"
-- загружаем
local walkTrack = animator:LoadAnimation(walk)
-- по кругу
walkTrack.Looped = true
-- скорость изменилась
humanoid.Running:Connect(function(speed)
-- идёт — шагаем, стоит — стоим
if speed > 0.5 and not walkTrack.IsPlaying then walkTrack:Play() elseif speed <= 0.5 then walkTrack:Stop() end
-- конец функции
end)
-- движение считает сервер
pcall(function() root:SetNetworkOwner(nil) end)
-- куда возвращаться
local home = root.Position
-- время прошлого укуса
local lastAttack = 0
-- пока жив
while humanoid.Health > 0 do
-- ближайший игрок
local best, bestRoot, bestDistance = nil, nil, SIGHT
-- все игроки
for _, player in ipairs(Players:GetPlayers()) do
-- персонаж
local character = player.Character
-- его Humanoid
local target = character and character:FindFirstChildOfClass("Humanoid")
-- его центр
local targetRoot = character and character:FindFirstChild("HumanoidRootPart")
-- жив
if target and targetRoot and target.Health > 0 then
-- расстояние
local distance = (targetRoot.Position - root.Position).Magnitude
-- ближе прошлого
if distance < bestDistance then best, bestRoot, bestDistance = target, targetRoot, distance end
-- конец проверки
end
-- конец перебора
end
-- есть цель
if best then
-- бежим к ней
humanoid:MoveTo(bestRoot.Position)
-- рядом и укус готов
if bestDistance <= ATTACK_RANGE and os.clock() - lastAttack >= COOLDOWN then
-- запоминаем
lastAttack = os.clock()
-- сильнее с уровнем моба
best:TakeDamage(3 + level.Value * 2)
-- конец проверки
end
-- никого
else
-- домой
humanoid:MoveTo(home)
-- конец проверки
end
-- 4 раза в секунду
task.wait(0.25)
-- конец цикла
endГде: Workspace → MapFolder → ExpStone · Script
Зачем: Камень опыта.
-- игроки
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 → Shop → класс → Potion · Script
Зачем: Зелье.
-- зелье
local tool = script.Parent
-- анимация глотка
local anim = tool:WaitForChild("Drink")
-- сколько лечит
local HEAL = 50
-- уже выпито
local used = false
-- клик — выпить
tool.Activated:Connect(function()
-- кто пьёт
local humanoid = tool.Parent:FindFirstChildOfClass("Humanoid")
-- уже, нет персонажа или погиб
if used or not humanoid or humanoid.Health <= 0 then return end
-- выпито
used = true
-- глоток
if anim.AnimationId ~= "" then humanoid:FindFirstChildOfClass("Animator"):LoadAnimation(anim):Play() end
-- +50, не выше максимума
humanoid.Health = math.min(humanoid.Health + HEAL, humanoid.MaxHealth)
-- зелья больше нет
tool:Destroy()
-- конец функции
end)Где: ServerStorage → Shop → класс → Scroll · Script
Зачем: Свиток.
-- свиток
local tool = script.Parent
-- клик — прочитать свиток
tool.Activated:Connect(function()
-- кто читает
local character = tool.Parent
-- безопасное место
local safe = workspace:FindFirstChild("SafePart")
-- места нет или нет персонажа
if not safe or not character:FindFirstChild("HumanoidRootPart") then return end
-- переносим персонажа целиком
character:PivotTo(safe.CFrame + Vector3.new(0, 4, 0))
-- свиток сгорел
tool:Destroy()
-- конец функции
end)Где: ServerStorage → Shop → класс → Halo · Script
Зачем: Нимб.
-- нимб (Accessory) — скрипт запускается, когда нимб надели
local halo = script.Parent
-- прибавка к скорости
local BONUS = 4
-- на ком нимб
local humanoid = halo.Parent and halo.Parent:FindFirstChildOfClass("Humanoid")
-- быстрее — до конца жизни персонажа
if humanoid then humanoid.WalkSpeed += BONUS endЗапусти Play (или Run) и убедись: