How to make an enemy AI wander randomly and stay within a zone?

So I need my enemy NPCs to be able to wander freely within a specific zone. Along with this, if they are chasing a player and the player leaves the zone, they should not follow them outside the zone. I have a cylinder part stretched over the zone they can stay in but I don’t know how to implement this aspect into my code. (I got this script from a tutorial since I’m not very familiar with AI or pathfinding)

local myHuman = script.Parent:WaitForChild("Humanoid")
local myRoot = script.Parent:WaitForChild("HumanoidRootPart")
local head = script.Parent:WaitForChild("Head")
local lowerTorso = script.Parent:WaitForChild("Torso")

local grab = script.Parent:WaitForChild("Grab")
local grabAnim = myHuman:LoadAnimation(grab)
local walk = script.Parent.Humanoid.walkani
local walkAnim = myHuman:LoadAnimation(walk)
grabAnim.Priority = Enum.AnimationPriority.Action

local damageAmt = 10

--local grabSound = head:WaitForChild("Attack")

local clone = script.Parent:Clone()

function walkRandomly()
	local xRand = math.random(-25,25)
	local zRand = math.random(-25,25)
	local goal = myRoot.Position + Vector3.new(xRand,0,zRand)

	local path = game:GetService("PathfindingService"):CreatePath()
	path:ComputeAsync(myRoot.Position, goal)
	local waypoints = path:GetWaypoints()

	if path.Status == Enum.PathStatus.Success then
		for _, waypoint in ipairs(waypoints) do
			if waypoint.Action == Enum.PathWaypointAction.Jump then
				myHuman.Jump = true
			end
			myHuman:MoveTo(waypoint.Position)
			local timeOut = myHuman.MoveToFinished:Wait(1)
			if not timeOut then
				print("Got stuck")
				myHuman.Jump = true
				walkRandomly()
			end
		end
	else
		print("Path failed")
		wait(1)
		walkRandomly()
	end
end

function findPath(target)
	local path = game:GetService("PathfindingService"):CreatePath()
	path:ComputeAsync(myRoot.Position,target.Position)
	local waypoints = path:GetWaypoints()

	if path.Status == Enum.PathStatus.Success then
		for _, waypoint in ipairs(waypoints) do
			if waypoint.Action == Enum.PathWaypointAction.Jump then
				myHuman.Jump = true
			end
			myHuman:MoveTo(waypoint.Position)
			local timeOut = myHuman.MoveToFinished:Wait(1)
			if not timeOut then
				myHuman.Jump = true
				print("Path too long!")
				findPath(target)
				break
			end
			if checkSight(target) then
				repeat
					print("Moving directly to the target")
					myHuman:MoveTo(target.Position)
					attack(target)
					wait(0.1)
					if target == nil then
						break
					elseif target.Parent == nil then
						break
					end
				until checkSight(target) == false or myHuman.Health < 1 or target.Parent.Humanoid.Health < 1
				break
			end
			if (myRoot.Position - waypoints[1].Position).magnitude > 20 then
				print("Target has moved, generating new path")
				findPath(target)
				break
			end
		end
	end
end

function checkSight(target)
	local ray = Ray.new(myRoot.Position, (target.Position - myRoot.Position).Unit * 40)
	local hit,position = workspace:FindPartOnRayWithIgnoreList(ray, {script.Parent})
	if hit then
		if hit:IsDescendantOf(target.Parent) and math.abs(hit.Position.Y - myRoot.Position.Y) < 3 then
			print("I can see the target")
			return true
		end
	end
	return false
end

function findTarget()
	local dist = 50
	local target = nil
	local potentialTargets = {}
	local seeTargets = {}
	for i,v in ipairs(workspace:GetChildren()) do
		local human = v:FindFirstChild("Humanoid")
		local torso = v:FindFirstChild("Torso") or v:FindFirstChild("HumanoidRootPart")
		if human and torso and not v:FindFirstChild("AI") then
			if (myRoot.Position - torso.Position).magnitude < dist and human.Health > 0 then
				table.insert(potentialTargets,torso)
			end
		end
	end
	if #potentialTargets > 0 then
		for i,v in ipairs(potentialTargets) do
			if checkSight(v) then
				table.insert(seeTargets, v)
			elseif #seeTargets == 0 and (myRoot.Position - v.Position).magnitude < dist then
				target = v
				dist = (myRoot.Position - v.Position).magnitude
			end
		end
	end
	if #seeTargets > 0 then
		dist = 200
		for i,v in ipairs(seeTargets) do
			if (myRoot.Position - v.Position).magnitude < dist then
				target = v
				dist = (myRoot.Position - v.Position).magnitude
			end
		end
	end
	return target
end

function died()
	wait(5)
	clone.Parent = workspace
	game:GetService("Debris"):AddItem(script.Parent,0.1)
end

myHuman.Died:Connect(died)

lowerTorso.Touched:Connect(function(obj)
	if not obj.Parent:FindFirstChild("Humanoid") then
		myHuman.Jump = true
	end
end)

function attack(target)
	if (myRoot.Position - target.Position).magnitude < 5 then
		--grabSound:Play()
		if target.Parent ~= nil and not target.Parent:FindFirstChild("AI") then
			grabAnim:Play()
			wait(0.3)
			target.Parent.Humanoid:TakeDamage(damageAmt)
		end
		wait(1.25)
	end
end

function main()
	local target = findTarget()
	if target then
		myHuman.WalkSpeed = 14
		findPath(target)
		walkAnim:Play()
	else
		myHuman.WalkSpeed = 9
		walkRandomly()
		walkAnim:Play()
	end
end

while wait(0.1) do
	if myHuman.Health < 1 then
		break
	end
	main()
end

Thank you for your help!

1 Like

Check whether the target location where it should walk towards is not outside your part, you could do it like so

if targetPosition.X < part.Position.X + part.Size.X / 2 and targetPosition.X > part.Position.X - part.Size.X / 2 then
   -- is within the X range of the part
   if targetPosition.Z < part.Position.Z + part.Size.Z / 2 and targetPosition.Z > part.Position.Z - part.Size.Z / 2 then
      -- is within the Z range of the part

   end
end
4 Likes

To expand on what @theworldis_socruel has said, inside of the if parameter you should set your enemies new movement goal to the center of the zone. I also recommend doing the above with y coordinates if dealing with slopes. I would also make your enemies targeting system a coroutine or any other function that can easily be broken so that the enemy doesn’t try to track you after it’s been directed to move back. Hope this helps.

2 Likes

Thank you, although I am not sure where I should insert this check in my script, I’ve tried different areas of my script but none of them have worked correctly. (I am not good with this AI stuff lol)

Right before you let the npc’s move, you should have those two if statements!

1 Like