Skip to main content

Graph

A Types.Pathfinder over hand-placed nodes with A*. Use it for the long legs of a level a navmesh cannot plan across (doors, one-way drops), and compose it with a local pathfinder through Hybrid. Name is "Graph".

local graph = VluxyAI.Pathfinders.Graph.FromInstances(workspace.NavGraph)
local pathfinder = VluxyAI.Pathfinders.Hybrid.new({
	Graph = graph,
	Local = VluxyAI.Pathfinders.Navmesh.new(),
})

Types​

interface GraphLink {
To: number--

The index of the node this link goes to.

Action: StepAction?--

Emitted on the step for the node the link leaves from. "Custom" for a door.

Label: string?--

Emitted with it; a Types.StepHandler registered for it runs there.

Instance: Instance?--

Emitted with it: the door itself.

}

A link with something to do on the way. A plain number is a walk.

GraphNode​

interface GraphNode {
Position: Vector3--

A floor position.

Links: {number | GraphLink}--

The nodes this one connects to, by index or as a Graph.GraphLink. Directed: add both ways for a two-way link.

}

One node of a Graph.

Functions​

LinkTarget​

Graph.LinkTarget(link: number | GraphLink) → number--

The index the link points at.

new​

Graph.new(
nodes: {GraphNode},
options: GraphOptions?
) → Pathfinder

Types

​

interface GraphOptions {
NearestLimit: number?--

Studs; a start or goal further than this from every node has no path. Default 60.

LineOfSight: ((
from: Vector3,
) → boolean)?--

When given, the entry and exit nodes are the nearest ones this returns true for, so a route never starts through a wall.

Avoid: ((position: Vector3) → boolean)?--

Nodes at positions this returns true for are never on a route, and a goal it returns true for has no path. Pass SafeAreas.Contains to keep agents out of safe areas.

}

Errors

TypeDescription
stringNo nodes, or a link to an index that does not exist.

AStar​

Graph.AStar(
nodes: {GraphNode},
start: number,--

Index of the start node.

goal: number,--

Index of the goal node.

blocked: ((
from: number,
to: number
) → boolean)?--

Links this returns true for are not taken.

) → {number}?--

Node indices from start to goal inclusive, or nil when unreachable.

A* over the links with straight-line distance as the heuristic. Pure.

Nearest​

Graph.Nearest(
nodes: {GraphNode},
position: Vector3,
limit: number,--

Studs; nodes further away are ignored.

accept: ((node: GraphNode) → boolean)?--

Skip nodes this returns false for.

) → number?--

The index of the nearest acceptable node within limit.

Pure.

Trim​

Graph.Trim(
nodes: {GraphNode},
route: {number},--

From Graph.AStar.

from: Vector3,
to: Vector3,
accept: ((
from: Vector3,
) → boolean)?--

The LineOfSight test, if any.

) → {number}--

The route without the nodes the agent is already past, possibly empty.

The entry node is the nearest to from, which may be behind an agent already on its way, and likewise the exit node behind the target. Drops leading nodes while from is nearer the next node than the first node is (and accept allows going straight there), and trailing nodes the same way from the target's side. Pure.

FromInstances​

Graph.FromInstances(
container: Instance,--

A folder of nodes.

) → Pathfinder

Types

​

interface GraphBuildOptions {
LinkRadius: number?--

Studs within which two nodes are linked automatically when the link test passes. Default 24.

AutoLink: boolean?--

Turn the automatic linking off to use only explicit links. Default true.

ResolveByName: boolean?--

A link that points outside the container is resolved to the container's node of the same name, instead of being warned about and skipped. For a graph inside a Roblox package, whose published copies keep Instance attributes pointing at the original nodes. Default false.

Ignore: {Instance}?--

Instances the linking rays pass through (the node parts themselves always are).

CanLink: (() → boolean)?--

Replaces the ray test: return true when an agent can walk from a to b. Ask your navmesh here for anything but flat floors.

Graph: GraphOptions?--

Options for the graph itself (NearestLimit, LineOfSight).

}

Reads a graph out of the place. Every BasePart or Attachment directly under container is a node at its position (a part's position is lowered to its bottom face, rotation taken into account).

Links are one-way; add the reverse for two-way. A node links to another in either of two ways:

  • An Instance attribute on the node whose value is another node, under any name (Link1, North, ToKitchen). The attributes Instance, Label and Action are the node's own, not links. This is the quick way to author in Studio: add an attribute of type Instance and pick the other node.
  • An ObjectValue named Link under the node whose value is another node. A Link with an Action or Label attribute, or an ObjectValue child named Instance, becomes a Graph.GraphLink of its own.

A node with a Label string attribute labels every link into it: give a node in a doorway Label = "Door" and an Instance attribute Instance pointing at the door, and a route through it carries a "Door" step with that door (on the node before it, where the Builder:OnStep handler runs). Action defaults to "Custom" there. A link that points outside the container is warned about and skipped, or, with ResolveByName, taken to the node of the same name under the container (Roblox does not remap Instance attributes in a package's copies, so every copy's links point at the original's nodes; a plain Clone remaps them and needs none of this). A name shared by two nodes cannot be resolved and is warned about. With AutoLink, nodes within LinkRadius of each other are linked both ways when CanLink says so, or, without it, when a ray 1.5 studs up between them is clear. The ray test is a line-of-sight test, not a walkability test: it links across a pit and refuses a staircase, so on anything but flat floors give CanLink a real answer.

Errors

TypeDescription
stringNo nodes under the container.

FindPath​

Graph.FindPath(
self: GraphClass,
from: Vector3,
to: Vector3,
agent: Agent
) → (
Path?,--

The route's node positions as Walk steps, trimmed of nodes already behind the agent or the target, then to lowered to the exit node's floor.

string?--

no node near <from|to>, no route, or <to> is in an avoided area.

)

Close​

Graph.Close(
self: GraphClass,
step: Step,--

The step a handler refused: its Instance, Label and Position find the link.

seconds: number--

How long to plan around it.

) → ()

Closes every link that emitted this step (same Instance when the step has one, else the same Label leaving the node at the step's position) for seconds, so the next plan goes another way. The Navigator calls this when a step handler returns false; a game may call it when a door locks.

PathLength​

Graph.PathLength(
self: GraphClass,
from: Vector3,
) → number?--

Length in studs of the route through the graph, or nil when there is none.

The "distance through the level" a chase or tension system wants, rather than a straight line through a wall.

GetNodes​

Graph.GetNodes(self: GraphClass) → {GraphNode}--

A copy of the nodes.

Destroy​

Graph.Destroy(self: GraphClass) → ()

Nothing to release; present so the pathfinder contract is complete.

Show raw api
{
    "functions": [
        {
            "name": "LinkTarget",
            "desc": "",
            "params": [
                {
                    "name": "link",
                    "desc": "",
                    "lua_type": "number | GraphLink"
                }
            ],
            "returns": [
                {
                    "desc": "The index the link points at.",
                    "lua_type": "number"
                }
            ],
            "function_type": "static",
            "source": {
                "line": 148,
                "path": "lib/Pathfinders/Graph.luau"
            }
        },
        {
            "name": "new",
            "desc": "",
            "params": [
                {
                    "name": "nodes",
                    "desc": "",
                    "lua_type": "{ GraphNode }"
                },
                {
                    "name": "options",
                    "desc": "",
                    "lua_type": "GraphOptions?"
                }
            ],
            "returns": [
                {
                    "desc": "",
                    "lua_type": "Pathfinder"
                }
            ],
            "function_type": "static",
            "errors": [
                {
                    "lua_type": "string",
                    "desc": "No nodes, or a link to an index that does not exist."
                }
            ],
            "source": {
                "line": 173,
                "path": "lib/Pathfinders/Graph.luau"
            }
        },
        {
            "name": "AStar",
            "desc": "A* over the links with straight-line distance as the heuristic. Pure.",
            "params": [
                {
                    "name": "nodes",
                    "desc": "",
                    "lua_type": "{ GraphNode }"
                },
                {
                    "name": "start",
                    "desc": "Index of the start node.",
                    "lua_type": "number"
                },
                {
                    "name": "goal",
                    "desc": "Index of the goal node.",
                    "lua_type": "number"
                },
                {
                    "name": "blocked",
                    "desc": "Links this returns `true` for are not taken.",
                    "lua_type": "((from: number, to: number) -> boolean)?"
                }
            ],
            "returns": [
                {
                    "desc": "Node indices from `start` to `goal` inclusive, or `nil` when unreachable.",
                    "lua_type": "{ number }?"
                }
            ],
            "function_type": "static",
            "source": {
                "line": 229,
                "path": "lib/Pathfinders/Graph.luau"
            }
        },
        {
            "name": "Nearest",
            "desc": "Pure.",
            "params": [
                {
                    "name": "nodes",
                    "desc": "",
                    "lua_type": "{ GraphNode }"
                },
                {
                    "name": "position",
                    "desc": "",
                    "lua_type": "Vector3"
                },
                {
                    "name": "limit",
                    "desc": "Studs; nodes further away are ignored.",
                    "lua_type": "number"
                },
                {
                    "name": "accept",
                    "desc": "Skip nodes this returns `false` for.",
                    "lua_type": "((node: GraphNode) -> boolean)?"
                }
            ],
            "returns": [
                {
                    "desc": "The index of the nearest acceptable node within `limit`.",
                    "lua_type": "number?"
                }
            ],
            "function_type": "static",
            "source": {
                "line": 308,
                "path": "lib/Pathfinders/Graph.luau"
            }
        },
        {
            "name": "Trim",
            "desc": "The entry node is the nearest to `from`, which may be behind an agent already on its way, and\nlikewise the exit node behind the target. Drops leading nodes while `from` is nearer the next\nnode than the first node is (and `accept` allows going straight there), and trailing nodes\nthe same way from the target's side. Pure.",
            "params": [
                {
                    "name": "nodes",
                    "desc": "",
                    "lua_type": "{ GraphNode }"
                },
                {
                    "name": "route",
                    "desc": "From [Graph.AStar].",
                    "lua_type": "{ number }"
                },
                {
                    "name": "from",
                    "desc": "",
                    "lua_type": "Vector3"
                },
                {
                    "name": "to",
                    "desc": "",
                    "lua_type": "Vector3"
                },
                {
                    "name": "accept",
                    "desc": "The LineOfSight test, if any.",
                    "lua_type": "((from: Vector3, to: Vector3) -> boolean)?"
                }
            ],
            "returns": [
                {
                    "desc": "The route without the nodes the agent is already past, possibly empty.",
                    "lua_type": "{ number }"
                }
            ],
            "function_type": "static",
            "source": {
                "line": 344,
                "path": "lib/Pathfinders/Graph.luau"
            }
        },
        {
            "name": "FromInstances",
            "desc": "Reads a graph out of the place. Every `BasePart` or `Attachment` directly under `container`\nis a node at its position (a part's position is lowered to its bottom face, rotation taken\ninto account).\n\nLinks are one-way; add the reverse for two-way. A node links to another in either of two ways:\n\n- **An Instance attribute** on the node whose value is another node, under any name\n  (`Link1`, `North`, `ToKitchen`). The attributes `Instance`, `Label` and `Action` are the\n  node's own, not links. This is the quick way to author in Studio: add an attribute of type\n  Instance and pick the other node.\n- **An `ObjectValue` named `Link`** under the node whose value is another node. A `Link` with\n  an `Action` or `Label` attribute, or an `ObjectValue` child named `Instance`, becomes a\n  [Graph.GraphLink] of its own.\n\nA node with a `Label` string attribute labels every link into it: give a node in a doorway\n`Label = \"Door\"` and an Instance attribute `Instance` pointing at the door, and a route through\nit carries a `\"Door\"` step with that door (on the node before it, where the [Builder:OnStep]\nhandler runs). `Action` defaults to `\"Custom\"` there. A link that points outside the\ncontainer is warned about and skipped, or, with `ResolveByName`, taken to the node of the same\nname under the container (Roblox does not remap Instance attributes in a package's copies, so\nevery copy's links point at the original's nodes; a plain `Clone` remaps them and needs none of\nthis). A name shared by two nodes cannot be resolved and is warned about. With `AutoLink`, nodes within `LinkRadius` of each other are linked both ways when\n`CanLink` says so, or, without it, when a ray 1.5 studs up between them is clear. The ray test\nis a line-of-sight test, not a walkability test: it links across a pit and refuses a staircase,\nso on anything but flat floors give `CanLink` a real answer.",
            "params": [
                {
                    "name": "container",
                    "desc": "A folder of nodes.",
                    "lua_type": "Instance"
                },
                {
                    "name": "options",
                    "desc": "",
                    "lua_type": "GraphBuildOptions?"
                }
            ],
            "returns": [
                {
                    "desc": "",
                    "lua_type": "Pathfinder"
                }
            ],
            "function_type": "static",
            "errors": [
                {
                    "lua_type": "string",
                    "desc": "No nodes under the container."
                }
            ],
            "source": {
                "line": 445,
                "path": "lib/Pathfinders/Graph.luau"
            }
        },
        {
            "name": "FindPath",
            "desc": "",
            "params": [
                {
                    "name": "self",
                    "desc": "",
                    "lua_type": "GraphClass"
                },
                {
                    "name": "from",
                    "desc": "",
                    "lua_type": "Vector3"
                },
                {
                    "name": "to",
                    "desc": "",
                    "lua_type": "Vector3"
                },
                {
                    "name": "agent",
                    "desc": "",
                    "lua_type": "Agent"
                }
            ],
            "returns": [
                {
                    "desc": "The route's node positions as Walk steps, trimmed of nodes already behind the agent or the target, then `to` lowered to the exit node's floor.",
                    "lua_type": "Path?"
                },
                {
                    "desc": "`no node near <from|to>`, `no route`, or `<to> is in an avoided area`.",
                    "lua_type": "string?"
                }
            ],
            "function_type": "static",
            "source": {
                "line": 643,
                "path": "lib/Pathfinders/Graph.luau"
            }
        },
        {
            "name": "Close",
            "desc": "Closes every link that emitted this step (same `Instance` when the step has one, else the\nsame `Label` leaving the node at the step's position) for `seconds`, so the next plan goes\nanother way. The [Navigator] calls this when a step handler returns `false`; a game may call\nit when a door locks.",
            "params": [
                {
                    "name": "self",
                    "desc": "",
                    "lua_type": "GraphClass"
                },
                {
                    "name": "step",
                    "desc": "The step a handler refused: its `Instance`, `Label` and `Position` find the link.",
                    "lua_type": "Step"
                },
                {
                    "name": "seconds",
                    "desc": "How long to plan around it.",
                    "lua_type": "number"
                }
            ],
            "returns": [],
            "function_type": "static",
            "source": {
                "line": 719,
                "path": "lib/Pathfinders/Graph.luau"
            }
        },
        {
            "name": "PathLength",
            "desc": "The \"distance through the level\" a chase or tension system wants, rather than a straight line\nthrough a wall.",
            "params": [
                {
                    "name": "self",
                    "desc": "",
                    "lua_type": "GraphClass"
                },
                {
                    "name": "from",
                    "desc": "",
                    "lua_type": "Vector3"
                },
                {
                    "name": "to",
                    "desc": "",
                    "lua_type": "Vector3"
                }
            ],
            "returns": [
                {
                    "desc": "Length in studs of the route through the graph, or `nil` when there is none.",
                    "lua_type": "number?"
                }
            ],
            "function_type": "static",
            "source": {
                "line": 746,
                "path": "lib/Pathfinders/Graph.luau"
            }
        },
        {
            "name": "GetNodes",
            "desc": "",
            "params": [
                {
                    "name": "self",
                    "desc": "",
                    "lua_type": "GraphClass"
                }
            ],
            "returns": [
                {
                    "desc": "A copy of the nodes.",
                    "lua_type": "{ GraphNode }"
                }
            ],
            "function_type": "static",
            "source": {
                "line": 764,
                "path": "lib/Pathfinders/Graph.luau"
            }
        },
        {
            "name": "Destroy",
            "desc": "Nothing to release; present so the pathfinder contract is complete.",
            "params": [
                {
                    "name": "self",
                    "desc": "",
                    "lua_type": "GraphClass"
                }
            ],
            "returns": [],
            "function_type": "static",
            "source": {
                "line": 777,
                "path": "lib/Pathfinders/Graph.luau"
            }
        }
    ],
    "properties": [],
    "types": [
        {
            "name": "GraphLink",
            "desc": "A link with something to do on the way. A plain number is a walk.",
            "fields": [
                {
                    "name": "To",
                    "lua_type": "number",
                    "desc": "The index of the node this link goes to."
                },
                {
                    "name": "Action",
                    "lua_type": "StepAction?",
                    "desc": "Emitted on the step for the node the link leaves from. `\"Custom\"` for a door."
                },
                {
                    "name": "Label",
                    "lua_type": "string?",
                    "desc": "Emitted with it; a [Types.StepHandler] registered for it runs there."
                },
                {
                    "name": "Instance",
                    "lua_type": "Instance?",
                    "desc": "Emitted with it: the door itself."
                }
            ],
            "source": {
                "line": 50,
                "path": "lib/Pathfinders/Graph.luau"
            }
        },
        {
            "name": "GraphNode",
            "desc": "One node of a [Graph].",
            "fields": [
                {
                    "name": "Position",
                    "lua_type": "Vector3",
                    "desc": "A floor position."
                },
                {
                    "name": "Links",
                    "lua_type": "{ number | GraphLink }",
                    "desc": "The nodes this one connects to, by index or as a [Graph.GraphLink]. Directed: add both ways for a two-way link."
                }
            ],
            "source": {
                "line": 66,
                "path": "lib/Pathfinders/Graph.luau"
            }
        },
        {
            "name": "GraphOptions",
            "desc": "",
            "fields": [
                {
                    "name": "NearestLimit",
                    "lua_type": "number?",
                    "desc": "Studs; a start or goal further than this from every node has no path. Default `60`."
                },
                {
                    "name": "LineOfSight",
                    "lua_type": "((from: Vector3, to: Vector3) -> boolean)?",
                    "desc": "When given, the entry and exit nodes are the nearest ones this returns `true` for, so a route never starts through a wall."
                },
                {
                    "name": "Avoid",
                    "lua_type": "((position: Vector3) -> boolean)?",
                    "desc": "Nodes at positions this returns `true` for are never on a route, and a goal it returns `true` for has no path. Pass [SafeAreas.Contains] to keep agents out of safe areas."
                }
            ],
            "source": {
                "line": 98,
                "path": "lib/Pathfinders/Graph.luau"
            }
        },
        {
            "name": "GraphBuildOptions",
            "desc": "",
            "fields": [
                {
                    "name": "LinkRadius",
                    "lua_type": "number?",
                    "desc": "Studs within which two nodes are linked automatically when the link test passes. Default `24`."
                },
                {
                    "name": "AutoLink",
                    "lua_type": "boolean?",
                    "desc": "Turn the automatic linking off to use only explicit links. Default `true`."
                },
                {
                    "name": "ResolveByName",
                    "lua_type": "boolean?",
                    "desc": "A link that points outside the container is resolved to the container's node of the same name, instead of being warned about and skipped. For a graph inside a Roblox package, whose published copies keep Instance attributes pointing at the original nodes. Default `false`."
                },
                {
                    "name": "Ignore",
                    "lua_type": "{ Instance }?",
                    "desc": "Instances the linking rays pass through (the node parts themselves always are)."
                },
                {
                    "name": "CanLink",
                    "lua_type": "((a: Vector3, b: Vector3) -> boolean)?",
                    "desc": "Replaces the ray test: return `true` when an agent can walk from `a` to `b`. Ask your navmesh here for anything but flat floors."
                },
                {
                    "name": "Graph",
                    "lua_type": "GraphOptions?",
                    "desc": "Options for the graph itself (`NearestLimit`, `LineOfSight`)."
                }
            ],
            "source": {
                "line": 115,
                "path": "lib/Pathfinders/Graph.luau"
            }
        }
    ],
    "name": "Graph",
    "desc": "A [Types.Pathfinder] over hand-placed nodes with A*. Use it for the long legs of a level a\nnavmesh cannot plan across (doors, one-way drops), and compose it with a local pathfinder\nthrough [Hybrid]. `Name` is `\"Graph\"`.\n\n```lua\nlocal graph = VluxyAI.Pathfinders.Graph.FromInstances(workspace.NavGraph)\nlocal pathfinder = VluxyAI.Pathfinders.Hybrid.new({\n\tGraph = graph,\n\tLocal = VluxyAI.Pathfinders.Navmesh.new(),\n})\n```",
    "source": {
        "line": 165,
        "path": "lib/Pathfinders/Graph.luau"
    }
}