How to consistently make an NPC move along a "wall"?

I’m trying to make it so when an NPC can’t move back anymore that it should move along an invisible barrier. Here is a picture of what I mean:

npc

The road block preventing me from figuring this out is that NPC is always facing the player and along with my poor knowledge of CFrame, can’t figure out how to always get it to move along a wall no matter the direction the NPC faces.

Any advice and help is greatly appreciated, thank you.

6 Likes

You can use the Humanoid’s :MoveTo() method to walk to a given point along the side of the wall, and make sure that AutoRotate is set to false on the humanoid to prevent the NPC turning while walking in this direction.
If you need to rotate the character to continue facing a point even while moving along the wall, then you can use CFrame.new(pos, lookAt) to make a new CFrame for the NPC looking at a position. I’d also note that there is a PathfindingService which if will return a list of points you walk along if you supply a point to move towards.(which may not suit your purposes if you just want to move away from a player…)

2 Likes

Thank you for the response, the problem that I was having was determining a given point along a wall. I just ended up putting some parts along each wall and then having the npc move to the closest one.

1 Like

Ah, okay. What I’d do is convert the NPC’s position to the wall’s local coordinates. This makes it easy to determine which face of the wall is the one the NPC is running into. Once you know which face they are running into, you can convert a vector along that surface to world space and your have the direction to travel! This is what it would look like:

local INSIDE_ERROR_MESSAGE = 'The given point is inside the wall'
local EDGE_ERROR_MESSAGE = 'The given point is not infront of a wall, but near a edge. Cannot tell which of two walls was hit.'
local CORNER_ERROR_MESSAGE = 'The given point is not infront of a wall, but near a corner. Cannot tell which of three walls was hit'
local xUnitVector = Vector3.new(1, 0, 0)
local yUnitVector = Vector3.new(0, 1, 0)
local zUnitVector = Vector3.new(0, 0, 1)
local function getSideDirections(npcPos, wall)

	-- Each surface has two vectors along it, one is up/down (y) and one is left/right (x)
	-- We'll need to decide later which is which
	local possibleVector1, possibleVector2

	-- To detect that we are infront of a surface, we should be in the bounds set by the size of
	-- the part in two axis's. Those two axis's are also the possible side directions to move
	-- along the side of the wall. (We wouldn't want to use the third axis and move into or away
	-- from the wall.)
	local bounds = wall.Size/2
	local localPos = wall.CFrame:pointToObjectSpace(npcPos)
	local inXBounds = localPos.X > -bounds.X and localPos.X < bounds.X
	local inYBounds = localPos.Y > -bounds.Y and localPos.Y < bounds.Y
	local inZBounds = localPos.Z > -bounds.Z and localPos.Z < bounds.Z
	if inXBounds then
		possibleVector1 = xUnitVector
	end
	if inYbounds then
		if possibleVector1 then
			possibleVector2 = yUnitVector
		else
			possibleVector1 = yUnitVector
		end
	end
	if inZBounds then
		if not possibleVector1 then
			-- Only 1 axis in bounds (z)
			error(EDGE_ERROR_MESSAGE)
		elseif not possibleVector2 then
			possibleVector2 = zUnitVector
		else
			-- All axis's in bounds
			error(INSIDE_ERROR_MESSAGE)
		end
	elseif not possibleVector1 then
		-- No axis's in bounds
		error(CORNER_ERROR_MESSAGE)
	elseif not possibleVector2 then
		-- Only 1 axis in bounds (x or y)
		error(EDGE_ERROR_MESSAGE)
	end

	-- Determine which vector seems like it is up/down, then pick the left/right to move along.
	possibleVector1 = wall.CFrame:vectorToWorldSpace(possibleVector1).Unit
	possibleVector2 = wall.CFrame:vectorToWorldSpace(possibleVector2).Unit
	if math.abs(possibleVector1:Dot(yUnitVector)) > math.abs(possibleVector2:Dot(yUnitVector)) then
		-- possibleVector1 is up/down
		return possibleVector2, -possibleVector2
	else
		-- possibleVector2 is up/down
		return possibleVector1, -possibleVector1
	end
end

Now just add the returned vector to the NPC’s position and you have a point to go to 1 stud away from the NPC (vectors returned are units).

1 Like

Which return vector do we use? You return 2 of them.

Edit: Oh never mind, I see what you did. You returned 2 vectors, one to go left and the other to go right.

If you want to determine which moves further from the player faster then use this function:

local function farthestFrom(vector, option1, option2)
	if vector:Dot(option1) < vector:Dot(option2) then
		return option1
	else
		return option2
	end
end

Where ‘vector’ is the vector from the NPC to the player (playerPos - npcPos) and option1/2 are the vectors returned by my previous function (left and right).

1 Like

Thank you!