Menu
CoddyTech

Fin del juego

Lección 18 de 18 del curso Juego de Roblox: defensa de torres de Coddy.

Tu juego está casi completo. La configuración termina con el Boss: 200 de salud, lento y le quita 20 a la base, así que una defensa que no pueda detenerlo pierde en la última oleada.

Perder es la última regla. Cuando la base llega a 0, el juego termina: el servidor establece Status en "Lost", quita a todos los enemigos del camino y no inicia más oleadas. La variable gameOver comunica eso a todos los bucles que lo necesitan:

if gameOver then
	break
end

La victoria al final solo cuenta si la base resistió: if not gameOver then.

Después, pulsa Play, construye una defensa y supera las cinco oleadas. Cambia Settings para equilibrarlo a tu manera.

challenge icon

Desafío

Difícil
  1. Añade local gameOver = false. En damageBase, cuando la salud llegue a 0 por primera vez, establece su valor, establece Status en "Lost" y elimina a todos los enemigos.
  2. En runWave, deja de generar enemigos cuando la partida termine; al principio del bucle de oleadas, detente cuando termine.
  3. Establece Status en "Won" solo cuando la partida no haya terminado.
  4. En showEnd de DefenseClient, muestra The base has fallen para "Lost".

Pulsa Jugar y después Comprobar: las comprobaciones dejan pasar a tres zombis cuando la base tiene 3 puntos de salud.

Pruébalo tú mismo

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

local remotes = game.ReplicatedStorage.Remotes
local enemies = workspace.Enemies
local towers = workspace.Towers
local towerPad = {} -- cada torre -> la plataforma sobre la que está
local skipped = false

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

-- Los puntos de ruta, en orden (se llaman 1, 2, 3...)
local waypoints = workspace.Path.Waypoints:GetChildren()
table.sort(waypoints, function(a, b)
	return tonumber(a.Name) < tonumber(b.Name)
end)

-- La salud de la base, como atributo de workspace: todos los clientes pueden leerla
workspace:SetAttribute("BaseHealth", Settings.BaseHealth)

-- Cada jugador recibe Money en la tabla de clasificación
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)

-- Un enemigo llegó a la base
local function damageBase(amount)
	local health = math.max(0, workspace:GetAttribute("BaseHealth") - amount)
	workspace:SetAttribute("BaseHealth", health)
end

-- Haz que un enemigo recorra el camino. MoveTo se rinde después de 8 segundos y lo indica
-- (MoveToFinished con false), así que se vuelve a enviar al enemigo lento hasta que llegue.
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
	-- llegó a la base
	damageBase(Settings.Enemies[enemy.Name].Damage)
	enemy:Destroy()
end

-- Un nuevo enemigo en la entrada, recorriendo el camino
local function spawnEnemy(kind)
	local enemy = ServerStorage.Enemies[kind]:Clone()
	-- aparece justo encima del primer punto de ruta
	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
	-- su barra de salud refleja su salud
	enemy.Humanoid.HealthChanged:Connect(function(health)
		enemy.HealthBar.Back.Fill.Size = UDim2.fromScale(health / enemy.Humanoid.MaxHealth, 1)
	end)
	enemy.Humanoid.Died:Connect(function()
		-- cada jugador recibe la recompensa
		for _, player in Players:GetPlayers() do
			player.leaderstats.Money.Value += info.Reward
		end
		enemy:Destroy()
	end)
	task.spawn(walkPath, enemy)
	return enemy
end

-- El enemigo vivo más cercano dentro del alcance de un punto, o nil
local function nearestEnemy(position, range)
	local best, bestDistance = nil, range
	for _, enemy in enemies:GetChildren() do
		local root = enemy:FindFirstChild("HumanoidRootPart")
		if root and enemy.Humanoid.Health > 0 then
			local distance = (root.Position - position).Magnitude
			if distance <= bestDistance then
				best, bestDistance = enemy, distance
			end
		end
	end
	return best
end

-- Un destello de luz desde una torre hasta su objetivo
local function beam(from, to)
	local ray = Instance.new("Part")
	ray.Anchored = true
	ray.CanCollide = false
	ray.CanTouch = false
	ray.Material = Enum.Material.Neon
	ray.Color = Color3.fromRGB(255, 240, 150)
	ray.Size = Vector3.new(0.2, 0.2, (to - from).Magnitude)
	ray.CFrame = CFrame.lookAt((from + to) / 2, to)
	ray.Parent = workspace
	Debris:AddItem(ray, 0.1)
end

-- Frost: un enemigo camina a una fracción de su velocidad durante 2 segundos
local function slow(enemy, factor)
	if enemy:GetAttribute("Slowed") then
		return
	end
	enemy:SetAttribute("Slowed", true)
	local speed = Settings.Enemies[enemy.Name].Speed
	enemy.Humanoid.WalkSpeed = speed * factor
	task.delay(2, function()
		if enemy.Parent then
			enemy.Humanoid.WalkSpeed = speed
			enemy:SetAttribute("Slowed", nil)
		end
	end)
end

-- Un disparo: daña al objetivo (y, si proviene de un Cannon, a todos los que estén cerca de él)
local function shoot(tower, target, info)
	local damage = info.Damage * tower:GetAttribute("Level")
	local at = target.HumanoidRootPart.Position
	if info.Splash then
		for _, enemy in enemies:GetChildren() do
			local root = enemy:FindFirstChild("HumanoidRootPart")
			if root and (root.Position - at).Magnitude <= info.Splash then
				enemy.Humanoid:TakeDamage(damage)
			end
		end
	else
		target.Humanoid:TakeDamage(damage)
	end
	if info.Slow and target.Parent then
		slow(target, info.Slow)
	end
	beam(tower.Head.Position, at)
end

-- La rutina de una torre: apunta al enemigo más cercano dentro de su alcance, dispara y espera a que termine su tiempo de recarga
local function runTower(tower)
	local info = Settings.Towers[tower.Name]
	while tower.Parent do
		local target = nearestEnemy(tower.Head.Position, info.Range)
		if target then
			tower.Head.CFrame = CFrame.lookAt(tower.Head.Position, target.HumanoidRootPart.Position)
			shoot(tower, target, info)
			task.wait(info.Cooldown)
		else
			task.wait(0.1)
		end
	end
end

-- Construye una torre sobre una plataforma
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:SetAttribute("Level", 1)
	tower.Parent = towers
	pad:SetAttribute("Used", true)
	towerPad[tower] = pad
	task.spawn(runTower, tower)
end

-- El cliente solicita colocar una torre sobre una plataforma. Comprueba todo: que la plataforma exista y esté libre,
-- que la torre exista y que haya dinero para pagarla.
remotes.Place.OnServerEvent:Connect(function(player, pad, kind)
	if typeof(pad) ~= "Instance" or pad.Parent ~= workspace.Pads or pad:GetAttribute("Used") then
		return
	end
	local info = typeof(kind) == "string" and Settings.Towers[kind]
	if not info then
		return
	end
	local money = player.leaderstats.Money
	if money.Value < info.Price then
		return
	end
	money.Value -= info.Price
	placeTower(pad, kind)
end)

-- Una mejora: el siguiente nivel cuesta cada vez más, y cada nivel vuelve a sumar el
-- daño de la torre
remotes.Upgrade.OnServerEvent:Connect(function(player, tower)
	if typeof(tower) ~= "Instance" or tower.Parent ~= towers then
		return
	end
	local level = tower:GetAttribute("Level")
	local cost = math.floor(Settings.Towers[tower.Name].Price * Settings.UpgradeCost * level)
	local money = player.leaderstats.Money
	if money.Value < cost then
		return
	end
	money.Value -= cost
	tower:SetAttribute("Level", level + 1)
end)

-- Venta: se recupera la mitad del precio y la plataforma vuelve a quedar libre
remotes.Sell.OnServerEvent:Connect(function(player, tower)
	if typeof(tower) ~= "Instance" or tower.Parent ~= towers then
		return
	end
	player.leaderstats.Money.Value += math.floor(Settings.Towers[tower.Name].Price / 2)
	towerPad[tower]:SetAttribute("Used", nil)
	towerPad[tower] = nil
	tower:Destroy()
end)

-- Skip: inicia la siguiente oleada antes de tiempo (solo durante una pausa)
remotes.Skip.OnServerEvent:Connect(function()
	if workspace:GetAttribute("Break") then
		skipped = true
	end
end)

-- Una oleada: sus enemigos aparecen uno por uno; después, espera hasta que todos hayan desaparecido
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

-- Las oleadas, cada una después de una pausa
for i, list in Settings.Waves do
	workspace:SetAttribute("Wave", i)
	workspace:SetAttribute("Break", true)
	skipped = false
	local started = time()
	while time() - started < Settings.WaveBreak and not skipped do
		task.wait(0.1)
	end
	workspace:SetAttribute("Break", false)
	runWave(list)
end

-- Han terminado todas las oleadas: la base ha resistido
workspace:SetAttribute("Status", "Won")
quiz iconPonte a prueba

Esta lección incluye un breve cuestionario. Empieza la lección para responderlo y registrar tu progreso.

Todas las lecciones de Juego de Roblox: defensa de torres