Roblox · Приключение · урок 3

Противники

Гоблины, которые замечают игрока, бегут к нему и бьют. Если никого нет — возвращаются на место.

Структура

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

Workspace
└─ Enemies (Folder)
   └─ Goblin (Model) — Rig Builder R15
      ├─ Humanoid (Humanoid) — MaxHealth 60, WalkSpeed 12
      └─ EnemyScript (Script)

Шаги

  1. Avatar → Rig Builder → R15, перекрась в зелёный, назови «Goblin». HumanoidRootPart — Anchored выкл.!
  2. Humanoid: MaxHealth и Health 60, WalkSpeed 12 — игрок (16) может убежать.
  3. Script «EnemyScript» в гоблина. Анимации — стандартные R15: ходьба и взмах. Для R6-противника нужны R6-анимации.
  4. Каждые 0.2 секунды ищем ближайшего живого игрока в радиусе SIGHT. Нашли — идём; ближе ATTACK_RANGE — бьём раз в секунду.
  5. TakeDamage вместо Health -= : не бьёт игрока под ForceField (сразу после появления).
  6. Атрибут Invisible у персонажа пригодится на следующем уроке — невидимого противник не замечает.
  7. Workspace → Folder «Enemies», гоблинов — туда (Ctrl+D для копий).
  8. Полоска здоровья над гоблином — тема «Полоска здоровья над мобом» в разделе «Интерфейсы».

Скрипты

EnemyScript

Где: Workspace → Enemies → Goblin · Script
Зачем: Поиск цели, погоня и удары.

-- игроки
local Players = game:GetService("Players")
-- модель противника
local enemy = script.Parent
-- Humanoid
local humanoid = enemy:WaitForChild("Humanoid")
-- центр
local root = enemy:WaitForChild("HumanoidRootPart")
-- Animator
local animator = humanoid:FindFirstChildOfClass("Animator") or Instance.new("Animator", humanoid)
-- видит игроков ближе 40 studs
local SIGHT = 40
-- бьёт ближе 5 studs
local ATTACK_RANGE = 5
-- урон
local DAMAGE = 10
-- секунд между ударами
local COOLDOWN = 1

-- ходьба
local walkAnim = Instance.new("Animation")
-- стандартная R15
walkAnim.AnimationId = "rbxassetid://507777826"
-- удар
local attackAnim = Instance.new("Animation")
-- стандартный взмах R15
attackAnim.AnimationId = "rbxassetid://522635514"
-- загружаем
local walkTrack = animator:LoadAnimation(walkAnim)
-- по кругу
walkTrack.Looped = true
-- загружаем
local attackTrack = animator:LoadAnimation(attackAnim)
-- поверх ходьбы
attackTrack.Priority = Enum.AnimationPriority.Action
-- куда возвращаться
local home = root.Position
-- время прошлого удара
local lastAttack = 0
-- физику считает сервер
pcall(function() root:SetNetworkOwner(nil) end)

-- скорость изменилась
humanoid.Running:Connect(function(speed)
    -- пошёл
    if speed > 0.5 and not walkTrack.IsPlaying then
        -- шагаем
        walkTrack:Play()
    -- встал
    elseif speed <= 0.5 and walkTrack.IsPlaying then
        -- стоим
        walkTrack:Stop()
    -- конец проверки
    end
-- конец функции
end)

-- ближайший живой видимый игрок
local function FindTarget()
    -- пока никого
    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 and not character:GetAttribute("Invisible") then
            -- расстояние
            local distance = (targetRoot.Position - root.Position).Magnitude
            -- ближе прошлого
            if distance < bestDistance then
                -- запоминаем
                best, bestRoot, bestDistance = target, targetRoot, distance
            -- конец проверки
            end
        -- конец проверки
        end
    -- конец перебора
    end
    -- цель, её центр, расстояние
    return best, bestRoot, bestDistance
-- конец функции
end

-- пока жив
while humanoid.Health > 0 do
    -- ищем цель
    local target, targetRoot, distance = FindTarget()
    -- нашли
    if target then
        -- идём к ней
        humanoid:MoveTo(targetRoot.Position)
        -- рядом и удар готов
        if distance <= ATTACK_RANGE and os.clock() - lastAttack >= COOLDOWN then
            -- запоминаем время
            lastAttack = os.clock()
            -- взмах
            attackTrack:Play()
            -- урон (ForceField защищает)
            target:TakeDamage(DAMAGE)
        -- конец проверки
        end
    -- никого рядом
    else
        -- возвращаемся на место
        humanoid:MoveTo(home)
    -- конец проверки
    end
    -- 5 раз в секунду
    task.wait(0.2)
-- конец цикла
end

Проверка

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