إيجاد المسار

*This content is translated using AI (Beta) and may contain errors. To view this page in English, click here.

إيجاد المسار هو العملية التي يتم من خلالها تحريك شخصية أو كائن (وكيل) على طول مسار منطقي حول العقبات للوصول إلى وجهة، مع تجنب المواد الخطرة أو المناطق المعينة عند الحاجة.

تصور التنقل

لمساعدة في تخطيط وإصلاح إيجاد المسار، يمكن لـ Studio عرض شبكة التنقل وتسميات المعدلات. لتفعيلها، قم بتفعيل شبكة التنقل و معدلات إيجاد المسار من أداة خيارات التصوير في الزاوية العليا اليمنى من نافذة العرض الثلاثي الأبعاد.

عرض مقرب لنافذة العرض الثلاثي الأبعاد مع الإشارة إلى زر خيارات التصوير في الزاوية العليا اليمنى.

مع تفعيل شبكة التنقل، تُظهر المناطق الملونة الأماكن التي قد تمشي أو تسبح فيها الشخصية. تُشير السهام الصغيرة إلى الأماكن التي ستحاول الشخصية الوصول إليها عن طريق القفز.

شبكة التنقل تظهر في Studio

التنفيذ

على الرغم من أنه يمكن تنفيذ إيجاد المسار بطرق مختلفة من خلال PathfindingService وطرقها المرتبطة مثل CreatePath(), ستستخدم هذه الفقرة نص إيجاد المسار التالي لشخصية اللاعب.

لاختبار أثناء القراءة:

  1. هام
    في المستكشف، حدد حاوية StarterPlayer. ثم، في نافذة الخصائص، قم بتعيين كل من DevComputerMovementMode و DevTouchMovementMode إلى Scriptable.

  2. انسخ الكود التالي إلى LocalScript داخل StarterCharacterScripts, أو احصل على هذه الحزمة وألقها في StarterCharacterScripts.

    PlayerPathFollow (LocalScript in StarterCharacterScripts)

    local PathfindingService = game:GetService("PathfindingService")
    local Players = game:GetService("Players")
    local RunService = game:GetService("RunService")
    local DESTINATION = Vector3.new(20, 0.5, 20)
    local GROUND_WAIT = 0.01
    local VELOCITY_MULTIPLIER = 0.0625
    local path = PathfindingService:CreatePath({
    AgentCanClimb = true,
    Costs = {
    Water = 20
    }
    })
    local character = script.Parent
    local humanoid = character:WaitForChild("Humanoid")
    local waypoints
    local nextWaypointIndex
    local blockedConnection
    local currentWaypointReachedConnection
    local currentWaypointPlaneNormal = Vector3.zero
    local currentWaypointPlaneDistance = 0
    local pathfinderWorking = false
    local function disconnectCurrentWaypointReachedConnection()
    if not currentWaypointReachedConnection then return end
    currentWaypointReachedConnection:Disconnect()
    currentWaypointReachedConnection = nil
    end
    local function isCurrentWaypointReached()
    if humanoid.FloorMaterial == Enum.Material.Air then
    return false
    end
    local reached = false
    if currentWaypointPlaneNormal ~= Vector3.zero then
    -- حساب المسافة من humanoid إلى الطائرة الوجهة
    local dist = currentWaypointPlaneNormal:Dot(humanoid.RootPart.Position) - currentWaypointPlaneDistance
    -- حساب مكون سرعة humanoid المتجه نحو الطائرة
    local velocity = -currentWaypointPlaneNormal:Dot(humanoid.RootPart.Velocity)
    -- حساب العتبة من الطائرة الوجهة بناءً على سرعة humanoid
    local threshold = math.max(1.0, VELOCITY_MULTIPLIER * velocity)
    -- اعتبار النقطة الوجهة قد تم الوصول إليها إذا كانت أقل من العتبة أمام الطائرة
    reached = dist < threshold
    else
    reached = true
    end
    if reached then
    currentWaypointPlaneNormal = Vector3.zero
    currentWaypointPlaneDistance = 0
    moveToNextWaypoint()
    end
    end
    local function calculateNextWaypointApproach()
    nextWaypointIndex += 1
    if nextWaypointIndex > #waypoints then
    return false
    end
    local currentWaypoint = waypoints[nextWaypointIndex - 1]
    local nextWaypoint = waypoints[nextWaypointIndex]
    -- بناء الطائرة الوجهة من النقطة الوجهة التالية نحو الحالية
    currentWaypointPlaneNormal = currentWaypoint.Position - nextWaypoint.Position
    -- تعيين العمودي لنقطة Y عندما لا نتسلق لأعلى
    if nextWaypoint.Label ~= "Climb" then
    currentWaypointPlaneNormal = Vector3.new(currentWaypointPlaneNormal.X, 0, currentWaypointPlaneNormal.Z)
    end
    if currentWaypointPlaneNormal.Magnitude > 0.000001 then
    currentWaypointPlaneNormal = currentWaypointPlaneNormal.Unit
    currentWaypointPlaneDistance = currentWaypointPlaneNormal:Dot(nextWaypoint.Position)
    end
    return true
    end
    local function resetWaypointData()
    humanoid:Move(Vector3.zero)
    currentWaypointPlaneNormal = Vector3.zero
    currentWaypointPlaneDistance = 0
    disconnectCurrentWaypointReachedConnection()
    pathfinderWorking = false
    end
    local function waitForGround()
    while humanoid.FloorMaterial == Enum.Material.Air do
    task.wait(GROUND_WAIT)
    end
    end
    function moveToNextWaypoint()
    if calculateNextWaypointApproach() then
    disconnectCurrentWaypointReachedConnection()
    currentWaypointReachedConnection = RunService.Heartbeat:Connect(isCurrentWaypointReached)
    local nextWaypointPosition = waypoints[nextWaypointIndex].Position
    local nextWaypointAction = waypoints[nextWaypointIndex].Action
    humanoid:Move(nextWaypointPosition - humanoid.RootPart.Position)
    if waypoints[nextWaypointIndex + 1] and waypoints[nextWaypointIndex + 1].Label == "UseBoat" then
    nextWaypointIndex += 1
    -- اتصل بوظيفتك المخصصة لجعل الوكيل يستخدم القارب
    elseif nextWaypointAction == Enum.PathWaypointAction.Jump then
    humanoid:ChangeState(Enum.HumanoidStateType.Jumping)
    while humanoid.FloorMaterial ~= Enum.Material.Air do
    task.wait(GROUND_WAIT)
    end
    humanoid:Move(nextWaypointPosition - humanoid.RootPart.Position)
    end
    else
    resetWaypointData()
    end
    end
    local function findStartingPoint(waypoints)
    nextWaypointIndex = 1
    while nextWaypointIndex + 1 <= #waypoints do
    local dist = waypoints[nextWaypointIndex + 1].Position - humanoid.RootPart.Position
    dist = Vector3.new(dist.X, 0, dist.Z)
    if dist.magnitude >= 2 then
    return
    end
    nextWaypointIndex += 1
    end
    end
    local function followPath()
    -- حساب المسار
    pathfinderWorking = true
    waitForGround()
    local success, errorMessage = pcall(function()
    path:ComputeAsync(character.PrimaryPart.Position, DESTINATION)
    end)
    if not success or path.Status ~= Enum.PathStatus.Success then
    warn("لم يُحسب المسار!", errorMessage)
    return
    end
    -- الحصول على نقاط الطريق
    waypoints = path:GetWaypoints()
    -- كشف ما إذا كان المسار محجوزًا
    blockedConnection = path.Blocked:Connect(function(blockedWaypointIndex)
    -- تحقق مما إذا كانت العقبة موجودة في مكانٍ ما بعيدًا في المسار
    if blockedWaypointIndex >= nextWaypointIndex then
    -- توقف عن كشف حجز المسار حتى يتم إعادة حساب المسار
    blockedConnection:Disconnect()
    resetWaypointData()
    -- استدعاء وظيفة لإعادة حساب مسار جديد
    followPath()
    end
    end)
    findStartingPoint(waypoints)
    moveToNextWaypoint()
    end
    followPath()
  3. قم بتحرير متغير DESTINATION (

    السطر 5
    ) إلى وجهة Vector3 ضمن العالم الثلاثي الأبعاد التي يمكن أن تصل إليها شخصية اللاعب.

  4. تابع من خلال الأقسام التالية لتتعلم عن حساب المسار وحركة الشخصية.

إنشاء المسار

يتم بدء إيجاد المسار من خلال PathfindingService وطريقة CreatePath() (

الأسطر 9–14
). تأخذ هذه الطريقة جدولًا اختياريًا من المعلمات التي تضبط كيفية تحرك الشخصية (الوكيل) على طول المسار.

المفتاحالوصفالنوعالإفتراضي
AgentRadiusنصف قطر الوكيل، بالسدادات. مفيد لتحديد الحد الأدنى من المسافة من العقبات.عدد صحيح2
AgentHeightارتفاع الوكيل، بالسدادات. سيُوسم الفضاء الفارغ الأصغر من هذه القيمة، مثل الفضاء تحت الدرج، بأنه غير قابل للعبور.عدد صحيح5
AgentCanJumpتحدد ما إذا كانت القفزات مسموحة أثناء إيجاد المسار.booleantrue
AgentCanClimbتحدد ما إذا كان التسلق في TrussParts مسموحًا أثناء إيجاد المسار. يتم تعيين مسار يمكن تسلقه بعلامة Label تدعى Climb وتكون التكلفة لمسار يمكن تسلقه هي 1 بشكل افتراضي.booleanfalse
WaypointSpacingتباعد بين نقاط الطريق المتوسطة في المسار. إذا تم تعيينها إلى math.huge، فلن تكون هناك نقاط طريق متوسطة.رقم4
Costsجدول المواد أو PathfindingModifiers المعرفة وتكاليفها للعبور. مفيد لجعل الوكيل يفضل مواد/مناطق معينة على الأخرى. راجع المعدلات للحصول على التفاصيل.جدولnil

حساب المسار

بعد إنشاء مسار صالح باستخدام CreatePath(), يجب حسابه من خلال استدعاء Path:ComputeAsync() مع Vector3 لكل من نقطة البدء والوجهة (

الأسطر 133–139
).

بداية/نهاية المسار موضحة عبر جسرين

بمجرد حساب Path, ستحتوي على سلسلة من نقاط الطريق التي تتبع المسار من البداية إلى النهاية. يمكن جمع هذه النقاط باستخدام طريقة Path:GetWaypoints() (

السطر 142
). يتم ترتيب المصفوفة العائدة حسب ترتيب نقاط الطريق من بداية المسار إلى نهايته.

نقاط الطريق موضحة عبر المسار المحسوب
نقاط الطريق موضحة عبر المسار المحسوب

حركة المسار

يتكون كل PathWaypoint من كل من Position (Vector3) و Action (PathWaypointAction). لتحريك شخصية تحتوي على Humanoid, مثل الشخصية العادية في Roblox, فإن أفضل طريقة هي استدعاء Humanoid:Move() من نقطة الطريق إلى نقطة الطريق واستخدام رد استدعاء isCurrentWaypointReached() في السكربت (

الأسطر 32–56
) لاكتشاف متى تصل الشخصية إلى كل نقطة طريق.

المسارات المحجوزة

تعتبر العديد من عوالم Roblox ديناميكية؛ قد تتحرك الأجزاء أو تسقط وقد تسقط الأرضيات. يمكن أن تمنع هذه الأشياء المسار المحسوب وتمنع الشخصية من الوصول إلى وجهتها. للتعامل مع هذا، يمكنك الاتصال بحدث Path.Blocked وإعادة حساب المسار حول ما قام بحجبه (

الأسطر 145–154
).

معدلات إيجاد المسار

بشكل افتراضي، تقوم Path:ComputeAsync() بإرجاع المسار الأقصر بين نقطة البدء ونقطة الوجهة، باستثناء أنها تحاول تجنب القفزات. قد يبدو هذا غير طبيعي في بعض الحالات؛ على سبيل المثال، قد يمر المسار عبر مياه المستنقع بدلاً من حوله ببساطة لأن المسار من خلال المياه هو الأقصر هندسيًا.

طريقتان موضحتان مع عدم كون المسار الأقصر أكثر منطقية بالضرورة

لتحسين إيجاد المسار بشكل أفضل، يمكنك تنفيذ معدلات إيجاد المسار لحساب مسارات أذكى عبر مواد مختلفة المواد، أو حول [المناطق] (#configure-regions) المعينة، أو لتجاهل العقبات.

تكاليف المواد

عند العمل مع مواد Terrain وBasePart, يمكنك تضمين جدول Costs داخل CreatePath() لجعل مواد معينة أكثر قابلية للعبور من غيرها. جميع المواد لها تكلفة افتراضية قدرها 1 ويمكن تعريف أي مادة على أنها غير قابلة للعبور عن طريق تعيين قيمتها إلى math.huge.

يجب أن تمثل المفاتيح في جدول Costs أسماء سلسلة تمثل أسماء Enum.Material، على سبيل المثال Water لـ Enum.Material.Water أو CrackedLava لـ Enum.Material.CrackedLava.

PlayerPathFollow (LocalScript)

local PathfindingService = game:GetService("PathfindingService")
local Players = game:GetService("Players")
local RunService = game:GetService("RunService")
local DESTINATION = Vector3.new(20, 0.5, 20)
local GROUND_WAIT = 0.01
local VELOCITY_MULTIPLIER = 0.0625
local path = PathfindingService:CreatePath({
AgentCanClimb = true,
Costs = {
Water = 20, CrackedLava = 100, Slate = 20
}
})

تكوين المناطق

في بعض الحالات، لا تكون تفضيل المواد كافية. على سبيل المثال، قد ترغب في تجنب الشخصيات منطقة معينة، بغض النظر عن المواد تحت الأقدام. يمكن تحقيق ذلك عن طريق إضافة كائن PathfindingModifier إلى جزء.

  1. قم بإنشاء جزء Anchored حول المنطقة واضبط خاصية CanCollide إلى false.

    جزء مثبت يحدد منطقة لتطبيق معدل إيجاد المسار.
  2. أدخل مثيل PathfindingModifier على الجزء، وابحث عن خاصية Label، وضعي اسمًا ذا مغزى مثل DangerZone.

    مثيل PathfindingModifier مع property Label تم تعيينه إلى DangerZone.
  3. أدرج مفتاح DangerZone مطابق وقيمة عددية مرتبطة ضمن جدول Costs لـ CreatePath(). يمكن تعريف معدل على أنه غير قابل للعبور بتعيين قيمته إلى math.huge.

    PlayerPathFollow (LocalScript)

    local PathfindingService = game:GetService("PathfindingService")
    local Players = game:GetService("Players")
    local RunService = game:GetService("RunService")
    local DESTINATION = Vector3.new(20, 0.5, 20)
    local GROUND_WAIT = 0.01
    local VELOCITY_MULTIPLIER = 0.0625
    local path = PathfindingService:CreatePath({
    AgentCanClimb = true,
    Costs = {
    DangerZone = math.huge, Water = 20, CrackedLava = 20, Slate = 20
    }
    })

تجاهل العقبات

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

  1. قم بإنشاء جزء Anchored حول الكائن واضبط خاصية CanCollide إلى false.

    جزء مثبت يحدد منطقة لتطبيق معدل إيجاد المسار.
  2. أدخل مثيل PathfindingModifier على الجزء وقم بتمكين خاصية PassThrough.

    مثيل PathfindingModifier مع خاصية PassThrough مفعلة.

    الآن، عندما يتم حساب مسار من NPC الزومبي إلى شخصية اللاعب، يمتد المسار إلى ما وراء الباب ويمكنك توجيه الزومبي لتجاوزه. حتى إذا لم يتمكن الزومبي من فتح الباب، فإنه يتفاعل كما لو "سمع" الشخصية خلف الباب.

    مسار NPC الزومبي يمر عبر الباب الذي كان يحجب الطريق سابقًا.

روابط إيجاد المسار

في بعض الأحيان، يكون من الضروري العثور على مسار عبر مساحة لا يمكن عبورها عادةً، مثل عبر وادٍ، وتنفيذ إجراء مخصص للوصول إلى نقطة الطريق التالية. يمكن تحقيق ذلك من خلال كائن PathfindingLink.

باستخدام المثال من أعلاه، يمكنك جعل الوكيل يستخدم قاربًا.

PathfindingLink يوضح كيف يمكن لوكيل استخدام قارب.

لإنشاء PathfindingLink باستخدام هذا المثال:

  1. اختياري
    قم بتفعيل روابط إيجاد المسار من أداة خيارات التصوير الموجودة في الزاوية العليا اليمنى من نافذة العرض الثلاثي الأبعاد. يساعد ذلك في التصوير وإصلاح الأخطاء عند تنفيذ روابط إيجاد المسار.

  2. أنشئ Attachments, واحد على مقعد القارب وآخر بالقرب من نقطة هبوط القارب.

    المرفقات تم إنشاؤها لنقطة البداية ونقطة النهاية لارتباط إيجاد المسار.
  3. أنشئ كائن PathfindingLink في مساحة العمل، ثم عيّن خاصيتي Attachment0 وAttachment1 للمرفقات بدءًا ونهايةً على التوالي.

    خصائص Attachment0/Attachment1 لـ PathfindingLink. PathfindingLink مصور في العالم الثلاثي الأبعاد.
  4. عيّن اسمًا ذا مغزى مثل UseBoat لخاصية Label. يُستخدم هذا الاسم كعلامة في سكربت إيجاد المسار لبدء إجراء مخصص عندما يصل الوكيل إلى نقطة بداية الارتباط.

    خاصية Label محددة لـ PathfindingLink.
  5. أضف جدول Costs ضمن CreatePath() يحتوي على مفتاح Water ومفتاح مخصص مطابق لاسم خاصية Label. عيّن المفتاح المخصص قيمة أقل من تلك الخاصة بـ Water.

    PlayerPathFollow (LocalScript)

    local PathfindingService = game:GetService("PathfindingService")
    local Players = game:GetService("Players")
    local RunService = game:GetService("RunService")
    local DESTINATION = Vector3.new(20, 0.5, 20)
    local GROUND_WAIT = 0.01
    local VELOCITY_MULTIPLIER = 0.0625
    local path = PathfindingService:CreatePath({
    AgentCanClimb = true,
    Costs = {
    UseBoat = 2, Water = 20
    }
    })
  6. في دالة moveToNextWaypoint() (

    الأسطر 93–114
    ), يمكن استخدام تحقق مخصص من اسم معدل Label لاتخاذ إجراء مختلف عن Humanoid:Move()؛ في هذه الحالة، قد تستدعي دالة لتجلس الوكيل في القارب، وتحرك القارب عبر الماء، وتخرج الوكيل عند نقطة هبوط القارب، ثم تتابع مسار الوكيل إلى وجهته النهائية.

التوافق مع التدفق

يعتبر [تدفق الواجهات] (../workspace/streaming/index.md) في اللعبة ميزة قوية تقوم بتحميل وإلغاء تحميل المحتويات الثلاثية الأبعاد ديناميكيًا مع تحرك شخصية اللاعب عبر العالم. أثناء استكشافهم للفضاء الثلاثي الأبعاد، يتم بث مجموعة جديدة من الفضاء إلى جهازهم وقد تتدفق بعض المجموعات الموجودة.

خذ في اعتبارك أفضل الممارسات التالية لاستخدام PathfindingService في الألعاب المدعومة بالتدفق:

  • يمكن أن تؤدي التدفقات إلى حجز أو إلغاء حجز مسار معين أثناء حركة الشخصية على طول ذلك. على سبيل المثال، بينما تجري شخصية عبر غابة، قد تتدفق شجرة في مكان ما أمامهم وتعيق الطريق. لجعل إيجاد المسار يعمل بسلاسة مع التدفق، من المستحسن بشدة أنك تستخدم تقنية التعامل مع المسارات المحجوزة وإعادة حساب المسار عند الضرورة.

  • نهج شائع في إيجاد المسار هو استخدام إحداثيات الكائنات الموجودة لالحساب، مثل تعيين وجهة المسار إلى موضع نموذج TreasureChest الموجود في العالم. هذا النهج متوافق تمامًا مع Scripts من جانب الخادم نظرًا لأن الخادم لديه رؤية كاملة للعالم طوال الوقت، ولكن قد تفشل LocalScripts وModuleScripts التي تعمل على العميل إذا حاولت حساب مسار إلى كائن لم يتم التدفق إليه.

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

القيود وعوامل الفشل

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

©2026 شركة Roblox Corporation. تُعد منصّة Roblox، وشعار Roblox وشعار "توسيع حدود المخيلة"، من ضمن علاماتنا التجارية المسجّلة وغير المسجّلة في الولايات المتحدة وبلدان أخرى.