How Does Tower Defense X achieve this range indicator visual?

Essentially I’m trying to achieve what this video does when it comes to range indicators.

I have an idea of how they do it with beams and attachments, but I fear this is incorrect because it just doesn’t look like it lol
image

Anyone got any ideas?

3 Likes

Maybe via raycasts and layering parts, as you can see the blue circle isn’t exactly round.

Shadow casting is a very similar, yet much more popular idea and if you try learning from videos about that, you should be able to apply it to this certain use case.

1 Like

Outlines might be done with handleadornment instances, and the circle might be done with :UnionAsync() created out of a bunch of wedges.

I was thinking it used raycasts, but it probably uses shapecasts to ensure poles are always accounted for.

2 Likes

They only use a little trigonometry to get points and form triangles in a circle.

You have the necessary algorithms below, it should work, however it is possible that the code presented is not optimal or does not work in its current state, it is mainly examples so that you understand the method.

local function getValidArcs(origin, radius)
	local arcs = {}
	 -- ideally, this should be defined using radius, the larger a circle is,
	 -- the more raycast we'll need to do for an accurate result
	local numArcs = 8

	for i = 1, numArcs do
		local angle = 2 * math.pi * i / numArcs
		local direction = radius * Vector3.new(math.cos(angle), 0, math.sin(angle))

		local arcResult = {}
		arcResult.hit = false
		arcResult.direction = direction
		arcResult.distance = radius

		local rayResult = workspace:Raycast(origin, direction, raycastParams)
		if rayResult ~= nil then
			local rayDirection = rayResult.Position - origin

			arcResult.hit = true
			arcResult.distance = rayDirection.Magnitude
		end

		table.insert(arcs, arcResult)
	end

	return arcs
end

The getValidArcs function calculates a series of points around a specified circle by raycasting in multiple directions from the circle’s origin. It uses trigonometry to define the direction for each ray based on the circle’s radius and checks for collisions or hits in those directions.

This enables us to obtain two points for each arc, which together with the circle’s center, can form any triangle. The challenge here is that we need right-angled triangles to construct the circle using WedgeParts.

Here is an algorithm that allows you to determine a 4th point to form the triangles.

local A = {x = 0, y = 0}
local B = {x = -1, y = 4}
local C = {x = 1, y = 4}

local function dotProduct(U, V)
    return U.x * V.x + U.y * V.y
end

local function vector(P, Q)
    return {x = Q.x - P.x, y = Q.y - P.y}
end

local function perpendicularFoot(A, B, C)
    local BC = vector(B, C)
    local BA = vector(B, A)
    local t = dotProduct(BA, BC) / dotProduct(BC, BC)
    local footX = B.x + t * BC.x
    local footY = B.y + t * BC.y
    return {x = footX, y = footY}
end

local D = perpendicularFoot(A, B, C)

And then you just have to create the WedgeParts using the 4 points, this for each arc.

2 Likes