النقاط عند القتل
الدرس 22 من 25 في دورة لعبة Roblox: ساحة القتال على Coddy.
ينبغي أن يُحتسب القضاء على روبوت. عندما يموت كائن Humanoid، لا شيء يحدد من قتله، لذا تتذكر hurt آخر لاعب أصاب كل كائن Humanoid. تمرر ضربة اللاعب أو هجومه ذلك اللاعب بصفته المهاجم؛ أما ضربة الروبوت فلا تمرر أي لاعب:
if attacker then
lastHit[humanoid] = attacker
endعند وقوع حدث Died للروبوت، يحصل آخر لاعب أصابه على عملية قتل في لوحة المتصدرين: مجلد باسم leaderstats داخل اللاعب، يحتوي على قيمة IntValue.
التحدي
صعباحتسب عملية قتل لصالح اللاعب الذي يقضي على روبوت.
- عندما ينضم لاعب، أضف مجلدًا
leaderstatsيحتوي على قيمة صحيحةKills. - أضف
local lastHit = {}؛ تخزّنhurt(humanoid, amount, attacker)القيمةlastHit[humanoid] = attacker؛ ويمرّر كل من التأرجح والضربة القويةplayer. - عند وقوع حدث
Diedلروبوت، أضف 1 إلى قيمة Kills لذلك اللاعب إذا كان موجودًا؛ ثم امسح المدخلة.
اضغط «العب» واقضِ على روبوت: تزداد قيمة Kills في لوحة المتصدرين. ثم اضغط «تحقق».
جرّب بنفسك
local Players = game:GetService("Players")
local ServerStorage = game:GetService("ServerStorage")
local Debris = game:GetService("Debris")
local PathfindingService = game:GetService("PathfindingService")
local Settings = require(game.ReplicatedStorage.Settings)
local remotes = game.ReplicatedStorage.Remotes
local bots = workspace.Bots
local lastSwing = {} -- اللاعب -> وقت آخر ضربة بسيفه
local combo = {} -- اللاعب -> { عدد الضربات المتتالية، وقت إصابة آخر ضربة }
-- حركات السيف الخاصة بـRoblox
local slash = Instance.new("Animation")
slash.AnimationId = "rbxassetid://522635514"
local lunge = Instance.new("Animation")
lunge.AnimationId = "rbxassetid://522638767"
print("Swords deal " .. Settings.SwordDamage .. " damage")
-- رقم يظهر عائمًا فوق من تلقّى الضربة للحظة
local function damageNumber(character, amount)
local root = character:FindFirstChild("HumanoidRootPart")
if not root then
return
end
local tag = Instance.new("BillboardGui")
tag.Name = "Damage"
tag.Size = UDim2.fromOffset(60, 30)
tag.StudsOffset = Vector3.new(0, 4.5, 0)
tag.Adornee = root
local text = Instance.new("TextLabel")
text.Size = UDim2.fromScale(1, 1)
text.BackgroundTransparency = 1
text.TextColor3 = Color3.fromRGB(255, 220, 80)
text.TextScaled = true
text.Text = "-" .. math.round(amount)
text.Parent = tag
tag.Parent = character
Debris:AddItem(tag, 0.8)
end
-- تمرّ كل ضربة في اللعبة من هنا
local function hurt(humanoid, amount)
if humanoid.Health <= 0 then
return
end
local character = humanoid.Parent
-- الصدّ يمتصّ معظم ضرر الضربة
if character:GetAttribute("Blocking") then
amount *= Settings.BlockMultiplier
end
humanoid:TakeDamage(amount)
damageNumber(character, amount)
end
-- الذهول يجمّد الشخص للحظة
local function stun(humanoid)
local character = humanoid.Parent
if character:GetAttribute("Stunned") then
return
end
character:SetAttribute("Stunned", true)
print(character.Name .. " is stunned")
local speed = humanoid.WalkSpeed
humanoid.WalkSpeed = 0
task.delay(Settings.StunTime, function()
if character.Parent then
humanoid.WalkSpeed = speed
character:SetAttribute("Stunned", nil)
end
end)
end
-- ضربة بالسيف: تُشغَّل الحركة، ثم يُصاب كل من داخل المنطقة أمامك
local function swing(player)
local character = player.Character
local root = character and character:FindFirstChild("HumanoidRootPart")
if not root or character.Humanoid.Health <= 0 then
return
end
local now = time()
if lastSwing[player] and now - lastSwing[player] < Settings.SwingCooldown then
return
end
lastSwing[player] = now
-- ثلاث ضربات متتالية تشكّل سلسلة ضربات؛ الثالثة منها اندفاع
local c = combo[player]
if c and now - c.last <= Settings.ComboWindow then
c.count = c.count % 3 + 1
else
c = { count = 1 }
combo[player] = c
end
c.last = now
local finisher = c.count == 3
character.Humanoid.Animator:LoadAnimation(finisher and lunge or slash):Play()
-- المنطقة أمامك، ولا تشمل جسدك أبدًا
local params = OverlapParams.new()
params.FilterDescendantsInstances = { character }
local box = root.CFrame * CFrame.new(0, 0, -3)
local hit = {} -- كل humanoid مرة واحدة في كل ضربة
for _, part in workspace:GetPartBoundsInBox(box, Settings.HitboxSize, params) do
local humanoid = part.Parent:FindFirstChildOfClass("Humanoid")
if humanoid and not hit[humanoid] then
hit[humanoid] = true
hurt(humanoid, Settings.ComboDamage[c.count])
if finisher then
stun(humanoid)
end
end
end
end
-- القدرات حسب أسمائها: وظيفة كل واحدة منها
local abilities = {}
-- يتحكّم جهاز اللاعب في حركة شخصيته، لذا يحدث الاندفاع عليه
function abilities.Dash(player)
remotes.Ability:FireClient(player, "Dash")
end
-- يُصاب كل من حولك ويُقذف بعيدًا عنك
function abilities.Slam(player, character, root)
local params = OverlapParams.new()
params.FilterDescendantsInstances = { character }
local hit = {}
for _, part in workspace:GetPartBoundsInRadius(root.Position, Settings.SlamRadius, params) do
local humanoid = part.Parent:FindFirstChildOfClass("Humanoid")
local theirRoot = part.Parent:FindFirstChild("HumanoidRootPart")
if humanoid and theirRoot and not hit[humanoid] then
hit[humanoid] = true
hurt(humanoid, Settings.SlamDamage)
local away = (theirRoot.Position - root.Position) * Vector3.new(1, 0, 1)
if away.Magnitude > 0 then
away = away.Unit
end
theirRoot:ApplyImpulse((away * Settings.SlamKnockback + Vector3.new(0, 40, 0)) * theirRoot.AssemblyMass)
end
end
end
function abilities.Block(player, character)
character:SetAttribute("Blocking", true)
task.delay(Settings.BlockTime, function()
if character.Parent then
character:SetAttribute("Blocking", nil)
end
end)
end
-- يطلب العميل قدرةً باسمها. ويتحقق الخادم من أنها قدرة حقيقية،
-- وأن اللاعب حي، وأن فترة انتظارها قد انتهت. ووقت جاهزيتها مجددًا
-- يُخزَّن كسمة للاعب، حتى تتمكن شاشته من عرضه.
remotes.Ability.OnServerEvent:Connect(function(player, name)
if typeof(name) ~= "string" or not abilities[name] then
return
end
local character = player.Character
local root = character and character:FindFirstChild("HumanoidRootPart")
if not root or character.Humanoid.Health <= 0 then
return
end
local now = workspace:GetServerTimeNow()
if now < (player:GetAttribute(name .. "ReadyAt") or 0) then
return
end
player:SetAttribute(name .. "ReadyAt", now + Settings.Cooldowns[name])
abilities[name](player, character, root)
end)
-- يحصل كل لاعب على سيف في كل حياة
Players.PlayerAdded:Connect(function(player)
-- الدرس 22: يُضاف Score Kills هنا
player.CharacterAdded:Connect(function()
local sword = ServerStorage.Sword:Clone()
sword.Parent = player.Backpack
sword.Activated:Connect(function()
swing(player)
end)
end)
end)
-- دمية التدريب: يعكس شريط صحتها مستوى صحتها، وإذا هُزمت
-- تعود بعد لحظة بنسخة جديدة في المكان نفسه
local function setupDummy(dummy)
local humanoid = dummy.Humanoid
humanoid.HealthChanged:Connect(function(health)
dummy.HealthBar.Back.Fill.Size = UDim2.fromScale(health / humanoid.MaxHealth, 1)
end)
humanoid.Died:Connect(function()
local spot = dummy:GetPivot()
task.wait(0.5)
dummy:Destroy()
task.wait(Settings.DummyRespawn)
local fresh = ServerStorage.Dummy:Clone()
fresh:PivotTo(spot)
fresh.Parent = workspace
setupDummy(fresh)
end)
end
setupDummy(workspace.Dummy)
-- أقرب شخصية لاعب حي يستطيع الروبوت رؤيتها، ومدى بُعدها عنه
local function nearestCharacter(position)
local best, bestDistance = nil, Settings.BotSight
for _, player in Players:GetPlayers() do
local character = player.Character
local root = character and character:FindFirstChild("HumanoidRootPart")
if root and character.Humanoid.Health > 0 then
local distance = (root.Position - position).Magnitude
if distance < bestDistance then
best, bestDistance = character, distance
end
end
end
return best, bestDistance
end
-- سِر نحو شخصية: مباشرةً إليها إذا لم يكن هناك ما يعترض الطريق، أو
-- إلى النقطة التالية في مسار يلتف حول العائق
local function walkToward(bot, target, path)
local root = bot.HumanoidRootPart
local goal = target.HumanoidRootPart.Position
local params = RaycastParams.new()
params.FilterDescendantsInstances = { bot }
local hit = workspace:Raycast(root.Position, goal - root.Position, params)
if not hit or hit.Instance:IsDescendantOf(target) then
bot.Humanoid:MoveTo(goal)
return
end
path:ComputeAsync(root.Position, goal)
local points = path:GetWaypoints()
if path.Status == Enum.PathStatus.Success and points[2] then
bot.Humanoid:MoveTo(points[2].Position)
end
end
-- عقل البوت: ينظر حوله ويتخذ قرارًا كل ربع ثانية
local function runBot(bot)
local humanoid = bot.Humanoid
local root = bot.HumanoidRootPart
local lastAttack = 0
local path = PathfindingService:CreatePath()
while bot.Parent and humanoid.Health > 0 do
local target, distance = nearestCharacter(root.Position)
local state
if bot:GetAttribute("Stunned") then
state = "Stunned"
elseif humanoid.Health < humanoid.MaxHealth * Settings.RetreatAt then
state = "Retreat"
elseif not target then
state = "Idle"
elseif distance <= Settings.BotRange then
state = "Attack"
else
state = "Chase"
end
bot:SetAttribute("State", state)
if state == "Chase" then
walkToward(bot, target, path)
elseif state == "Attack" then
humanoid:MoveTo(root.Position)
if time() - lastAttack >= Settings.BotAttackCooldown then
lastAttack = time()
humanoid.Animator:LoadAnimation(slash):Play()
hurt(target.Humanoid, Settings.BotDamage)
end
elseif state == "Retreat" and target then
local away = (root.Position - target.HumanoidRootPart.Position) * Vector3.new(1, 0, 1)
if away.Magnitude > 0 then
humanoid:MoveTo(root.Position + away.Unit * 12)
end
else
humanoid:MoveTo(root.Position)
end
task.wait(0.25)
end
end
-- بوت جديد على المنصة الحمراء، مع قيمه الرقمية وشريط صحته
local function spawnBot()
local bot = ServerStorage.Bot:Clone()
bot:PivotTo(workspace.BotSpawn.CFrame + Vector3.new(0, 3, 0))
bot.Humanoid.MaxHealth = Settings.BotHealth
bot.Humanoid.Health = Settings.BotHealth
bot.Humanoid.WalkSpeed = Settings.BotSpeed
bot.Parent = bots
-- أبقِ محاكاة حركة البوت على الخادم، حتى يتمكن الدفع الناتج عن الهجوم الساحق من تحريكه
bot.HumanoidRootPart:SetNetworkOwner(nil)
bot.Humanoid.HealthChanged:Connect(function(health)
bot.HealthBar.Back.Fill.Size = UDim2.fromScale(health / bot.Humanoid.MaxHealth, 1)
end)
bot.Humanoid.Died:Connect(function()
task.wait(1)
bot:Destroy()
task.wait(Settings.BotRespawn)
spawnBot()
end)
task.spawn(runBot, bot)
end
for _ = 1, Settings.Bots do
spawnBot()
end
جميع دروس لعبة Roblox: ساحة القتال
تدرّب بنفسك: Playground لـ Roblox