Roblox · Анимации
Стражник обходит точки, увидев игрока — бежит к нему и бьёт. Каждое действие — своя анимация: стойка, ходьба, удар. Анимации играет сервер, поэтому их видят все игроки.
Синим — что нужно создать.
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)
Где: 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) и убедись: