Roblox · Аркада · урок 3

Враги, очки и волны

Враги появляются волнами и бегут к ближайшему игроку. Убил — +10 очков. Каждая волна больше и быстрее. Вверху — номер волны и очки.

Структура

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

Workspace
├─ Arena → EnemySpawns (Folder) — невидимые точки по углам
└─ Enemies (Folder) — живые враги
ServerStorage
└─ Enemy (Model) — R15-риг (Avatar → Rig Builder), без Animate
ServerScriptService
└─ EnemyServer (Script)
StarterGui
└─ ScoreGui (ScreenGui) — Info (TextLabel); ScoreLocal

Шаги

  1. Враг: Avatar → Rig Builder → R15, перекрась, назови «Enemy», положи в ServerStorage.
  2. Точки появления — невидимые детали в Arena → «EnemySpawns» (по углам, подальше от центра).
  3. EnemyServer: волна N — 2 + 2N врагов, скорость 10 + N. Следующая волна — когда убиты все.
  4. Кто добил: снаряд записывает в Humanoid врага атрибут LastHit = номер игрока. При смерти врага этот игрок получает очки в leaderstats → Score.
  5. Враг бьёт касанием: ближе 4 studs — 10 урона раз в секунду.
  6. ScoreGui → TextLabel «Info» вверху по центру, ScoreLocal пишет «Волна 3 · Очки 120».

Скрипты

EnemyServer

Где: ServerScriptService · Script
Зачем: Волны, погоня, очки.

-- игроки
local Players = game:GetService("Players")
-- атрибут волны
local RS = game:GetService("ReplicatedStorage")
-- образец врага
local SS = game:GetService("ServerStorage")
-- враг (R15-риг)
local sample = SS:WaitForChild("Enemy")
-- точки появления
local spawns = workspace:WaitForChild("Arena"):WaitForChild("EnemySpawns")
-- живые враги
local folder = workspace:WaitForChild("Enemies")
-- очков за врага
local REWARD = 10
-- урон при касании
local TOUCH_DAMAGE = 10
-- номер волны
local wave = 0

-- очки игрока
local function Score(player)
    -- таблица справа
    local stats = player:FindFirstChild("leaderstats") or Instance.new("Folder")
    -- имя
    stats.Name = "leaderstats"
    -- в игрока
    stats.Parent = player
    -- очки
    local score = stats:FindFirstChild("Score") or Instance.new("IntValue")
    -- имя
    score.Name = "Score"
    -- в таблицу
    score.Parent = stats
    -- отдаём
    return score
-- конец функции
end
-- новые игроки
Players.PlayerAdded:Connect(Score)
-- кто зашёл раньше скрипта
for _, player in ipairs(Players:GetPlayers()) do Score(player) end

-- ближайший живой игрок
local function Nearest(position)
    -- пока никого
    local best, bestDistance = nil, math.huge
    -- все игроки
    for _, player in ipairs(Players:GetPlayers()) do
        -- персонаж
        local character = player.Character
        -- центр
        local root = character and character:FindFirstChild("HumanoidRootPart")
        -- Humanoid
        local humanoid = character and character:FindFirstChildOfClass("Humanoid")
        -- жив и ближе
        if root and humanoid and humanoid.Health > 0 and (root.Position - position).Magnitude < bestDistance then
            -- запоминаем
            best, bestDistance = humanoid, (root.Position - position).Magnitude
        -- конец проверки
        end
    -- конец перебора
    end
    -- Humanoid цели и расстояние
    return best, bestDistance
-- конец функции
end

-- один враг
local function Spawn()
    -- точки
    local points = spawns:GetChildren()
    -- копия
    local enemy = sample:Clone()
    -- Humanoid
    local humanoid = enemy:FindFirstChildOfClass("Humanoid")
    -- центр
    local root = enemy:FindFirstChild("HumanoidRootPart")
    -- на случайную точку
    enemy:PivotTo(points[math.random(1, #points)].CFrame + Vector3.new(0, 3, 0))
    -- с каждой волной быстрее
    humanoid.WalkSpeed = 10 + wave
    -- в мир
    enemy.Parent = folder
    -- движение считает сервер
    root:SetNetworkOwner(nil)
    -- убит
    humanoid.Died:Connect(function()
        -- кто добил
        local player = Players:GetPlayerByUserId(humanoid:GetAttribute("LastHit") or 0)
        -- +10 очков
        if player then Score(player).Value += REWARD end
        -- тело исчезает
        task.delay(1, function() enemy:Destroy() end)
    -- конец функции
    end)
    -- мозг врага
    task.spawn(function()
        -- время прошлого удара
        local lastHit = 0
        -- пока жив
        while enemy.Parent and humanoid.Health > 0 do
            -- к кому бежать
            local target, distance = Nearest(root.Position)
            -- есть
            if target then
                -- бежит к игроку
                humanoid:MoveTo(target.Parent.HumanoidRootPart.Position)
                -- рядом и удар готов
                if distance < 4 and os.clock() - lastHit > 1 then
                    -- запоминаем
                    lastHit = os.clock()
                    -- урон
                    target:TakeDamage(TOUCH_DAMAGE)
                -- конец проверки
                end
            -- конец проверки
            end
            -- думает 3 раза в секунду
            task.wait(0.3)
        -- конец цикла
        end
    -- конец функции
    end)
-- конец функции
end

-- волна за волной
while true do
    -- ждём игроков
    repeat task.wait(1) until #Players:GetPlayers() > 0
    -- следующая волна
    wave += 1
    -- интерфейс покажет номер
    RS:SetAttribute("Wave", wave)
    -- врагов всё больше
    for i = 1, 2 + wave * 2 do
        -- враг
        Spawn()
        -- не все сразу
        task.wait(0.4)
    -- конец цикла
    end
    -- ждём, пока всех убьют
    repeat task.wait(1) until #folder:GetChildren() == 0
    -- передышка
    task.wait(4)
-- конец цикла
end

ScoreLocal

Где: StarterGui → ScoreGui · LocalScript
Зачем: Волна и очки.

-- игроки
local Players = game:GetService("Players")
-- номер волны
local RS = game:GetService("ReplicatedStorage")
-- этот игрок
local player = Players.LocalPlayer
-- надпись вверху
local label = script.Parent:WaitForChild("Info")

-- постоянно
while true do
    -- таблица
    local stats = player:FindFirstChild("leaderstats")
    -- очки
    local score = stats and stats:FindFirstChild("Score")
    -- текст
    label.Text = "Волна " .. (RS:GetAttribute("Wave") or 0) .. "   ·   Очки " .. (score and score.Value or 0)
    -- 4 раза в секунду
    task.wait(0.25)
-- конец цикла
end

Проверка

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