Roblox · Песочница · урок 9

Противники: поиск пути и патруль

Зомби патрулирует лес по точкам, обходит стены и прыгает через препятствия. Заметил игрока — бежит быстрее и бьёт. Скорость и урон настраиваются атрибутами.

Структура

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

Workspace
├─ Zombie (Model) — атрибуты Damage, WalkSpeed, SprintSpeed
│  └─ EnemyScript (Script)
└─ Waypoints (Folder)
   └─ Point (Part) — невидимые, Anchored, CanCollide выкл.

Шаги

  1. Простой ИИ бежит напролом сквозь стены. PathfindingService строит путь в обход: CreatePath с AgentRadius, AgentHeight, AgentCanJump, AgentCanClimb, WaypointSpacing и Costs (Water = 100 — воду обходит, если можно).
  2. path:ComputeAsync(откуда, куда) → path:GetWaypoints() — точки пути; у точки Action Jump — надо прыгнуть.
  3. Атрибуты — свои свойства объекта: модель противника → Properties → Attributes → «+»: Damage (10), WalkSpeed (8), SprintSpeed (18), тип number. В скрипте — enemy:GetAttribute("Damage"). Меняешь число — скрипт не трогаешь.
  4. Точки патруля — невидимые детали в папке «Waypoints». Противник ходит к случайной точке.
  5. В методичке выбирался последний игрок в списке, а не ближайший, удар шёл без перезарядки и по мёртвым. Здесь — ближайший живой, удар раз в секунду; в погоне путь пересчитывается каждые 3 точки.
  6. Наглядно: можно добавить шарики на точках пути (Part, Neon, Anchored), чтобы увидеть, как строится путь.

Скрипты

EnemyScript

Где: Workspace → Zombie · Script
Зачем: Патруль, погоня, удары.

-- поиск пути в обход стен
local PathfindingService = game:GetService("PathfindingService")
-- игроки
local Players = game:GetService("Players")
-- противник
local enemy = script.Parent
-- Humanoid
local humanoid = enemy:WaitForChild("Humanoid")
-- центр
local root = enemy:WaitForChild("HumanoidRootPart")
-- точки патруля
local waypoints = workspace:WaitForChild("Waypoints")
-- замечает игрока ближе 40 studs
local SIGHT = 40
-- бьёт ближе 4 studs
local ATTACK_RANGE = 4
-- секунд между ударами
local COOLDOWN = 1
-- время прошлого удара
local lastAttack = 0
-- Animator
local animator = humanoid:FindFirstChildOfClass("Animator") or Instance.new("Animator", humanoid)
-- ходьба
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 path = PathfindingService:CreatePath({
    -- ширина противника
    AgentRadius = 2,
    -- рост
    AgentHeight = 6,
    -- умеет прыгать
    AgentCanJump = true,
    -- не лазает
    AgentCanClimb = false,
    -- вода «дорогая» — обходит, если можно
    Costs = {Water = 100},
-- конец настроек
})

-- ближайший живой игрок в поле зрения
local function FindTarget()
    -- пока никого
    local best, bestHumanoid, 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, bestHumanoid, bestDistance = targetRoot, target, distance end
        -- конец проверки
        end
    -- конец перебора
    end
    -- центр, Humanoid и расстояние
    return best, bestHumanoid, bestDistance
-- конец функции
end

-- идти по найденному пути
local function MoveAlong(destination, chasing)
    -- строим путь
    local ok = pcall(function() path:ComputeAsync(root.Position, destination) end)
    -- путь не нашёлся
    if not ok or path.Status ~= Enum.PathStatus.Success then
        -- идём напрямик
        humanoid:MoveTo(destination)
        -- немного
        task.wait(0.5)
        -- потом снова подумаем
        return
    -- конец проверки
    end
    -- точки пути
    for i, waypoint in ipairs(path:GetWaypoints()) do
        -- тут надо прыгнуть
        if waypoint.Action == Enum.PathWaypointAction.Jump then humanoid.Jump = true end
        -- к точке
        humanoid:MoveTo(waypoint.Position)
        -- дошли
        humanoid.MoveToFinished:Wait()
        -- в погоне путь пересчитываем каждые 3 точки — игрок двигается
        if chasing and i >= 3 then return end
        -- в патруле заметил игрока — бросаем патруль
        if not chasing and FindTarget() then return end
    -- конец перебора
    end
-- конец функции
end

-- пока жив
while humanoid.Health > 0 do
    -- есть кто рядом?
    local targetRoot, target, distance = FindTarget()
    -- есть
    if targetRoot then
        -- погоня — быстрее (атрибут SprintSpeed)
        humanoid.WalkSpeed = enemy:GetAttribute("SprintSpeed") or 18
        -- рядом
        if distance <= ATTACK_RANGE then
            -- удар готов
            if os.clock() - lastAttack >= COOLDOWN then
                -- запоминаем
                lastAttack = os.clock()
                -- урон из атрибута Damage
                target:TakeDamage(enemy:GetAttribute("Damage") or 10)
            -- конец проверки
            end
            -- немного
            task.wait(0.2)
        -- далеко
        else
            -- бежим в обход препятствий
            MoveAlong(targetRoot.Position, true)
        -- конец проверки
        end
    -- никого
    else
        -- патруль — медленно
        humanoid.WalkSpeed = enemy:GetAttribute("WalkSpeed") or 8
        -- точки патруля
        local points = waypoints:GetChildren()
        -- есть
        if #points > 0 then
            -- случайная точка
            local point = points[math.random(1, #points)]
            -- идём к ней
            MoveAlong(point:IsA("Model") and point:GetPivot().Position or point.Position, false)
        -- точек нет
        else
            -- стоим
            task.wait(1)
        -- конец проверки
        end
    -- конец проверки
    end
-- конец цикла
end

Проверка

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