Menu
CoddyTech

No te alcanza

Lección 10 de 18 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

En el controlador Place, retorna sin construir cuando:

  1. la plataforma no es una Instance, no está en workspace.Pads o tiene el atributo Used,
  2. el tipo no es una cadena que nombre una torre en Settings.Towers,
  3. el dinero del jugador es inferior al Price de la torre.

De lo contrario, cobra el precio y construye. En placeTower, establece el atributo Used de la plataforma en true.

Pulsa Play y después Check.

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)

-- 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 a un enemigo lento hasta que llega.
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()
	-- colocado 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()
		-- 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

-- Un disparo: daña 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

-- El ciclo 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 una torre sobre una plataforma
remotes.Place.OnServerEvent:Connect(function(player, pad, kind)
	placeTower(pad, kind)
end)

-- Una oleada: aparecen sus enemigos uno por uno; después, 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