Menu
CoddyTech

اربح اللعبة

الدرس 21 من 22 في دورة لعبة Roblox: الدفاع عن الأبراج على Coddy.

عند انتهاء الموجة الأخيرة، صمدت القاعدة. ويؤكد الخادم ذلك باستخدام سمة في مساحة العمل، Status، ويعرض العميل تسمية Message المخفية في واجهة HUD.

يعرض العميل النهاية مرة واحدة، تحسبًا لانضمامه متأخرًا، ثم يعرضها مجددًا كلما تغيّرت الحالة:

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

التحدي

صعب

اربح اللعبة عند هزيمة كل موجة.

  1. في DefenseServer، بعد حلقة الموجات، عيّن سمة مساحة العمل Status إلى "Won".
  2. في DefenseClient، اكتب showEnd(): عندما تكون Status هي "Won"، عيّن hud.Message.Text إلى You defended the base! واجعلها مرئية.
  3. استدعِها مرة واحدة، وعند كل تغيير في Status.

اضغط «العب» واهزم الموجات الخمس كلها: ستظهر رسالة تفيد بأنك دافعت عن القاعدة. ثم اضغط «تحقق».

جرّب بنفسك

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

-- التخطي: ابدأ الموجة التالية مبكرًا (أثناء الاستراحة فقط)
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

جميع دروس لعبة Roblox: الدفاع عن الأبراج

تدرّب بنفسك: Playground لـ Roblox