Menu
CoddyTech

النقاط عند القتل

الدرس 22 من 25 في دورة لعبة Roblox: ساحة القتال على Coddy.

ينبغي أن يُحتسب القضاء على روبوت. عندما يموت كائن Humanoid، لا شيء يحدد من قتله، لذا تتذكر hurt آخر لاعب أصاب كل كائن Humanoid. تمرر ضربة اللاعب أو هجومه ذلك اللاعب بصفته المهاجم؛ أما ضربة الروبوت فلا تمرر أي لاعب:

if attacker then
	lastHit[humanoid] = attacker
end

عند وقوع حدث Died للروبوت، يحصل آخر لاعب أصابه على عملية قتل في لوحة المتصدرين: مجلد باسم leaderstats داخل اللاعب، يحتوي على قيمة IntValue.

challenge icon

التحدي

صعب

احتسب عملية قتل لصالح اللاعب الذي يقضي على روبوت.

  1. عندما ينضم لاعب، أضف مجلدًا leaderstats يحتوي على قيمة صحيحة Kills.
  2. أضف local lastHit = {}؛ تخزّن hurt(humanoid, amount, attacker) القيمة lastHit[humanoid] = attacker؛ ويمرّر كل من التأرجح والضربة القوية player.
  3. عند وقوع حدث 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