게임 오버
Coddy의 Roblox 게임: 타워 디펜스 코스 레슨. 18개 중 18번째.
게임이 거의 완성되었습니다. 설정은 Boss로 끝납니다. 체력은 200이고 속도는 느리지만, 기지에 20의 피해를 주므로 이를 막지 못하는 방어로는 마지막 웨이브에서 패배합니다.
패배가 마지막 규칙입니다. 기지의 체력이 0이 되면 게임이 끝납니다. 서버는 Status를 "Lost"로 설정하고, 경로에 있는 모든 적을 제거하며, 더 이상 웨이브를 시작하지 않습니다. 변수 gameOver는 이 정보를 필요한 모든 루프에 전달합니다.
if gameOver then
break
end마지막에 승리로 인정되는 것은 기지가 버텼을 때뿐입니다. if not gameOver then.
그런 다음 Play를 누르고, 방어 시설을 건설해 다섯 웨이브를 모두 물리치세요. 원하는 방식으로 균형을 맞추려면 Settings를 변경하세요.
챌린지
어려움local gameOver = false를 추가합니다.damageBase에서 체력이 처음으로 0에 도달하면 이를 설정하고,Status를"Lost"로 설정한 다음 모든 적을 제거합니다.runWave에서 게임이 끝난 후에는 적 생성을 중지합니다. 웨이브 반복문의 시작 부분에서 게임이 끝났는지 확인하고, 끝났다면 중지합니다.- 게임이 끝나지 않은 경우에만
Status를"Won"으로 설정합니다. DefenseClient의showEnd에서"Lost"일 때The base has fallen을 표시합니다.
Play를 누른 다음 Check를 누릅니다. 검사에서는 체력이 3인 기지에 좀비 세 마리가 도달하도록 합니다.
직접 해보기
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 = {} -- 각 타워 -> 타워가 서 있는 패드
local skipped = false
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)
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)
-- Skip: 다음 웨이브를 일찍 시작한다(휴식 시간에만 가능)
remotes.Skip.OnServerEvent:Connect(function()
if workspace:GetAttribute("Break") then
skipped = true
end
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)
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
-- 모든 웨이브가 끝났다: 기지가 버텨냈다
workspace:SetAttribute("Status", "Won")
이 레슨에는 짧은 퀴즈가 포함되어 있습니다. 레슨을 시작해 문제를 풀고 진행 상황을 기록하세요.