Roblox · Анимации

Анимации NPC: стоит, ходит, атакует

Стражник обходит точки, увидев игрока — бежит к нему и бьёт. Каждое действие — своя анимация: стойка, ходьба, удар. Анимации играет сервер, поэтому их видят все игроки.

Структура

Синим — что нужно создать.

Workspace
└─ Guard (Model) — R15 из Avatar → Rig Builder
   ├─ Humanoid (Humanoid)
   │  └─ Animator (Animator)
   ├─ Animations (Folder)
   │  └─ Idle, Walk, Attack (Animation)
   ├─ Patrol (Folder) — точки 1, 2, 3… — прозрачные, Anchored, CanCollide выкл.
   ├─ Configuration (Configuration) — WalkSpeed, SightRange, AttackRange, AttackCooldown, Damage
   └─ NPCBrain (Script)

Шаги

  1. Avatar → Rig Builder → R15 — это NPC. Если внутри есть скрипт Animate — удали: у NPC свой мозг.
  2. Folder «Animations»: Idle (507766388), Walk (507777826), Attack (522635514) — стандартные Roblox; можно свои.
  3. Folder «Patrol»: точки обхода 1, 2, 3… (прозрачные, Anchored, CanCollide выкл.).
  4. Как работает: Humanoid.Running сообщает скорость — больше 0,5 → ходьба, иначе стойка. Цикл 5 раз в секунду: игрок вплотную — стоп и удар (анимация + урон через 0,3 с, если ещё рядом), игрок видно — бежит к нему, никого — идёт по точкам.
  5. root:SetNetworkOwner(nil) — физику NPC считает сервер, движение у всех одинаковое.

Скрипты

NPCBrain

Где: Workspace → Guard · Script
Зачем: Анимации и поведение NPC.

-- все игроки
local Players = game:GetService("Players")
-- модель NPC
local npc = script.Parent
-- его Humanoid
local humanoid = npc:WaitForChild("Humanoid")
-- его центр
local root = npc:WaitForChild("HumanoidRootPart")
-- проигрывает анимации
local animator = humanoid:WaitForChild("Animator")
-- настройки
local config = npc:WaitForChild("Configuration")
-- Idle, Walk, Attack
local anims = npc:WaitForChild("Animations")
-- точки обхода 1, 2, 3…
local patrol = npc:WaitForChild("Patrol"):GetChildren()
-- по номерам
table.sort(patrol, function(a, b) return (tonumber(a.Name) or 0) < (tonumber(b.Name) or 0) end)

-- стоит
local idle = animator:LoadAnimation(anims.Idle)
-- идёт
local walk = animator:LoadAnimation(anims.Walk)
-- бьёт
local attack = animator:LoadAnimation(anims.Attack)
-- по кругу
idle.Looped = true
-- по кругу
walk.Looped = true
-- удар главнее ходьбы
attack.Priority = Enum.AnimationPriority.Action
-- сначала стоит
idle:Play()
-- скорость
humanoid.WalkSpeed = config.WalkSpeed.Value
-- физику NPC считает сервер — у всех одинаково
root:SetNetworkOwner(nil)

-- NPC пошёл или остановился
humanoid.Running:Connect(function(speed)
    -- идёт
    if speed > 0.5 then
        -- ходьба ещё не играет
        if not walk.IsPlaying then
            -- включаем ходьбу
            walk:Play(0.2)
            -- выключаем стойку
            idle:Stop(0.2)
        -- конец проверки
        end
    -- стоит, а стойка не играет
    elseif not idle.IsPlaying then
        -- включаем стойку
        idle:Play(0.2)
        -- выключаем ходьбу
        walk:Stop(0.2)
    -- конец проверки
    end
-- конец обработчика
end)

-- ближайший живой игрок в поле зрения
local function NearestPlayer()
    -- дальше SightRange не видит
    local best, bestDistance = nil, config.SightRange.Value
    -- каждый игрок
    for _, player in ipairs(Players:GetPlayers()) do
        -- его персонаж
        local character = player.Character
        -- Humanoid
        local h = character and character:FindFirstChildOfClass("Humanoid")
        -- центр
        local r = character and character:FindFirstChild("HumanoidRootPart")
        -- жив
        if h and r and h.Health > 0 then
            -- расстояние
            local distance = (r.Position - root.Position).Magnitude
            -- ближе всех
            if distance < bestDistance then best, bestDistance = character, distance end
        -- конец проверки
        end
    -- конец перебора
    end
    -- персонаж и расстояние
    return best, bestDistance
-- конец функции
end

-- к какой точке идём
local index = 1
-- время прошлого удара
local lastAttack = 0
-- пока NPC жив
while humanoid.Health > 0 do
    -- кого видит
    local target, distance = NearestPlayer()
    -- игрок вплотную
    if target and distance <= config.AttackRange.Value then
        -- останавливаемся
        humanoid:MoveTo(root.Position)
        -- можно бить
        if os.clock() - lastAttack >= config.AttackCooldown.Value then
            -- запоминаем
            lastAttack = os.clock()
            -- анимация удара
            attack:Play()
            -- рука долетает
            task.wait(0.3)
            -- Humanoid цели
            local h = target:FindFirstChildOfClass("Humanoid")
            -- центр цели
            local r = target:FindFirstChild("HumanoidRootPart")
            -- всё ещё рядом
            if h and r and (r.Position - root.Position).Magnitude <= config.AttackRange.Value + 1 then
                -- урон
                h:TakeDamage(config.Damage.Value)
            -- конец проверки
            end
        -- конец проверки
        end
    -- видит, но далеко
    elseif target then
        -- бежит к игроку
        humanoid:MoveTo(target.HumanoidRootPart.Position)
    -- никого — обход
    elseif #patrol > 0 then
        -- текущая точка
        local point = patrol[index]
        -- идём к ней
        humanoid:MoveTo(point.Position)
        -- дошли
        if ((point.Position - root.Position) * Vector3.new(1, 0, 1)).Magnitude < 3 then
            -- следующая; после последней — первая
            index = index % #patrol + 1
        -- конец проверки
        end
    -- конец выбора
    end
    -- 5 раз в секунду
    task.wait(0.2)
-- конец цикла
end
-- погиб — анимации стоп
idle:Stop()
-- тоже
walk:Stop()

Проверка

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

Собрать NPC одной командой