기다리지 않기
Coddy의 Roblox 게임: 타워 디펜스 코스 레슨. 22개 중 20번째.
준비가 되었을 때 다음 웨이브를 기다리는 것은 지루합니다. HUD의 Skip wait 버튼은 Remotes.Skip을 통해 서버에 다음 웨이브를 지금 시작하도록 요청합니다.
서버는 일찍 멈출 수 있도록 휴식 시간을 짧은 간격으로 기다립니다. workspace 속성 Break는 휴식 중인지 나타내고, 변수 skipped는 각 휴식이 시작될 때 초기화됩니다. ready가 true가 되지 않는 한 10초 동안 기다리는 동일한 루프입니다.
ready = false
local started = time()
while time() - started < 10 and not ready do
task.wait(0.1)
end챌린지
어려움플레이어가 웨이브가 시작되기 전의 대기 시간을 건너뛸 수 있도록 하세요.
DefenseServer에서local skipped = false를 추가하세요. 작업 공간의Break속성이 켜져 있는 동안remotes.Skip.OnServerEvent가 해당 값을 true로 설정합니다.- 각 웨이브가 시작되기 전에
Break를 true로 설정하고,skipped를 재설정한 다음,Settings.WaveBreak시간이 지나거나skipped가 설정될 때까지 0.1초 간격으로 기다린 후Break를 false로 설정하세요. DefenseClient에서hud.Skip이remotes.Skip을 실행하도록 하세요.
‘플레이’를 누르고 대기 건너뛰기를 클릭하세요. 웨이브가 즉시 시작됩니다. 그런 다음 ‘확인’을 누르세요.
직접 해보기
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 = {} -- 각 타워 -> 타워가 서 있는 받침대
print("The base can take " .. Settings.BaseHealth .. " hits")
-- 순서대로 나열한 경유지 (이름은 1, 2, 3...)
local waypoints = workspace.Path.Waypoints:GetChildren()
table.sort(waypoints, function(a, b)
return tonumber(a.Name) < tonumber(b.Name)
end)
-- workspace의 속성으로 저장된 기지의 체력: 모든 클라이언트가 읽을 수 있음
workspace:SetAttribute("BaseHealth", Settings.BaseHealth)
-- 모든 플레이어는 리더보드에서 Money를 받음
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)
-- 적이 기지에 도달함
local function damageBase(amount)
local health = math.max(0, workspace:GetAttribute("BaseHealth") - amount)
workspace:SetAttribute("BaseHealth", health)
print("Base: " .. health)
end
-- 적을 경로를 따라 이동시킨다. MoveTo는 8초 후 시도를 중단하고 이를 알린다
-- (MoveToFinished가 false를 반환), 따라서 느린 적은 도착할 때까지 다시 이동 명령을 받는다.
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
-- 적이 기지에 도달함
damageBase(Settings.Enemies[enemy.Name].Damage)
enemy:Destroy()
end
-- 관문에 나타나 경로를 따라 이동하는 새로운 적
local function spawnEnemy(kind)
local enemy = ServerStorage.Enemies[kind]:Clone()
-- 첫 번째 경유지 바로 위에 배치됨
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.HealthChanged:Connect(function(health)
enemy.HealthBar.Back.Fill.Size = UDim2.fromScale(health / enemy.Humanoid.MaxHealth, 1)
end)
enemy.Humanoid.Died:Connect(function()
-- 모든 플레이어가 보상을 받음
for _, player in Players:GetPlayers() do
player.leaderstats.Money.Value += info.Reward
end
enemy:Destroy()
end)
task.spawn(walkPath, enemy)
return enemy
end
-- 한 지점에서 사정거리 안에 있는 가장 가까운 살아 있는 적, 없으면 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
-- 타워에서 목표물로 뻗어 나가는 섬광
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: 적이 2초 동안 원래 속도의 일부로 이동한다
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
-- 발사: 목표물에 피해를 주고, Cannon이라면 주변의 모든 적에게도 피해를 준다
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
-- 타워의 작동 과정: 사정거리 안의 가장 가까운 적을 조준하고 발사한 뒤 재사용 대기시간을 기다린다
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
-- 패드 위에 타워를 건설한다
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
-- 클라이언트가 패드에 타워를 설치해 달라고 요청한다. 모든 조건을 확인한다: 실제로 존재하고 비어 있는 패드인지,
-- 실제로 존재하는 타워인지, 설치 비용을 낼 돈이 있는지.
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)
-- 업그레이드: 다음 레벨의 비용은 매번 늘어나고, 레벨이 오를 때마다
-- 타워의 피해량이 한 번 더 추가된다
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)
-- 판매: 가격의 절반을 돌려받고 패드는 다시 비게 된다
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)
-- 한 웨이브: 적들이 하나씩 나타난 뒤 모두 사라질 때까지 기다린다
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
-- 각 웨이브는 휴식 시간 후에 시작됩니다
for i, list in Settings.Waves do
workspace:SetAttribute("Wave", i)
-- Lesson 20: Skip the Wait는 여기에 들어갑니다
task.wait(Settings.WaveBreak)
runWave(list)
end
Roblox 게임: 타워 디펜스의 모든 레슨
직접 연습해 보세요: Roblox 플레이그라운드