Roblox · RPG · урок 10

Боевая система: мобы и индикатор урона

Мобы для охоты: у каждого свой уровень, полоса здоровья с числами, они нападают на игрока, а за победу дают опыт и монеты. Над каждым ударом взлетает цифра урона.

Структура

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

Workspace
└─ Mobs (Folder)
   └─ Wolf (Model) — Humanoid: DisplayDistanceType None
      ├─ HealthGui (BillboardGui) — Adornee = Head
      │  ├─ Lvl (TextLabel)
      │  └─ Background (Frame)
      │     ├─ Bar (Frame) — полоса
      │     │  └─ HealthBar (Script)
      │     └─ HealthValue (TextLabel)
      └─ MobAI (Script)
ReplicatedStorage
└─ Indicator (Part) — маленький, прозрачный, Anchored
   └─ Num (BillboardGui)
      └─ Damage (TextLabel)
ServerScriptService
├─ MobManager (Script)
└─ DamageIndicatorSystem (Script)

Шаги

  1. Моб — Rig Builder или модель из Toolbox (удали из неё чужие скрипты). Humanoid → DisplayDistanceType None — стандартные имя и полоска не нужны.
  2. HealthGui (BillboardGui, Adornee = Head моба): TextLabel «Lvl», Frame «Background» → Frame «Bar» и TextLabel «HealthValue». Размеры — через Scale. В Bar — Script «HealthBar».
  3. MobAI — моб замечает игрока ближе 30 studs, бежит и кусает: 3 + уровень × 2.
  4. Workspace → Folder «Mobs», мобов — туда. MobManager задаёт каждому случайный уровень 1–5 (здоровье 50 × уровень), за победу даёт опыт 40 × уровень и монеты 5 × уровень тому, кто ударил последним, и возрождает через 15 секунд.
  5. Индикатор: маленький Part «Indicator» (прозрачный, CanCollide выкл., Anchored) с BillboardGui «Num» → TextLabel «Damage». В ReplicatedStorage.
  6. DamageIndicatorSystem следит и за новыми персонажами и мобами — в методичке только за теми, кто был при старте.
  7. GetDescendants ищет на всех уровнях вложенности, GetChildren — только прямых детей.

Скрипты

HealthBar

Где: Workspace → Mobs → Wolf → HealthGui → Background → Bar · Script
Зачем: Полоса здоровья моба.

-- полоса здоровья
local bar = script.Parent
-- HealthGui: Bar → Background → HealthGui
local gui = bar.Parent.Parent
-- «120 / 150»
local healthText = bar.Parent:WaitForChild("HealthValue")
-- «Ур. 3»
local lvlText = gui:WaitForChild("Lvl")
-- модель моба
local mob = gui.Parent
-- его здоровье
local humanoid = mob:WaitForChild("Humanoid")
-- уровень моба (ставит MobManager)
local level = mob:WaitForChild("Level")

-- обновить полоску
local function Update()
    -- длина = доля здоровья
    bar.Size = UDim2.fromScale(math.clamp(humanoid.Health / humanoid.MaxHealth, 0, 1), 1)
    -- числа
    healthText.Text = math.ceil(humanoid.Health) .. " / " .. humanoid.MaxHealth
    -- уровень
    lvlText.Text = "Ур. " .. level.Value
-- конец функции
end
-- урон
humanoid.HealthChanged:Connect(Update)
-- новый максимум
humanoid:GetPropertyChangedSignal("MaxHealth"):Connect(Update)
-- новый уровень
level.Changed:Connect(Update)
-- сразу
Update()

MobAI

Где: Workspace → Mobs → Wolf · Script
Зачем: Погоня и укусы.

-- игроки
local Players = game:GetService("Players")
-- моб
local mob = script.Parent
-- Humanoid
local humanoid = mob:WaitForChild("Humanoid")
-- центр
local root = mob:WaitForChild("HumanoidRootPart")
-- уровень — от него урон
local level = mob:WaitForChild("Level")
-- Animator
local animator = humanoid:FindFirstChildOfClass("Animator") or Instance.new("Animator", humanoid)
-- замечает игрока ближе 30 studs
local SIGHT = 30
-- кусает ближе 5 studs
local ATTACK_RANGE = 5
-- секунд между укусами
local COOLDOWN = 1.2

-- ходьба
local walk = Instance.new("Animation")
-- стандартная R15
walk.AnimationId = "rbxassetid://507777826"
-- загружаем
local walkTrack = animator:LoadAnimation(walk)
-- по кругу
walkTrack.Looped = true
-- скорость изменилась
humanoid.Running:Connect(function(speed)
    -- идёт — шагаем, стоит — стоим
    if speed > 0.5 and not walkTrack.IsPlaying then walkTrack:Play() elseif speed <= 0.5 then walkTrack:Stop() end
-- конец функции
end)
-- движение считает сервер
pcall(function() root:SetNetworkOwner(nil) end)
-- куда возвращаться
local home = root.Position
-- время прошлого укуса
local lastAttack = 0

-- пока жив
while humanoid.Health > 0 do
    -- ближайший игрок
    local best, bestRoot, bestDistance = nil, nil, SIGHT
    -- все игроки
    for _, player in ipairs(Players:GetPlayers()) do
        -- персонаж
        local character = player.Character
        -- его Humanoid
        local target = character and character:FindFirstChildOfClass("Humanoid")
        -- его центр
        local targetRoot = character and character:FindFirstChild("HumanoidRootPart")
        -- жив
        if target and targetRoot and target.Health > 0 then
            -- расстояние
            local distance = (targetRoot.Position - root.Position).Magnitude
            -- ближе прошлого
            if distance < bestDistance then best, bestRoot, bestDistance = target, targetRoot, distance end
        -- конец проверки
        end
    -- конец перебора
    end
    -- есть цель
    if best then
        -- бежим к ней
        humanoid:MoveTo(bestRoot.Position)
        -- рядом и укус готов
        if bestDistance <= ATTACK_RANGE and os.clock() - lastAttack >= COOLDOWN then
            -- запоминаем
            lastAttack = os.clock()
            -- сильнее с уровнем моба
            best:TakeDamage(3 + level.Value * 2)
        -- конец проверки
        end
    -- никого
    else
        -- домой
        humanoid:MoveTo(home)
    -- конец проверки
    end
    -- 4 раза в секунду
    task.wait(0.25)
-- конец цикла
end

MobManager

Где: ServerScriptService · Script
Зачем: Уровни, награда, возрождение.

-- все мобы
local mobs = workspace:WaitForChild("Mobs")
-- через сколько секунд моб вернётся
local RESPAWN = 15

-- следим за одним мобом
local function Setup(mob)
    -- копия целого — для возрождения
    local sample = mob:Clone()
    -- где стоял
    local spot = mob:GetPivot()
    -- Humanoid
    local humanoid = mob:WaitForChild("Humanoid")
    -- уровень моба
    local level = mob:FindFirstChild("Level") or Instance.new("IntValue")
    -- имя — его читают HealthBar и MobAI
    level.Name = "Level"
    -- случайный уровень
    level.Value = math.random(1, 5)
    -- в моба
    level.Parent = mob
    -- здоровье растёт с уровнем
    humanoid.MaxHealth = 50 * level.Value
    -- полное
    humanoid.Health = humanoid.MaxHealth
    -- моб побеждён
    humanoid.Died:Connect(function()
        -- метка от оружия
        local tag = humanoid:FindFirstChild("creator")
        -- кто победил
        local player = tag and tag.Value
        -- игрок ещё в игре
        if player and player.Parent and player:FindFirstChild("Exp") then
            -- опыт
            player.Exp.Value += 40 * level.Value
            -- монеты
            player.leaderstats["Монеты"].Value += 5 * level.Value
        -- конец проверки
        end
        -- ждём
        task.wait(RESPAWN)
        -- убираем тело
        mob:Destroy()
        -- новый моб
        local new = sample:Clone()
        -- на старое место
        new:PivotTo(spot)
        -- в игру
        new.Parent = mobs
        -- следим и за ним
        Setup(new)
    -- конец функции
    end)
-- конец функции
end

-- все мобы
for _, mob in ipairs(mobs:GetChildren()) do Setup(mob) end

DamageIndicatorSystem

Где: ServerScriptService · Script
Зачем: Цифры урона.

-- образец индикатора
local RS = game:GetService("ReplicatedStorage")
-- удаляет индикаторы
local Debris = game:GetService("Debris")
-- кубик с BillboardGui Num → Damage
local template = RS:WaitForChild("Indicator")

-- следим за здоровьем одного персонажа или моба
local function Watch(humanoid)
    -- здоровье до изменения
    local last = humanoid.Health
    -- изменилось
    humanoid.HealthChanged:Connect(function(health)
        -- сколько потерял
        local damage = math.floor(last - health + 0.5)
        -- запоминаем
        last = health
        -- где он
        local root = humanoid.Parent and humanoid.Parent:FindFirstChild("HumanoidRootPart")
        -- это лечение или нет центра
        if damage <= 0 or not root then return end
        -- новый индикатор
        local indicator = template:Clone()
        -- рядом, чуть в стороне
        indicator.CFrame = CFrame.new(root.Position + Vector3.new(math.random(-2, 2), 2 + math.random(0, 2), math.random(-2, 2)))
        -- текст урона
        indicator.Num.Damage.Text = "-" .. damage
        -- показываем
        indicator.Parent = workspace
        -- 0.7 секунды
        Debris:AddItem(indicator, 0.7)
    -- конец функции
    end)
-- конец функции
end

-- все, кто уже есть
for _, obj in ipairs(workspace:GetDescendants()) do
    -- следим
    if obj:IsA("Humanoid") then Watch(obj) end
-- конец перебора
end
-- новые: игроки, возродившиеся мобы
workspace.DescendantAdded:Connect(function(obj)
    -- следим и за ними
    if obj:IsA("Humanoid") then Watch(obj) end
-- конец функции
end)

Проверка

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