Menu
CoddyTech

타워 패널

Coddy의 Roblox 게임: 타워 디펜스 코스 레슨. 22개 중 13번째.

타워는 레벨이 올라갈수록 강해집니다. 레벨은 타워 모델의 Level 속성이며, 1부터 시작합니다. 각 업그레이드 비용은 타워 가격의 일정 비율인 Settings.UpgradeCost에 현재 레벨을 곱한 값이며, math.floor를 사용해 정수로 만듭니다. 레벨 2인 Frost 타워의 다음 업그레이드 비용은 다음과 같습니다.

math.floor(90 * 0.75 * 2) --> 135

타워를 클릭하면 이름, 레벨, 가격이 표시된 HUD의 Panel이 열립니다. 클릭은 타워의 파트 중 하나를 대상으로 하므로, target:FindFirstAncestorOfClass("Model")을 사용하면 해당 파트가 속한 타워를 찾을 수 있습니다.

challenge icon

챌린지

중급

타워를 클릭하여 패널을 여세요.

  1. placeTower에서 타워의 Level 속성을 1로 설정하세요.
  2. DefenseClient에서 showPanel()을 작성하세요. chosen 타워에 대해 hud.Panel.Title의 텍스트를 Archer level 1로, hud.Panel.Upgrade의 텍스트를 Upgrade $45로 설정하고 패널을 표시하세요.
  3. Button1Down에서 workspace.Towers에 있는 타워를 클릭하면 해당 타워를 선택하도록 하세요.

‘플레이’를 누르고 직접 만든 Archer를 클릭하세요. 타워 패널이 열립니다. 그런 다음 ‘확인’을 누르세요.

직접 해보기

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")

-- 순서대로 정렬된 경유 지점들(이름은 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.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

-- 사격: 대상에게 피해를 입힌다
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

-- 타워의 동작: 사거리 안에서 가장 가까운 적을 조준하고 발사한 뒤 재사용 대기시간만큼 기다린다
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.Parent = towers
	pad:SetAttribute("Used", true)
	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)

-- 한 웨이브: 적을 하나씩 생성한 뒤 모두 사라질 때까지 기다린다
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
	task.wait(Settings.WaveBreak)
	runWave(list)
end

Roblox 게임: 타워 디펜스의 모든 레슨

직접 연습해 보세요: Roblox 플레이그라운드