تخطَّ واربح
الدرس 17 من 18 في دورة لعبة Roblox: الدفاع عن الأبراج على Coddy.
يكون انتظار الموجة التالية مملاً عندما تكون مستعدًا. يطلب زر Skip wait في HUD من الخادم بدء الموجة التالية الآن، عبر Remotes.Skip.
ينتظر الخادم الاستراحة على خطوات صغيرة، حتى يتمكن من إيقاف الانتظار مبكرًا. تحدد سمة مساحة العمل Break ما إذا كانت الاستراحة جارية، ويُعاد تعيين المتغير skipped عند بداية كل استراحة:
skipped = false
local started = time()
while time() - started < Settings.WaveBreak and not skipped do
task.wait(0.1)
endعند انتهاء الموجة الأخيرة، تكون القاعدة قد صمدت: يضبط الخادم سمة مساحة العمل Status على "Won"، ويعرض العميل Message في HUD.
التحدي
صعب- في
DefenseServer، أضفlocal skipped = false، وصِلremotes.Skip.OnServerEventلتعيينها إلى true أثناء تفعيل السمةBreak. - قبل كل موجة: عيّن
Breakإلى true، وأعِد ضبطskipped، وانتظر حتى تنتهي الاستراحة أو يتم تخطيها (بخطوات مقدارها 0.1)، ثم عيّنBreakإلى false. - بعد الحلقة، عيّن سمة مساحة العمل
Statusإلى"Won". - في
DefenseClient، يرسل زرSkipالحدثremotes.Skip؛ وتعرضshowEnd()، التي تُستدعى مرة واحدة وعند كل تغيير فيStatus، التسميةMessageالتي تحتوي علىYou defended the base!عندما تكون قيمتها"Won".
يتكون عالم هذا الدرس من موجتين قصيرتين. اضغط على 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
local towerPad = {} -- كل برج -> المنصة التي يقف عليها
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.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)
-- موجة واحدة: يظهر أعداؤها واحدًا تلو الآخر، ثم ننتظر حتى يختفوا جميعًا
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)
task.wait(Settings.WaveBreak)
runWave(list)
end
يتضمن هذا الدرس اختبارًا قصيرًا. ابدأ الدرس للإجابة عليه وتتبّع تقدمك.