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

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

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

إنشاء المثيلات التنبؤية (خياطة المثيلات)

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

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

سلوك تقني

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

إذا توافق العميل والخادم على المدخلات، فإنهما ينتجان معرفات 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 -- أصبح الجزء الآن في نموذج البيانات؛ أي تغييرات على الوصول غير المحاكى ستؤدي إلى خطأ بعد ذلك
-- الوجود الفوري للجزء على العميل وسيتم الوفاق مع الخادم
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 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 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) إلى فتح مصور سلطة الخادم في ستوديو والذي يظهر العديد من القطع المعلومات الأساسية:

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

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

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

نصف قطر المحاكاة

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

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