Menu
CoddyTech

Aim

Lesson 7 of 18 in Coddy's Roblox Game: Tower Defense course.

A tower guards the ground within its Range. Each tower runs its own loop in its own thread: find the nearest living enemy in range, and turn its Head to face it.

Finding the nearest is a classic loop: start with nothing and the range as the best distance, and keep any enemy closer than the best so far.

local best, bestDistance = nil, range
for _, enemy in enemies:GetChildren() do
	-- closer than the best so far? keep it
end
return best

CFrame.lookAt(from, at) is a CFrame standing at from and looking at at, so this turns the head without moving it:

tower.Head.CFrame = CFrame.lookAt(tower.Head.Position, target.HumanoidRootPart.Position)

A loop that runs forever must wait in every turn, or it freezes the game: wait 0.1 seconds whether or not it found a target.

challenge icon

Challenge

Medium
  1. Write nearestEnemy(position, range): the living enemy (it has a HumanoidRootPart and Health > 0) nearest to position, within range, or nil.
  2. Write runTower(tower): while the tower is in the game, find the nearest enemy to its head within Settings.Towers[tower.Name].Range, turn the head to face it, and task.wait(0.1).
  3. Start it at the end of placeTower with task.spawn(runTower, tower).

Press Play, build an Archer by the start, and watch it track. Then Check.

Try it yourself

local Players = game:GetService("Players")
local ServerStorage = game:GetService("ServerStorage")
local Settings = require(game.ReplicatedStorage.Settings)

local remotes = game.ReplicatedStorage.Remotes
local enemies = workspace.Enemies
local towers = workspace.Towers

print("The base can take " .. Settings.BaseHealth .. " hits")

-- The waypoints, in order (they are named 1, 2, 3...)
local waypoints = workspace.Path.Waypoints:GetChildren()
table.sort(waypoints, function(a, b)
	return tonumber(a.Name) < tonumber(b.Name)
end)

-- The base's health, as an attribute of the workspace: every client can read it
workspace:SetAttribute("BaseHealth", Settings.BaseHealth)

-- Every player gets Money on the leaderboard
Players.PlayerAdded:Connect(function(player)
	local leaderstats = Instance.new("Folder")
	leaderstats.Name = "leaderstats"
	leaderstats.Parent = player

	local money = Instance.new("IntValue")
	money.Name = "Money"
	money.Value = Settings.StartMoney
	money.Parent = leaderstats
end)

-- An enemy reached the base
local function damageBase(amount)
	local health = math.max(0, workspace:GetAttribute("BaseHealth") - amount)
	workspace:SetAttribute("BaseHealth", health)
end

-- Walk an enemy along the path. MoveTo gives up after 8 seconds and says so
-- (MoveToFinished with false), so a slow enemy is sent again until it arrives.
local function walkPath(enemy)
	local humanoid = enemy.Humanoid
	for i = 2, #waypoints do
		repeat
			humanoid:MoveTo(waypoints[i].Position)
		until humanoid.MoveToFinished:Wait() or not enemy.Parent
		if not enemy.Parent then
			return
		end
	end
	-- it reached the base
	damageBase(Settings.Enemies[enemy.Name].Damage)
	enemy:Destroy()
end

-- A new enemy at the gate, walking the path
local function spawnEnemy(kind)
	local enemy = ServerStorage.Enemies[kind]:Clone()
	-- dropped in just above the first waypoint
	enemy:PivotTo(waypoints[1].CFrame + Vector3.new(0, 5, 0))
	local info = Settings.Enemies[kind]
	enemy.Humanoid.MaxHealth = info.Health
	enemy.Humanoid.Health = info.Health
	enemy.Humanoid.WalkSpeed = info.Speed
	enemy.Parent = enemies
	task.spawn(walkPath, enemy)
	return enemy
end

-- Build a tower on a pad
local function placeTower(pad, kind)
	local tower = ServerStorage.Towers[kind]:Clone()
	tower:PivotTo(pad.CFrame + Vector3.new(0, pad.Size.Y / 2 + 1, 0))
	tower.Parent = towers
end

-- The client asks for a tower on a pad
remotes.Place.OnServerEvent:Connect(function(player, pad, kind)
	placeTower(pad, kind)
end)

-- One wave: its enemies one by one, then wait until they are all gone
local function runWave(list)
	for _, kind in list do
		spawnEnemy(kind)
		task.wait(Settings.SpawnGap)
	end
	while #enemies:GetChildren() > 0 do
		task.wait(0.2)
	end
end

-- The waves, each after a break
for i, list in Settings.Waves do
	task.wait(Settings.WaveBreak)
	runWave(list)
end
quiz iconTest yourself

This lesson includes a short quiz. Start the lesson to answer it and track your progress.

All lessons in Roblox Game: Tower Defense