I've made this enemy AI which just follows the player and attacks when close enough. It uses the path finding service, so it's horribly inefficient at getting to a moving target. Is there any better way to balance out efficiency (with pathfinding) and getting to the target as fast as possible?

Here is my code:

local Stats = {walk = 6, run = 22, health = 250, radius = 20, range = 10, id = 2549507798, damage = 10, attackspeed = 1}

local PathfindingService = game:GetService("PathfindingService")
local NpcTarget = require(game.ServerScriptService.Modules.NpcTarget)

local pointModel = Instance.new("Model")
pointModel.Name = "Points"
pointModel.Parent = workspace

local subject = script.Parent
local humanoid = subject.Humanoid

local lastHealth = humanoid.Health
local active = false

local lastFailedDirection = nil

local origin = subject.HumanoidRootPart.Position

local target = nil
local walkCount = 0
local canAttack = true
local dead = false

local startPos = subject.HumanoidRootPart.Position

local rng = Random.new()

local animation = Instance.new("Animation")
animation.AnimationId = "http://www.roblox.com/Asset?ID="..Stats.id

local track = humanoid:LoadAnimation(animation)

function outOfRangeRegen()
	for i = 1,5 do
		wait(0.1)
		humanoid.Health = humanoid.Health + 5
	end
end	

function attackCooldown()
	canAttack = false
	coroutine.resume(coroutine.create(function()
		wait(Stats.attackspeed)
		canAttack = true
	end))
end

function findRandomPos()
	local pos = Vector3.new(subject.HumanoidRootPart.Position.X + rng:NextInteger(-25,25), 
								subject.HumanoidRootPart.Position.Y + rng:NextInteger(-5,5), 
								subject.HumanoidRootPart.Position.Z + rng:NextInteger(-25,25))
	
	
	if not testOverLand(pos) then
		pos = findRandomPos()
	end

	return pos
end

function calcPath(pos)
	local start = subject.HumanoidRootPart.Position
	local path = PathfindingService:FindPathAsync(start, pos)
	
    return path
end

function originalPos()
	walk(startPos)
end

function findTarget()
	local testTarget = NpcTarget.findClosest(subject)
	if not testTarget then
		active = false
		originalPos()
	else
		--[[if testTarget ~= target then
			local onDied = target.Humanoid.Died:Connect(function()
				target = findTarget()
			end)
		end]]
	end
	return testTarget
end

function detectTargets()
	coroutine.resume(coroutine.create(function()
		while not active do
			wait(1)
			for i,v in pairs(game.Players:GetChildren()) do
				local char = v.Character
				if char then
					if (char.HumanoidRootPart.Position - subject.HumanoidRootPart.Position).magnitude < Stats.range then
						local target = findTarget()
						active = true
					end
				end
			end
		end
	end))
end

function testOverLand(pos)
	local ray = Ray.new(pos, ((pos - Vector3.new(0,100,0)) - pos).unit * 300)
	local part, position = workspace:FindPartOnRay(ray, subject, false, true)
	
	if part then
		if part.Name == "Baseplate" then
			return false
		end
	else
		return false
	end
	
	return pos
end

function walk(opPos)
	humanoid.WalkSpeed = Stats.walk
	local pos = findRandomPos()
	
	if opPos then
		pos = opPos
	end
		
	local path = calcPath(pos)
	
	if path:CheckOcclusionAsync(1) == -1 then
		humanoid:MoveTo(pos)
		wait(0.01)
	end
		
	local points = path:GetWaypoints()
	local distance = 0
	
	for i = 2,#points do
		local lastWaypoint = points[i - 1]
		local currentWaypoint = points[i]
		
		humanoid:MoveTo(currentWaypoint.Position)
		repeat
			wait(0.01)
			distance = (subject.HumanoidRootPart.Position - currentWaypoint.Position).magnitude
		until distance <= 2.5 or active
		
		if currentWaypoint.Action == Enum.PathWaypointAction.Jump then
			humanoid.Jump = true
		end

	end	

	return true
end

function testRangeDistance()
	if not active then
		if (subject.HumanoidRootPart.Position - origin).magnitude > Stats.radius then
			return true
		end
	end
	
	return false
end

function pause()
	local w = math.random(1,5) 
	local count = 0
	
	while not active and count < w do
		wait(1)
		count = count + 1
		target = findTarget()
	end
end

function testTargetDistance()
	if (target.HumanoidRootPart.Position - subject.HumanoidRootPart.Position).magnitude <= 5 then
		return true
	end
	
	return false
end

function run()
	humanoid.WalkSpeed = 18
	local pos = target.HumanoidRootPart.Position
	local path = calcPath(pos)
	
	if path.Status == Enum.PathStatus.NoPath then
		outOfRangeRegen()
		return
	end
	
	if path:CheckOcclusionAsync(1) == -1 and not testTargetDistance() then
		humanoid:MoveTo(target.HumanoidRootPart.Position)
		wait(0.01)
	end
	
	local points = path:GetWaypoints()
	local distance = 0
	
	local willAttack = false
	
	for i = 2,#points do
		local lastWaypoint = points[i - 1]
		local currentWaypoint = points[i]
		
		humanoid:MoveTo(currentWaypoint.Position)
		
		repeat
			wait(0.05)
			distance = (subject.HumanoidRootPart.Position - currentWaypoint.Position).magnitude
			willAttack = testTargetDistance()
		until distance <= 6 or not active or target.Humanoid.Health <= 0 or willAttack
		
		if willAttack then
			humanoid:MoveTo(subject.HumanoidRootPart.Position)
			attack()
		end
		
		if currentWaypoint.Action == Enum.PathWaypointAction.Jump then
			humanoid.Jump = true
		end

	end	
		
	return true
end

function attack()
	if testTargetDistance() and canAttack and not dead then
		track:Play()
		target.Humanoid:TakeDamage(Stats.damage)
		attackCooldown()
	end
end

function died()
	local function fadePart(part)
		coroutine.resume(coroutine.create(function()
			for i = 1,25 do
				wait(0.01)
				part.Transparency = part.Transparency + 0.04
			end
		end))
	end
	dead = true
	subject.Humanoid.WalkSpeed = 0
	subject.Humanoid.JumpPower = 0
	local animation = Instance.new("Animation")
	animation.AnimationId = "http://www.roblox.com/Asset?ID=2614501451"
	local track = subject.Humanoid:LoadAnimation(animation)
	track:Play()
	
	track.KeyframeReached:Connect(function(keyframe)
		if keyframe == "Dead" then
			for i,v in pairs(subject:GetChildren()) do
				if v:IsA("BasePart") then
					fadePart(v)
				end
			end
		end
		
		game.Debris:AddItem(subject, 1)
	end)
end

function tookDamage()
	active = true
end

humanoid.HealthChanged:Connect(function(health)
	if health <= 1 then
		local ff = Instance.new("ForceField")
		ff.Parent = subject
		ff.Visible = false
		died()
	end
	if health < lastHealth and not active then
		tookDamage()
	end
end)

while not dead do
	wait()
	if not active then
		detectTargets()
		local decision = math.random(1,3)
		
		if decision > 1 and walkCount < 4 then
			local success, msg = pcall(function()
				walk()
			end)
			
			walkCount = walkCount + 1
			wait(1)
		else
			pause()
			walkCount = 0
		end
		
		if testRangeDistance() then
			local success, msg = pcall(function()
				walk(origin)
			end)
		end
	else
		target = findTarget()
		if not target then
			active = false
		end
		local success, msg = pcall(function()
			run()
		end)
	end
end

1 comment

0

this is too much, good luck on finding an answer xd