How to make NPC able to climb?

I want my NPC to be able to climb a truss but it doesn’t seem to work.

local RunService = game:GetService("RunService")
local PathfindingService = game:GetService("PathfindingService")

local character = script.Parent
local humanoid = character:WaitForChild("Humanoid")
local WalkSpeed = humanoid.WalkSpeed

local DestinationsFolder = workspace:WaitForChild("Destinations")

local waypoints
local nextWaypointIndex
local reachedConnection
local blockedConnection

local currentPath = nil
local loopConnetion

local function randomWaypoint()
	local waypoint = DestinationsFolder:GetChildren()
	local random = waypoint[math.random(1, #waypoint)]
	return random
end

local function WalkTo(destination)
	local pathParams = {
		AgentHeight = character:GetExtentsSize().Y,
		AgentRadius = (character:GetExtentsSize().X + character:GetExtentsSize().Z)/4,
		AgentCanJump = true,
		AgentCanClimb = true,
		Costs = {
			Danger = math.huge,
			Climb = 2,
		}
	}

	local path = PathfindingService:CreatePath(pathParams)

	local success, errorMessage = pcall(function()
		path:ComputeAsync(character.PrimaryPart.Position, destination.Position)
	end)
	
	waypoints = {}
	
	blockedConnection = path.Blocked:Connect(function(blockedWaypointIndex)
		if blockedWaypointIndex >= nextWaypointIndex then
			blockedConnection:Disconnect()
			WalkTo(destination)
		end
	end)

	if success and path.Status == Enum.PathStatus.Success then
		waypoints = path:GetWaypoints()

		if not reachedConnection then
			reachedConnection = humanoid.MoveToFinished:Connect(function(reached)
				if reached and nextWaypointIndex < #waypoints then
					nextWaypointIndex += 1
					humanoid:MoveTo(waypoints[nextWaypointIndex].Position)
				else
					WalkTo(randomWaypoint())
					currentPath = nil
				end
			end)
		end
		
		nextWaypointIndex = 2
		humanoid:MoveTo(waypoints[nextWaypointIndex].Position)
		
		if waypoints[nextWaypointIndex].Action == Enum.PathWaypointAction.Jump then
			humanoid.Jump = true
		end
		
	elseif not success and path.Status ~= Enum.PathStatus.Success then
		humanoid:MoveTo(destination.Position)
		
	elseif success and path.Status == Enum.PathStatus.NoPath then
		if reachedConnection then
			reachedConnection:Disconnect()
		end
	end
end

loopConnetion = RunService.Heartbeat:Connect(function()
	if currentPath == nil then
		WalkTo(randomWaypoint())
		currentPath = randomWaypoint()
	else
		WalkTo(currentPath)
	end

	if humanoid.Health <= 0 and loopConnetion then
		loopConnetion:Disconnect()
	end
end)

Go look on the documentation, there you will find everything https://create.roblox.com/docs/mechanics/pathfinding

I did. But I wanted to change a bit of it.

Like I changed a bit of the code.

I know the problem is the RunService loop but I don’t know how I would solve it.

Agent parameters and local path should be defined outside of the WalkTo function.
Try it out and tell me if it doesn’t work.

I just used the term ‘npc climb’ and found this Announcements post from November.

Pathfinding now supports Climbable Trusses

As @Dede_4242 mentioned, it’s all in the documentation as well.

If you’ve changed your script then post it so we can see exactly what’s going on. Statements such as “Like I changed a bit of the code” don’t really tell us anything about what you’ve changed.

Somewhat late but I believe a hacky solution to this problem is setting nextWaypointIndex to 3 instead of 2 on line 66.
I think this problem is caused by the fact that humanoid.MoveToFinished passes true even if the humanoid is not at the same y level as the destination point, meaning it will walk up to the ladder, think that it successfully reached the target waypoint, add 1 to the nextWaypointIndex (making it 3, the waypoint at the top of the ladder) and then begin moving to waypoint 3, except immediately after this it will set the nextWaypointIndex to 2, and begin moving towards waypoint 2, which overwrites the movement to waypoint 3.

It will then reach waypoint 2, and, ofcourse, humanoid.MoveToFinished will say that it reached waypoint 2, which will set it to move to waypoint 3, only for it to then be overwritten by moving to waypoint 2.

So if it just always sets it to waypoint 3, it would climb the ladder in this specific case.
I think.
Probably.
:crossed_fingers:

I just added a runservice loop to find the path.

It works but now it errors. attempt to index nil with ‘Position’.

local function WalkTo(destination)
	local pathParams = {
		AgentHeight = character:GetExtentsSize().Y,
		AgentRadius = (character:GetExtentsSize().X + character:GetExtentsSize().Z)/4,
		AgentCanJump = true,
		AgentCanClimb = true,
		Costs = {
			Danger = math.huge,
			Climb = 2,
		}
	}

	local path = PathfindingService:CreatePath(pathParams)

	local success, errorMessage = pcall(function()
		path:ComputeAsync(character.PrimaryPart.Position, destination.Position)
	end)
	
	waypoints = {}
	
	blockedConnection = path.Blocked:Connect(function(blockedWaypointIndex)
		if blockedWaypointIndex >= nextWaypointIndex then
			blockedConnection:Disconnect()
			WalkTo(destination)
		end
	end)

	if success and path.Status == Enum.PathStatus.Success then
		waypoints = path:GetWaypoints()

		if not reachedConnection then
			reachedConnection = humanoid.MoveToFinished:Connect(function(reached)
				if reached and nextWaypointIndex < #waypoints then
					nextWaypointIndex += 1
					humanoid:MoveTo(waypoints[nextWaypointIndex].Position)
				else
					WalkTo(randomWaypoint())
					currentPath = nil
					reachedConnection:Disconnect()
				end
			end)
		end
		
		nextWaypointIndex = 3
		humanoid:MoveTo(waypoints[nextWaypointIndex].Position)
		
		if waypoints[nextWaypointIndex].Action == Enum.PathWaypointAction.Jump then
			humanoid.Jump = true
		end
		
	elseif not success and path.Status ~= Enum.PathStatus.Success then
		humanoid:MoveTo(destination.Position)
		
	elseif success and path.Status == Enum.PathStatus.NoPath then
		if reachedConnection then
			reachedConnection:Disconnect()
		end
	end
end

Sorry for again being late, I genuinely cannot think of how to properly solve this problem, so here’s another extremely hacky solution: Rather than setting the nextWaypointIndex to 3 instead of 2 (which runs into the risk of it looking for a third waypoint when there are only 2 waypoints at the end), you can add an if statement to see if it found the next waypoint in the Humanoid.MoveToFinished event, which looks like this.

local RunService = game:GetService("RunService")
local PathfindingService = game:GetService("PathfindingService")

local character = script.Parent
local humanoid = character:WaitForChild("Humanoid")
local WalkSpeed = humanoid.WalkSpeed

local DestinationsFolder = workspace:WaitForChild("Destinations")

local waypoints
local nextWaypointIndex
local reachedConnection
local blockedConnection
local foundNextWaypoint = false

local currentPath = nil
local loopConnetion

local function randomWaypoint()
	local waypoint = DestinationsFolder:GetChildren()
	local random = waypoint[math.random(1, #waypoint)]
	return random
end

local function WalkTo(destination)
	local pathParams = {
		AgentHeight = character:GetExtentsSize().Y,
		AgentRadius = (character:GetExtentsSize().X + character:GetExtentsSize().Z)/4,
		AgentCanJump = true,
		AgentCanClimb = true,
		Costs = {
			Danger = math.huge,
			Climb = 2,
		}
	}

	local path = PathfindingService:CreatePath(pathParams)

	local success, errorMessage = pcall(function()
		path:ComputeAsync(character.PrimaryPart.Position, destination.Position)
	end)

	waypoints = {}

	blockedConnection = path.Blocked:Connect(function(blockedWaypointIndex)
		if blockedWaypointIndex >= nextWaypointIndex then
			blockedConnection:Disconnect()
			WalkTo(destination)
		end
	end)

	if success and path.Status == Enum.PathStatus.Success then
		waypoints = path:GetWaypoints()

		if not reachedConnection then
			reachedConnection = humanoid.MoveToFinished:Connect(function(reached)
				if reached and nextWaypointIndex < #waypoints then
					nextWaypointIndex += 1
					humanoid:MoveTo(waypoints[nextWaypointIndex].Position)
					foundNextWaypoint = true
				else
					WalkTo(randomWaypoint())
					currentPath = nil
					foundNextWaypoint = false
				end
			end)
		end
		
		if foundNextWaypoint==false then
			nextWaypointIndex = 2
			humanoid:MoveTo(waypoints[nextWaypointIndex].Position)

			if waypoints[nextWaypointIndex].Action == Enum.PathWaypointAction.Jump then
				humanoid.Jump = true
			end
		end

	elseif not success and path.Status ~= Enum.PathStatus.Success then
		humanoid:MoveTo(destination.Position)

	elseif success and path.Status == Enum.PathStatus.NoPath then
		if reachedConnection then
			reachedConnection:Disconnect()
		end
	end
end

loopConnetion = RunService.Heartbeat:Connect(function()
	if currentPath == nil then
		WalkTo(randomWaypoint())
		currentPath = randomWaypoint()
	else
		WalkTo(currentPath)
	end

	if humanoid.Health <= 0 and loopConnetion then
		loopConnetion:Disconnect()
	end
end)

I just tested it and it worked on my end so :crossed_fingers:.

1 Like

For some reason, the NPC won’t jump.

Did you try copying and pasting that script I put verbatim?

(Terribly sorry, I had to leave my computer moments after this reply)

Yes I did. But it wouldn’t jump.

By the way, the code does not loop. How can I loop the code?

I actually fixed it, but when the path gets unblocked, the NPC would not climb the ladder anymore.

Never mind, I fixed it. Thanks for the help!

1 Like

This topic was automatically closed 14 days after the last reply. New replies are no longer allowed.