Roblox · Tower Defense · урок 5

База и конец игры

Враг, дошедший до конца пути, бьёт базу. Над базой — полоска здоровья; при нуле волны останавливаются.

Структура

Синим — что добавляем на этом уроке.

ServerStorage
└─ Bindables (Folder)
   ├─ DamageEvent (BindableEvent) — Mob → Base
   └─ GameOverEvent (BindableEvent) — Base → MainScript
Workspace
└─ Grass (Folder)
   └─ Base (Model) — в конце маршрута
      └─ HealthGui (BillboardGui) — AlwaysOnTop, Adornee = парт базы
         ├─ MaxHealth (Frame) — красный, Size 1,0,0.5,0, Position 0,0,0.5,0
         ├─ Health (Frame) — зелёный, такой же размер и позиция
         └─ Title (TextLabel) — Size 1,0,0.5,0
ServerScriptService
└─ MainScript (Script)
   ├─ Mob (ModuleScript)
   ├─ Tower (ModuleScript)
   └─ Base (ModuleScript)

Шаги

  1. BindableEvent передаёт сигнал сервер → сервер (RemoteEvent — только клиент ↔ сервер).
  2. HealthGui: в свойстве Adornee выбери главный парт базы, иначе полоска не появится.
  3. Поднять полоску над базой — ExtentsOffsetWorldSpace (например, 0, 6, 0).
  4. Health должен быть ниже MaxHealth в списке (ZIndex больше), чтобы зелёная полоса была поверх красной.

Скрипты

Mob

Где: ServerScriptService → MainScript · ModuleScript
Зачем: В конце пути сообщает базе урон (оставшееся здоровье врага).

-- хранилище с образцами врагов
local SS = game:GetService("ServerStorage")
-- папка с событиями сервер → сервер
local bindables = SS:WaitForChild("Bindables")
-- событие «враг дошёл до базы»
local damageEvent = bindables:WaitForChild("DamageEvent")
-- таблица модуля — сюда кладём функции
local mob = {}

-- ведёт врага по маршруту
function mob.Move(enemy, map)
    -- Humanoid управляет движением
    local humanoid = enemy:WaitForChild("Humanoid")
    -- точки маршрута
    local waypoints = workspace:FindFirstChild("Waypoints")
    -- анимация ходьбы внутри врага
    local animation = enemy:FindFirstChild("Animation")
    -- загружаем её в Humanoid
    local animTrack = humanoid:LoadAnimation(animation)
    -- запускаем ходьбу
    animTrack:Play()

    -- по точкам по порядку
    for waypoint = 1, #waypoints:GetChildren() do
        -- идём к точке
        humanoid:MoveTo(waypoints[waypoint].Position)
        -- ждём, пока дойдёт
        humanoid.MoveToFinished:Wait()
        -- враг погиб по дороге — дальше не идёт
        if humanoid.Health <= 0 then return end
    -- следующая точка
    end
    -- урон базе = оставшееся здоровье врага
    damageEvent:Fire(humanoid.Health)
    -- дошёл до конца — убираем
    enemy:Destroy()
-- конец функции
end

-- создаёт quantity врагов с именем name
function mob.Spawn(name, quantity, map)
    -- ищем образец
    local mobModel = SS.Mobs:FindFirstChild(name)
    -- образец найден
    if mobModel then
        -- нужное количество раз
        for i = 1, quantity do
            -- пауза между врагами
            task.wait(0.5)
            -- копия врага
            local mobClone = mobModel:Clone()
            -- ставим на старт
            mobClone.PrimaryPart.CFrame = map.SpawnPart.CFrame
            -- кладём на карту
            mobClone.Parent = workspace.Mobs
            -- все детали врага
            for _, obj in pairs(mobClone:GetDescendants()) do
                -- только физические
                if obj:IsA("BasePart") then
                    -- группа коллизий Mob
                    obj.CollisionGroup = "Mob"
                -- конец проверки
                end
            -- конец перебора
            end
            -- враг погиб
            mobClone.Humanoid.Died:Connect(function()
                -- короткая пауза
                task.wait(0.5)
                -- убираем тело с карты
                mobClone:Destroy()
            -- конец обработчика смерти
            end)
            -- движение параллельно — не ждём, пока враг пройдёт путь
            coroutine.wrap(mob.Move)(mobClone, map)
        -- следующий враг
        end
    -- конец проверки
    end
-- конец функции
end

-- отдаём таблицу тому, кто вызвал require
return mob

Base

Где: ServerScriptService → MainScript · ModuleScript
Зачем: Хранит здоровье базы, рисует полоску, при нуле сообщает о конце игры.

-- серверное хранилище
local SS = game:GetService("ServerStorage")
-- события сервер → сервер
local bindables = SS:WaitForChild("Bindables")
-- враг дошёл до базы
local damageEvent = bindables:WaitForChild("DamageEvent")
-- база разрушена
local gameOverEvent = bindables:WaitForChild("GameOverEvent")

-- таблица модуля
local base = {}

-- задаёт базу и её здоровье
function base.SetParams(map, health)
    -- модель базы на карте
    base.Model = map:WaitForChild("Base")
    -- максимум здоровья
    base.MaxHealth = health
    -- текущее здоровье
    base.Health = health
    -- сразу показываем полоску
    base.NewHealth(0)
-- конец функции
end

-- получает урон и обновляет полоску
function base.NewHealth(damage)
    -- база уже разрушена — ничего не делаем
    if base.Health <= 0 then return end
    -- вычитаем урон, но не ниже нуля
    base.Health = math.max(base.Health - damage, 0)
    -- полоска над базой
    local gui = base.Model.HealthGui
    -- доля оставшегося здоровья
    local percent = base.Health / base.MaxHealth
    -- зелёная полоса укорачивается
    gui.Health.Size = UDim2.new(percent, 0, 0.5, 0)
    -- здоровье кончилось
    if base.Health <= 0 then
        -- пишем, что база разрушена
        gui.Title.Text = "Base: Destroyed"
        -- сообщаем главному скрипту
        gameOverEvent:Fire()
    -- база ещё стоит
    else
        -- текст «Base: 450 / 500»
        gui.Title.Text = "Base: " .. base.Health .. " / " .. base.MaxHealth
    -- конец проверки
    end
-- конец функции
end

-- враг дошёл → урон базе
damageEvent.Event:Connect(base.NewHealth)

-- отдаём таблицу модуля
return base

MainScript

Где: ServerScriptService · Script
Зачем: Задаёт базе 500 здоровья и останавливает волны при поражении.

-- модуль врагов
local mob = require(script.Mob)
-- модуль башен
local tower = require(script.Tower)
-- модуль базы
local base = require(script.Base)
-- карта
local map = workspace.Grass

-- серверное хранилище
local SS = game:GetService("ServerStorage")
-- события сервер → сервер
local bindables = SS:WaitForChild("Bindables")
-- база разрушена
local gameOverEvent = bindables:WaitForChild("GameOverEvent")
-- игра ещё идёт
local gameOver = false

-- пришёл сигнал о разрушении базы
gameOverEvent.Event:Connect(function()
    -- игра окончена
    gameOver = true
-- конец обработчика
end)

-- у базы 500 здоровья
base.SetParams(map, 500)

-- волны по очереди
for wave = 1, 5 do
    -- обычные: 2, 4, 6…
    mob.Spawn("NormalRig", 2 * wave, map)
    -- быстрые
    mob.Spawn("FastRig", 2 * wave, map)
    -- сильные
    mob.Spawn("StrongRig", 2 * wave, map)
    -- ждём: карта пуста или база разрушена
    repeat task.wait(1) until #workspace.Mobs:GetChildren() == 0 or gameOver
    -- база разрушена — волн больше нет
    if gameOver then break end
-- следующая волна
end

Проверка

Запусти Play (или Run) и убедись: