Roblox · Королевская битва · урок 2
Безопасная зона — светящийся круг, который сужается волнами. За ним теряешь здоровье, и с каждой волной всё быстрее. Вся логика — в модульном скрипте, его потом использует раунд.
Синим — что добавляем на этом уроке.
Workspace └─ SafeZone (Part) — Cylinder лёжа на боку, ForceField, Anchored, CanCollide/CanQuery/CastShadow выкл. ServerScriptService ├─ ZoneModule (ModuleScript) └─ ZoneServer (Script) — для проверки; в уроке «Десантирование» заменит RoundServer StarterGui └─ RoundGui (ScreenGui) — ResetOnSpawn выкл. ├─ TimerFrame (Frame) — Info, Time (TextLabel); вверху по центру ├─ WarnText (TextLabel) — «Ты за зоной!», красный, Visible выкл. └─ RoundLocal (LocalScript)
Где: 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 Zone = require(script.Parent:WaitForChild("ZoneModule"))
-- волны идут отдельно от урона
task.spawn(function()
-- по кругу — для проверки
while true do
-- все волны
Zone.Run(function() return true end)
-- пауза и заново
task.wait(10)
-- конец цикла
end
-- конец функции
end)
-- раз в секунду
while true do
-- секунда
task.wait(1)
-- урон всем, кто за зоной
Zone.Hurt(Players:GetPlayers())
-- конец цикла
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 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()Запусти Play (или Run) и убедись: