mcpField
R
roblox-npc-ai

roblox-npc-ai

github.com/el4cteo开发61573 次安装2 周前更新
代码仓库

关于 roblox-npc-ai

面向 Roblox 开发者的 NPC AI 实现指南,介绍服务端权威逻辑、寻路、状态机和目标感知。重点是以固定频率集中处理 NPC,避免逐个脚本持续循环造成负担。

主要功能

  1. 01在服务端运行 NPC 权威逻辑
  2. 02集中循环并错峰处理 NPC
  3. 03使用 PathfindingService 计算路径
  4. 04通过状态机组织 NPC 行为
  5. 05按距离或视野检测目标
  6. 06配置寻路修饰器与连接点

适用场景

  1. 01实现追逐玩家的 NPC
  2. 02设计巡逻、攻击和返回等行为
  3. 03为大量 NPC 降低寻路与计算负担
  4. 04配置门、梯子等特殊路径

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/el4cteo/roblox-skills --skill roblox-npc-ai

安装后在 Claude Code 中输入/roblox-npc-ai确认已加载。

附带文件references/scaling-npcs.md

详细介绍

NPC 与 AI

架构

  • NPC 逻辑在服务端运行(权威目标、伤害、掉落)。视觉附加效果(待机动画、名牌、特效)可以在客户端运行。
  • 一个 NPC 服务通过单个循环驱动所有 NPC(例如 5–10 Hz 的“思考”频率,错开执行),而不是每个 NPC 一个脚本 + while true。给 NPC 模型打标签(CollectionService),并将每个 NPC 的状态保存在表中。
  • 服务端拥有物理所有权:生成后执行 npc.PrimaryPart:SetNetworkOwner(nil),这样当所有权在附近玩家之间转移时 NPC 不会卡顿(利用漏洞者也无法推动它们)。
  • 对于需要行走/跳跃/攀爬的 NPC,使用 Humanoid;对于简单移动体(炮塔、漂浮敌人、群体),使用 AnimationController + Animator,并通过约束或 CFrame 移动。

寻路

--!strict
local PathfindingService = game:GetService("PathfindingService")

local path = PathfindingService:CreatePath({
	AgentRadius = 2,
	AgentHeight = 5,
	AgentCanJump = true,
	AgentCanClimb = false,
	WaypointSpacing = 4,
	Costs = {
		Water = 20, -- avoid water (Enum.Material name)
		DangerZone = math.huge, -- PathfindingModifier.Label: never cross
	},
})

-- Walk `humanoid` to `goal`. Returns true on arrival, false if unreachable or interrupted.
local function walkTo(humanoid: Humanoid, goal: Vector3): boolean
	local root = humanoid.RootPart
	if not root then
		return false
	end
	local ok = pcall(function()
		path:ComputeAsync(root.Position, goal)
	end)
	if not ok or path.Status ~= Enum.PathStatus.Success then
		return false -- NoPath: try a fallback (direct MoveTo, new goal, teleport if stuck long)
	end

	local waypoints = path:GetWaypoints()
	local blocked = false
	local blockedConnection = path.Blocked:Connect(function(blockedIndex)
		blocked = true -- an obstacle appeared on a waypoint ahead; caller recomputes
		print("path blocked at waypoint", blockedIndex)
	end)

	for index = 2, #waypoints do
		local waypoint = waypoints[index]
		if waypoint.Action == Enum.PathWaypointAction.Jump then
			humanoid.Jump = true
		end
		humanoid:MoveTo(waypoint.Position)
		local reached = humanoid.MoveToFinished:Wait() -- false after 8 s timeout
		if not reached or blocked or humanoid.Health <= 0 then
			blockedConnection:Disconnect()
			return false
		end
	end
	blockedConnection:Disconnect()
	return true
end

return walkTo
  • CreatePath + ComputeAsync 是当前 API(FindPathAsync 已被取代)。按 Agent 类型复用 Path 对象;ComputeAsync 会让出执行——绝不要每帧为每个 NPC 调用它。
  • 追逐移动目标:当目标自上次计算以来移动超过几 studs 或每隔约 0.5–1 s 重新计算;当有清晰视线时完全跳过寻路(从 NPC 到目标执行 workspace:Raycast)——只需 MoveTo 目标。
  • PathfindingModifier(作为 part/region 的子对象,Label 匹配 Costs 键,PassThrough 用于门)和 PathfindingLink(位于两个 attachment 之间,例如梯子、传送器、船;路点带有 Label 和 Action = Custom)会塑造路径。
  • 限制:起点→终点直线距离 ≤ 3,000 studs,节点预算为 20,000;较远的目标需要中间目标。使用 Studio 的 Navigation Mesh 可视化选项来可视化导航网格。
  • 在客户端,寻路只能看到已流入的几何体:请在服务端计算路径。

行为:状态机

--!strict
type State = "Idle" | "Chase" | "Attack" | "Return"

type Npc = {
	model: Model,
	humanoid: Humanoid,
	home: Vector3,
	state: State,
	target: Model?,
	lastAttack: number,
}

local AGGRO_RANGE = 40
local ATTACK_RANGE = 5
local LEASH_RANGE = 80
local ATTACK_COOLDOWN = 1.2

local losParams = RaycastParams.new()
losParams.FilterType = Enum.RaycastFilterType.Exclude

local function hasLineOfSight(from: Vector3, target: BasePart, ignore: { Instance }): boolean
	losParams.FilterDescendantsInstances = ignore
	local result = workspace:Raycast(from, target.Position - from, losParams)
	return result == nil or result.Instance:IsDescendantOf(target.Parent :: Instance)
end

local function think(npc: Npc, findTarget: (Vector3, number) -> Model?)
	local root = npc.humanoid.RootPart
	if not root or npc.humanoid.Health <= 0 then
		return
	end
	local targetRoot = npc.target and npc.target:FindFirstChild("HumanoidRootPart") :: BasePart?
	local distance = if targetRoot then (targetRoot.Position - root.Position).Magnitude else math.huge

	if npc.state == "Idle" then
		npc.target = findTarget(root.Position, AGGRO_RANGE)
		if npc.target then
			npc.state = "Chase"
		end
	elseif npc.state == "Chase" then
		if not targetRoot or (root.Position - npc.home).Magnitude > LEASH_RANGE then
			npc.state = "Return"
		elseif distance <= ATTACK_RANGE then
			npc.state = "Attack"
		elseif hasLineOfSight(root.Position, targetRoot, { npc.model }) then
			npc.humanoid:MoveTo(targetRoot.Position)
		end -- else: request a path (throttled) via walkTo in a separate thread
	elseif npc.state == "Attack" then
		if not targetRoot or distance > ATTACK_RANGE * 1.5 then
			npc.state = "Chase"
		elseif os.clock() - npc.lastAttack >= ATTACK_COOLDOWN then
			npc.lastAttack = os.clock()
			local targetHumanoid = npc.target and npc.target:FindFirstChildOfClass("Humanoid")
			if targetHumanoid then
				targetHumanoid:TakeDamage(10)
			end
		end
	elseif npc.state == "Return" then
		npc.target = nil
		npc.humanoid:MoveTo(npc.home)
		if (root.Position - npc.home).Magnitude < 4 then
			npc.state = "Idle"
		end
	end
end

return think

对于复杂 AI(多种行为、优先级),请使用行为树或效用 AI;保持相同的“以固定频率思考,通过小型原语行动”的结构。有关数百个 NPC 的生成、池化和性能,请参阅 references/scaling-npcs.md。

感知提示

  • 通过 workspace:GetPartBoundsInRadius 针对仅包含角色的碰撞组/过滤器查找目标,或遍历 Players:GetPlayers()(数量较少时更便宜)并比较平方距离。
  • 添加反应延迟和视野检查(root.CFrame.LookVector:Dot(direction.Unit) > 0.5),让 NPC 感觉公平。
  • 错开思考:npcIndex % 5 == frame % 5 将工作分散到各帧。

相关技能

roblox-characters-animation(humanoid、动画)、roblox-physics(所有权、射线检测)、roblox-performance(性能分析、并行 Luau)、roblox-game-systems(波次、战斗、掉落)。

评论

支持 Markdown