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

روبوت يطارد

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

والآن الخصم. الروبوت هو نموذج في ServerStorage.Bot يحتوي على Humanoid، ويُنسخ إلى المنصة الحمراء. عقله عبارة عن حلقة في خيطه الخاص: كل ربع ثانية، يعثر على أقرب لاعب حي يمكنه رؤيته (ضمن Settings.BotSight) ويمشي نحوه باستخدام Humanoid:MoveTo.

while bot.Parent and humanoid.Health > 0 do
	local target = nearestCharacter(root.Position)
	if target then
		humanoid:MoveTo(target.HumanoidRootPart.Position)
	end
	task.wait(0.25)
end

العثور على الأقرب هو حلقة تقليدية: ابدأ بلا شيء، واجعل نطاق الرؤية هو أفضل مسافة، ثم احتفظ بكل من هو أقرب.

تتيح Roblox لعميل لاعب قريب تشغيل فيزياء نموذج غير مثبت، وعندئذٍ لا يفعل استدعاء من الخادم مثل ApplyImpulse أي شيء. تُطيح ضربتك بالروبوتات إلى الخلف من الخادم، لذا أبقِ فيزياء كل روبوت على الخادم: bot.HumanoidRootPart:SetNetworkOwner(nil)، بعد أن يصبح الروبوت في مساحة العمل.

challenge icon

التحدي

صعب
  1. أضف local bots = workspace.Bots.
  2. اكتب nearestCharacter(position): شخصية أقرب لاعب على قيد الحياة ضمن Settings.BotSight، والمسافة إليها (أو nil).
  3. اكتب runBot(bot): ما دام الروبوت على قيد الحياة، تحرّك نحو أقرب شخصية أو ابقَ ساكنًا، وانتظر 0.25 ثانية.
  4. اكتب spawnBot(): استنسخ Bot على ارتفاع 3 studs فوق workspace.BotSpawn، واضبط MaxHealth وHealth وWalkSpeed من Settings، واجعل الأب هو bots، وأبقِ الفيزياء على الخادم، واجعل شريط الصحة يتبع صحته، وابدأ runBot. أنشئ روبوتًا واحدًا.

اضغط 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 lastSwing = {} -- player -> وقت آخر ضربة نفّذها
local combo = {} -- player -> { عدد الضربات المتتالية، ووقت إصابة آخر ضربة }

-- حركات السيف الخاصة بـ 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)
quiz iconاختبر نفسك

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

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