Fixing NPC AI Code

Hello guys! I’m currently working on an entity-AI system, but I’ve had some trouble.

Script:

local pathfindingservice = game:GetService("PathfindingService")
local runservice = game:GetService("RunService")
local players = game:GetService("Players")

local npc = script.Parent
local humanoid = npc.Humanoid
local head = npc.Head
local hrp = npc.HumanoidRootPart

local settings = require(npc.Settings)

hrp:SetNetworkOwner(nil)

function findTarget()
	local nearestTarget = nil
	local nearestDistance = math.huge

	for i,v in game.Players:GetPlayers() do
		local character = v.Character
		local humanoid = character and character:FindFirstChildOfClass("Humanoid")

		if humanoid and humanoid.Health > 0 then
			local Distance = (character.HumanoidRootPart.Position - hrp.Position).Magnitude

			if Distance < nearestDistance then
				if settings.checkTarget(character) then
					nearestTarget = v
					nearestDistance = Distance
					return nearestTarget
				end
			end
		end
	end
end

function AttackTarget(target)
	
	if target then
		-- found target
		local char = target.Character
		
		
	else
		warn("no target given.")
	end
end

function targetAngle(character)
	
	print("TARGETANGLE")
	
	local npctocharacter = (character.HumanoidRootPart.Position-hrp.Position).Unit
	local npclook = hrp.CFrame.LookVector
	
	local dot = npctocharacter:Dot(npclook)
	
	local maxdist = 100
	
	local dist = (character.Head.Position-hrp.Position).Magnitude
	
	local direction = (character.Head.Position-hrp.Position)
	
	local params = RaycastParams.new()
	params.FilterDescendantsInstances = {npc, character}
	params.FilterType = Enum.RaycastFilterType.Exclude
	
	local ray = workspace:Raycast(hrp.Position, direction*direction.Magnitude, params)
	
	if ray and ray.Instance then
		
		print("RAY TRUE")
		print(ray.Instance.Name)
		
		if dot > .5 then
			print("DOT TRUE")
			if dist <= maxdist then
				-- char is in fov
				print("DIST TRUE")
				return true
			else
				return false
			end
		else
			-- char is not in fov
			return false
		end
		
	else
		-- something is blocking the way
		return false
	end
end

function followTarget()
	
	local target = findTarget()
	
	if target then
		
		if targetAngle(target.Character) then
			print("followTarget")

			local char = target.Character
			local charhead = char.Head

			local path = pathfindingservice:CreatePath(settings.agentparams)
			path:ComputeAsync(hrp.Position, char.HumanoidRootPart.Position)
			local waypoints = path:GetWaypoints()

			humanoid.WalkSpeed = settings.MaxSpeed

			if path.Status == Enum.PathStatus.Success then
				for _, waypoint in pairs(waypoints) do
					if waypoint.Action == Enum.PathWaypointAction.Jump then
						humanoid:ChangeState(Enum.HumanoidStateType.Jumping)
					end
					humanoid:MoveTo(waypoint.Position)
					humanoid.MoveToFinished:Wait()
				end
			else
				warn("Path not found!")
			end
		end
		
	end
	
end

function Patrol()

	print("patrol")

	humanoid.WalkSpeed = settings.PatrolSpeed

	local patrolfolder = workspace.PatrolArea:GetChildren()

	local randompatrol = patrolfolder[math.random(1,#patrolfolder)]

	local path = pathfindingservice:CreatePath(settings.agentparams)
	path:ComputeAsync(hrp.Position, randompatrol.Position)

	local waypoints = path:GetWaypoints()

	if path.Status == Enum.PathStatus.Success then
		for index, waypoint in pairs(waypoints) do

			local target = findTarget()
			
			if target then
				followTarget()
			else
				if waypoint.Action == Enum.PathWaypointAction.Jump then
					humanoid:ChangeState(Enum.HumanoidStateType.Jumping)
				end

				humanoid:MoveTo(waypoint.Position)
				humanoid.MoveToFinished:Wait()
			end
		end
	else
		warn("Path not computed!", path.Status)
	end

end

while task.wait() do
	Patrol()
end

I’ve added some prints to understand where the bugs occurred, so please don’t mind those.

I’m having a difficult time comprehending my code, which is a bit embarrassing, but I’m left with no choice.

What I want it to do is find a player that is nearest to the entity, use pathfinding to attempt to attack them, which is blank for now, (I’ll add stuff soon.) The entity cannot detect the player in 3 ways,

  1. A direct ray must be established between the entity and the player so that the entity cannot see through objects.
  2. The entity can see through a limited angle.
  3. A maximum distance, so the entity cannot attack something that is extremely far.

So far, I’ve implemented all three, but the NPC is super buggy, as long as these requirements are met, I’m fine with changing the code.

I’ve tried to look for other solutions but I couldn’t find too much.

Any help is appreciated! :smile:

1 Like

return nearestTarget should be at the end of the function if you want to get the nearest target, and not the first valid target it sees. Also check the player’s character in case it hasn’t loaded in.

function findTarget()
	local nearestTarget = nil
	local nearestDistance = math.huge

	for i,v in game.Players:GetPlayers() do
		local character = v.Character

		if character then
			local humanoid = character and character:FindFirstChildOfClass("Humanoid")

			if humanoid and humanoid.Health > 0 then
				local Distance = (character.HumanoidRootPart.Position - hrp.Position).Magnitude

				if settings.checkTarget(character) and Distance < nearestDistance then
					nearestTarget = v
					nearestDistance = Distance
				end
			end
		end
	end

	return nearestTarget
end
1 Like

What “direction*direction.Magnitude” does is that the raycast range is multiplied by itself, or basically is to the power of 2, so the raycast is probably hitting any walls behind the player.

This makes it the correct distance: “local ray = workspace:Raycast(hrp.Position, direction, params)

image

Correct me if I’m wrong, but shouldn’t direction be a unit towards the direction? I should’ve maybe used .Unit but If I just multiply the direction with the distance between the player and monster, I should get a ray that’s only between them?

Or does the direction return the given ray?

Hello, just hopping in to clarify this, the Direction argument of a raycast is pretty much the vector that the ray travels on from the origin. The ending position is equal to the origin plus the direction, so endPosition = originPosition + direction. Since you want to raycast between just the player and the monster, you can just move around the equation to get: direction = endPosition - originPosition, which gives us a raycast direction exactly from the monster (origin) to the player (end).

Ah, thank you for clarifying! Just one thing, if I were to do direction.Unit, it would only return the direction without distance?

direction.Unit converts the direction vector to a unit vector, which is a vector in the same direction, with a length/magnitude of 1. Multiplying (a scalar/single number) by a unit vector allows you to convert it to whatever length/magnitude you desire (since 1*x = x).