Having tower shoot first enemy for Tower Defense game

I am creating a tower defense game and can detect which enemy is closest to a tower, but I want the tower to attack the enemy closest to the end (or the first enemy).

Spawn time will not work as some enemies are faster than others, and overtake them.

What would be a good system/feature to achieve this? I was thinking an array/table could work.

2 Likes

Idea:
You got the path from the start to the end right? So if you calculate the total distance the enemy will do (basically add up each part of the path’s size) you get the total distance, and then you can make each enemy have an IntValue (or NumberValue) and it’s value will be equal to the distance they already did.
Then, your tower should check which enemy has the highest value.

2 Likes

I made a system where it works the other way around: instead of having the mobs move along the path and then calculating how far along it they’ve moved at any given point, it keeps track of their “path offset” and calculates the actual position based on that. That way it’s trivially easy to compare two mobs to see which one is further along the path.

Here’s the relevant code, although it’s very unfinished. Might want to return an object from setupPathFollower and put them all in a table.

--PathFollowerManager

local TagS = game:GetService("CollectionService")
local RunS = game:GetService("RunService")
local InputS = game:GetService("UserInputService")

function getPathNodes(path)
	local nodes = {}
	local nodeDists = {}

	local nextNode = path.FirstNode.Value
	local cumulativeDist = 0

	while nextNode do
		table.insert(nodes, nextNode)
		nodeDists[nextNode] = cumulativeDist

		local prevNode = nextNode
		nextNode = nextNode.Next.Value
		if nextNode then
			cumulativeDist += (nextNode.Position - prevNode.Position).Magnitude
		end
	end

	return nodes, nodeDists, cumulativeDist
end

function getPathOffsetPoint(nodes, nodeDists, pathLength, offset): Vector3
	if #nodes == 1 then
		return nodes[1].Position
	end

	if offset >= pathLength then 
		return getPathOffsetPoint(nodeDists, pathLength, offset % pathLength)
	end

	local prevNodeI, nextNodeI = 1, 2

	while offset > nodeDists[nodes[nextNodeI]] do
		prevNodeI += 1
		nextNodeI += 1
	end
	
	local segmentDiff = nodes[nextNodeI].Position - nodes[prevNodeI].Position
	
	return nodes[prevNodeI].Position + segmentDiff.Unit * (offset - nodeDists[nodes[prevNodeI]])
end

function getPathUnitOffsetPoint(nodes, nodeDists, pathLength, offset): Vector3
	error("Not yet implemented!")
end

function findGroundPointUnderPoint(point: Vector3): Vector3
	local params = RaycastParams.new()
	params.FilterType = Enum.RaycastFilterType.Whitelist
	params.FilterDescendantsInstances = {game.Workspace.Baseplate}
	local result = game.Workspace:Raycast(
		point,
		Vector3.FromNormalId(Enum.NormalId.Bottom) * 5000,
		params
	)
	
	if result then
		return result.Position
	end
	
	return point
end

function setupPathFollower(pathFollower)
	local path
	local pathNodes
	local pathNodeDistances
	local pathLength
	local offset = 0

	RunS.Heartbeat:Connect(function(dt)
		if path ~= pathFollower.Path.Value then
			path = pathFollower.Path.Value
			pathNodes, pathNodeDistances, pathLength = getPathNodes(path)
		end

		if #pathNodes > 0 then
			offset += 10 * dt
			offset = offset % pathLength
			
			local offsetPoint = getPathOffsetPoint(pathNodes, pathNodeDistances, pathLength, offset)
			local groundPoint = findGroundPointUnderPoint(offsetPoint)
			pathFollower:SetPrimaryPartCFrame(CFrame.new() + groundPoint)
		end
	end)
end

table.foreachi(TagS:GetTagged("PathFollower"), function(_, v) setupPathFollower(v) end)
TagS:GetInstanceAddedSignal("PathFollower"):Connect(setupPathFollower)
1 Like

This question gets asked so frequently someone should make a tutorial for it.

That’s true. You wanna do it? :wink:

Hi, I am not the best scripter but I am trying hard to get better. Is this basically just getting the length between path nodes and adding them up depending on the mobs position? And would this allow me to find the 1st mob in the tower’s range?

I already have something like this implemented, now I need to figure out how to find the first enemy in the range of the tower.

Agreed, It’s often overlooked in tutorials, but can prove to be quite a challange

If you want a really easy way to determine this, you can put parts along the path and trigger whenever the first enemy touches it and update the towers current target. It would probably look something like this

local updateTargets = game:GetService("ServerStorage"):WaitForChild("UpdateTargets") -- bindable event to update the towers targets
local detectionParts = workspace:WaitForChild("DetectionParts"):GetChildren() -- a group or folder containing the detection parts

function onDetection (model)
	updateTargets:Fire(model)
end

function addEvent ()
	for i,v in pairs(detectionParts) do
		v.Touched:Connect(function(hit)
			if hit.Parent:FindFirstChild("Humanoid") --[[ you should probably change this to something only the enemies exclusively have ]] and hit.Parent:FindFirstChild("alreadyTouched") == false --[[ bool value to ensure that the part hasn't been touched anymore ]] not game.Players:GetPlayerFromCharacter(hit.Parent) and not game.Players:GetPlayerFromCharacter(hit.Parent.Parent) then -- ensure it's an enemy and not a player, you should probably edit this
				onDetection(hit.Parent) -- send the model over
                hit.Parent.alreadyTouched.Value = true
			end
		end)
	end
end

addEvent()

-- then in your enemy script

local updateTargets = game:GetService("ServerStorage"):WaitForChild("UpdateTargets")
local currentTarget

updateTargets.Event:Connect(function(model)
	currentTarget = model -- update target
end)

if you don’t feel comfortable using .Touched events, you can loop raycasts, although that’s going to be more costly on performance than you would probably like.

hope this helps :smile:

I also did a post similar to this very recently, perhaps take a look?

I’d do a for loop that scans each enemy. first, check it’s distance from the tower. if the distance is lower than what you want, you can then see if it is the farthest.

Thanks a lot! I will defo try this out. How far would you recommend spacing out the detection parts?

I have never heard of zoneplus before, I presume this can be used to find positions within the zone? I will take a look into it.

If you’re using the .Touched event it won’t cause much lag at all, so you can place the parts close to each other or far apart depending on how fast you want towers to react to the first enemy. Although I recommend adding debounce to the .Touched event to prevent the event firing multiple times.

Maybe a 0.1 second debounce, considering towers wont be attacking at such a fast speed. Again I am not the best of scripters but I will attempt to refine the code and implement it into my game. This is actually the best solution I have heard to this problem yet.

1 Like