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

حالات الروبوت

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

أصبح عقل الروبوت مشوشًا. تُكتب عادةً برمجيات الذكاء الاصطناعي للألعاب على شكل حالات: في كل نبضة، حدّد الحالة التي يكون فيها الروبوت، ثم نفّذ ما تفعله تلك الحالة. لهذا الروبوت خمس حالات:

  • Stunned: جمّدتْه سلسلتك الهجومية. لا يفعل شيئًا.
  • Retreat: عندما تنخفض صحته عن Settings.RetreatAt، يهرب منك.
  • Idle: لا أحد في مرمى نظره.
  • Attack: أنت ضمن المدى.
  • Chase: أنت في مرمى نظره.

ترتيب عمليات التحقق يحدد أولويات الروبوت. الحالة سمة للروبوت، State، لذا يمكنك معرفة ما يفكر فيه.

challenge icon

التحدي

صعب

أعِد كتابة محتوى الحلقة داخل runBot على هيئة حالات. حدّد state بالترتيب المذكور أعلاه، واضبط سمة State للبوت؛ بعد ذلك، تتحرك حالة Chase نحو الهدف، وتقف حالة Attack وتُوجّه الضربات (كما من قبل)، وتتحرك حالة Retreat مسافة 12 studs مبتعدةً مباشرةً عن الهدف، بينما تبقى أي حالة أخرى ثابتة.

اضغط على 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)
		if target and distance <= Settings.BotRange then
			-- قريب بما يكفي: توقف واضرب مرة كل BotAttackCooldown
			humanoid:MoveTo(root.Position)
			if time() - lastAttack >= Settings.BotAttackCooldown then
				lastAttack = time()
				humanoid.Animator:LoadAnimation(slash):Play()
				hurt(target.Humanoid, Settings.BotDamage)
			end
		elseif target then
			humanoid:MoveTo(target.HumanoidRootPart.Position)
		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: ساحات القتال