Menu
CoddyTech

Gagner la partie

Leçon 21 sur 22 du cours Jeu Roblox : défense de tours de Coddy.

Lorsque la dernière vague est terminée, la base a tenu bon. Le serveur l’indique avec un attribut de workspace, Status, et le client affiche le libellé Message masqué du HUD.

Le client affiche la fin une première fois, au cas où il aurait rejoint la partie en retard, puis de nouveau chaque fois que le statut change :

workspace:GetAttributeChangedSignal("Status"):Connect(showEnd)
challenge icon

Défi

Difficile

Gagne la partie lorsque toutes les vagues sont terminées.

  1. Dans DefenseServer, après la boucle des vagues, définis l’attribut du workspace Status sur "Won".
  2. Dans DefenseClient, écris showEnd() : lorsque Status vaut "Won", définis hud.Message.Text sur You defended the base! et rends-le visible.
  3. Appelle-la une fois, puis à chaque changement de Status.

Appuie sur « Jouer » et termine les cinq vagues : un message indique que tu as défendu la base. Puis appuie sur « Vérifier ».

Essayez vous-même

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 = {} -- chaque tour -> la plateforme sur laquelle elle se trouve
local skipped = false

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

-- Les points de passage, dans l’ordre (ils sont nommés 1, 2, 3...)
local waypoints = workspace.Path.Waypoints:GetChildren()
table.sort(waypoints, function(a, b)
	return tonumber(a.Name) < tonumber(b.Name)
end)

-- La santé de la base, stockée comme attribut de workspace : chaque client peut la lire
workspace:SetAttribute("BaseHealth", Settings.BaseHealth)

-- Chaque joueur reçoit Money dans le classement
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)

-- Un ennemi a atteint la base
local function damageBase(amount)
	local health = math.max(0, workspace:GetAttribute("BaseHealth") - amount)
	workspace:SetAttribute("BaseHealth", health)
	print("Base: " .. health)
end

-- Faire avancer un ennemi le long du chemin. MoveTo abandonne après 8 secondes et le signale
-- (MoveToFinished avec false) ; on relance donc un ennemi lent jusqu’à ce qu’il arrive.
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
	-- il a atteint la base
	damageBase(Settings.Enemies[enemy.Name].Damage)
	enemy:Destroy()
end

-- Un nouvel ennemi à l’entrée, qui suit le chemin
local function spawnEnemy(kind)
	local enemy = ServerStorage.Enemies[kind]:Clone()
	-- placé juste au-dessus du premier point de passage
	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
	-- sa barre de santé reflète sa santé
	enemy.Humanoid.HealthChanged:Connect(function(health)
		enemy.HealthBar.Back.Fill.Size = UDim2.fromScale(health / enemy.Humanoid.MaxHealth, 1)
	end)
	enemy.Humanoid.Died:Connect(function()
		-- chaque joueur reçoit la récompense
		for _, player in Players:GetPlayers() do
			player.leaderstats.Money.Value += info.Reward
		end
		enemy:Destroy()
	end)
	task.spawn(walkPath, enemy)
	return enemy
end

-- L’ennemi vivant le plus proche à portée d’un point, ou 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

-- Un éclair de lumière reliant une tour à sa cible
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 : un ennemi se déplace à une fraction de sa vitesse pendant 2 secondes
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

-- Un tir : inflige des dégâts à la cible (et, avec un Cannon, à tous ceux qui se trouvent près d’elle)
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

-- Le fonctionnement d’une tour : viser l’ennemi le plus proche à portée, tirer, attendre la fin du délai de rechargement
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

-- Construire une tour sur un emplacement
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

-- Le client demande à placer une tour sur un emplacement. Tout vérifier : un emplacement valide et libre,
-- une tour valide et assez d’argent pour la payer.
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)

-- Une amélioration : le niveau suivant coûte de plus en plus cher, et chaque niveau ajoute de nouveau les
-- dégâts de la tour
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)

-- Vente : la moitié du prix est remboursée et l’emplacement redevient libre
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 : lancer la vague suivante plus tôt (uniquement pendant une pause)
remotes.Skip.OnServerEvent:Connect(function()
	if workspace:GetAttribute("Break") then
		skipped = true
	end
end)

-- Une vague : ses ennemis arrivent un par un, puis on attend qu’ils aient tous disparu
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

-- Les vagues, chacune après une pause
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

Toutes les leçons de Jeu Roblox : défense de tours

Entraînez-vous par vous-même : Playground Roblox