Скрипт: NPC, который ходит по точкам и говорит

Заготовка простого NPC: он идёт от точки к точке по кругу, а когда игрок подходит близко — над ним появляется реплика. Всё считает сервер.

Куда положить

Внутрь модели NPC, тип объекта Script

Что должно быть в проекте

  • Модель NPC с Humanoid и HumanoidRootPart — подойдёт обычный Rig из вкладки Avatar
  • Модель Waypoints в Workspace с деталями-точками маршрута

Код

Workspace/Guard/Patrol.lua
  1. local Players = game:GetService("Players")
  2.  
  3. local npc = script.Parent
  4. local humanoid = npc:WaitForChild("Humanoid")
  5. local root = npc:WaitForChild("HumanoidRootPart")
  6. local waypoints = workspace:WaitForChild("Waypoints"):GetChildren()
  7.  
  8. table.sort(waypoints, function(a, b)
  9. return a.Name < b.Name
  10. end)
  11.  
  12. local function say(text: string)
  13. local existing = root:FindFirstChild("Line")
  14.  
  15. if existing then
  16. existing:Destroy()
  17. end
  18.  
  19. local gui = Instance.new("BillboardGui")
  20. gui.Name = "Line"
  21. gui.Size = UDim2.fromScale(6, 1.4)
  22. gui.StudsOffset = Vector3.new(0, 3.5, 0)
  23. gui.Parent = root
  24.  
  25. local label = Instance.new("TextLabel")
  26. label.Size = UDim2.fromScale(1, 1)
  27. label.BackgroundTransparency = 0.2
  28. label.TextScaled = true
  29. label.Text = text
  30. label.Parent = gui
  31.  
  32. task.delay(4, function()
  33. gui:Destroy()
  34. end)
  35. end
  36.  
  37. task.spawn(function()
  38. while npc.Parent do
  39. for _, point in ipairs(waypoints) do
  40. humanoid:MoveTo(point.Position)
  41. humanoid.MoveToFinished:Wait()
  42. task.wait(1)
  43. end
  44. end
  45. end)
  46.  
  47. task.spawn(function()
  48. while npc.Parent do
  49. task.wait(1)
  50.  
  51. for _, player in ipairs(Players:GetPlayers()) do
  52. local character = player.Character
  53. local playerRoot = character and character:FindFirstChild("HumanoidRootPart")
  54.  
  55. if playerRoot and (playerRoot.Position - root.Position).Magnitude < 12 then
  56. say("Привет, " .. player.Name .. "!")
  57. break
  58. end
  59. end
  60. end
  61. end)

Проверка расстояния идёт раз в секунду, а не каждый кадр: NPC не стоит того, чтобы жечь на нём производительность.

На что обратить внимание

  • Точки маршрута называй Point1, Point2 — сортировка по имени задаёт порядок обхода
  • MoveToFinished ждёт прихода или таймаута, поэтому цикл не зависнет на застрявшем NPC
  • Для сложных маршрутов с препятствиями смотри PathfindingService