Menu
CoddyTech

Przeskocz i wygraj

Lekcja 17 z 18 w kursie Gra Roblox: Obrona wieży w Coddy.

Czekanie na następną falę jest nudne, gdy jesteś gotowy. Przycisk Skip wait w HUD prosi serwer o natychmiastowe rozpoczęcie następnej fali za pośrednictwem Remotes.Skip.

Serwer odlicza przerwę małymi krokami, dzięki czemu może zakończyć ją wcześniej. Atrybut workspace Break informuje, czy trwa przerwa, a zmienna skipped jest resetowana na początku każdej przerwy:

skipped = false
local started = time()
while time() - started < Settings.WaveBreak and not skipped do
	task.wait(0.1)
end

Gdy ostatnia fala dobiegnie końca, baza przetrwała: serwer ustawia atrybut workspace Status na "Won", a klient wyświetla Message w HUD.

challenge icon

Wyzwanie

Trudny
  1. W DefenseServer dodaj local skipped = false i połącz remotes.Skip.OnServerEvent, aby ustawiało tę zmienną na true, gdy atrybut Break jest włączony.
  2. Przed każdą falą ustaw Break na true, zresetuj skipped, czekaj, aż przerwa się skończy lub zostanie pominięta (w krokach co 0.1), a następnie ustaw Break na false.
  3. Po pętli ustaw atrybut workspace Status na "Won".
  4. W DefenseClient przycisk Skip wywołuje remotes.Skip; funkcja showEnd(), wywołana raz i przy każdej zmianie Status, wyświetla etykietę Message z tekstem You defended the base!, gdy jego wartość to "Won".

Świat w tej lekcji ma dwie krótkie fale. Naciśnij Play, a następnie Check.

Spróbuj swoich sił

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 = {} -- każda wieża -> podest, na którym stoi

print("The base can take " .. Settings.BaseHealth .. " hits")

-- Punkty trasy w kolejności (noszą nazwy 1, 2, 3...)
local waypoints = workspace.Path.Waypoints:GetChildren()
table.sort(waypoints, function(a, b)
	return tonumber(a.Name) < tonumber(b.Name)
end)

-- Zdrowie bazy jako atrybut workspace: każdy klient może je odczytać
workspace:SetAttribute("BaseHealth", Settings.BaseHealth)

-- Każdy gracz otrzymuje Money w tabeli wyników
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)

-- Wróg dotarł do bazy
local function damageBase(amount)
	local health = math.max(0, workspace:GetAttribute("BaseHealth") - amount)
	workspace:SetAttribute("BaseHealth", health)
end

-- Poprowadź wroga wzdłuż trasy. MoveTo przerywa próbę po 8 sekundach i to sygnalizuje
-- (MoveToFinished z wartością false), więc powolny wróg jest kierowany ponownie, aż dotrze na miejsce.
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
	-- dotarł do bazy
	damageBase(Settings.Enemies[enemy.Name].Damage)
	enemy:Destroy()
end

-- Nowy wróg przy bramie, podążający trasą
local function spawnEnemy(kind)
	local enemy = ServerStorage.Enemies[kind]:Clone()
	-- umieszczony tuż nad pierwszym punktem trasy
	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
	-- jego pasek zdrowia odzwierciedla stan jego zdrowia
	enemy.Humanoid.HealthChanged:Connect(function(health)
		enemy.HealthBar.Back.Fill.Size = UDim2.fromScale(health / enemy.Humanoid.MaxHealth, 1)
	end)
	enemy.Humanoid.Died:Connect(function()
		-- każdy gracz otrzymuje nagrodę
		for _, player in Players:GetPlayers() do
			player.leaderstats.Money.Value += info.Reward
		end
		enemy:Destroy()
	end)
	task.spawn(walkPath, enemy)
	return enemy
end

-- Najbliższy żywy wróg w zasięgu danego punktu albo 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

-- Błysk światła od wieży do jej celu
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: wróg porusza się z ułamkiem swojej prędkości przez 2 sekundy
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

-- Strzał: zadaj obrażenia celowi (a jeśli strzela Cannon, także wszystkim w jego pobliżu)
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

-- Działanie wieży: celuj w najbliższego wroga w zasięgu, strzelaj i czekaj na koniec czasu odnowienia
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

-- Zbuduj wieżę na polu
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

-- Klient prosi o postawienie wieży na polu. Sprawdź wszystko: czy pole istnieje i jest wolne,
-- czy wieża istnieje i czy wystarczy pieniędzy, by za nią zapłacić.
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)

-- Ulepszenie: każdy kolejny poziom kosztuje coraz więcej, a każdy poziom ponownie dodaje
-- obrażenia zadawane przez wieżę
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)

-- Sprzedaż: zwrot połowy ceny, a pole znów staje się wolne
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)

-- Jedna fala: wypuszczaj kolejnych wrogów, a potem czekaj, aż wszyscy znikną
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

-- Fale, każda po przerwie
for i, list in Settings.Waves do
	workspace:SetAttribute("Wave", i)
	task.wait(Settings.WaveBreak)
	runWave(list)
end
quiz iconSprawdź się

Ta lekcja zawiera krótki quiz. Zacznij lekcję, żeby na niego odpowiedzieć i śledzić swoje postępy.

Wszystkie lekcje w sekcji Gra Roblox: Obrona wieży