Roblox · Королевская битва · урок 12
Итог курса на одной странице: полная структура королевской битвы и финальные версии всех скриптов. Удобно сверить свою игру или собрать её заново.
Синим — что добавляем на этом уроке.
Workspace — StreamingEnabled выкл. ├─ Terrain (Terrain) — остров ├─ SafeZone (Part) — цилиндр зоны ├─ SpawnRange, PlaneStart, PlaneEnd (Part) — невидимые ├─ Houses (Folder) — дома ├─ LootSpawns (Folder) — плитки на полах ├─ Loot (Folder) — лут на карте ├─ Helicopter (Model) — Body, VehicleSeat, MainBlades, BackBlades, HelicopterServer ├─ Zip (Model) — Start (Beam, ProximityPrompt → ZipScript), End └─ Lobby (Model) — SpawnLocation, Gate (GateScript), ReadyZone, LeaveRoomPart, SafeZone ReplicatedStorage — атрибуты Phase, Alive, Winner, ZoneText, TimeLeft └─ PlaneEvent, ParachuteEvent, LeaveEvent, TeleportingEvent, DeathEvent, FlashEvent (RemoteEvent) ServerStorage ├─ Loot (Folder) — Pistol, Rifle, Shotgun, Medkit, Flashbang, SmokeGrenade ├─ Plane (Model) — PrimaryPart — Body ├─ Parachute (Model) — PrimaryPart — MainPart ├─ FallTrail (Trail) ├─ HealthDrop (Part) — Sound └─ RoundStart (BindableEvent) ServerScriptService ├─ ZoneModule, WeaponEffects (ModuleScript) └─ RoundServer, LootSpawn, ParachuteServer, BackpackLimit, DeathServer, DropServer, SafeZoneScript (Script) StarterPlayer → StarterCharacterScripts └─ HelicopterLocal, PlaneLocal, ParachuteLocal, ScrollSwitch (LocalScript) StarterPlayer → StarterPlayerScripts └─ FlashEffect (LocalScript) StarterGui — все ResetOnSpawn выкл. ├─ RoundGui (ScreenGui) — TimerFrame, WarnText, AliveText, CenterText; RoundLocal ├─ MainGui (ScreenGui) — Teleporting, LeaveButton ├─ DeathGui (ScreenGui) — DeathFrame; DeathLocal └─ StartGui (ScreenGui) — StartFrame; StartLocal Players — CharacterAutoLoads выкл.
Где: ServerScriptService · ModuleScript
Зачем: Зона и волны.
-- плавное сужение
local TweenService = game:GetService("TweenService")
-- сюда пишем текст таймера для игроков
local RS = game:GetService("ReplicatedStorage")
-- таблица модуля — её получают через require
local Zone = {}
-- цилиндр безопасной зоны
Zone.Part = workspace:WaitForChild("SafeZone")
-- центр зоны
Zone.Center = Zone.Part.Position
-- у лежащего цилиндра диаметр — Size.Y и Size.Z
Zone.StartRadius = Zone.Part.Size.Y / 2
-- урон в секунду за пределами зоны
Zone.Damage = 1
-- волны: ждём wait секунд, сужаемся до radius за shrink секунд, урон damage
Zone.WAVES = {
-- 1 волна
{wait = 60, radius = 140, shrink = 20, damage = 2},
-- 2 волна
{wait = 45, radius = 80, shrink = 20, damage = 4},
-- 3 волна
{wait = 40, radius = 40, shrink = 15, damage = 7},
-- 4 волна
{wait = 30, radius = 12, shrink = 15, damage = 12},
-- финал — пятачок, все лицом к лицу
{wait = 20, radius = 2, shrink = 10, damage = 20},
-- конец таблицы
}
-- радиус сейчас
function Zone.Radius()
-- половина диаметра
return Zone.Part.Size.Y / 2
-- конец функции
end
-- точка внутри зоны?
function Zone.IsInside(position)
-- расстояние по земле — высота не важна
local flat = Vector3.new(position.X - Zone.Center.X, 0, position.Z - Zone.Center.Z)
-- ближе радиуса — внутри
return flat.Magnitude <= Zone.Radius()
-- конец функции
end
-- зона снова во всю карту
function Zone.Reset()
-- размер
local size = Zone.Part.Size
-- исходный диаметр
Zone.Part.Size = Vector3.new(size.X, Zone.StartRadius * 2, Zone.StartRadius * 2)
-- урон снова слабый
Zone.Damage = 1
-- таймер прячется
RS:SetAttribute("ZoneText", "")
-- конец функции
end
-- текст и время для таймера у всех игроков
local function Show(text, seconds)
-- что происходит
RS:SetAttribute("ZoneText", text)
-- сколько секунд
RS:SetAttribute("TimeLeft", seconds)
-- конец функции
end
-- все волны по очереди; isRunning() вернёт false — стоп
function Zone.Run(isRunning)
-- с начала
Zone.Reset()
-- каждая волна
for _, wave in ipairs(Zone.WAVES) do
-- ожидание
for t = wave.wait, 1, -1 do
-- раунд кончился
if not isRunning() then return end
-- таймер
Show("Зона сузится через", t)
-- секунда
task.wait(1)
-- конец ожидания
end
-- за зоной бьёт сильнее
Zone.Damage = wave.damage
-- размер
local size = Zone.Part.Size
-- сужение
local tween = TweenService:Create(Zone.Part, TweenInfo.new(wave.shrink, Enum.EasingStyle.Linear), {Size = Vector3.new(size.X, wave.radius * 2, wave.radius * 2)})
-- поехали
tween:Play()
-- пока сужается
for t = wave.shrink, 1, -1 do
-- раунд кончился
if not isRunning() then tween:Cancel() return end
-- таймер
Show("Зона сужается", t)
-- секунда
task.wait(1)
-- конец сужения
end
-- конец волн
end
-- дальше сужаться некуда
Show("Финальная зона", 0)
-- конец функции
end
-- урон тем, кто за зоной (вызывать раз в секунду)
function Zone.Hurt(players)
-- каждый игрок
for _, player in ipairs(players) do
-- персонаж
local character = player.Character
-- Humanoid
local humanoid = character and character:FindFirstChildOfClass("Humanoid")
-- центр
local root = character and character:FindFirstChild("HumanoidRootPart")
-- жив и за зоной
local outside = humanoid ~= nil and root ~= nil and humanoid.Health > 0 and not Zone.IsInside(root.Position)
-- интерфейс покажет предупреждение
player:SetAttribute("OutsideZone", outside)
-- урон
if outside then humanoid:TakeDamage(Zone.Damage) end
-- конец перебора
end
-- конец функции
end
-- отдаём модуль
return ZoneГде: ServerScriptService · Script
Зачем: Самолёт, бой, победа.
-- игроки
local Players = game:GetService("Players")
-- атрибуты раунда и события
local RS = game:GetService("ReplicatedStorage")
-- образец самолёта
local SS = game:GetService("ServerStorage")
-- каждый кадр
local RunService = game:GetService("RunService")
-- сохранение побед
local DSS = game:GetService("DataStoreService")
-- модуль зоны
local Zone = require(script.Parent:WaitForChild("ZoneModule"))
-- хранилище
local store = DSS:GetDataStore("BattleRoyale")
-- лобби отправило игроков
local roundStart = SS:WaitForChild("RoundStart")
-- самолёт (PrimaryPart — Body)
local planeSample = SS:WaitForChild("Plane")
-- начало маршрута
local planeStart = workspace:WaitForChild("PlaneStart")
-- конец маршрута
local planeEnd = workspace:WaitForChild("PlaneEnd")
-- над этой областью можно прыгать
local spawnRange = workspace:WaitForChild("SpawnRange")
-- куда вернуть победителя
local lobbySpawn = workspace:WaitForChild("Lobby"):WaitForChild("SpawnLocation")
-- прыжок из самолёта
local planeEvent = RS:WaitForChild("PlaneEvent")
-- секунд полёта от края до края
local FLIGHT_TIME = 30
-- игрок → его персонаж: кто ещё жив в раунде
local alive = {}
-- игрок → true: кто ещё в самолёте
local onBoard = {}
-- самолёт сейчас
local plane = nil
-- Lobby, Plane или Fight
RS:SetAttribute("Phase", "Lobby")
-- имени победителя пока нет
RS:SetAttribute("Winner", "")
-- игрок зашёл: таблица лидеров
local function Setup(player)
-- папка
local stats = Instance.new("Folder")
-- так Roblox показывает её в таблице справа
stats.Name = "leaderstats"
-- в игрока
stats.Parent = player
-- победы
local wins = Instance.new("IntValue")
-- имя
wins.Name = "Wins"
-- в таблицу
wins.Parent = stats
-- загружаем (pcall — сервер может не ответить)
local ok, value = pcall(function() return store:GetAsync(player.UserId .. "Wins") end)
-- были победы — ставим
if ok and value then wins.Value = value end
-- конец функции
end
-- сохранить победы
local function Save(player)
-- таблица
local stats = player:FindFirstChild("leaderstats")
-- сохраняем
if stats then pcall(function() store:SetAsync(player.UserId .. "Wins", stats.Wins.Value) end) end
-- конец функции
end
-- новые игроки
Players.PlayerAdded:Connect(Setup)
-- кто зашёл раньше скрипта
for _, player in ipairs(Players:GetPlayers()) do Setup(player) end
-- вышел
Players.PlayerRemoving:Connect(function(player)
-- сохраняем
Save(player)
-- больше не в раунде
alive[player] = nil
-- и не в самолёте
onBoard[player] = nil
-- конец функции
end)
-- сервер выключается — сохраняем всех
game:BindToClose(function() for _, player in ipairs(Players:GetPlayers()) do Save(player) end end)
-- самолёт над островом?
local function OverRange(position)
-- координаты относительно SpawnRange
local p = spawnRange.CFrame:PointToObjectSpace(position)
-- внутри по ширине и длине
return math.abs(p.X) <= spawnRange.Size.X / 2 and math.abs(p.Z) <= spawnRange.Size.Z / 2
-- конец функции
end
-- выпустить игрока из самолёта
local function Drop(player)
-- уже прыгнул
if not onBoard[player] or not plane then return end
-- больше не в самолёте
onBoard[player] = nil
-- интерфейс: подсказка пропадает
player:SetAttribute("OnPlane", false)
-- камера снова за персонажем
planeEvent:FireClient(player, false)
-- центр персонажа
local root = player.Character and player.Character:FindFirstChild("HumanoidRootPart")
-- персонажа нет
if not root then return end
-- под самолётом
root.CFrame = plane:GetPivot() * CFrame.new(0, -15, 0)
-- падает
root.Anchored = false
-- конец функции
end
-- игрок нажал пробел
planeEvent.OnServerEvent:Connect(function(player)
-- только над островом
if plane and OverRange(plane:GetPivot().Position) then Drop(player) end
-- конец функции
end)
-- посадить игроков в самолёт
local function Board(list)
-- новый раунд
table.clear(alive)
-- самолёт пуст
table.clear(onBoard)
-- каждый из лобби
for _, player in ipairs(list) do
-- персонаж
local character = player.Character
-- Humanoid
local humanoid = character and character:FindFirstChildOfClass("Humanoid")
-- центр
local root = character and character:FindFirstChild("HumanoidRootPart")
-- живой и в игре
if player.Parent and humanoid and root and humanoid.Health > 0 then
-- в раунде
alive[player] = character
-- в самолёте
onBoard[player] = true
-- летит вместе с самолётом, а не падает
root.Anchored = true
-- интерфейс: «Пробел — прыгнуть»
player:SetAttribute("OnPlane", true)
-- камера смотрит на самолёт
planeEvent:FireClient(player, true)
-- конец проверки
end
-- конец перебора
end
-- конец функции
end
-- полёт самолёта от PlaneStart к PlaneEnd
local function Fly()
-- в начале смотрит на конец
local startCF = CFrame.lookAt(planeStart.Position, planeEnd.Position)
-- в конце — тот же поворот
local endCF = startCF.Rotation + planeEnd.Position
-- новый самолёт
plane = planeSample:Clone()
-- в начало
plane:PivotTo(startCF)
-- в мир
plane.Parent = workspace
-- сколько летим
local t = 0
-- пока не долетели
while t < FLIGHT_TIME do
-- прошёл кадр
t += RunService.Heartbeat:Wait()
-- точка на маршруте
local cf = startCF:Lerp(endCF, math.min(t / FLIGHT_TIME, 1))
-- самолёт летит
plane:PivotTo(cf)
-- кто внутри
for player in pairs(onBoard) do
-- центр
local root = player.Character and player.Character:FindFirstChild("HumanoidRootPart")
-- внутри самолёта
if root then root.CFrame = cf end
-- конец перебора
end
-- конец полёта
end
-- кто не прыгнул — сбрасываем в конце маршрута
for player in pairs(onBoard) do Drop(player) end
-- самолёт улетел
plane:Destroy()
-- самолёта нет
plane = nil
-- конец функции
end
-- убрать погибших и вышедших; вернуть число живых
local function CheckAlive()
-- счётчик и последний живой
local count, last = 0, nil
-- каждый в раунде
for player, character in pairs(alive) do
-- Humanoid
local humanoid = character:FindFirstChildOfClass("Humanoid")
-- вышел, возродился или погиб
if not player.Parent or player.Character ~= character or not humanoid or humanoid.Health <= 0 then
-- выбыл
alive[player] = nil
-- жив
else
-- считаем
count += 1
-- запоминаем
last = player
-- конец проверки
end
-- конец перебора
end
-- интерфейс: «Живых: N»
RS:SetAttribute("Alive", count)
-- число и последний
return count, last
-- конец функции
end
-- весь раунд
local function RunRound(list)
-- полёт
RS:SetAttribute("Phase", "Plane")
-- в самолёт
Board(list)
-- один игрок — раунд до его смерти (для проверки)
local solo = CheckAlive() <= 1
-- летим 30 секунд
Fly()
-- бой
RS:SetAttribute("Phase", "Fight")
-- раунд идёт
local running = true
-- волны зоны — отдельно
task.spawn(Zone.Run, function() return running end)
-- победитель
local winner = nil
-- раз в секунду
while true do
-- секунда
task.wait(1)
-- кто жив
local count, last = CheckAlive()
-- список живых
local players = {}
-- собираем
for player in pairs(alive) do table.insert(players, player) end
-- урон тем, кто за зоной
Zone.Hurt(players)
-- остался один (или никого)
if count == 0 or (count == 1 and not solo) then
-- он победил
winner = last
-- раунд окончен
break
-- конец проверки
end
-- конец цикла
end
-- зона останавливается
running = false
-- есть победитель
if winner then
-- +1 победа
winner.leaderstats.Wins.Value += 1
-- все видят имя
RS:SetAttribute("Winner", winner.DisplayName)
-- конец проверки
end
-- празднуем
task.wait(6)
-- надпись пропадает
RS:SetAttribute("Winner", "")
-- предупреждения гаснут
for _, player in ipairs(Players:GetPlayers()) do player:SetAttribute("OutsideZone", false) end
-- победитель — в лобби
if winner and winner.Character then winner.Character:PivotTo(lobbySpawn.CFrame + Vector3.new(0, 4, 0)) end
-- раунда больше нет
table.clear(alive)
-- зона снова большая
Zone.Reset()
-- лобби снова набирает игроков
RS:SetAttribute("Phase", "Lobby")
-- конец функции
end
-- лобби отправило игроков
roundStart.Event:Connect(function(list)
-- раунд не идёт — начинаем
if RS:GetAttribute("Phase") == "Lobby" and #list > 0 then RunRound(list) end
-- конец функции
end)Где: ServerScriptService · Script
Зачем: Случайный лут.
-- образцы лута
local SS = game:GetService("ServerStorage")
-- оружие, гранаты, аптечки (Tool)
local loot = SS:WaitForChild("Loot")
-- невидимые плитки на полах домов
local spawns = workspace:WaitForChild("LootSpawns")
-- сюда кладём появившийся лут
local folder = workspace:WaitForChild("Loot")
-- сколько предметов лежит одновременно
local MAX_ACTIVE = 10
-- через сколько секунд после подбора появится новый
local RESPAWN_DELAY = 10
-- сколько лежит сейчас
local active = 0
-- случайная точка на случайной плитке
local function RandomPoint()
-- все плитки
local tiles = spawns:GetChildren()
-- случайная
local tile = tiles[math.random(1, #tiles)]
-- по ширине плитки
local x = (math.random() - 0.5) * tile.Size.X
-- по длине
local z = (math.random() - 0.5) * tile.Size.Z
-- чуть выше плитки — предмет упадёт на пол
return (tile.CFrame * CFrame.new(x, tile.Size.Y / 2 + 1.5, z)).Position
-- конец функции
end
-- положить один предмет
local function SpawnItem()
-- образцы
local items = loot:GetChildren()
-- нечего, некуда или хватит
if #items == 0 or #spawns:GetChildren() == 0 or active >= MAX_ACTIVE then return end
-- случайный предмет
local item = items[math.random(1, #items)]:Clone()
-- за неё двигаем — приваренные детали едут следом
local handle = item:FindFirstChild("Handle")
-- предмет без Handle
if not handle then return end
-- место и случайный поворот
handle.CFrame = CFrame.new(RandomPoint()) * CFrame.Angles(0, math.rad(math.random(0, 359)), 0)
-- в мир
item.Parent = folder
-- лежит на один больше
active += 1
-- подписка на подбор
local connection
-- предмет куда-то переложили
connection = item:GetPropertyChangedSignal("Parent"):Connect(function()
-- всё ещё лежит
if item.Parent == folder then return end
-- больше не следим
connection:Disconnect()
-- подобрали
active -= 1
-- через 10 секунд — новый где-то ещё
task.delay(RESPAWN_DELAY, SpawnItem)
-- конец функции
end)
-- конец функции
end
-- сразу раскладываем лут по карте
for i = 1, MAX_ACTIVE do SpawnItem() endГде: ServerScriptService · Script
Зачем: Парашют и следы.
-- события
local RS = game:GetService("ReplicatedStorage")
-- образцы
local SS = game:GetService("ServerStorage")
-- просьбы клиента
local event = RS:WaitForChild("ParachuteEvent")
-- модель парашюта (PrimaryPart — MainPart)
local parachuteSample = SS:WaitForChild("Parachute")
-- след в воздухе
local trailSample = SS:WaitForChild("FallTrail")
-- скорость спуска на парашюте
local FALL_SPEED = 20
-- где рисуем следы (R15)
local TRAIL_PARTS = {"LeftHand", "RightHand", "LeftFoot", "RightFoot"}
-- удалить всё с этим именем
local function Remove(character, name)
-- все части персонажа
for _, item in ipairs(character:GetDescendants()) do
-- нашли — удаляем
if item.Name == name then item:Destroy() end
-- конец перебора
end
-- конец функции
end
-- клиент попросил
event.OnServerEvent:Connect(function(player, action)
-- персонаж
local character = player.Character
-- Humanoid
local humanoid = character and character:FindFirstChildOfClass("Humanoid")
-- центр
local root = character and character:FindFirstChild("HumanoidRootPart")
-- погиб
if not humanoid or not root or humanoid.Health <= 0 then return end
-- долгое падение — следы
if action == "Fall" then
-- старые убираем
Remove(character, "FallTrail")
-- руки и ноги
for _, partName in ipairs(TRAIL_PARTS) do
-- часть тела
local part = character:FindFirstChild(partName)
-- есть (у R6 таких нет)
if part then
-- начало следа
local a0 = Instance.new("Attachment")
-- имя — чтобы потом удалить
a0.Name = "FallTrail"
-- с одной стороны
a0.Position = Vector3.new(0.4, 0, 0)
-- в часть тела
a0.Parent = part
-- конец следа
local a1 = a0:Clone()
-- с другой стороны
a1.Position = Vector3.new(-0.4, 0, 0)
-- в часть тела
a1.Parent = part
-- след
local trail = trailSample:Clone()
-- имя
trail.Name = "FallTrail"
-- между двумя точками
trail.Attachment0 = a0
-- получается лента
trail.Attachment1 = a1
-- в часть тела
trail.Parent = part
-- конец проверки
end
-- конец перебора
end
-- раскрыть парашют
elseif action == "Open" then
-- уже раскрыт
if character:FindFirstChild("Parachute") then return end
-- туловище R15 или R6
local torso = character:FindFirstChild("UpperTorso") or character:FindFirstChild("Torso")
-- нет туловища
if not torso then return end
-- парашют
local chute = parachuteSample:Clone()
-- имя
chute.Name = "Parachute"
-- над головой
chute:PivotTo(torso.CFrame * CFrame.new(0, 6, 0))
-- приварим к туловищу
local weld = Instance.new("WeldConstraint")
-- туловище
weld.Part0 = torso
-- MainPart парашюта
weld.Part1 = chute.PrimaryPart
-- в парашют
weld.Parent = chute.PrimaryPart
-- в персонажа
chute.Parent = character
-- точка для силы
local attachment = Instance.new("Attachment")
-- имя
attachment.Name = "ParachuteForce"
-- в центр
attachment.Parent = root
-- держит скорость падения
local slow = Instance.new("LinearVelocity")
-- имя
slow.Name = "ParachuteForce"
-- за центр
slow.Attachment0 = attachment
-- только по одной оси…
slow.VelocityConstraintMode = Enum.VelocityConstraintMode.Line
-- …вертикальной: по сторонам игрок управляет сам
slow.LineDirection = Vector3.new(0, 1, 0)
-- вниз 20 studs/с
slow.LineVelocity = -FALL_SPEED
-- достаточно, чтобы затормозить
slow.MaxForce = 100000
-- в центр
slow.Parent = root
-- приземлился
elseif action == "Landed" then
-- парашют
Remove(character, "Parachute")
-- торможение
Remove(character, "ParachuteForce")
-- следы
Remove(character, "FallTrail")
-- конец проверки
end
-- конец функции
end)Где: ServerScriptService · Script
Зачем: Лимит рюкзака.
-- игроки
local Players = game:GetService("Players")
-- сколько предметов можно нести
local LIMIT = 4
-- все предметы: рюкзак и руки
local function Tools(player, character)
-- список
local list = {}
-- рюкзак
for _, tool in ipairs(player.Backpack:GetChildren()) do
-- только инструменты
if tool:IsA("Tool") then table.insert(list, tool) end
-- конец перебора
end
-- руки
for _, tool in ipairs(character:GetChildren()) do
-- в руках
if tool:IsA("Tool") then table.insert(list, tool) end
-- конец перебора
end
-- список
return list
-- конец функции
end
-- подобрал предмет — не много ли?
local function Check(player, character, newTool)
-- влезает
if not newTool:IsA("Tool") or #Tools(player, character) <= LIMIT then return end
-- центр
local root = character:FindFirstChild("HumanoidRootPart")
-- персонажа нет
if not root then return end
-- что в руках
local held = character:FindFirstChildOfClass("Tool")
-- меняем: выбрасываем то, что в руках, иначе новое
local drop = (held and held ~= newTool) and held or newTool
-- сразу во время подбора менять родителя нельзя — чуть позже
task.defer(function()
-- за что двигать
local handle = drop:FindFirstChild("Handle")
-- на землю
drop.Parent = workspace:FindFirstChild("Loot") or workspace
-- в 6 studs позади
if handle then handle.CFrame = root.CFrame * CFrame.new(0, 0, 6) end
-- конец функции
end)
-- конец функции
end
-- игрок зашёл
local function Setup(player)
-- каждый новый персонаж (рюкзак тоже новый)
player.CharacterAdded:Connect(function(character)
-- рюкзак
local backpack = player:WaitForChild("Backpack")
-- что-то попало в рюкзак
backpack.ChildAdded:Connect(function(tool) Check(player, character, tool) end)
-- или сразу в руки (в методичке Check вызывался без игрока)
character.ChildAdded:Connect(function(tool) Check(player, character, tool) end)
-- конец функции
end)
-- конец функции
end
-- новые игроки
Players.PlayerAdded:Connect(Setup)
-- кто зашёл раньше скрипта
for _, player in ipairs(Players:GetPlayers()) do Setup(player) endГде: ServerScriptService · Script
Зачем: Возрождение.
-- игроки
local Players = game:GetService("Players")
-- события
local RS = game:GetService("ReplicatedStorage")
-- кнопка «В лобби»
local deathEvent = RS:WaitForChild("DeathEvent")
-- столько же, сколько отсчёт на экране смерти
local RESPAWN_TIME = 8
-- сами решаем, когда игрок возрождается
Players.CharacterAutoLoads = false
-- игрок мёртв?
local function IsDead(player)
-- Humanoid
local humanoid = player.Character and player.Character:FindFirstChildOfClass("Humanoid")
-- нет персонажа или здоровье 0
return not humanoid or humanoid.Health <= 0
-- конец функции
end
-- возродить в лобби
local function Respawn(player)
-- только мёртвых — живой не «перезапустится» кнопкой
if player.Parent and IsDead(player) then player:LoadCharacter() end
-- конец функции
end
-- игрок зашёл
local function Setup(player)
-- новый персонаж
player.CharacterAdded:Connect(function(character)
-- погиб
character:WaitForChild("Humanoid").Died:Connect(function()
-- страховка: если клиент не нажал — возродим сами
task.delay(RESPAWN_TIME + 2, Respawn, player)
-- конец функции
end)
-- конец функции
end)
-- первый персонаж — сами (автозагрузка выключена)
if not player.Character then player:LoadCharacter() end
-- конец функции
end
-- новые игроки
Players.PlayerAdded:Connect(Setup)
-- кто зашёл раньше скрипта
for _, player in ipairs(Players:GetPlayers()) do task.spawn(Setup, player) end
-- нажал «В лобби» или кончился отсчёт
deathEvent.OnServerEvent:Connect(Respawn)Где: ServerScriptService · Script
Зачем: Лут погибшего.
-- игроки
local Players = game:GetService("Players")
-- образец дропа
local SS = game:GetService("ServerStorage")
-- удаление через время
local Debris = game:GetService("Debris")
-- шар здоровья (внутри Sound)
local healthSample = SS:WaitForChild("HealthDrop")
-- сколько лечит
local HEAL = 40
-- через сколько секунд исчезнет, если не взяли
local LIFETIME = 120
-- шар здоровья на месте смерти
local function DropHealth(position)
-- копия
local orb = healthSample:Clone()
-- висит
orb.Anchored = true
-- сквозь
orb.CanCollide = false
-- где погиб
orb.Position = position
-- в мир
orb.Parent = workspace
-- ещё не взяли
local used = false
-- коснулись
orb.Touched:Connect(function(hit)
-- персонаж
local humanoid = hit.Parent:FindFirstChildOfClass("Humanoid")
-- уже взяли или мёртв
if used or not humanoid or humanoid.Health <= 0 then return end
-- один раз (в методичке лечил, пока играл звук)
used = true
-- лечим
humanoid.Health = math.min(humanoid.Health + HEAL, humanoid.MaxHealth)
-- звук
orb.Sound:Play()
-- шар исчезает
orb.Transparency = 1
-- удалим, когда доиграет звук
Debris:AddItem(orb, 2)
-- конец функции
end)
-- не взяли — исчезнет сам
Debris:AddItem(orb, LIFETIME)
-- конец функции
end
-- предметы погибшего — на землю
local function DropTools(player, character, position)
-- список
local tools = {}
-- рюкзак
for _, tool in ipairs(player.Backpack:GetChildren()) do
-- инструменты
if tool:IsA("Tool") then table.insert(tools, tool) end
-- конец перебора
end
-- что было в руках
local held = character:FindFirstChildOfClass("Tool")
-- тоже
if held then table.insert(tools, held) end
-- каждый
for i, tool in ipairs(tools) do
-- за что двигать
local handle = tool:FindFirstChild("Handle")
-- есть
if handle then
-- раскладываем по кругу
local angle = i / #tools * math.pi * 2
-- на землю
tool.Parent = workspace:FindFirstChild("Loot") or workspace
-- вокруг места смерти
handle.CFrame = CFrame.new(position + Vector3.new(math.cos(angle) * 4, 1, math.sin(angle) * 4))
-- конец проверки
end
-- конец перебора
end
-- конец функции
end
-- игрок зашёл
local function Setup(player)
-- новый персонаж
player.CharacterAdded:Connect(function(character)
-- погиб
character:WaitForChild("Humanoid").Died:Connect(function()
-- центр
local root = character:FindFirstChild("HumanoidRootPart")
-- уже нет
if not root then return end
-- место смерти
local position = root.Position
-- лут погибшего
DropTools(player, character, position)
-- секунда
task.wait(1)
-- шар здоровья
DropHealth(position)
-- конец функции
end)
-- конец функции
end)
-- конец функции
end
-- новые игроки
Players.PlayerAdded:Connect(Setup)
-- кто зашёл раньше скрипта
for _, player in ipairs(Players:GetPlayers()) do Setup(player) endГде: ServerScriptService · Script
Зачем: Бессмертие в лобби.
-- игроки
local Players = game:GetService("Players")
-- невидимая коробка над всем лобби
local zone = workspace:WaitForChild("Lobby"):WaitForChild("SafeZone")
-- точка в коробке?
local function Inside(position)
-- координаты относительно коробки
local p = zone.CFrame:PointToObjectSpace(position)
-- внутри по всем трём осям
return math.abs(p.X) <= zone.Size.X / 2 and math.abs(p.Y) <= zone.Size.Y / 2 and math.abs(p.Z) <= zone.Size.Z / 2
-- конец функции
end
-- постоянно
while true do
-- 5 раз в секунду
task.wait(0.2)
-- каждый игрок
for _, player in ipairs(Players:GetPlayers()) do
-- персонаж
local character = player.Character
-- Humanoid
local humanoid = character and character:FindFirstChildOfClass("Humanoid")
-- центр
local root = character and character:FindFirstChild("HumanoidRootPart")
-- жив
if humanoid and root and humanoid.Health > 0 then
-- щит
local shield = character:FindFirstChild("LobbyShield")
-- в лобби
if Inside(root.Position) then
-- здоровье полное
humanoid.Health = humanoid.MaxHealth
-- щита нет
if not shield then
-- ForceField: TakeDamage по нему не проходит
shield = Instance.new("ForceField")
-- имя
shield.Name = "LobbyShield"
-- без светящегося шара
shield.Visible = false
-- в персонажа
shield.Parent = character
-- конец проверки
end
-- вышел из лобби
elseif shield then
-- щит снимаем
shield:Destroy()
-- конец проверки
end
-- конец проверки
end
-- конец перебора
end
-- конец цикла
endГде: ServerScriptService · ModuleScript
Зачем: Эффекты оружия (раздел «Оружие»).
-- удаление по таймеру
local Debris = game:GetService("Debris")
-- игроки — для ослепления
local Players = game:GetService("Players")
-- норма скорости и прыжка игрока
local StarterPlayer = game:GetService("StarterPlayer")
-- изменения каждой цели; исчезнувшие цели забываются сами
local mods = setmetatable({}, {__mode = "k"})
-- во сколько раз сильнее урон по цели с меткой
local MARK_BONUS = 1.5
-- таблица модуля
local Effects = {}
-- читает настройку; нет такой — значит 0 (эффект выключен)
local function Get(config, name)
-- ищем контейнер
local v = config:FindFirstChild(name)
-- есть — его значение
if v then return v.Value end
-- нет — ноль
return 0
-- конец функции
end
-- норма характеристики: к ней всё возвращается, когда эффекты кончились
local function Base(humanoid, stat)
-- уже знаем норму этой цели?
local saved = humanoid:GetAttribute("Base" .. stat)
-- знаем — её и берём (можно задать вручную атрибутом)
if saved then return saved end
-- NPC: его собственное значение (у быстрого врага — своё)
local value = humanoid[stat]
-- это игрок?
local player = Players:GetPlayerFromCharacter(humanoid.Parent)
-- скорость игрока
if player and stat == "WalkSpeed" then
-- норма из StarterPlayer — по умолчанию 16
value = StarterPlayer.CharacterWalkSpeed
-- прыжок игрока
elseif player and stat == "JumpHeight" then
-- норма из StarterPlayer — по умолчанию 7.2
value = StarterPlayer.CharacterJumpHeight
-- конец выбора
end
-- запоминаем норму на цели
humanoid:SetAttribute("Base" .. stat, value)
-- норма
return value
-- конец функции
end
-- пересчитывает характеристику: норма + все действующие изменения
local function Recalc(humanoid, stat)
-- сумма прибавок, произведение множителей и делителей, «установить»
local add, mul, div, set = 0, 1, 1, nil
-- текущее время
local now = os.clock()
-- все изменения этой цели
for _, m in pairs(mods[humanoid] or {}) do
-- та же характеристика и ещё действует
if m.stat == stat and (m.untilTime == nil or now < m.untilTime) then
-- прибавить
if m.op == "add" then add += m.value
-- умножить
elseif m.op == "mul" then mul *= m.value
-- разделить
elseif m.op == "div" then div *= m.value
-- установить — важнее всего
elseif m.op == "set" then set = m.value end
-- конец проверки
end
-- конец перебора
end
-- формула: (норма + прибавки) × множители ÷ делители
local value = (Base(humanoid, stat) + add) * mul / div
-- «установить» перекрывает всё (заморозка = 0)
if set then value = set end
-- не меньше нуля
value = math.max(value, 0)
-- прыжок
if stat == "JumpHeight" then
-- высота прыжка
humanoid.JumpHeight = value
-- та же высота через силу прыжка — работает при любой настройке игры
humanoid.JumpPower = math.sqrt(2 * workspace.Gravity * value)
-- скорость или здоровье
else
-- записываем
humanoid[stat] = value
-- конец выбора
end
-- конец функции
end
-- изменение: id — имя, stat — WalkSpeed / JumpHeight / MaxHealth, op — add / mul / div / set
function Effects.Modify(humanoid, id, stat, op, value, seconds)
-- на ноль не делим
if op == "div" and value == 0 then return end
-- список изменений цели
mods[humanoid] = mods[humanoid] or {}
-- он
local list = mods[humanoid]
-- без seconds — действует, пока не уберут
local untilTime = nil
-- изменение на время
if seconds and seconds > 0 then
-- до какого момента
untilTime = os.clock() + seconds
-- такое же изменение уже есть?
local old = list[id]
-- и оно кончится позже
if old and old.untilTime and old.untilTime > untilTime then
-- не укорачиваем
untilTime = old.untilTime
-- конец проверки
end
-- время конца — для видимых эффектов
humanoid:SetAttribute(id .. "Until", untilTime)
-- когда время выйдет
task.delay(seconds + 0.05, function()
-- это изменение
local m = list[id]
-- не продлили — кончилось
if m and m.untilTime and os.clock() >= m.untilTime then
-- убираем
list[id] = nil
-- конец проверки
end
-- пересчитываем: без эффектов — снова норма
Recalc(humanoid, stat)
-- конец отложенной функции
end)
-- конец проверки
end
-- записываем изменение (то же имя — заменяет старое)
list[id] = {stat = stat, op = op, value = value, untilTime = untilTime}
-- пересчитываем сразу
Recalc(humanoid, stat)
-- конец функции
end
-- убирает изменение по имени (например, отпустили Shift)
function Effects.Remove(humanoid, id)
-- изменения цели
local list = mods[humanoid]
-- такого нет
if not list or not list[id] then return end
-- какую характеристику оно меняло
local stat = list[id].stat
-- убираем
list[id] = nil
-- пересчитываем
Recalc(humanoid, stat)
-- конец функции
end
-- видимый эффект, пока действует attr; второй такой же не создаём
local function Show(humanoid, attr, name, make)
-- уже показан — время уже продлили
if humanoid.Parent:FindFirstChild(name, true) then return end
-- создаём эффект и узнаём, куда его положить
local visual, parent = make()
-- имя — чтобы найти его в следующий раз
visual.Name = name
-- показываем
visual.Parent = parent
-- отдельный поток — следит за временем
task.spawn(function()
-- пока цель жива и время не вышло
while humanoid.Parent and humanoid.Health > 0 and os.clock() < (humanoid:GetAttribute(attr) or 0) do
-- ждём
task.wait(0.1)
-- конец цикла
end
-- убираем эффект
visual:Destroy()
-- конец потока
end)
-- конец функции
end
-- урон каждую секунду: огонь, яд, кровотечение
local function Dot(humanoid, attr, name, seconds, damage, make, movingBonus)
-- продлеваем
humanoid:SetAttribute(attr, math.max(humanoid:GetAttribute(attr) or 0, os.clock() + seconds))
-- уже действует — хватит продления
if humanoid.Parent:FindFirstChild(name, true) then return end
-- видимый эффект
local visual, parent = make()
-- имя
visual.Name = name
-- показываем
visual.Parent = parent
-- центр цели — по нему видно, бежит ли она
local root = humanoid.Parent:FindFirstChild("HumanoidRootPart")
-- отдельный поток
task.spawn(function()
-- пока цель жива и время не вышло
while humanoid.Health > 0 and os.clock() < humanoid:GetAttribute(attr) do
-- раз в секунду
task.wait(1)
-- цель бежит?
local moving = root and root.AssemblyLinearVelocity.Magnitude > 2
-- кровотечение у бегущей цели
if movingBonus and moving then
-- вдвое сильнее
humanoid:TakeDamage(damage * 2)
-- иначе
else
-- обычный урон
humanoid:TakeDamage(damage)
-- конец выбора
end
-- конец цикла
end
-- эффект закончился
visual:Destroy()
-- конец потока
end)
-- конец функции
end
-- отбрасывает цель от стрелка или от центра взрыва
local function Knockback(root, attacker, origin, power)
-- центр взрыва, если он есть
local from = origin
-- это не взрыв
if not from then
-- центр атакующего
local attackerRoot = attacker and attacker:FindFirstChild("HumanoidRootPart")
-- не знаем, откуда толкать
if not attackerRoot then return end
-- толкаем от атакующего
from = attackerRoot.Position
-- конец проверки
end
-- направление по земле
local flat = (root.Position - from) * Vector3.new(1, 0, 1)
-- стоят в одной точке — направления нет
if flat.Magnitude < 0.1 then return end
-- точка приложения
local attachment = Instance.new("Attachment")
-- в центр цели
attachment.Parent = root
-- толчок; работает и на игроках — их физику считает их компьютер
local push = Instance.new("LinearVelocity")
-- куда приложен
push.Attachment0 = attachment
-- без ограничения силы
push.MaxForce = math.huge
-- назад и немного вверх
push.VectorVelocity = flat.Unit * power + Vector3.new(0, power * 0.4, 0)
-- толкаем
push.Parent = root
-- 0,2 секунды
Debris:AddItem(push, 0.2)
-- убираем и точку
Debris:AddItem(attachment, 0.2)
-- конец функции
end
-- молния между двумя точками
local function Beam(a, b)
-- парт-луч
local beam = Instance.new("Part")
-- не падает
beam.Anchored = true
-- ни с чем не сталкивается
beam.CanCollide = false
-- не мешает поиску целей
beam.CanQuery = false
-- светится
beam.Material = Enum.Material.Neon
-- фиолетовый
beam.Color = Color3.fromRGB(150, 120, 255)
-- длина — от точки до точки
beam.Size = Vector3.new(0.2, 0.2, (b - a).Magnitude)
-- между точками
beam.CFrame = CFrame.new(a, b) * CFrame.new(0, 0, -(b - a).Magnitude / 2)
-- показываем
beam.Parent = workspace
-- вспышка 0,2 секунды
Debris:AddItem(beam, 0.2)
-- конец функции
end
-- цепная молния: перескакивает на ближайших
local function Chain(attacker, first, config)
-- сколько прыжков
local count = Get(config, "ChainCount")
-- 0 — выключена
if count <= 0 then return end
-- дальность прыжка
local range = Get(config, "ChainRange")
-- не задана — 12
if range <= 0 then range = 12 end
-- первую цель уже ударили
local hit = {[first] = true}
-- откуда прыгает
local from = first.Parent:FindFirstChild("HumanoidRootPart")
-- настройки поиска
local params = OverlapParams.new()
-- не замечать из списка
params.FilterType = Enum.RaycastFilterType.Exclude
-- атакующего не бьём
params.FilterDescendantsInstances = {attacker}
-- прыжок за прыжком
for i = 1, count do
-- прыгать неоткуда
if not from then return end
-- ближайшая новая цель
local best, bestRoot, bestDist = nil, nil, range
-- всё вокруг
for _, part in pairs(workspace:GetPartBoundsInRadius(from.Position, range, params)) do
-- модель детали
local model = part:FindFirstAncestorOfClass("Model")
-- живая?
local target = model and model:FindFirstChildOfClass("Humanoid")
-- её центр
local targetRoot = model and model:FindFirstChild("HumanoidRootPart")
-- живая и ещё не ударена
if target and targetRoot and target.Health > 0 and not hit[target] then
-- расстояние
local dist = (targetRoot.Position - from.Position).Magnitude
-- ближе найденной
if dist < bestDist then
-- запоминаем
best, bestRoot, bestDist = target, targetRoot, dist
-- конец проверки
end
-- конец проверки
end
-- конец перебора
end
-- рядом никого — молния гаснет
if not best then return end
-- запоминаем
hit[best] = true
-- рисуем молнию
Beam(from.Position, bestRoot.Position)
-- урон молнии
best:TakeDamage(Get(config, "ChainDamage"))
-- следующий прыжок — отсюда
from = bestRoot
-- следующий прыжок
end
-- конец функции
end
-- белый экран у задетого игрока
local function Blind(humanoid, seconds)
-- это игрок?
local player = Players:GetPlayerFromCharacter(humanoid.Parent)
-- NPC не ослепить — у него нет экрана
if not player then return end
-- продлеваем
humanoid:SetAttribute("BlindUntil", math.max(humanoid:GetAttribute("BlindUntil") or 0, os.clock() + seconds))
-- экран игрока
local playerGui = player:FindFirstChild("PlayerGui")
-- уже ослеплён — хватит продления
if not playerGui or playerGui:FindFirstChild("BlindEffect") then return end
-- слой поверх экрана
local gui = Instance.new("ScreenGui")
-- имя
gui.Name = "BlindEffect"
-- на весь экран, включая верхнюю панель
gui.IgnoreGuiInset = true
-- белая заливка
local flash = Instance.new("Frame")
-- во весь экран
flash.Size = UDim2.fromScale(1, 1)
-- белая
flash.BackgroundColor3 = Color3.new(1, 1, 1)
-- почти непрозрачная
flash.BackgroundTransparency = 0.05
-- в слой
flash.Parent = gui
-- на экран игрока
gui.Parent = playerGui
-- следим за временем
task.spawn(function()
-- пока не вышло
while gui.Parent and os.clock() < humanoid:GetAttribute("BlindUntil") do
-- ждём
task.wait(0.1)
-- конец цикла
end
-- снова видно
gui:Destroy()
-- конец потока
end)
-- конец функции
end
-- попадание: урон и все эффекты из Configuration
function Effects.Hit(attacker, humanoid, config, origin)
-- цель уже мертва
if humanoid.Health <= 0 then return end
-- модель цели
local character = humanoid.Parent
-- её центр
local root = character:FindFirstChild("HumanoidRootPart")
-- урон
local damage = Get(config, "Damage")
-- на цели метка
if os.clock() < (humanoid:GetAttribute("MarkedUntil") or 0) then
-- урон ×1,5
damage = damage * MARK_BONUS
-- конец проверки
end
-- наносим урон
humanoid:TakeDamage(damage)
-- замедление
if Get(config, "SlowTime") > 0 then
-- скорость × SlowFactor на SlowTime секунд
Effects.Modify(humanoid, "Slow", "WalkSpeed", "mul", Get(config, "SlowFactor"), Get(config, "SlowTime"))
-- конец
end
-- заморозка
if Get(config, "FreezeTime") > 0 then
-- стоит
Effects.Modify(humanoid, "Freeze", "WalkSpeed", "set", 0, Get(config, "FreezeTime"))
-- не прыгает
Effects.Modify(humanoid, "FreezeJump", "JumpHeight", "set", 0, Get(config, "FreezeTime"))
-- ледяная подсветка
Show(humanoid, "FreezeUntil", "FreezeEffect", function()
-- подсветка всей модели
local ice = Instance.new("Highlight")
-- голубой лёд
ice.FillColor = Color3.fromRGB(150, 220, 255)
-- плотный
ice.FillTransparency = 0.3
-- белый контур
ice.OutlineColor = Color3.new(1, 1, 1)
-- на модель цели
return ice, character
-- конец функции
end)
-- конец
end
-- огонь, яд или ничего
local effect = config:FindFirstChild("Effect") and config.Effect.Value or "None"
-- огонь или яд
if root and (effect == "Fire" or effect == "Poison") and Get(config, "EffectTime") > 0 then
-- урон каждую секунду
Dot(humanoid, "EffectUntil", "WeaponEffect", Get(config, "EffectTime"), Get(config, "EffectDamage"), function()
-- огонь
if effect == "Fire" then
-- пламя на цели
return Instance.new("Fire"), root
-- конец
end
-- яд — облако
local smoke = Instance.new("Smoke")
-- ядовито-зелёное
smoke.Color = Color3.fromRGB(90, 220, 60)
-- полупрозрачное
smoke.Opacity = 0.5
-- на цели
return smoke, root
-- бонуса за бег нет
end, false)
-- конец
end
-- кровотечение
if root and Get(config, "BleedTime") > 0 then
-- урон каждую секунду
Dot(humanoid, "BleedUntil", "BleedEffect", Get(config, "BleedTime"), Get(config, "BleedDamage"), function()
-- капли
local drops = Instance.new("ParticleEmitter")
-- тёмно-красные
drops.Color = ColorSequence.new(Color3.fromRGB(170, 0, 0))
-- мелкие
drops.Size = NumberSequence.new(0.2)
-- живут полсекунды
drops.Lifetime = NumberRange.new(0.5)
-- 15 капель в секунду
drops.Rate = 15
-- падают вниз
drops.Acceleration = Vector3.new(0, -30, 0)
-- из центра цели
return drops, root
-- у бегущей цели — вдвое сильнее
end, true)
-- конец
end
-- отбрасывание
if root and Get(config, "Knockback") > 0 then
-- толкаем
Knockback(root, attacker, origin, Get(config, "Knockback"))
-- конец
end
-- вампиризм: доля урона в лечение
local steal = Get(config, "Lifesteal")
-- Humanoid атакующего
local attackerHumanoid = attacker and attacker:FindFirstChildOfClass("Humanoid")
-- вампиризм включён, атакующий жив
if steal > 0 and attackerHumanoid and attackerHumanoid.Health > 0 then
-- лечим, но не выше максимума
attackerHumanoid.Health = math.min(attackerHumanoid.MaxHealth, attackerHumanoid.Health + damage * steal)
-- конец
end
-- цепная молния, если ChainCount > 0
Chain(attacker, humanoid, config)
-- ослепление
if Get(config, "BlindTime") > 0 then
-- белый экран у игрока
Blind(humanoid, Get(config, "BlindTime"))
-- конец
end
-- метка
if Get(config, "MarkTime") > 0 then
-- продлеваем
humanoid:SetAttribute("MarkedUntil", math.max(humanoid:GetAttribute("MarkedUntil") or 0, os.clock() + Get(config, "MarkTime")))
-- красный контур
Show(humanoid, "MarkedUntil", "MarkEffect", function()
-- подсветка
local mark = Instance.new("Highlight")
-- без заливки
mark.FillTransparency = 1
-- красный контур — видно сквозь стены
mark.OutlineColor = Color3.fromRGB(255, 40, 40)
-- на модель цели
return mark, character
-- конец функции
end)
-- конец
end
-- конец функции
end
-- усиление: kind = "Speed" (скорость) или "Jump" (прыжок)
function Effects.Boost(humanoid, kind, factor, seconds)
-- мёртвым не помогает
if humanoid.Health <= 0 then return end
-- скорость
local stat = "WalkSpeed"
-- или прыжок
if kind == "Jump" then stat = "JumpHeight" end
-- × factor на seconds секунд; повторный приём продлевает
Effects.Modify(humanoid, "Boost" .. kind, stat, "mul", factor, seconds)
-- искры, пока действует
Show(humanoid, "Boost" .. kind .. "Until", "Boost" .. kind .. "Effect", function()
-- искры
local sparkles = Instance.new("Sparkles")
-- прыжок
if kind == "Jump" then
-- голубые
sparkles.SparkleColor = Color3.fromRGB(80, 200, 255)
-- скорость
else
-- жёлтые
sparkles.SparkleColor = Color3.fromRGB(255, 220, 60)
-- конец выбора
end
-- на центр персонажа
return sparkles, humanoid.Parent:FindFirstChild("HumanoidRootPart")
-- конец функции
end)
-- конец функции
end
-- лечение: сразу amount, потом regen в секунду seconds секунд
function Effects.Heal(humanoid, amount, regen, seconds)
-- мёртвых не лечим
if humanoid.Health <= 0 then return end
-- сразу, не выше максимума
humanoid.Health = math.min(humanoid.MaxHealth, humanoid.Health + amount)
-- восстановление выключено
if regen <= 0 or seconds <= 0 then return end
-- сколько в секунду
humanoid:SetAttribute("RegenRate", regen)
-- до какого времени
humanoid:SetAttribute("RegenUntil", math.max(humanoid:GetAttribute("RegenUntil") or 0, os.clock() + seconds))
-- уже восстанавливается — хватит продления
if humanoid.Parent:FindFirstChild("RegenEffect", true) then return end
-- зелёные искры, пока лечит
Show(humanoid, "RegenUntil", "RegenEffect", function()
-- искры
local sparkles = Instance.new("Sparkles")
-- зелёные
sparkles.SparkleColor = Color3.fromRGB(80, 255, 120)
-- на центр персонажа
return sparkles, humanoid.Parent:FindFirstChild("HumanoidRootPart")
-- конец функции
end)
-- отдельный поток
task.spawn(function()
-- пока жив и время не вышло
while humanoid.Health > 0 and os.clock() < humanoid:GetAttribute("RegenUntil") do
-- раз в секунду
task.wait(1)
-- прибавляем здоровье
humanoid.Health = math.min(humanoid.MaxHealth, humanoid.Health + humanoid:GetAttribute("RegenRate"))
-- конец цикла
end
-- конец потока
end)
-- конец функции
end
-- отдаём модуль
return EffectsГде: Workspace → Lobby → Gate · Script
Зачем: Лобби.
-- игроки
local Players = game:GetService("Players")
-- события
local RS = game:GetService("ReplicatedStorage")
-- сигнал начала раунда
local SS = game:GetService("ServerStorage")
-- модель Gate
local gate = script.Parent
-- модель Lobby
local lobby = gate.Parent
-- светящаяся рамка — войти в неё
local gatePart = gate:WaitForChild("Gate")
-- табло над воротами
local board = gate:WaitForChild("GuiPart"):WaitForChild("Board"):WaitForChild("Frame")
-- площадка готовых
local readyPart = lobby:WaitForChild("ReadyZone"):WaitForChild("TeleportPart")
-- куда вернуть, если передумал
local leavePart = lobby:WaitForChild("LeaveRoomPart")
-- кнопка «Выйти»
local leaveEvent = RS:WaitForChild("LeaveEvent")
-- затемнение экрана
local teleportingEvent = RS:WaitForChild("TeleportingEvent")
-- BindableEvent: RoundServer начинает раунд
local roundStart = SS:WaitForChild("RoundStart")
-- мест в раунде
local MAX_PLAYERS = 30
-- для проверки одному; в настоящей игре поставь 2 и больше
local MIN_PLAYERS = 1
-- секунд отсчёта
local WAIT_TIME = 30
-- игрок → true: кто вошёл в ворота
local ready = {}
-- идёт отправка — ворота закрыты
local sending = false
-- сколько готовых
local function Count()
-- счётчик
local n = 0
-- каждый готовый
for player in pairs(ready) do
-- вышел из игры — убираем
if player.Parent then n += 1 else ready[player] = nil end
-- конец перебора
end
-- число
return n
-- конец функции
end
-- обновить табло
local function UpdateBoard(status, seconds)
-- готовые
board.Players.Text = "Игроков: " .. Count() .. "/" .. MAX_PLAYERS
-- состояние
board.Status.Text = status
-- отсчёт
board.Time.Text = seconds and (seconds .. " с") or ""
-- конец функции
end
-- кто-то вошёл в ворота
gatePart.Touched:Connect(function(hit)
-- модель персонажа
local character = hit.Parent
-- игрок (а не NPC или предмет)
local player = Players:GetPlayerFromCharacter(character)
-- Humanoid
local humanoid = character:FindFirstChildOfClass("Humanoid")
-- уже готов, отправка или погиб
if not player or ready[player] or sending or not humanoid or humanoid.Health <= 0 then return end
-- раунд уже идёт
if RS:GetAttribute("Phase") ~= "Lobby" then return end
-- мест нет
if Count() >= MAX_PLAYERS then return end
-- готов
ready[player] = true
-- на площадку готовых
character:PivotTo(readyPart.CFrame + Vector3.new(math.random(-6, 6), 3, math.random(-6, 6)))
-- показать кнопку «Выйти»
leaveEvent:FireClient(player, true)
-- конец функции
end)
-- нажал «Выйти»
leaveEvent.OnServerEvent:Connect(function(player)
-- не в списке или уже отправляем
if not ready[player] or sending then return end
-- не готов
ready[player] = nil
-- обратно в лобби
if player.Character then player.Character:PivotTo(leavePart.CFrame + Vector3.new(0, 3, 0)) end
-- спрятать кнопку
leaveEvent:FireClient(player, false)
-- конец функции
end)
-- игрок зашёл
local function Setup(player)
-- персонаж пропал — не готов
player.CharacterRemoving:Connect(function() ready[player] = nil end)
-- конец функции
end
-- новые игроки
Players.PlayerAdded:Connect(Setup)
-- кто зашёл раньше скрипта
for _, player in ipairs(Players:GetPlayers()) do Setup(player) end
-- бесконечно
while true do
-- идёт раунд
while RS:GetAttribute("Phase") ~= "Lobby" do
-- табло
UpdateBoard("ИДЁТ РАУНД")
-- ждём
task.wait(1)
-- конец ожидания
end
-- отсчёт с начала
local left = WAIT_TIME
-- пока не 0
while left > 0 do
-- мало игроков
if Count() < MIN_PLAYERS then
-- отсчёт заново
left = WAIT_TIME
-- табло
UpdateBoard("ЖДЁМ ИГРОКОВ")
-- хватает
else
-- табло с секундами
UpdateBoard("ГОТОВЬСЯ", left)
-- минус секунда
left -= 1
-- конец проверки
end
-- секунда
task.wait(1)
-- конец отсчёта
end
-- ворота закрыты
sending = true
-- кого отправляем
local list = {}
-- готовые
for player in pairs(ready) do
-- ещё в игре
if player.Parent then
-- в список
table.insert(list, player)
-- кнопка «Выйти» больше не нужна
leaveEvent:FireClient(player, false)
-- экран темнеет
teleportingEvent:FireClient(player)
-- конец проверки
end
-- конец перебора
end
-- список пуст
table.clear(ready)
-- табло
UpdateBoard("ОТПРАВКА")
-- экран успел потемнеть
task.wait(2)
-- RoundServer сажает их в самолёт
roundStart:Fire(list)
-- раунд успел начаться
task.wait(1)
-- ворота снова открыты
sending = false
-- конец цикла
endГде: Workspace → Helicopter · Script
Зачем: Вертолёт.
-- игроки
local Players = game:GetService("Players")
-- модель Helicopter
local heli = script.Parent
-- кресло пилота
local seat = heli:WaitForChild("VehicleSeat")
-- моторы лопастей
local blades = {heli:WaitForChild("MainBlades"):WaitForChild("HingeConstraint"), heli:WaitForChild("BackBlades"):WaitForChild("HingeConstraint")}
-- скорость вращения лопастей
local BLADE_SPEED = 25
-- точка, к которой приложены силы
local attachment = Instance.new("Attachment")
-- имя
attachment.Name = "FlyAttachment"
-- в кресло
attachment.Parent = seat
-- держит скорость вертолёта
local move = Instance.new("LinearVelocity")
-- имя — его ищет HelicopterLocal
move.Name = "Move"
-- за кресло
move.Attachment0 = attachment
-- сильнее гравитации и веса
move.MaxForce = 10000000
-- висит на месте
move.VectorVelocity = Vector3.zero
-- пока пилота нет — выключено
move.Enabled = false
-- в кресло
move.Parent = seat
-- держит поворот: не кувыркается
local turn = Instance.new("AlignOrientation")
-- имя — его ищет HelicopterLocal
turn.Name = "Turn"
-- к заданному направлению
turn.Mode = Enum.OrientationAlignmentMode.OneAttachment
-- за кресло
turn.Attachment0 = attachment
-- сильно
turn.MaxTorque = 10000000
-- поворачивает плавно
turn.Responsiveness = 15
-- пока пилота нет — выключено
turn.Enabled = false
-- в кресло
turn.Parent = seat
-- сел или встал
seat:GetPropertyChangedSignal("Occupant"):Connect(function()
-- кто сидит
local humanoid = seat.Occupant
-- игрок
local player = humanoid and Players:GetPlayerFromCharacter(humanoid.Parent)
-- пилот сел
if player then
-- куда смотрит вертолёт
local _, yaw = seat.CFrame:ToOrientation()
-- держим ровно в эту сторону
turn.CFrame = CFrame.Angles(0, yaw, 0)
-- висим
move.VectorVelocity = Vector3.zero
-- силы включены
move.Enabled = true
-- поворот включён
turn.Enabled = true
-- лопасти крутятся
for _, hinge in ipairs(blades) do hinge.AngularVelocity = BLADE_SPEED end
-- физику вертолёта считает компьютер пилота — без задержек
seat:SetNetworkOwner(player)
-- пилот вышел
else
-- силы выключены — вертолёт падает и садится
move.Enabled = false
-- поворот выключен
turn.Enabled = false
-- лопасти останавливаются
for _, hinge in ipairs(blades) do hinge.AngularVelocity = 0 end
-- физику снова считает сервер
seat:SetNetworkOwner(nil)
-- конец проверки
end
-- конец функции
end)Где: Workspace → Zip → Start → ProximityPrompt · Script
Зачем: Зиплайн.
-- каждый кадр
local RunService = game:GetService("RunService")
-- ProximityPrompt «Спуститься»
local prompt = script.Parent
-- верхняя точка троса
local zipStart = prompt.Parent
-- нижняя точка
local zipEnd = zipStart.Parent:WaitForChild("End")
-- скорость спуска, studs/с
local SPEED = 40
-- персонаж висит под тросом
local OFFSET = Vector3.new(0, -3.5, 0)
-- игрок → true: кто едет
local riding = {}
-- нажал E
prompt.Triggered:Connect(function(player)
-- персонаж
local character = player.Character
-- Humanoid
local humanoid = character and character:FindFirstChildOfClass("Humanoid")
-- центр
local root = character and character:FindFirstChild("HumanoidRootPart")
-- уже едет или погиб
if riding[player] or not humanoid or not root or humanoid.Health <= 0 then return end
-- едет (другие тоже могут — кнопка не блокируется)
riding[player] = true
-- откуда
local from = zipStart.Position + OFFSET
-- куда
local to = zipEnd.Position + OFFSET
-- смотрит вдоль троса, а не боком
local facing = CFrame.lookAt(from, Vector3.new(to.X, from.Y, to.Z)).Rotation
-- время спуска — одинаковая скорость на любом тросе
local duration = (to - from).Magnitude / SPEED
-- едет по тросу, а не падает
root.Anchored = true
-- сколько едем
local t = 0
-- пока не доехали
while t < duration and root.Parent and humanoid.Health > 0 do
-- прошёл кадр
t += RunService.Heartbeat:Wait()
-- точка на тросе — от начала к концу
root.CFrame = facing + from:Lerp(to, math.min(t / duration, 1))
-- конец спуска
end
-- отпускаем
if root.Parent then root.Anchored = false end
-- приехал
riding[player] = nil
-- конец функции
end)Где: ServerStorage → Loot → Medkit · Script
Зачем: Аптечка.
-- аптечка
local tool = script.Parent
-- звук
local healSound = tool:WaitForChild("Handle"):WaitForChild("Heal")
-- сколько лечит
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
-- звук
healSound:Play()
-- лечим, не выше максимума
humanoid.Health = math.min(humanoid.Health + HEAL, humanoid.MaxHealth)
-- звук доиграет
task.wait(0.5)
-- аптечки больше нет
tool:Destroy()
-- конец функции
end)Где: ServerStorage → Loot → Flashbang / SmokeGrenade · LocalScript
Зачем: Бросок.
-- игроки
local Players = game:GetService("Players")
-- граната
local tool = script.Parent
-- просим сервер бросить
local throwEvent = tool:WaitForChild("Throw")
-- бросок; ключевой кадр «Release» — момент, когда граната вылетает
local anim = tool:WaitForChild("Animation")
-- куда целимся
local mouse = Players.LocalPlayer:GetMouse()
-- бросок уже начат
local busy = false
-- граната вылетает
local function Release()
-- точка под курсором
throwEvent:FireServer(mouse.Hit.Position)
-- конец функции
end
-- клик
tool.Activated:Connect(function()
-- кто держит
local humanoid = tool.Parent:FindFirstChildOfClass("Humanoid")
-- уже бросает или погиб
if busy or not humanoid or humanoid.Health <= 0 then return end
-- одна граната — один бросок
busy = true
-- анимации нет — бросаем сразу
if anim.AnimationId == "" then Release() return end
-- загружаем бросок
local track = humanoid:WaitForChild("Animator"):LoadAnimation(anim)
-- граната ещё в руке
local released = false
-- анимация дошла до ключевого кадра
track.KeyframeReached:Connect(function(name)
-- кадр «Release» — бросок
if name == "Release" and not released then released = true Release() end
-- конец функции
end)
-- анимация кончилась
track.Stopped:Once(function()
-- кадра «Release» нет — бросаем в конце
if not released then released = true Release() end
-- конец функции
end)
-- замах
track:Play()
-- конец функции
end)Где: ServerStorage → Loot → Flashbang · Script
Зачем: Вспышка.
-- игроки
local Players = game:GetService("Players")
-- световая граната
local tool = script.Parent
-- сама граната
local handle = tool:WaitForChild("Handle")
-- клиент бросил
local throwEvent = tool:WaitForChild("Throw")
-- ослепить игрока (ловит FlashEffect у каждого)
local flashEvent = game:GetService("ReplicatedStorage"):WaitForChild("FlashEvent")
-- дальше вспышка не слепит
local RADIUS = 40
-- секунд до взрыва
local FUSE = 2
-- сила броска
local POWER = 70
-- уже брошена
local thrown = false
-- клиент прислал точку
throwEvent.OnServerEvent:Connect(function(player, target)
-- кто держит
local character = tool.Parent
-- уже, чужая граната или мусор
if thrown or Players:GetPlayerFromCharacter(character) ~= player or typeof(target) ~= "Vector3" then return end
-- центр
local root = character:FindFirstChild("HumanoidRootPart")
-- персонажа нет
if not root then return end
-- брошена
thrown = true
-- летящая копия
local grenade = handle:Clone()
-- имя
grenade.Name = "Thrown" .. tool.Name
-- отскакивает от стен и пола
grenade.CanCollide = true
-- у неё есть вес
grenade.Massless = false
-- перед лицом
grenade.CFrame = root.CFrame * CFrame.new(0, 2, -2)
-- в мир
grenade.Parent = workspace
-- к точке
local direction = (target - grenade.Position).Unit
-- бросок чуть вверх — по дуге
grenade.AssemblyLinearVelocity = (direction + Vector3.new(0, 0.35, 0)).Unit * POWER
-- полёт считает сервер — у всех одинаково
grenade:SetNetworkOwner(nil)
-- звук броска
grenade.Throw:Play()
-- в руке гранаты больше нет
handle.Transparency = 1
-- её нельзя подобрать
handle.CanTouch = false
-- не падает сквозь карту
handle.Anchored = true
-- инструмент из рук убираем; скрипт продолжает работать (если удалить — остановится)
tool.Parent = workspace
-- фитиль
task.wait(FUSE)
-- взрыв
grenade.Boom:Play()
-- проверка прямой видимости
local params = RaycastParams.new()
-- исключаем…
params.FilterType = Enum.RaycastFilterType.Exclude
-- …саму гранату
params.FilterDescendantsInstances = {grenade}
-- все игроки
for _, other in ipairs(Players:GetPlayers()) do
-- голова
local head = other.Character and other.Character:FindFirstChild("Head")
-- близко (в методичке вычитали длины векторов)
if head and (head.Position - grenade.Position).Magnitude <= RADIUS then
-- луч от гранаты к голове
local result = workspace:Raycast(grenade.Position, head.Position - grenade.Position, params)
-- стены между ними нет
if not result or result.Instance:IsDescendantOf(other.Character) then
-- ослепить
flashEvent:FireClient(other, grenade.Position)
-- конец проверки
end
-- конец проверки
end
-- конец перебора
end
-- звук доиграет
task.wait(1)
-- гранаты больше нет
grenade:Destroy()
-- и инструмента
tool:Destroy()
-- конец функции
end)Где: ServerStorage → Loot → SmokeGrenade · Script
Зачем: Дым.
-- игроки
local Players = game:GetService("Players")
-- дымовая граната
local tool = script.Parent
-- сама граната
local handle = tool:WaitForChild("Handle")
-- клиент бросил
local throwEvent = tool:WaitForChild("Throw")
-- сколько секунд держится дым
local SMOKE_TIME = 20
-- секунд до взрыва
local FUSE = 2
-- сила броска
local POWER = 70
-- уже брошена
local thrown = false
-- клиент прислал точку
throwEvent.OnServerEvent:Connect(function(player, target)
-- кто держит
local character = tool.Parent
-- уже, чужая граната или мусор
if thrown or Players:GetPlayerFromCharacter(character) ~= player or typeof(target) ~= "Vector3" then return end
-- центр
local root = character:FindFirstChild("HumanoidRootPart")
-- персонажа нет
if not root then return end
-- брошена
thrown = true
-- летящая копия
local grenade = handle:Clone()
-- имя
grenade.Name = "Thrown" .. tool.Name
-- отскакивает от стен и пола
grenade.CanCollide = true
-- у неё есть вес
grenade.Massless = false
-- перед лицом
grenade.CFrame = root.CFrame * CFrame.new(0, 2, -2)
-- в мир
grenade.Parent = workspace
-- к точке
local direction = (target - grenade.Position).Unit
-- бросок чуть вверх — по дуге
grenade.AssemblyLinearVelocity = (direction + Vector3.new(0, 0.35, 0)).Unit * POWER
-- полёт считает сервер — у всех одинаково
grenade:SetNetworkOwner(nil)
-- звук броска
grenade.Throw:Play()
-- в руке гранаты больше нет
handle.Transparency = 1
-- её нельзя подобрать
handle.CanTouch = false
-- не падает сквозь карту
handle.Anchored = true
-- инструмент из рук убираем; скрипт продолжает работать (если удалить — остановится)
tool.Parent = workspace
-- фитиль
task.wait(FUSE)
-- взрыв
grenade.Boom:Play()
-- граната лежит на месте
grenade.Anchored = true
-- эффекты дыма
for _, smoke in ipairs(grenade:GetChildren()) do
-- включаем
if smoke:IsA("Smoke") then smoke.Enabled = true end
-- конец перебора
end
-- дым держится
task.wait(SMOKE_TIME)
-- гранаты больше нет
grenade:Destroy()
-- и инструмента
tool:Destroy()
-- конец функции
end)Где: StarterPlayer → StarterCharacterScripts · LocalScript
Зачем: Управление вертолётом.
-- привязка клавиш к действиям
local CAS = game:GetService("ContextActionService")
-- каждый кадр
local RunService = game:GetService("RunService")
-- игроки
local Players = game:GetService("Players")
-- этот игрок
local player = Players.LocalPlayer
-- Humanoid персонажа
local humanoid = script.Parent:WaitForChild("Humanoid")
-- скорость вперёд-назад
local SPEED = 45
-- скорость вверх-вниз
local UP_SPEED = 18
-- поворот, радиан в секунду
local TURN_SPEED = 1.5
-- действие → клавиша
local ACTIONS = {
-- вверх, вниз
HeliUp = Enum.KeyCode.E, HeliDown = Enum.KeyCode.Q,
-- вперёд, назад
HeliForward = Enum.KeyCode.W, HeliBack = Enum.KeyCode.S,
-- поворот
HeliLeft = Enum.KeyCode.A, HeliRight = Enum.KeyCode.D,
-- конец таблицы
}
-- какие действия сейчас зажаты
local held = {}
-- кресло вертолёта, если сидим
local seat = nil
-- куда повёрнут вертолёт
local yaw = 0
-- дальность камеры до посадки
local oldZoom = player.CameraMaxZoomDistance
-- нажали или отпустили клавишу действия
local function OnAction(name, state)
-- зажата?
held[name] = state == Enum.UserInputState.Begin
-- клавиша больше никуда не идёт
return Enum.ContextActionResult.Sink
-- конец функции
end
-- включить или выключить управление
local function Bind(on)
-- каждое действие
for name, key in pairs(ACTIONS) do
-- привязать / отвязать (false — без кнопки на телефоне)
if on then CAS:BindAction(name, OnAction, false, key) else CAS:UnbindAction(name) end
-- конец перебора
end
-- ничего не зажато
table.clear(held)
-- конец функции
end
-- 1, -1 или 0 по двум клавишам
local function Axis(plus, minus)
-- обе зажаты — 0
return (held[plus] and 1 or 0) - (held[minus] and 1 or 0)
-- конец функции
end
-- сел или встал
humanoid.Seated:Connect(function(active, seatPart)
-- сел в вертолёт
if active and seatPart and seatPart:IsA("VehicleSeat") and seatPart.Parent.Name == "Helicopter" then
-- запоминаем
seat = seatPart
-- куда смотрит
local _, y = seat.CFrame:ToOrientation()
-- начальный поворот
yaw = y
-- запоминаем камеру
oldZoom = player.CameraMaxZoomDistance
-- камеру можно отдалить — видно вертолёт
player.CameraMaxZoomDistance = 60
-- управление
Bind(true)
-- вышел (пробел) или погиб
elseif seat then
-- не в вертолёте
seat = nil
-- камера как была
player.CameraMaxZoomDistance = oldZoom
-- клавиши свободны
Bind(false)
-- конец проверки
end
-- конец функции
end)
-- каждый кадр
RunService.RenderStepped:Connect(function(dt)
-- не летим
if not seat then return end
-- скорость (создаёт HelicopterServer)
local move = seat:FindFirstChild("Move")
-- поворот
local turn = seat:FindFirstChild("Turn")
-- вертолёт без скрипта
if not move or not turn then return end
-- D — вправо, A — влево
yaw -= Axis("HeliRight", "HeliLeft") * TURN_SPEED * dt
-- направление вертолёта
local facing = CFrame.Angles(0, yaw, 0)
-- поворачиваем
turn.CFrame = facing
-- вперёд-назад и вверх-вниз
move.VectorVelocity = facing.LookVector * Axis("HeliForward", "HeliBack") * SPEED + Vector3.new(0, Axis("HeliUp", "HeliDown") * UP_SPEED, 0)
-- конец функции
end)Где: StarterPlayer → StarterCharacterScripts · LocalScript
Зачем: Камера самолёта.
-- общее хранилище
local RS = game:GetService("ReplicatedStorage")
-- каждый кадр
local RunService = game:GetService("RunService")
-- прыжок
local UIS = game:GetService("UserInputService")
-- игроки
local Players = game:GetService("Players")
-- сервер: «ты в самолёте» / «ты прыгнул»
local planeEvent = RS:WaitForChild("PlaneEvent")
-- камера
local camera = workspace.CurrentCamera
-- уже в самолёте?
local onPlane = Players.LocalPlayer:GetAttribute("OnPlane") == true
-- сервер сообщил
planeEvent.OnClientEvent:Connect(function(value)
-- запоминаем
onPlane = value
-- прыгнул — камера за персонажем
if not value then camera.CameraType = Enum.CameraType.Custom end
-- конец функции
end)
-- каждый кадр
RunService.RenderStepped:Connect(function()
-- самолёт
local plane = workspace:FindFirstChild("Plane")
-- не летим
if not onPlane or not plane then return end
-- камерой управляет скрипт
camera.CameraType = Enum.CameraType.Scriptable
-- где самолёт
local cf = plane:GetPivot()
-- сзади-сверху смотрим на самолёт
camera.CFrame = CFrame.lookAt(cf.Position - cf.LookVector * 120 + Vector3.new(0, 50, 0), cf.Position)
-- конец функции
end)
-- пробел (или кнопка прыжка на телефоне)
UIS.JumpRequest:Connect(function()
-- просим выпрыгнуть — сервер проверит, что над островом
if onPlane then planeEvent:FireServer() end
-- конец функции
end)Где: StarterPlayer → StarterCharacterScripts · LocalScript
Зачем: Падение и парашют.
-- клавиатура
local UIS = game:GetService("UserInputService")
-- события
local RS = game:GetService("ReplicatedStorage")
-- каждый кадр
local RunService = game:GetService("RunService")
-- персонаж
local character = script.Parent
-- Humanoid
local humanoid = character:WaitForChild("Humanoid")
-- центр
local root = character:WaitForChild("HumanoidRootPart")
-- просим сервер: следы, парашют, приземлились
local event = RS:WaitForChild("ParachuteEvent")
-- поза падения (ID можно не вставлять)
local anim = script:WaitForChild("Animation")
-- загружаем, если ID есть
local track = anim.AnimationId ~= "" and humanoid:WaitForChild("Animator"):LoadAnimation(anim) or nil
-- ниже этой высоты парашют раскроется сам
local AUTO_HEIGHT = 60
-- ниже — открывать поздно
local MIN_HEIGHT = 20
-- когда начали падать
local fallStart = nil
-- парашют уже открыт
local opened = false
-- следы уже включены
local trails = false
-- настройки луча
local params = RaycastParams.new()
-- исключаем…
params.FilterType = Enum.RaycastFilterType.Exclude
-- …самого себя
params.FilterDescendantsInstances = {character}
-- высота над землёй
local function Height()
-- луч строго вниз (в методичке летел не туда)
local result = workspace:Raycast(root.Position, Vector3.new(0, -2000, 0), params)
-- до земли или «очень высоко»
return result and root.Position.Y - result.Position.Y or math.huge
-- конец функции
end
-- раскрыть парашют
local function Open()
-- уже
if opened then return end
-- раскрыт
opened = true
-- сервер вешает парашют и замедляет падение
event:FireServer("Open")
-- конец функции
end
-- состояние персонажа сменилось
humanoid.StateChanged:Connect(function(_, new)
-- начал падать
if new == Enum.HumanoidStateType.Freefall then
-- запоминаем время
fallStart = os.clock()
-- падение закончилось
elseif fallStart then
-- не падаем
fallStart = nil
-- поза падения выключается
if track then track:Stop() end
-- убрать парашют и следы
if opened or trails then event:FireServer("Landed") end
-- к следующему падению всё сначала
opened, trails = false, false
-- конец проверки
end
-- конец функции
end)
-- нажали клавишу
UIS.InputBegan:Connect(function(input, processed)
-- пробел во время падения
if processed or input.KeyCode ~= Enum.KeyCode.Space or not fallStart then return end
-- падаем давно и ещё высоко
if os.clock() - fallStart > 0.7 and Height() > MIN_HEIGHT then Open() end
-- конец функции
end)
-- каждый кадр
RunService.Heartbeat:Connect(function()
-- обычный прыжок короче секунды — не трогаем
if not fallStart or os.clock() - fallStart < 1 then return end
-- падаем долго — значит с высоты
if not trails then
-- один раз
trails = true
-- поза падения
if track then track:Play() end
-- сервер добавит следы-Trail на руки и ноги
event:FireServer("Fall")
-- конец проверки
end
-- забыл раскрыть — раскроется сам
if not opened and Height() < AUTO_HEIGHT then Open() end
-- конец функции
end)Где: StarterPlayer → StarterCharacterScripts · LocalScript
Зачем: Колесо мыши.
-- игроки
local Players = game:GetService("Players")
-- этот игрок
local player = Players.LocalPlayer
-- персонаж
local character = script.Parent
-- Humanoid
local humanoid = character:WaitForChild("Humanoid")
-- рюкзак
local backpack = player:WaitForChild("Backpack")
-- колёсико мыши
local mouse = player:GetMouse()
-- звук переключения (Sound внутри скрипта — по желанию)
local click = script:FindFirstChild("Click")
-- колесо теперь переключает оружие…
player.CameraMinZoomDistance = 12
-- …поэтому камеру фиксируем на одном расстоянии
player.CameraMaxZoomDistance = 12
-- инструменты по порядку — каждый раз заново (в методичке список был один при входе)
local function Tools()
-- список
local list = {}
-- рюкзак
for _, tool in ipairs(backpack:GetChildren()) do
-- только инструменты
if tool:IsA("Tool") then table.insert(list, tool) end
-- конец перебора
end
-- что в руках
local equipped = character:FindFirstChildOfClass("Tool")
-- тоже в списке
if equipped then table.insert(list, equipped) end
-- по имени — порядок не прыгает
table.sort(list, function(a, b) return a.Name < b.Name end)
-- список и что в руках
return list, equipped
-- конец функции
end
-- следующий (1) или предыдущий (-1)
local function Switch(step)
-- погиб
if humanoid.Health <= 0 then return end
-- что есть
local list, equipped = Tools()
-- рюкзак пуст
if #list == 0 then return end
-- номер того, что в руках
local index = table.find(list, equipped)
-- что-то в руках
if index then
-- по кругу: после последнего — первый
index = (index - 1 + step) % #list + 1
-- руки пусты
else
-- первый или последний
index = step > 0 and 1 or #list
-- конец проверки
end
-- берём в руки
humanoid:EquipTool(list[index])
-- щелчок
if click then click:Play() end
-- конец функции
end
-- колесо от себя — назад
mouse.WheelForward:Connect(function() Switch(-1) end)
-- колесо на себя — вперёд
mouse.WheelBackward:Connect(function() Switch(1) end)Где: StarterPlayer → StarterPlayerScripts · LocalScript
Зачем: Ослепление.
-- события
local RS = game:GetService("ReplicatedStorage")
-- плавность
local TS = game:GetService("TweenService")
-- эффекты экрана
local Lighting = game:GetService("Lighting")
-- сервер: тебя ослепило
local flashEvent = RS:WaitForChild("FlashEvent")
-- точка взрыва
flashEvent.OnClientEvent:Connect(function(position)
-- камера
local camera = workspace.CurrentCamera
-- направление на вспышку
local toFlash = (position - camera.CFrame.Position).Unit
-- 1 — смотрит прямо, −1 — спиной
local facing = camera.CFrame.LookVector:Dot(toFlash)
-- отвернулся — слепит слабее
local strength = facing > 0.3 and 1 or 0.35
-- белый экран — только у этого игрока
local flash = Instance.new("ColorCorrectionEffect")
-- имя
flash.Name = "Flash"
-- яркость
flash.Brightness = 2 * strength
-- включаем
flash.Parent = Lighting
-- ослеплён
task.wait(1.5 * strength)
-- проходит
local tween = TS:Create(flash, TweenInfo.new(1.5), {Brightness = 0})
-- плавно
tween:Play()
-- ждём
tween.Completed:Wait()
-- убираем
flash:Destroy()
-- конец функции
end)Где: StarterGui → RoundGui · LocalScript
Зачем: Таймер, живые, победитель.
-- игроки
local Players = game:GetService("Players")
-- атрибуты раунда
local RS = game:GetService("ReplicatedStorage")
-- этот игрок
local player = Players.LocalPlayer
-- RoundGui
local gui = script.Parent
-- таймер зоны
local timerFrame = gui:WaitForChild("TimerFrame")
-- что происходит
local info = timerFrame:WaitForChild("Info")
-- мм:сс
local timeLabel = timerFrame:WaitForChild("Time")
-- «Ты за зоной!»
local warnText = gui:WaitForChild("WarnText")
-- сколько живых
local aliveText = gui:WaitForChild("AliveText")
-- подсказка и победитель
local centerText = gui:WaitForChild("CenterText")
-- обновить таймер
local function UpdateTimer()
-- текст от модуля зоны
local text = RS:GetAttribute("ZoneText") or ""
-- секунды
local left = RS:GetAttribute("TimeLeft") or 0
-- нет раунда — таймер скрыт
timerFrame.Visible = text ~= ""
-- текст
info.Text = text
-- минуты и секунды двумя цифрами
timeLabel.Text = string.format("%02d:%02d", left // 60, left % 60)
-- конец функции
end
-- текст сменился
RS:GetAttributeChangedSignal("ZoneText"):Connect(UpdateTimer)
-- секунда прошла
RS:GetAttributeChangedSignal("TimeLeft"):Connect(UpdateTimer)
-- сразу
UpdateTimer()
-- предупреждение
local function UpdateWarn()
-- за зоной — видно
warnText.Visible = player:GetAttribute("OutsideZone") == true
-- конец функции
end
-- вышел или вернулся
player:GetAttributeChangedSignal("OutsideZone"):Connect(UpdateWarn)
-- сразу
UpdateWarn()
-- живые, подсказка, победитель
local function UpdateRound()
-- имя победителя
local winner = RS:GetAttribute("Winner") or ""
-- счётчик — только во время боя
aliveText.Visible = RS:GetAttribute("Phase") == "Fight"
-- сколько
aliveText.Text = "Живых: " .. (RS:GetAttribute("Alive") or 0)
-- раунд закончен
if winner ~= "" then
-- поздравляем
centerText.Text = "Победитель: " .. winner
-- в самолёте
elseif player:GetAttribute("OnPlane") then
-- подсказка
centerText.Text = "Пробел — прыгнуть"
-- иначе
else
-- пусто
centerText.Text = ""
-- конец проверки
end
-- конец функции
end
-- атрибуты раунда
for _, name in ipairs({"Phase", "Alive", "Winner"}) do
-- изменился — обновить
RS:GetAttributeChangedSignal(name):Connect(UpdateRound)
-- конец перебора
end
-- сел в самолёт или прыгнул
player:GetAttributeChangedSignal("OnPlane"):Connect(UpdateRound)
-- сразу
UpdateRound()Где: StarterGui → MainGui → Teleporting · LocalScript
Зачем: Затемнение.
-- общее хранилище
local RS = game:GetService("ReplicatedStorage")
-- плавность
local TS = game:GetService("TweenService")
-- Frame «Teleporting» на весь экран
local background = script.Parent
-- надпись
local text = background:WaitForChild("TextLabel")
-- сервер отправляет
local teleportingEvent = RS:WaitForChild("TeleportingEvent")
-- 2 секунды
local info = TweenInfo.new(2)
-- отправка
teleportingEvent.OnClientEvent:Connect(function()
-- экран темнеет
TS:Create(background, info, {BackgroundTransparency = 0}):Play()
-- надпись проявляется
TS:Create(text, info, {TextTransparency = 0}):Play()
-- пока переносят
task.wait(3.5)
-- экран светлеет
TS:Create(background, info, {BackgroundTransparency = 1}):Play()
-- надпись исчезает
TS:Create(text, info, {TextTransparency = 1}):Play()
-- конец функции
end)Где: StarterGui → MainGui → LeaveButton · LocalScript
Зачем: Кнопка «Выйти».
-- общее хранилище
local RS = game:GetService("ReplicatedStorage")
-- кнопка «Выйти»
local button = script.Parent
-- событие
local leaveEvent = RS:WaitForChild("LeaveEvent")
-- сначала скрыта
button.Visible = false
-- сервер: показать или спрятать
leaveEvent.OnClientEvent:Connect(function(show)
-- видна или нет
button.Visible = show
-- конец функции
end)
-- клик
button.MouseButton1Click:Connect(function()
-- просим вернуть в лобби
leaveEvent:FireServer()
-- конец функции
end)Где: StarterGui → DeathGui → DeathFrame · LocalScript
Зачем: Экран смерти.
-- игроки
local Players = game:GetService("Players")
-- события
local RS = game:GetService("ReplicatedStorage")
-- плавность
local TS = game:GetService("TweenService")
-- размытие
local Lighting = game:GetService("Lighting")
-- этот игрок
local player = Players.LocalPlayer
-- DeathFrame
local frame = script.Parent
-- «В лобби»
local button = frame:WaitForChild("TeleportButton")
-- отсчёт
local timeLabel = frame:WaitForChild("TimeToTeleport")
-- попросить возрождение
local deathEvent = RS:WaitForChild("DeathEvent")
-- секунд до автоматического возвращения
local RESPAWN_TIME = 8
-- размытие мира
local blur = Lighting:FindFirstChild("DeathScreenBlur") or Instance.new("BlurEffect")
-- имя
blur.Name = "DeathScreenBlur"
-- пока не размыто
blur.Size = 0
-- включено (сила 0)
blur.Parent = Lighting
-- плавно
local info = TweenInfo.new(1, Enum.EasingStyle.Quint, Enum.EasingDirection.InOut)
-- сейчас мёртв?
local dead = false
-- экран смерти скрыт
frame.Visible = false
-- в лобби
local function Leave()
-- уже
if not dead then return end
-- больше не ждём
dead = false
-- прячем экран
frame.Visible = false
-- мир снова чёткий
TS:Create(blur, info, {Size = 0}):Play()
-- сервер возрождает
deathEvent:FireServer()
-- конец функции
end
-- подписка одна (в методичке — при каждой смерти)
button.MouseButton1Click:Connect(Leave)
-- новый персонаж
local function OnCharacter(character)
-- Humanoid
local humanoid = character:WaitForChild("Humanoid")
-- жив
dead = false
-- экран скрыт
frame.Visible = false
-- без размытия
blur.Size = 0
-- погиб
humanoid.Died:Connect(function()
-- мёртв
dead = true
-- мир размывается
TS:Create(blur, info, {Size = 20}):Play()
-- секунда
task.wait(1)
-- уже нажал
if not dead then return end
-- экран смерти
frame.Visible = true
-- отсчёт
for i = RESPAWN_TIME, 1, -1 do
-- нажал кнопку
if not dead then return end
-- текст
timeLabel.Text = "В лобби через " .. i .. " с"
-- секунда
task.wait(1)
-- конец отсчёта
end
-- время вышло
Leave()
-- конец функции
end)
-- конец функции
end
-- каждый новый персонаж
player.CharacterAdded:Connect(OnCharacter)
-- текущий
if player.Character then OnCharacter(player.Character) endГде: StarterGui → StartGui → StartFrame · LocalScript
Зачем: Стартовый экран.
-- плавность
local TS = game:GetService("TweenService")
-- StartFrame на весь экран
local frame = script.Parent
-- логотип
local logoImage = frame:WaitForChild("LogoFrame"):WaitForChild("LogoImage")
-- название игры (в методичке то TextLabel, то LogoText)
local logoText = frame.LogoFrame:WaitForChild("LogoText")
-- «Игру сделал»
local presentedBy = frame:WaitForChild("PresentedBy")
-- имя разработчика
local me = frame:WaitForChild("Me")
-- плавно изменить прозрачность
local function Fade(object, property, value, seconds)
-- анимация
local tween = TS:Create(object, TweenInfo.new(seconds or 1), {[property] = value})
-- запуск
tween:Play()
-- отдаём
return tween
-- конец функции
end
-- экран закрыт
frame.Visible = true
-- чёрный
frame.BackgroundTransparency = 0
-- пауза
task.wait(1)
-- логотип проявляется
Fade(logoImage, "ImageTransparency", 0)
-- название
Fade(logoText, "TextTransparency", 0)
-- смотрим
task.wait(2.5)
-- исчезает
Fade(logoImage, "ImageTransparency", 1)
-- ждём
Fade(logoText, "TextTransparency", 1).Completed:Wait()
-- «Игру сделал»
Fade(presentedBy, "TextTransparency", 0)
-- смотрим
task.wait(2)
-- исчезает
Fade(presentedBy, "TextTransparency", 1).Completed:Wait()
-- имя
Fade(me, "TextTransparency", 0)
-- смотрим
task.wait(2)
-- исчезает
Fade(me, "TextTransparency", 1).Completed:Wait()
-- экран открывается
Fade(frame, "BackgroundTransparency", 1, 2).Completed:Wait()
-- больше не мешает кликам
frame.Visible = falseЗапусти Play (или Run) и убедись: