roblox-npc-ai について
Roblox 開発者向けの NPC AI 実装ガイドです。サーバー側の権威あるロジック、経路探索、ステートマシン、ターゲット認識を扱い、NPC ごとの常時ループではなく、更新の集約と間隔調整を重視します。
主な機能
- 01サーバー側で権威ある NPC ロジックを実行
- 02NPC の更新を集約し、処理を分散
- 03PathfindingService による経路計算
- 04ステートマシンによる NPC 行動の整理
- 05距離や視野によるターゲット検出
- 06経路修飾子とリンクの設定
活用シーン
- 01プレイヤーを追跡する NPC の実装
- 02巡回、攻撃、帰還行動の設計
- 03多数の NPC による経路探索と計算負荷の軽減
- 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 のロジックはサーバー上で実行します (権威的なターゲット、ダメージ、ドロップ)。視覚的な演出 (アイドル アニメーション、ネームプレート、エフェクト) はクライアントで実行してもかまいません。
- 1 つの NPC サービスが、単一のループ (例: 5〜10 Hz の「思考」レート、スタッガード) で全 NPC を駆動します。NPC ごとに
スクリプト +
while trueを置くのではありません。NPC モデルにタグを付け (CollectionService)、NPC ごとの状態はテーブルで保持します。 - サーバー所有の物理: スポーン後に
npc.PrimaryPart:SetNetworkOwner(nil)を実行し、近くのプレイヤー間で 所有権が移動しても NPC がカクつかないようにします (また、悪用者が 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は置き換えられました)。Pathオブジェクトは エージェントタイプごとに再利用します。ComputeAsyncは yield するため、全 NPC に対して毎フレーム呼び出してはいけません。- 移動するターゲットの追跡: 最後の計算以降にターゲットが数スタッド以上移動したとき、または約 0.5〜1 秒ごとに
再計算し、視線が通っている場合 (
workspace:Raycastを NPC からターゲットへ) は経路探索を完全にスキップして 単にターゲットへMoveToします。 PathfindingModifier(パート/リージョンの子、LabelがCostsキーと一致、ドアにはPassThrough) とPathfindingLink(2 つのアタッチメント間、例: はしご、テレポーター、ボート。ウェイポイントにLabelとAction = Customを持つ) が経路を形成します。- 制限: 開始→目標の直線距離は 3,000 スタッド以下、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 (ヒューマノイド、アニメーション)、roblox-physics (所有権、レイキャスト)、
roblox-performance (プロファイリング、並列 Luau)、roblox-game-systems (ウェーブ、戦闘、ドロップ)。
コメント
Markdown 対応