Menu
CoddyTech
flag Ar iconالعربيةdown icon

حول الجدران

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

اختبئ في الملجأ عند زاوية الساحة، وسيسير الروبوت مباشرةً نحو جداره الخلفي ويبقى هناك. لا ينجح السير مباشرةً نحوك إلا عندما لا يوجد شيء في الطريق.

أولًا، هل يوجد شيء في الطريق؟ ترسل workspace:Raycast(origin, direction, params) شعاعًا وتُعيد RaycastResult لأول جزء يصيبه، أو nil. إذا لم يصب شيئًا، أو أصابك أنت، فسِر مباشرةً.

وإلا، فاطلب من PathfindingService إيجاد مسار حول الجدران، وسِر إلى نقطة المسار التالية. تعيد الحلقة المحاولة بعد ربع ثانية، من الموضع الذي وصل إليه الروبوت:

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

points[1] هو موضع الروبوت الحالي، لذا فالخطوة التالية هي points[2].

challenge icon

التحدي

صعب
  1. احصل على PathfindingService، وأنشئ مسارًا واحدًا لكل روبوت في runBot: local path = PathfindingService:CreatePath().
  2. اكتب walkToward(bot, target, path): أجرِ raycast من جذر الروبوت إلى جذر الهدف، مع استبعاد الروبوت؛ إذا لم يُصَب أي شيء (أو أُصيب الهدف)، فاستخدم MoveTo للانتقال إلى الهدف؛ وإلا فاحسب المسار واستخدم MoveTo للانتقال إلى نقطة المسار الثانية.
  3. طارد باستخدام walkToward.

اضغط على 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 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)
	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, character, root)
	root.AssemblyLinearVelocity = root.CFrame.LookVector * Settings.DashSpeed + Vector3.new(0, 15, 0)
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)

-- يحصل كل لاعب على Kills في لوحة الصدارة، وعلى سيف في كل مرة تعود فيها شخصيته إلى الحياة
Players.PlayerAdded:Connect(function(player)
	local leaderstats = Instance.new("Folder")
	leaderstats.Name = "leaderstats"
	leaderstats.Parent = player

	local kills = Instance.new("IntValue")
	kills.Name = "Kills"
	kills.Parent = leaderstats

	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)

-- شخصية أقرب لاعب على قيد الحياة يستطيع bot رؤيتها، والمسافة إليها
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

-- عقل bot: ينظر حوله ويتخذ قرارًا كل ربع ثانية
local function runBot(bot)
	local humanoid = bot.Humanoid
	local root = bot.HumanoidRootPart
	local lastAttack = 0
	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
			humanoid:MoveTo(target.HumanoidRootPart.Position)
		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

-- bot جديد على المنصة الحمراء، بقيمه وشريط صحته
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 الفيزيائية على الخادم، حتى تدفعه قوة الارتداد الناتجة عن الضربة الساحقة
	bot.HumanoidRootPart:SetNetworkOwner(nil)
	bot.Humanoid.HealthChanged:Connect(function(health)
		bot.HealthBar.Back.Fill.Size = UDim2.fromScale(health / bot.Humanoid.MaxHealth, 1)
	end)
	task.spawn(runBot, bot)
end

spawnBot()
quiz iconاختبر نفسك

يتضمن هذا الدرس اختبارًا قصيرًا. ابدأ الدرس للإجابة عليه وتتبّع تقدمك.

جميع دروس لعبة Roblox: ساحات القتال