Menu
CoddyTech

살 수 없어요

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

지금까지 타워는 무료이고, 서버는 요청받은 것은 무엇이든 만듭니다. 플레이어는 자신의 기기를 제어하므로, 악용자는 아무렇게나 Remotes.Place를 호출할 수 있습니다. 다른 타워 위에 타워를 놓거나, 베이스플레이트 위에 놓거나, 존재하지 않는 타워를 놓거나, 돈이 하나도 없어도 말이죠.

따라서 서버는 타워를 만들기 전에 모든 요청을 확인하고, 잘못된 점을 처음 발견하면 즉시 반환합니다.

  • 패드는 workspace.Pads 안의 Instance이며 아직 타워가 없어야 합니다(placeTower가 설정하는 Used 속성).
  • 종류는 실제 타워의 이름을 나타내는 문자열이어야 합니다.
  • 플레이어에게 돈이 있어야 합니다. 그러면 서버가 돈을 차감합니다.
if typeof(pad) ~= "Instance" or pad.Parent ~= workspace.Pads then
	return
end

클라이언트가 요청하고 서버가 결정합니다. 이 수업의 검사 항목들은 악용자 역할을 합니다.

challenge icon

챌린지

중급

Place 핸들러에서 다음의 경우 타워를 건설하지 않고 반환하세요:

  1. 패드가 Instance가 아니거나, workspace.Pads 안에 있지 않거나, Used 속성이 있는 경우,
  2. kind가 Settings.Towers에 있는 타워를 나타내는 문자열이 아닌 경우,
  3. 플레이어의 Money가 타워의 Price보다 적은 경우.

그렇지 않으면 가격을 차감하고 건설하세요. placeTower에서 패드의 Used 속성을 true로 설정하세요.

Play를 누른 다음 Check를 누르세요.

직접 해보기

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)
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
	task.spawn(runTower, tower)
end

-- 클라이언트가 패드 위에 타워를 건설해 달라고 요청한다
remotes.Place.OnServerEvent:Connect(function(player, pad, kind)
	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
quiz icon실력 점검

이 레슨에는 짧은 퀴즈가 포함되어 있습니다. 레슨을 시작해 문제를 풀고 진행 상황을 기록하세요.

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