Custom rig problems

Hi, I’m making a robot rig model that is supposed to chase the player. However, there are a few issues I’ve been trying to resolve:

  1. When it encounters stairs (the stairs are a union object), it cannot climb up the stairs. However, it can go down the stairs normally but not up.
  2. Its AI is dumb because if the player is even slightly hidden, it immediately ignores them. The goal is to give the robot a bit more intelligence. If the player is completely out of sight, then it shouldn’t notice.
  3. If I set CanCollide to false for everything and everything is unanchored, the robot usually ends up falling through the ground into the void, and all the objects disappear.

(Unanchored, because otherwise it can’t animate or move, and the AI code requires it to be completely unanchored. CanCollide is set to false because otherwise, it has trouble passing through small objects and may encounter lower ceilings.)

What I’ve already tried:

  1. I tried using a wedge object and setting Friction to 1. Additionally, I changed the Humanoid MaxSlopeAngle to 0. It still slides and cannot climb up.
  2. I haven’t really found a way to improve its AI. It was already difficult to get it to ignore the player when they are slightly hidden.
  3. I set some of the robot’s parts to CanCollide = false, but I don’t like that style because it can get stuck. If I set the HumanoidRootPart to Anchored = true then it doesn’t fall, but then npc.PrimaryPart(nil) no longer works.

Here is the AI code:

local pf = game:GetService("PathfindingService")
local plrs = game:GetService("Players")
local rs = game:GetService("RunService")

local npc = script.Parent
local hum = npc:WaitForChild("Humanoid")
npc.PrimaryPart:SetNetworkOwner(nil)

local waypoints
local nextwaypointindex
local reachedconnection
local blockedconnection

local damage = npc:GetAttribute("Damage")
local walkspeed = npc:GetAttribute("WalkSpeed")
local sprintspeed = npc:GetAttribute("SprintSpeed")

local function getpath(dest)
	local path = pf:CreatePath({
		AgentHeight = 6;      -- Adjust if needed
		AgentRadius = 3.5;    -- Adjust if needed
		AgentCanJump = true;  -- Allow NPC to jump
		AgentCanClimb = true; -- Allow NPC to climb
		AgentJumpHeight = 5;  -- Adjust jump height if needed
		AgentMaxSlope = 45;   -- Allow steeper slopes
		Costs = {
			Water = 100;
			DangerZone = math.huge
		}
	})

	path:ComputeAsync(npc.HumanoidRootPart.Position, dest.Position)

	return path
end

local function CanSeeTarget(target)
	local NPCtoChar = (target.Head.Position - npc.HeadConnector.Position).Unit
	local NPClook = npc.HeadConnector.CFrame.LookVector
	local dotproduct = NPCtoChar:Dot(NPClook)
	local origin = npc.HumanoidRootPart.Position
	local direction = (target.HumanoidRootPart.Position - npc.HumanoidRootPart.Position).Unit * 100
	local ray = Ray.new(origin, direction)
	local hit, pos = workspace:FindPartOnRay(ray, npc)

	if dotproduct > -0.1 then
		if hit and hit:IsDescendantOf(target) then
			return true
		end
	else
		return false
	end
end

local function FindTarget()
	local MaxDistance = 300
	local NearestTarget

	for i, p in pairs(plrs:GetPlayers()) do
		if p.Character then
			local target = p.Character
			local distance = (npc.HumanoidRootPart.Position - target.HumanoidRootPart.Position).Magnitude

			if distance < MaxDistance and CanSeeTarget(target) then
				NearestTarget = target
				MaxDistance = distance
			end
		end
	end

	return NearestTarget
end

local function capture(target)
	local distance = (npc.HumanoidRootPart.Position - target.HumanoidRootPart.Position).Magnitude

	if distance > 2 then
		hum:MoveTo(target.HumanoidRootPart.Position)
	else
		target.Humanoid.Health = target.Humanoid.Health - damage
		wait(7) -- Pause for 7 seconds to simulate capture animation
	end
end

local function walkto(dest)
	local path = getpath(dest)

	if path.Status == Enum.PathStatus.Success then
		local waypoints = path:GetWaypoints()
		for i, wp in pairs(waypoints) do
			local target = FindTarget()

			if target then
				capture(target)
				npc.Humanoid.WalkSpeed = sprintspeed
				break
			else
				npc.Humanoid.WalkSpeed = walkspeed
				hum:MoveTo(wp.Position)
				hum.MoveToFinished:Wait()

				-- Pause for 3-5 seconds randomly with a 10% chance
				if math.random() < 0.1 then
					local pauseTime = math.random(3, 5)
					wait(pauseTime)
				end
			end
		end
	else
		hum:MoveTo(dest.Position - (npc.HumanoidRootPart.CFrame.LookVector * 10))
	end
end

local function patrol()
	local wayp = workspace.WayPoints:GetChildren()
	local RandomWayPoint = math.random(1, #wayp)

	walkto(wayp[RandomWayPoint])
end

local touched = false
script.Parent.Hitbox.Touched:Connect(function(obj)
	if not touched then
		local player = game:GetService("Players"):GetPlayerFromCharacter(obj.Parent)
		if player then
			touched = true
			wait(9)
			touched = false
		end
	end
end)

while true do
	wait(0.01)
	if not touched then
		patrol()
	end
end

2 Likes

On point #3 you should not have to do that, except for objects you need it to go through like a door

Have you turned on in studio where you can see the pathfinding lines ? To see if the stairs are blocked?

Have you added print statements in the script to see where it is at when it gets stuck ?

Are the stairs blocking the view of the player so it does not know to go up them?

What is the normal angle set to ?

If you comment out the is in vision code part can it still not get up them ?

1 Like

Hey,

I have a screenshot:
IMG

Are the stairs blocking the view of the player so it does not know to go up them?

Well, the robot can’t see if it is at the top and the player is far enough down the stairs. However, it can see if it is at the bottom and the player is at the top. It always tries to move to where the player is, but it doesn’t know how to climb.

On point #3 you should not have to do that, except for objects you need it to go through like a door

So should I then set everything to CanCollide = true but CanTouch = false? The map has a lot of objects, like trash, cardboard boxes, wooden crates, and all sorts of other items, and it always gets stuck on them. The player shouldn’t go through larger objects because it looks silly, but the robot should go through smaller objects.

Have you added print statements in the script to see where it is at when it gets stuck ?

If you comment out the is in vision code part can it still not get up them ?

I commented out some parts of code and the output looks like this:

  10:44:14.821  Pathfinding success!  -  Server - Chasing:91
  10:44:14.821  Moving to waypoint 1 at -519.775390625, 89, -74.94502258300781  -  Server - Chasing:102
  10:44:14.824  Pausing for 3 seconds  -  Server - Chasing:110
  10:44:17.826  Moving to waypoint 2 at -518.0910034179688, 89, -78.57308197021484  -  Server - Chasing:102
  10:44:18.386  Moving to waypoint 3 at -516.5, 89, -82  -  Server - Chasing:102
  10:44:19.171  Moving to waypoint 4 at -516.5, 70.35714721679688, -87.5  -  Server - Chasing:102
  10:44:19.888  Moving to waypoint 5 at -514.0447998046875, 70.5896224975586, -84.34334564208984  -  Server - Chasing:102
  10:44:19.913  Moving to waypoint 6 at -511.58929443359375, 70.26176452636719, -81.18631744384766  -  Server - Chasing:102
  10:44:21.503  Moving to waypoint 7 at -509.1351318359375, 70.18333435058594, -78.03101348876953  -  Server - Chasing:102
  10:44:22.572  Pausing for 3 seconds  -  Server - Chasing:110
  10:44:25.594  Moving to waypoint 8 at -506.6826477050781, 70.39108276367188, -74.87789154052734  -  Server - Chasing:102
  10:44:26.395  Moving to waypoint 9 at -504.23016357421875, 70.59883117675781, -71.72476959228516  -  Server - Chasing:102
  10:44:27.223  Moving to waypoint 10 at -501.7776794433594, 70.80657958984375, -68.57164001464844  -  Server - Chasing:102
  10:44:28.352  Moving to waypoint 11 at -500.39007568359375, 70.92412567138672, -66.78759765625  -  Server - Chasing:102
  10:44:29.358  Pathfinding success!  -  Server - Chasing:91
  10:44:29.358  Moving to waypoint 1 at -501.0804443359375, 70.86649322509766, -67.42189025878906  -  Server - Chasing:102
  10:44:29.360  Moving to waypoint 2 at -500.39007568359375, 70.92412567138672, -66.78759765625  -  Server - Chasing:102
  10:44:30.360  Pathfinding success!  -  Server - Chasing:91
  10:44:30.361  Moving to waypoint 1 at -501.0804443359375, 70.86649322509766, -67.42189025878906  -  Server - Chasing:102
  10:44:30.363  Pausing for 3 seconds  -  Server - Chasing:110
  10:44:33.449  Moving to waypoint 2 at -504.29296875, 70.61612701416016, -65.05532836914062  -  Server - Chasing:102
  10:44:34.055  Moving to waypoint 3 at -507.5046081542969, 70, -62.68940353393555  -  Server - Chasing:102
  10:44:36.458  Moving to waypoint 4 at -510.72509765625, 70, -60.31696701049805  -  Server - Chasing:102
  10:44:37.311  Moving to waypoint 5 at -510.9735412597656, 70, -60.13394546508789  -  Server - Chasing:102
  10:44:37.388  Moving to waypoint 6 at -514.9725952148438, 70.72003173828125, -60.22210693359375  -  Server - Chasing:102
  10:44:45.439  Moving to waypoint 7 at -518.970458984375, 70.4984130859375, -60.31024169921875  -  Server - Chasing:102
  10:44:46.612  Moving to waypoint 8 at -522.96875, 70.27676391601562, -60.39838790893555  -  Server - Chasing:102
  10:44:47.501  Moving to waypoint 9 at -526.9674682617188, 70.05509948730469, -60.486541748046875  -  Server - Chasing:102
  10:44:48.332  Moving to waypoint 10 at -530.9664916992188, 70, -60.574703216552734  -  Server - Chasing:102
  10:44:49.558  Pausing for 5 seconds  -  Server - Chasing:110
  10:44:54.582  Moving to waypoint 11 at -534.9655151367188, 70, -60.662864685058594  -  Server - Chasing:102
  10:44:55.371  Moving to waypoint 12 at -538.9645385742188, 70, -60.75102615356445  -  Server - Chasing:102
  10:44:56.221  Pausing for 5 seconds  -  Server - Chasing:110
  10:45:01.256  Moving to waypoint 13 at -542.9635620117188, 70, -60.83918762207031  -  Server - Chasing:102
  10:45:02.220  Moving to waypoint 14 at -544.9595947265625, 70, -60.8831901550293  -  Server - Chasing:102
  10:45:02.631  Moving to waypoint 15 at -548.7977294921875, 70, -59.757835388183594  -  Server - Chasing:102
  10:45:03.538  Moving to waypoint 16 at -552.6357421875, 70, -58.632503509521484  -  Server - Chasing:102
  10:45:04.484  Pausing for 4 seconds  -  Server - Chasing:110
  10:45:08.643  Moving to waypoint 17 at -556.4736328125, 70, -57.5072135925293  -  Server - Chasing:102
  10:45:09.877  Moving to waypoint 18 at -560.311279296875, 70, -56.38199234008789  -  Server - Chasing:102
  10:45:17.891  Pausing for 3 seconds  -  Server - Chasing:110
  10:45:20.904  Moving to waypoint 19 at -564.1484985351562, 70, -55.25689697265625  -  Server - Chasing:102
  10:45:29.068  Moving to waypoint 20 at -567.9848022460938, 70, -54.13206481933594  -  Server - Chasing:102
  10:45:31.993  Moving to waypoint 21 at -571.8185424804688, 70, -53.00798416137695  -  Server - Chasing:102
  10:45:40.018  Pausing for 5 seconds  -  Server - Chasing:110
  10:45:45.028  Moving to waypoint 22 at -575.63916015625, 69.63185119628906, -51.88774871826172  -  Server - Chasing:102
  10:45:45.420  Moving to waypoint 23 at -575.822998046875, 69.59710693359375, -51.83385467529297  -  Server - Chasing:102
  10:45:45.434  Moving to waypoint 24 at -576.0052490234375, 69.4230728149414, -47.8406867980957  -  Server - Chasing:102
  10:45:53.456  Moving to waypoint 25 at -576.1875, 69.28694915771484, -43.84716033935547  -  Server - Chasing:102
  10:45:53.976  Moving to waypoint 26 at -576.3697509765625, 69.15083312988281, -39.853633880615234  -  Server - Chasing:102
  10:45:54.733  Moving to waypoint 27 at -576.5, 69.05357360839844, -37  -  Server - Chasing:102
  10:45:55.326  Moving to waypoint 28 at -576.5, 66.0999984741211, -31.5  -  Server - Chasing:102
  10:45:56.451  Moving to waypoint 29 at -576.5, 66.19999694824219, -32  -  Server - Chasing:102
  10:45:56.469  Moving to waypoint 30 at -576.5, 63.099998474121094, -26.5  -  Server - Chasing:102
  10:45:57.431  Moving to waypoint 31 at -576.5, 63.20000076293945, -27  -  Server - Chasing:102
  10:45:57.451  Moving to waypoint 32 at -576.5, 59, -21.5  -  Server - Chasing:102
  10:45:58.488  Moving to waypoint 33 at -577.5, 57.44444274902344, -18  -  Server - Chasing:102
  10:45:59.223  Moving to waypoint 34 at -577.5, 54.02262496948242, -12.5  -  Server - Chasing:102
  10:46:00.263  Moving to waypoint 35 at -578.3829345703125, 53.23964309692383, -8.67436695098877  -  Server - Chasing:102
  10:46:01.264  Moving to waypoint 36 at -579.266357421875, 52.45624923706055, -4.846728324890137  -  Server - Chasing:102
  10:46:02.060  Moving to waypoint 37 at -579.8350830078125, 52, -2.3826217651367188  -  Server - Chasing:102
  10:46:03.690  Pathfinding success!  -  Server - Chasing:91
  10:46:03.690  Moving to waypoint 1 at -579.6322631835938, 52.134822845458984, -3.2786312103271484  -  Server - Chasing:102
  10:46:03.695  Moving to waypoint 2 at -578.0304565429688, 52.961307525634766, -6.853118896484375  -  Server - Chasing:102
  10:46:04.189  Moving to waypoint 3 at -576.4278564453125, 53.788211822509766, -10.429405212402344  -  Server - Chasing:102
  10:46:12.436  Moving to waypoint 4 at -575.5, 54.22634506225586, -12.5  -  Server - Chasing:102
  10:46:20.450  Moving to waypoint 5 at -575.5, 57.44444274902344, -18  -  Server - Chasing:102
  10:46:28.470  Pausing for 3 seconds  -  Server - Chasing:110
  10:46:31.480  Moving to waypoint 6 at -573.0335083007812, 58.72338104248047, -20.877605438232422  -  Server - Chasing:102

The code looks like this now:

local pf = game:GetService("PathfindingService")
local plrs = game:GetService("Players")
local rs = game:GetService("RunService")

local npc = script.Parent
local hum = npc:WaitForChild("Humanoid")
npc.PrimaryPart:SetNetworkOwner(nil)

local waypoints
local nextwaypointindex
local reachedconnection
local blockedconnection

local damage = npc:GetAttribute("Damage")
local walkspeed = npc:GetAttribute("WalkSpeed")
local sprintspeed = npc:GetAttribute("SprintSpeed")

local function getpath(dest)
	local path = pf:CreatePath({
		AgentHeight = 6;
		AgentRadius = 3.5;
		AgentCanJump = true;
		AgentCanClimb = true;
		AgentJumpHeight = 7;
		AgentMaxSlope = 60;
		Costs = {
			Water = 100;
			DangerZone = math.huge
		}
	})

	path:ComputeAsync(npc.HumanoidRootPart.Position, dest.Position)
	return path
end


--[[
local function CanSeeTarget(target)
    local NPCtoChar = (target.Head.Position - npc.HeadConnector.Position).Unit
    local NPClook = npc.HeadConnector.CFrame.LookVector
    local dotproduct = NPCtoChar:Dot(NPClook)
    local origin = npc.HumanoidRootPart.Position
    local direction = (target.HumanoidRootPart.Position - npc.HumanoidRootPart.Position).Unit * 100
    local ray = Ray.new(origin, direction)
    local hit, pos = workspace:FindPartOnRay(ray, npc)

    if dotproduct > -0.1 then
        if hit and hit:IsDescendantOf(target) then
            return true
        end
    else
        return false
    end
end
]]

local function FindTarget()
	local MaxDistance = 300
	local NearestTarget

	for i, p in pairs(plrs:GetPlayers()) do
		if p.Character then
			local target = p.Character
			local distance = (npc.HumanoidRootPart.Position - target.HumanoidRootPart.Position).Magnitude

			if distance < MaxDistance --[[and CanSeeTarget(target)]] then
				NearestTarget = target
				MaxDistance = distance
			end
		end
	end

	return NearestTarget
end

local function capture(target)
	local distance = (npc.HumanoidRootPart.Position - target.HumanoidRootPart.Position).Magnitude

	if distance > 2 then
		hum:MoveTo(target.HumanoidRootPart.Position)
	else
		target.Humanoid.Health = target.Humanoid.Health - damage
		wait(7)
	end
end

local function walkto(dest)
	local path = getpath(dest)

	if path.Status == Enum.PathStatus.Success then
		print("Pathfinding success!")
		local waypoints = path:GetWaypoints()
		for i, wp in pairs(waypoints) do
			local target = FindTarget()

			if target then
				print("Target found: " .. target.Name)
				capture(target)
				npc.Humanoid.WalkSpeed = sprintspeed
				break
			else
				print("Moving to waypoint " .. i .. " at " .. tostring(wp.Position))
				npc.Humanoid.WalkSpeed = walkspeed
				hum:MoveTo(wp.Position)
				hum.MoveToFinished:Wait()

				
				if math.random() < 0.1 then
					local pauseTime = math.random(3, 5)
					print("Pausing for " .. pauseTime .. " seconds")
					wait(pauseTime)
				end
			end
		end
	else
		print("Pathfinding failed!")
		hum:MoveTo(dest.Position - (npc.HumanoidRootPart.CFrame.LookVector * 10))
	end
end

local function patrol()
	local wayp = workspace.WayPoints:GetChildren()
	local RandomWayPoint = math.random(1, #wayp)

	walkto(wayp[RandomWayPoint])
end

local touched = false
script.Parent.Hitbox.Touched:Connect(function(obj)
	if not touched then
		local player = game:GetService("Players"):GetPlayerFromCharacter(obj.Parent)
		if player then
			touched = true
			wait(9)
			touched = false
		end
	end
end)

while true do
	wait(0.01)
	if not touched then
		patrol()
	end
end

When it does not do the stairs is it taking a different path to get to the player ?

If it is stuck what does the output say when stuck?

Maybe provide a .rbxl of just the stairs , the npc , and script to duplicate the issue

1 Like

Here you go:
StairsNRIG.rbxm (155.9 KB)

Also you have to create WayPoints folder and put parts inside it.

umm… Can I get answers pls??? I need it to continue my project.

I’ve tried to find a solution, but apparently this is a problem with many devs – from what I’ve seen around here in the Devforum – and this could be a limitation of Roblox’s Pathfinding.

I opened your file, and tried adding a ramp in the stairs, but it still doesn’t work, and I couldn’t find out why. What you could do:

  • Try making the stairs less steep, maybe this could work
  • Make the rig be able to jump, just add some logic to change its HumanoidStateType with Humanoid:ChangeState
  • Research to see if you find any alternative, since I’ve seen videos on Youtube of people being able to make their NPCs go up stairs.

I can keep searching, but all the posts I found with a similar question, were unanswered for.

EDIT: I just asked ChatGPT for help, cause why not, and this is what he told me, could you check it out?

  • Increase the AgentHeight: Adjust the AgentHeight parameter of the PathfindingService. This setting helps the pathfinding algorithm understand the clearance needed to move through different spaces. If the rig is getting stuck, increasing the AgentHeight might help it understand the stairs better.
  • Use Slope Constraints: Instead of an invisible ramp, try adjusting the SlopeAngle property of the rig or NPC’s humanoid. This property defines the maximum angle that the character can walk up. By increasing this value, the rig may be able to walk up steeper inclines without jumping.
  • PathfindingLink: Use PathfindingLinks to manually define connections between the bottom and top of the stairs. PathfindingLinks allow you to specify custom paths that the PathfindingService should recognize. Place one link at the bottom of the stairs and another at the top, and connect them.

Add PathfindingModifiers in the stairs that have the arrows over them, and mark the “PassThrough” property.

1 Like

wdym?? What arrows? I added PathfindingModifiers and marked PassTrough. I tried to use attachments and then use PathFindingLink. It still doesn’t work.

I tried to use inspiration from this video:

mine looks like this:

I did some tests and now I figured out the problem! When I used a normal Rig character (R15), it climbed without any problems. However, my custom rig cannot climb the stairs. So the problem is with my custom rig. I tried to change the HRP’s pivot offset to 0, -1.481, 0, and now it looks like this:

I also added a wedge part and removed the CanCollide feature from my rig’s feet and toes. It seems to work fine now, but the toes go through the stairs because the collisions are off. If I move my rig’s HRP lower, then my rig will fly. I don’t know how to fix this. I also don’t know how to prevent the rig from getting stuck on small obstacles while not allowing the player to go through them.

ummm… does anyone have any idea, how should I do this???

Why no one helps me anymore???