Menu
CoddyTech

No te alcanza

Lección 12 de 22 del curso Juego de Roblox: Defensa de torres de Coddy.

Hasta ahora las torres son gratis y el servidor construye cualquier cosa que se le pida. Un jugador controla su propio dispositivo, así que alguien que hace trampas puede activar Remotes.Place con cualquier cosa: una torre encima de otra, sobre la placa base, una torre que no existe, sin nada de dinero.

Así que el servidor comprueba cada solicitud antes de construir y se detiene ante el primer error:

  • la plataforma es una instancia de workspace.Pads y todavía no tiene ninguna torre (un atributo Used que placeTower establece),
  • el tipo es una cadena que nombra una torre real,
  • el jugador tiene el dinero; después, el servidor se lo quita.
if typeof(pad) ~= "Instance" or pad.Parent ~= workspace.Pads then
	return
end

El cliente lo solicita; el servidor decide. Las comprobaciones de esta lección hacen el papel de quien hace trampas.

challenge icon

Desafío

Intermedio

Haz que las torres cuesten dinero y rechaza las solicitudes no válidas.

  1. En el controlador Place, usa return salvo que la plataforma sea una Instance de workspace.Pads sin el atributo Used.
  2. Usa return salvo que el tipo sea una cadena que nombre una torre en Settings.Towers.
  3. Usa return cuando Money sea inferior a Price; si no, réstalo y construye la torre.
  4. placeTower establece Used de la plataforma en true.

Pulsa «Jugar» y compra hasta que te quede poco Money: se rechazará la siguiente torre. Después, pulsa «Comprobar».

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

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)

-- Todos los jugadores reciben 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)
	print("Base: " .. health)
end

-- Haz que un enemigo recorra el camino. MoveTo desiste después de 8 segundos y lo indica
-- (MoveToFinished con false), así que se vuelve a enviar a un 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 enemigo nuevo 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
	enemy.Humanoid.Died:Connect(function()
		-- todos los jugadores reciben 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

-- Un disparo: inflige daño al objetivo
local function shoot(tower, target, info)
	local damage = info.Damage
	local at = target.HumanoidRootPart.Position
	target.Humanoid:TakeDamage(damage)
	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.Parent = towers
	task.spawn(runTower, tower)
end

-- El cliente solicita colocar una torre sobre una plataforma
remotes.Place.OnServerEvent:Connect(function(player, pad, kind)
	-- Lección 12: aquí va «No te alcanza el dinero»
	placeTower(pad, kind)
end)

-- Una oleada: aparecen sus enemigos uno por uno; luego se espera hasta que no quede ninguno
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
	task.wait(Settings.WaveBreak)
	runWave(list)
end
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

Practica por tu cuenta: Playground de Roblox