لا تملك ما يكفي
الدرس 12 من 22 في دورة لعبة Roblox: الدفاع عن الأبراج على Coddy.
حتى الآن، الأبراج مجانية، والخادم يبني أي شيء يُطلب منه. يتحكم اللاعب في جهازه، لذا يمكن لمستغلّ أن يطلق Remotes.Place بأي شيء: برج فوق برج آخر، أو على قاعدة البناء، أو برج غير موجود، ومن دون أي مال على الإطلاق.
لذا يتحقق الخادم من كل طلب قبل البناء، ويتوقف عند أول شيء غير صحيح:
- أن يكون اللوح Instance ضمن
workspace.Padsوألا يكون عليه برج بعد (أي إن الخاصيةUsedالتي يعيّنهاplaceTowerغير موجودة)، - أن يكون النوع سلسلة نصية تسمّي برجًا حقيقيًا،
- أن يملك اللاعب المال؛ ثم يأخذه الخادم.
if typeof(pad) ~= "Instance" or pad.Parent ~= workspace.Pads then
return
endيطلب العميل، والخادم يقرر. تؤدي عمليات التحقق في هذا الدرس دور المستغلّ.
التحدي
متوسطاجعل الأبراج تكلّف مالًا، وارفض الطلبات غير الصالحة.
- في معالج
Place، أعد التنفيذ ما لم تكن المنصة من النوع Instance داخلworkspace.Padsولا تحمل السمةUsed. - أعد التنفيذ ما لم يكن النوع سلسلة نصية تشير إلى برج في
Settings.Towers. - أعد التنفيذ عندما يكون Money أقل من
Price؛ وإلا فاقتطع المبلغ وابنِ البرج. - تضبط
placeTowerسمةUsedللمنصة على true.
اضغط «العب» واشترِ الأبراج حتى ينخفض رصيد Money: سيُرفض طلب شراء البرج التالي. ثم اضغط «تحقق».
جرّب بنفسك
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)
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.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)
-- الدرس 12: يوضع Can't Afford It هنا
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
يتضمن هذا الدرس اختبارًا قصيرًا. ابدأ الدرس للإجابة عليه وتتبّع تقدمك.
جميع دروس لعبة Roblox: الدفاع عن الأبراج
تدرّب بنفسك: Playground لـ Roblox