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)

Шаги

  1. Три вида скриптов: Script — на сервере, LocalScript — у игрока, ModuleScript — набор функций, который подключают другие скрипты через require. Изменил модуль — изменилось везде.
  2. Модуль начинается с local Zone = {} и заканчивается return Zone. Функции объявляются как function Zone.Имя().
  3. Зона: Part → Shape Cylinder. Цилиндр «лежит» вдоль оси X — поверни его Orientation (0, 0, 90): стал вертикальным. Size = (высота, диаметр, диаметр), например (600, 420, 420). Центр — посередине острова, Material ForceField, Anchored, CanCollide, CanQuery и CastShadow выкл. Имя «SafeZone».
  4. В методичке зону делали из четырёх стен и ZonePlus, а цикл урона запускался заново при каждом входе — урон рос без конца. Здесь свой модуль: внутри или снаружи — по расстоянию до центра.
  5. Волны в таблице Zone.WAVES: сколько ждать, до какого радиуса сузиться, за сколько секунд и какой урон. Хочешь больше раундов — добавь строчку.
  6. Модуль пишет текст таймера в атрибуты ReplicatedStorage (ZoneText, TimeLeft), а «за зоной» — в атрибут игрока OutsideZone. RoundLocal просто показывает их — никаких RemoteEvent.
  7. RoundGui: Frame «TimerFrame» с TextLabel «Info» и «Time» вверху; TextLabel «WarnText» красный по центру, Visible выкл.

Скрипты

ZoneModule

Где: 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

ZoneServer

Где: 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

RoundLocal

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