تقنيات سلطة الخادم

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

هذا الدليل يحدد تقنيات متنوعة لإنشاء ألعاب متعددة اللاعبين عالية الجودة وسلسة باستخدام نموذج سلطة الخادم.

إنشاء الكائنات التنبؤية (تجميع الكائنات)

تتيح تجميع الكائنات للسكريبتات في العميل إنشاء كائنات Instances بشكل تنبؤي داخل استدعاءات RunService:BindToSimulation(). يقوم العميل بإنشاء Instance على الفور دون انتظار جولة الخادم؛ عندما تصل النسخة المعتمدة من الخادم، يتم دمج الكائن الذي أنشأه العميل والنسخة المعتمدة من الخادم في واحدة. من وجهة نظر سكربتك، يوجد Instance على الفور ويتوافق مع الخادم.

تكون تجميع الكائنات مفيدة في الحالات التي يجب أن يكون فيها الكائن مرئيًا ونشطًا على العميل في أسرع وقت ممكن. بينما سيقوم الخادم في النهاية بإعادة تكرار أي كائن يحتاجه العميل (بمعرفة أي آثار قد تكون له على العالم)، فإن هذه العملية تستلزم على الأقل جولة واحدة من التأخير بسبب الاتصال بالخادم. تشمل الأمثلة إطلاق صاروخ وخلق قيود فيزيائية — بدون التجميع، سيرى العميل الصاروخ يظهر من بعيد، أو بعض الاهتزازات عند إعادة تكرار القيود الجديدة إلى العميل.

السلوك الفني

تعمل تجميع الكائنات عن طريق توليد نفس GUID المحدد على كل من العميل والخادم. يتم اشتقاق GUID من أربعة مدخلات: نوع Instance الذي يتم إنشاؤه، هوية المصدر (انظر أدناه)، إطار المحاكاة الحالي، وعدد استدعاءات لكل سكريبت يتم إعادة ضبطه في كل إطار.

  • لـ Instance.new() — المصدر هو السكريبت نفسه (يعتبر السكريبتان اللذان يحتويان على نفس النص مختلفين).
  • لـ Instance.fromExisting() — المصدر هو Instance التي تقوم باستدعاء Instance.fromExisting() عليها.
  • لـ Instance:Clone() — تستخدم كل نسخة مستنسخة GUID الكائن المصدر كالبذور المرجعية.

إذا اتفق العميل والخادم على المدخلات، فإنهما ينتجان GUIDات متطابقة وينجح التجميع.

التنفيذ

لاستخدام تجميع الكائنات، قم باستدعاء Instance.new()، Instance:Clone()، أو Instance.fromExisting() داخل استدعاء BindToSimulation() من ModuleScript يتم استدعاؤه على كل من العميل و الخادم. لا يحتاج أي شيء آخر على جانبك؛ يتعامل النظام مع تعيين GUID والتسوية تلقائيًا.

يمكنك ضبط الخصائص غير الوصول على المحاكاة مثل Name، Size، أو Parent على كائن قبل أن يتم تأصيله في DataModel.

المحاكاة (ModuleScript) - إنشاء كائن في استدعاء BindToSimulation()

local RunService = game:GetService("RunService")
local Simulation = {}
Simulation.Initialize = function()
RunService:BindToSimulation(function(deltaTime)
local part = Instance.new("Part")
part.Name = "PredictedPart"
part.Size = Vector3.new(2, 2, 2)
part.Parent = workspace -- الكائن الآن في DataModel؛ ستظهر أي تغييرات على الوصول غير المحاكي خطأ بعد ذلك
-- الكائن موجود على الفور على العميل وسيُعاد تسويته مع الخادم
end)
end
return Simulation

Instance:Clone() و Instance.fromExisting() تتجمع بشكل صحيح عندما تم تكرار الكائن المصدر لكل من العميل والخادم؛ كل جانب يقوم بالتقليد من GUIDات المصدر المتطابقة وينتج GUIDات تنبؤية متطابقة.

المحاكاة (ModuleScript) - استنساخ كائن في استدعاء BindToSimulation()

local RunService = game:GetService("RunService")
local Simulation = {}
local sourceTemplate -- كائن مستنسخ
Simulation.Initialize = function()
RunService:BindToSimulation(function(deltaTime)
local cloned = sourceTemplate:Clone()
cloned.Parent = workspace
-- يتم تجميع التسلسل الهرمي المستنسخ مع النسخة المعتمدة من الخادم
end)
end
return Simulation

تلطيف الموقع

يمكنك تلطيف الموقع بصريًا للكائنات المتزامنة التي تم التنبؤ بها بشكل خاطئ عن طريق عرض كائن مختلف عما يتم محاكاته.

  1. اجعل الكائن المحاكى غير مرئي.
  2. اجعل كائن المصمم كنسخة غير ثقيلة، غير قابلة للتصادم، وللعرض فقط لتتبع الكائن المحاكى.
  3. قم بإرفاق سكريبت على كائن المصمم يتتبع بسلاسة موقع الكائن المحاكى، المصمم بين عرض ومحاكاة، مما يسمح لك بتغيير موقع كائن المصمم لإنشاء تجربة بصري سلسة.

في عينة Script التالية، يتتبع الكائن المعروض (الأب) بسلاسة الكائن المحاكى. الكائن المعروض يكون دائمًا "خلف" الكائن المحاكى، وهو عادةً ما يكون مقبولًا ولكن قد يكون غير مرغوب فيه في بعض الحالات.

تتبع بسلاسة لموقع الـBasePart باستخدام كائن المصمم

local RunService = game:GetService("RunService")
local TweenService = game:GetService("TweenService")
local Workspace = game:GetService("Workspace")
-- الكائن لتتبع بسلاسة
local smoothTarget:BasePart = Workspace.SimulatedPart
-- الكائن البصري الذي سيتم تلطيفه
local renderer:BasePart = script.Parent
-- الوقت لتلطيفه؛ أصغر يعني أسرع
local smoothTime = 0.07
-- تخزين البيانات اللازمة لحساب الموقع السلس
local smoothVelocity = Vector3.new()
-- تعطيل فيزياء كائن المصمم
renderer.Massless = true
renderer.Anchored = true
renderer.CanCollide = false
RunService.RenderStepped:Connect(function(deltaTime: number)
-- تتبع بسلاسة الكائن المستهدف
local smoothPosition, smoothVelocity = TweenService:SmoothDamp(
renderer.Position,
smoothTarget.Position,
smoothVelocity,
smoothTime,
math.huge,
deltaTime)
renderer.Position = smoothPosition
end)

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

كتابة كود الرسوم المتحركة

تحت سلطة الخادم، يمكن تراجع محاكاة العميل وإعادة محاكاتها عندما يصحح الخادم تنبؤًا خاطئًا. خلال التراجع، يتم إعادة ضبط حالة الرسوم المتحركة، مما يعني أن AnimationTrack التي خزنتها في إطارات سابقة قد لا تكون صحيحة بعد الآن.

عكس منطق الرسوم المتحركة

كما هو الحال مع أي منطق أساسي في اللعبة، يجب أن يتزامن منطق التحكم في الرسوم المتحركة بين الخادم والعميل، وإلا فقد تحدث تنبؤات خاطئة وسلوك اهتزاز. انظر تزامن المحاكاة لنمط يربط الدوال عبر RunService:BindToSimulation() في ModuleScript يتم تهيئته على كل من العميل والخادم.

تجنب تخزين مسارات الرسوم المتحركة

نمط شائع في السكريبتات التي لا تحتوي على سلطة الخادم هو تخزين كائنات AnimationTrack عند تحميلها وإعادة استخدامها إلى ما لا نهاية. يفشل هذا النمط في لعبة تحت سلطة الخادم عندما يقوم الخادم بتصحيح تنبؤ خاطئ ويحاول العميل إعادة تشغيل محاكاته ببيانات مصححة. إذا كانت سكريبتك لا تزال تحتفظ بمرجع لمسار متوقف أو مستبدل، فإن الاستدعاءات مثل AdjustWeight() أو AdjustSpeed() ستعمل على مسار لم يعد ممثلاً بصريًا.

تخزين المسارات في العميل (غير موثوق)

local Players = game:GetService("Players")
local RunService = game:GetService("RunService")
local player = Players.LocalPlayer
local character = player.Character or player.CharacterAdded:Wait()
local humanoid = character:WaitForChild("Humanoid")
local animator = humanoid:WaitForChild("Animator")
-- تخزين مسارات الرسوم المتحركة
local tracks = {}
tracks["WalkForward"] = animator:LoadAnimation(walkForwardAnim)
RunService:BindToSimulation(function(dt: number)
tracks["WalkForward"]:AdjustSpeed(1 + math.cos(time()))
end)

بدلاً من الاحتفاظ بكائنات المسار، قم بتخزين معرفات الرسوم المتحركة (أو مثيلات Animation) واستعلام الـ Animator للحصول على المسار الحالي كلما احتجت للتفاعل معه. هناك واجهتان متاحتان لذلك:

  • Animator:GetTrackByAnimationId() — تُرجع المسار النشط حاليًا لمعرف رسوم متحركة محدد، أو nil إذا لم يكن هناك رسوم متحركة نشطة بهذا المعرف. استخدم هذا عندما تعرف أي رسوم متحركة محددة تبحث عنها.
  • Animator:GetPlayingAnimationTracks() — تُرجع جميع المسارات النشطة (تشغيل، تتلاشى، أو متوقفة). استخدم هذا عندما تحتاج للتكرار عبر كل شيء نشط (على سبيل المثال، لإيقاف جميع الرسوم المتحركة أو العثور على المسارات بناءً على بعض المعايير).

ModuleScript باسم CustomAnimate في ReplicatedStorage:

CustomAnimate

local ReplicatedStorage = game:GetService("ReplicatedStorage")
local RunService = game:GetService("RunService")
local CustomAnimate = {}
-- تخزين مراجع الرسوم المتحركة (ليس المسارات المحملة)
local animations = {
WalkForward = ReplicatedStorage.Animations.WalkForward,
}
local function getOrLoadTrack(animator: Animator, animation: Animation): AnimationTrack
local track = animator:GetTrackByAnimationId(animation.AnimationId)
if not track then
track = animator:LoadAnimation(animation)
end
return track
end
CustomAnimate.SyncAnimations = function(character)
local humanoid = character:WaitForChild("Humanoid")
local animator = humanoid:WaitForChild("Animator")
RunService:BindToSimulation(function(dt: number)
local walkTrack = getOrLoadTrack(animator, animations.WalkForward)
if not walkTrack.isPlaying then
walkTrack.Looped = true
walkTrack.Priority = Enum.AnimationPriority.Core
walkTrack:Play()
end
walkTrack:AdjustSpeed(1 + math.cos(time()))
end)
end
return CustomAnimate

تشغيل الأصوات والآثار المرئية

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

استراتيجية جيدة لعرض محاكاة متوقعة هي مزامنة نمط آلة الحالة ضمن حلقة المحاكاة وعرض التغييرات في وظيفة خطوة العرض. المثال التالي يحاكي قنبلة باستخدام نمط آلة الحالة:

آلة حالة بسيطة لتتبع قنبلة (ModuleScript)

local Workspace = game:GetService("Workspace")
local module = {}
module.GrenadeStates = {
Idle = 0,
Lit = 1,
Exploded = 2,
Defused = 3,
}
module.GrenadeExplodeTime = 3.0
module.Initialize = function(grenade)
RunService:BindToSimulation(function(deltaTime)
-- تهيئة حالة قنبلة فارغة
local grenadeState = grenade:GetAttribute("State")
if grenadeState == nil then
grenadeState = module.GrenadeStates.Idle
grenade:SetAttribute("State", grenadeState)
grenade:SetAttribute("Timer", 0.0)
end
-- زيادة عداد قنبلة الوقت
local timer = grenade:GetAttribute("Timer")
timer = timer + deltaTime
grenade:SetAttribute("Timer", timer)
-- انفجار القنابل المضيئة
if grenadeState == module.GrenadeStates.Lit then
if timer >= module.GrenadeExplodeTime then
grenadeState = module.GrenadeStates.Exploded
grenade:SetAttribute("State", grenadeState)
grenade:SetAttribute("Timer", 0.0)
end
end
end)
end
return module

مع وجود آلة الحالة السابقة في مكانها، يمكنك عرض تأثيرات القنبلة في اتصال RunService.RenderStepped ضمن سكربت منفصل بناءً على حالة القنبلة المتزامنة:

عرض الجسيمات والأصوات بناءً على حالة القنبلة المتزامنة

local ReplicatedStorage = game:GetService("ReplicatedStorage")
local RunService = game:GetService("RunService")
local Simulation = require(ReplicatedStorage.Simulation)
local grenade = script.Parent
local previousGrenadeState = nil
-- إبراز الكائن للإشارة إلى حالة القنبلة
local highlight = Instance.new("Highlight")
highlight.Parent = grenade
highlight.FillTransparency = 1
highlight.OutlineTransparency = 1
highlight.DepthMode = Enum.HighlightDepthMode.Occluded
RunService.RenderStepped:Connect(function(deltaTime: number)
local grenadeState = grenade:GetAttribute("State")
local grenadeTimer = grenade:GetAttribute("Timer")
-- إطلاق الجسيمات المضيئة إذا كانت القنبلة مضيئة
grenade.LitEmitter.Enabled = grenadeState == Simulation.GrenadeStates.Lit
-- تشغيل مؤثر الانفجار إذا انفجرت القنبلة للتو
if previousGrenadeState ~= grenadeState then
if grenadeState == Simulation.GrenadeStates.Exploded and grenadeTimer < 0.2 then
grenade.ExplosionEmitter:Emit(100)
grenade.ExplosionSound:Play()
end
previousGrenadeState = grenadeState
end
-- تغيير لون إبراز القنبلة بناءً على الحالة والوقت
if grenadeState == Simulation.GrenadeStates.Lit then
highlight.FillColor = Color3.fromRGB(255, 0, 0)
highlight.FillTransparency = 1 - (grenadeTimer / Simulation.GrenadeExplodeTime)
elseif grenadeState == Simulation.GrenadeStates.Idle then
highlight.FillTransparency = 1
elseif grenadeState == Simulation.GrenadeStates.Exploded then
highlight.FillTransparency = 1
elseif grenadeState == Simulation.GrenadeStates.Defused then
highlight.FillColor = Color3.fromRGB(0, 255, 125)
highlight.FillTransparency = 0.5
end
end)

التصميم حول تأخير الشبكة

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

على سبيل المثال، ستبدو لعبة بحركة لاعب بمتطلبات تسارع أبطأ أكثر سلاسة من لعبة مع تسارع أعلى لأن الفرق في الموقع الناتج عن تأخير الشبكة لمدخلات اللاعب سيكون أقل في لعبة مع تسارع عالٍ.

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

التنبؤ بمدخلات اللاعبين الآخرين

بشكل افتراضي، لا يقوم Roblox بتمرير المدخلات من كل عميل إلى كل عميل آخر. ما إذا كان هذا مناسبًا للعبتك يعتمد على تصميمها:

  • بالنسبة للحركة الأساسية للإنسان، يعني السلوك الافتراضي أن حركات الشخصيات الأخرى لن يتم استقراءها من حالة الخادم الموثوقة ونتيجة لذلك، لن تسيء الشخصيات الأخرى التنبؤ ولكن ستظهر في بعض الوقت الماضي.
  • في لعبة سباق، على العكس، يعني السلوك الافتراضي أن العملاء لن يعرفوا ما إذا كان اللاعبون الآخرون قد قاموا بتطبيق دواسة الوقود أو المدخلات الأخرى، لذا قد تظهر السيارات الأخرى خلف اللاعب المحلي حتى لو كانت في الحقيقة أمامه. لتخفيف ذلك، يمكنك تخزين مدخلات اللاعب في السمات على الخادم والعمل على هذه السمات المتزامنة من جانب العميل باستخدام RunService:BindToSimulation() كما هو موضح في المثال التالي والكود الموجود في قالب السباق. تتيح لك هذه الطريقة استخدام السمات كمدخلات لمحاكاتك للحصول على مدخلات اللاعب كاملة.
تخزين مدخلات اللاعب في السمات (ModuleScript)

local Players = game:GetService("Players")
local RunService = game:GetService("RunService")
local module = {}
module.storePlayerInput = function(player:Player, humanoidRootPart:BasePart)
local inputContext:InputContext = player.PlayerGui.InputContext
local throttle = inputContext.DefuseAction:GetState()
humanoidRootPart:SetAttribute("Throttle", throttle)
-- كتابة أي مدخلات أخرى في السمات...
end
module.Initialize = function()
RunService:BindToSimulation(function(deltaTime)
if RunService:IsServer() then
-- تمرير المدخلات من الخادم إلى جميع العملاء
for _, player in Players:GetPlayers() do
local humanoidRootPart:BasePart = player.Character.HumanoidRootPart
local inputContext:InputContext = player.PlayerGui.InputContext
module.storePlayerInput(player, humanoidRootPart)
end
else
-- كتابة مدخلات اللاعب المحلي كسمات
local player = Players.LocalPlayer
local humanoidRootPart:BasePart = player.Character.HumanoidRootPart
local inputContext:InputContext = player.PlayerGui.InputContext
module.storePlayerInput(player, humanoidRootPart)
end
-- استخدم السمات كمدخلات للعبة
for _, player in Players:GetPlayers() do
local humanoidRootPart:BasePart = player.Character.HumanoidRootPart
local throttle = humanoidRootPart:GetAttribute("Throttle")
if throttle then
-- تطبيق دواسة الوقود على مركبة اللاعب
end
end
end)
end)
return module

تصحيح الأخطاء

هناك بعض الأدوات والتقنيات الجديدة التي يمكنك استخدامها لتصحيح أخطاء لعبة تحت سلطة الخادم.

مُصور سلطة الخادم

عند الضغط على CtrlShiftF6 (Windows) أو ShiftF6 (Mac) يفتح مُصوّر سلطة الخادم في الاستوديو والذي يظهر عدة قطع رئيسية من المعلومات:

التفاصيلالوصف
نسبة نجاح توقع الكائناتنسبة الكائنات المتوقعة بشكل صحيح على مدى آخر 8 ثوانٍ.
نسبة قبول المدخلاتنسبة جميع المدخلات اللاعبين التي وصلت في الوقت المحدد إلى الخادم. المدخلات المتأخرة ستقلل من هذه الرقم.
الدلتا بين خطوة العميل والخادمعدد الإطارات بين العميل والخادم، بما في ذلك زمن انضمام العميل. تمثل استقرار هذا الرقم استقرار اتصالك بالخادم.
FPS نبض RCCمعدل الإطارات للمحاكاة على الخادم. إذا انخفض هذا الرقم عن 59، فإن الخادم لا يمكنه مواكبة المحاكاة وسيتدهور جودة اللعبة.
عدد الكائنات المتوقعةعدد الكائنات التي يقوم عميلك بتنبؤ بها.
أسباب إيقاف المدخلات

عدد المرات التي قام فيها الخادم بإسقاط مدخلات لكل سبب:

  • [x] قديم جدًا — وصلت المدخلات متأخرة، مما يعني أن شبكتك قد ساءت أو أن العميل لم يستطع مواكبة المحاكاة.
  • [x] خارج الترتيب — حدث خطأ في الشبكة تسبب في إعادة ترتيب مدخلاتك وإسقاطها.
  • [x] ممتلئ — لم يستطع الخادم تخزين مدخلك. إما أن شبكتك قد تحسنت فجأة أو أن الخادم لم يستطع مواكبة المحاكاة.

دائرة المحاكاة

عند الاعتماد على التوقع التلقائي (Enum.PredictionMode.Automatic)، يمكنك عرض دائرة التوقع حول شخصية لاعبك عن طريق تمكين هل المناطق ممكنة في إعدادات الاستوديو (AltS على Windows؛ S على Mac). تشير الأسطوانة الخضراء إلى النطاق حول شخصيتك الذي يتم فيه توقع الكائنات، وينمو نصف القطر الخاص بها وينكمش بناءً على خصائص أداء الجهاز.

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