Salta y gana
Lección 17 de 18 del curso Juego de Roblox: defensa de torres de Coddy.
Esperar a la siguiente oleada es aburrido cuando ya estás listo. El botón Skip wait del HUD pide al servidor que inicie la siguiente oleada ahora, mediante Remotes.Skip.
El servidor espera durante el descanso en pequeños intervalos, para poder detenerse antes. Un atributo del espacio de trabajo Break indica cuándo hay un descanso activo, y la variable skipped se restablece al inicio de cada descanso:
skipped = false
local started = time()
while time() - started < Settings.WaveBreak and not skipped do
task.wait(0.1)
endCuando termina la última oleada, la base resistió: el servidor establece el atributo del espacio de trabajo Status en "Won", y el cliente muestra el Message del HUD.
Desafío
Difícil- En
DefenseServer, añadelocal skipped = falsey conectaremotes.Skip.OnServerEventpara establecerlo en true mientras el atributoBreakesté activo. - Antes de cada oleada: establece
Breaken true, restableceskipped, espera hasta que termine la pausa o se omita (en intervalos de 0.1) y estableceBreaken false. - Después del bucle, establece el atributo
Statusdel espacio de trabajo en"Won". - En
DefenseClient, el botónSkipactivaremotes.Skip; yshowEnd(), que se llama una vez y cada vez que cambiaStatus, muestra la etiquetaMessagecon el textoYou defended the base!cuando su valor es"Won".
El mundo de esta lección tiene dos oleadas cortas. 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
local towerPad = {} -- cada torre -> la plataforma sobre la que está
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 tras 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 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 procede de un Cannon, a todos los que estén cerca)
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
-- 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: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 más cada vez, y cada nivel vuelve a añadir
-- 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)
-- Una oleada: aparecen sus enemigos uno a 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
workspace:SetAttribute("Wave", i)
task.wait(Settings.WaveBreak)
runWave(list)
end
Esta lección incluye un breve cuestionario. Empieza la lección para responderlo y registrar tu progreso.