الظهور هو عملية إنشاء كائن أو شخصية في لعبة، وإعادة الظهور هي عملية إضافة كائن أو شخصية مرة أخرى إلى اللعبة بعد أن يلتقوا بشرط الإزالة، مثل وصول صحة الشخصية إلى الصفر أو السقوط من الخريطة. كلا العمليتين مهمتان لأنهما تضمنان قدرة اللاعبين على الانضمام إلى لعبتك، ويمكنهم الاستمرار في اللعب لتحسين مهاراتهم.
باستخدام لعبة وسم الليزر النموذجية كمرجع، يعلمك هذا القسم من البرنامج التعليمي كيفية استخدام وتخصيص ميزات Roblox المدمجة للتعامل مع الظهور وإعادة الظهور، بما في ذلك إرشادات البرمجة حول:
- تكوين مواقع الظهور بحيث يمكن للاعبين الظهور فقط في منطقة ظهور فريقهم.
- إضافة لاعبين جدد وشخصياتهم إلى الجولة عند انضمامهم إلى اللعبة.
- تخصيص مجالات القوة التي تمنع الضرر أثناء ظهور اللاعبين وإعادة ظهورهم.
- التعامل مع حالة العميل بحيث تعمل طريقة اللعب بشكل صحيح في الوقت المناسب.
- إعادة ظهور الشخصيات بعد أن يتم وسمها خارج الجولة.
- تنفيذ إجراءات صغيرة ومتنوعة تعتبر حاسمة لتعيين معلمات طريقة اللعب والشخصية.
يتضمن هذا القسم الكثير من محتوى البرمجة، ولكن بدلاً من كتابة كل شيء من الصفر عند إنشاء لعبة، فإنه يشجعك على الاستفادة من المكونات الموجودة، والتكرار بسرعة، واكتشاف الأنظمة التي تحتاج إلى تنفيذ مخصص لتتناسب مع رؤيتك. بعد إكمال هذا القسم، ستتعلم كيفية تنفيذ طريقة لعب قائمة على الجولات تتعقب النقاط، وتراقب حالة اللاعب، وتعرض نتائج الجولة.
تكوين مواقع الظهور
إذا كنت ستختبر اللعبة الآن، فسيظهر جميع اللاعبين عشوائيًا في إما كائن SpawnLocation في منطقة ظهور الفريق الأخضر، أو كائن SpawnLocation في منطقة ظهور الفريق الوردي. هذه تمثل مشكلة في طريقة اللعب حيث يمكن للاعبين وسم بعضهم البعض داخل كل منطقة ظهور بمجرد اختفاء مجال القوة الخاص بالخصم.
لمكافحة هذه المشكلة، تقوم لعبة وسم الليزر النموذجية بتكوين كلا موقعَي الظهور مع خاصية Neutral مضبوطة على false لتقييد لاعبي الفريق المعارض من الظهور في منطقة الظهور الخاطئة، وخاصية TeamColor مضبوطة على القيمة المقابلة لـ Team.Color من تعيين ألوان الفرق في القسم السابق من البرنامج التعليمي:


عندما ينضم لاعب إلى اللعبة، يقوم ServerScriptService ⟩ Gameplay ⟩ Rounds ⟩ spawnPlayersInMap بالتحقق من عدد اللاعبين الموجودين بالفعل في كل فريق، ثم يعيد الفريق الذي لديه أقل عدد من اللاعبين.
local function getSmallestTeam(): Team
local teams = Teams:GetTeams()
-- ترتيب الفرق بترتيب تصاعدي من الأصغر إلى الأكبر
table.sort(teams, function(teamA: Team, teamB: Team)
return #teamA:GetPlayers() < #teamB:GetPlayers()
end)
-- إرجاع الفريق الأصغر
return teams[1]
endبمجرد أن يعرف الفريق الذي لديه أقل عدد من اللاعبين، يقوم بترتيب اللاعب في ذلك الفريق، ويضبط خاصية Player.Neutral على false بحيث يمكن للاعب الظهور وإعادة الظهور فقط في موقع ظهور فريقه، ثم يضبط حالة PlayerState على SelectingBlaster، والتي ستتعلم المزيد عنها لاحقًا في البرنامج التعليمي.
local function spawnPlayersInMap(players: { Player })
for _, player in players do
player.Team = getSmallestTeam()
player.Neutral = false
player:SetAttribute(PlayerAttribute.playerState, PlayerState.SelectingBlaster)
task.spawn(function()
player:LoadCharacter()
end)
end
endإذا قمت بفحص Workspace ⟩ World ⟩ Map ⟩ Spawns، يمكنك أن ترى أن هناك موقع ظهور آخر في الخريطة: NeutralSpawn. هذا الموقع فريد من الآخرين لأنه لا يحتوي على خاصية TeamColor مضبوطة على أحد الفريقين في اللعبة؛ بدلاً من ذلك، يحتوي هذا الموقع على خاصية Neutral التي تتغير اعتمادًا على ما إذا كانت الجولة نشطة.
على سبيل المثال، إذا كانت الجولة نشطة، يتم ضبط خاصية Neutral على false بحيث يمكن لـ spawnPlayersInMap ترتيب اللاعبين في الفرق وإظهارهم في الساحة. ومع ذلك، إذا لم تكن الجولة نشطة، مثل الوقت بين جولة وأخرى، يتم ضبط خاصية Neutral على true بحيث يمكن للاعبين الظهور هناك بغض النظر عن حالة فريقهم. هذه العملية هي ما يجعل موقع الظهور Neutral مكانًا وظيفيًا للانتظار.

لتوضيح ذلك، إذا قمت بفحص ServerScriptService ⟩ Gameplay ⟩ Rounds ⟩ SpawnPlayersInLobby، والذي يعمل في نهاية الجولة، يمكنك أن ترى أنه لكل لاعب يتم تمريره إلى جدول players: { Player }، يقوم السكربت:
- بضبط خاصية Player.Neutral على true لإعادة تعيين خاصية Player.Team إلى nil تلقائيًا، مما يسمح للاعب بإعادة الظهور في الردهة عندما لا تكون الجولة نشطة، حيث يتم أيضًا ضبط خاصية Neutral على true.
- تغيير حالة PlayerState إلى InLobby لإزالة مسدس اللاعب وواجهة المستخدم المرئية من منظور الشخص الأول.
لمزيد من المعلومات حول منطقة الظهور المحايدة ووظيفتها لكل جولة، انظر إضافة جولات في القسم التالي من البرنامج التعليمي.
local function spawnPlayersInLobby(players: { Player })
for _, player in players do
player.Neutral = true
player:SetAttribute(PlayerAttribute.playerState, PlayerState.InLobby)
task.spawn(function()
player:LoadCharacter()
end)
end
endربط اللاعبين الجدد
كود Luau في الاستوديو غالبًا ما يكون مدفوعًا بالأحداث، مما يعني أن السكربتات تستمع للأحداث من خدمة Roblox، ثم تستدعي دالة استجابة. على سبيل المثال، عند إضافة لاعبين جدد إلى لعبة متعددة اللاعبين، يجب أن يكون هناك حدث يتعامل مع كل ما هو ضروري لنجاح اتصال اللاعبين. في لعبة وسم الليزر النموذجية، هذا الحدث المقابل هو Players.PlayerAdded:Connect.
Players.PlayerAdded:Connect هو جزء من عدة سكربتات في اللعبة. إذا استخدمت اختصار Ctrl/Cmd+Shift+F وبحثت عن Players.PlayerAdded:Connect، فإن النتائج توفر نقطة انطلاق جيدة لفهم الإعداد الأولي للعبة.

لتوضيح ذلك، افتح ServerScriptService ⟩ SetupHumanoid. التمييز بين Player و Character هو مفتاح لفهم هذا السكربت:
- يحتاج اللاعبون إلى اختيار مسدس وإضافتهم إلى لوحة المتصدرين. تحتاج الشخصيات إلى الظهور واستلام مسدس.
يتحقق SetupHumanoid على الفور مما إذا كان اللاعب لديه شخصية (انضم للتو) أو لا (يتم إعادة ظهوره). بعد أن يجد واحدة، يستدعي onCharacterAdded()، يحصل على نموذج Humanoid من الشخصية، ويمرره إلى ServerScriptService ⟩ SetupHumanoid ⟩ setupHumanoidAsync للتخصيص. بعد تعيين هذه القيم، ينتظر السكربت حتى تصل صحة الشخصية إلى الصفر. ستتعلم المزيد عن إعادة الظهور لاحقًا في هذا القسم من البرنامج التعليمي.
local function setupHumanoidAsync(player: Player, humanoid: Humanoid)
humanoid.DisplayDistanceType = Enum.HumanoidDisplayDistanceType.Subject
humanoid.NameDisplayDistance = 1000
humanoid.HealthDisplayDistance = 1000
humanoid.NameOcclusion = Enum.NameOcclusion.OccludeAll
humanoid.HealthDisplayType = Enum.HumanoidHealthDisplayType.AlwaysOn
humanoid.BreakJointsOnDeath = false
humanoid.Died:Wait()
onHumanoidDied(player, humanoid)
endالملاحظة المهمة مع هذا السكربت هي أن الخصائص اختيارية تمامًا، مما يعني أنه إذا قمت بإزالة الأسطر الستة الأولى من الدالة، ستظل اللعبة تعمل بشكل صحيح. بدلاً من أن تكون متطلبات وظيفية، تسمح لك كل خاصية باتخاذ قرارات تصميم تلبي أهداف طريقة اللعب الخاصة بك. على سبيل المثال:
- إذا كنت تريد أن تظهر أسماء الشخصيات على مسافات أقرب، قلل من قيمة Humanoid.NameDisplayDistance.
- إذا كنت تريد أن تظهر صحة الشخصية فقط إذا كانت أقل من 100%، اضبط Humanoid.HealthDisplayType على DisplayWhenDamaged.
- إذا كنت تريد أن تتفكك الشخصيات عندما تصل صحتها إلى 0، اضبط Humanoid.BreakJointsOnDeath على True.
إذا قمت بتغيير قيم هذه الخصائص، من المهم اختبار اللعبة حتى تتمكن من رؤية تأثير إعداداتك الجديدة. يمكنك إعادة إنشاء ما يختبره اللاعبون في محاكاة متعددة العملاء عن طريق اختيار شخصيتين على الأقل في اختبار Server & Clients من الميزانين.

مثال آخر على حدث Players.PlayerAdded:Connect هو في ServerScriptService ⟩ PlayerStateHandler. تمامًا كما في المثال السابق، يتحقق PlayerStateHandler على الفور من وجود شخصية. إذا لم يكن اللاعب في الردهة، يقوم السكربت بضبط خاصية اللاعب على حالة SelectingBlaster، وهي الحالة الأولية لجولة يمكن فيها للاعبين الاختيار من بين نوعين مختلفين من المسدسات بعد الظهور في الساحة. تتضمن هذه الحالة أيضًا مجال قوة يمنع اللاعبين من تلقي الضرر أثناء قيامهم باختيارهم.
local function onPlayerAdded(player: Player)
player.CharacterAdded:Connect(function()
if not player.Neutral then
player:SetAttribute(PlayerAttribute.playerState, PlayerState.SelectingBlaster)
onPlayerStateChanged(player, PlayerState.SelectingBlaster)
end
end)تستحق متغير معين في PlayerStateHandler المناقشة: attributeChangedConnectionByPlayer. يقوم هذا الجدول بتخزين جميع اللاعبين و Connections الخاصة بهم إلى GetAttributeChangedSignal. السبب في تخزين هذا الاتصال في جدول هو أن PlayerStateHandler يمكنه فصله عندما يغادر اللاعب اللعبة. تعمل هذه العملية كنوع من إدارة الذاكرة لمنع عدد الاتصالات من النمو بشكل متزايد بمرور الوقت.
local attributeChangedConnectionByPlayer = {}
local function onPlayerAdded(player: Player)
-- التعامل مع جميع التحديثات المستقبلية لحالة اللاعب
attributeChangedConnectionByPlayer[player] = player
:GetAttributeChangedSignal(PlayerAttribute.playerState)
:Connect(function()
local newPlayerState = player:GetAttribute(PlayerAttribute.playerState)
onPlayerStateChanged(player, newPlayerState)
end)
end
-- فصل الاتصال عند تغيير حالة اللاعب عندما يغادر اللاعب
local function onPlayerRemoving(player: Player)
if attributeChangedConnectionByPlayer[player] then
attributeChangedConnectionByPlayer[player]:Disconnect()
attributeChangedConnectionByPlayer[player] = nil
end
endيمكنك أن ترى أن كلا الوظيفتين المتصلتين في onPlayerAdded() تستدعيان onPlayerStateChanged(). خلال الإعداد الأولي بعد أن يتم ترتيب اللاعب في فريق، يتم ضبط PlayerState على SelectingBlaster، لذا فإن جملة if الأولى تقيم إلى false وتعطل BlasterState. في القسم اللاحق تنفيذ المسدسات من البرنامج التعليمي، ستتعلم المزيد من التفاصيل حول هذه العملية.
local function onPlayerStateChanged(player: Player, newPlayerState: string)
-- حالة المسدس هي 'جاهز' فقط إذا كانت حالة اللاعب هي 'يلعب'
local newBlasterState = if newPlayerState == PlayerState.Playing then BlasterState.Ready else BlasterState.Disabled
-- جدولة منطق تدمير مجال القوة عندما يبدأ اللاعب في اللعب
if newPlayerState == PlayerState.Playing then
scheduleDestroyForceField(player)
end
player:SetAttribute(PlayerAttribute.blasterStateServer, newBlasterState)
endإذا أضفت نقاط توقف أو حتى مجرد عبارة print()، يمكنك أن ترى أن onPlayerStateChanged() يتم استدعاؤها بشكل متكرر طوال اللعبة: مثل خلال الإعداد الأولي لجولة، لتعيين نفسها على المسار الرئيسي، بعد أن يختار اللاعب مسدسًا، وعندما يعود اللاعب إلى الردهة، أو موقع الظهور Neutral. علاوة على ذلك، بعد أن يختار اللاعب مسدسًا، يقوم ServerScriptService ⟩ BlasterSelectedHandler بضبط PlayerState على Playing، ويمكن لـ PlayerStateHandler أخيرًا إزالة مجال القوة عن طريق استدعاء scheduleDestroyForceField().
تخصيص مجالات القوة
بدلاً من استخدام تنفيذ مخصص، تستخدم لعبة وسم الليزر النموذجية فئة ForceField المدمجة في الاستوديو لمنع اللاعبين من تلقي الضرر أثناء اختيارهم لمسدسهم. يضمن ذلك أن الشرط الوحيد لظهور اللاعبين مع مجال قوة هو تضمين مواقع الظهور مع خاصية SpawnLocation.Duration التي تزيد عن 0. تستخدم العينة قيمة تعسفية قدرها 9,999 لتمكين مجالات القوة، ثم تتعامل مع المدة الفعلية برمجيًا في ReplicatedStorage ⟩ ForceFieldClientVisuals.
على غرار setupHumanoidAsync، فإن معظم الأسطر في ForceFieldClientVisuals اختيارية. على سبيل المثال، إذا قمت بالتعليق على محتويات الدالة كما يفعل السكربت التالي، ستستخدم اللعبة مجال القوة المتلألئ الافتراضي بدلاً من السكربت السداسي في StarterGui ⟩ ForceFieldGui.
local function onCharacterAddedAsync(character: Model)
-- local forceField = character:WaitForChild("ForceField", 3)
-- if not forceField then
-- return
-- end
-- forceField.Visible = false
-- localPlayer.PlayerGui:WaitForChild("ForceFieldGui").Enabled = true
-- forceField.Destroying:Wait()
-- localPlayer.PlayerGui.ForceFieldGui.Enabled = false
endنظرًا لأن مجال القوة المخصص هو واجهة مستخدم بدلاً من ParticleEmitter جديد، فإن سكربت ForceFieldClientVisuals يؤثر فقط على المرئيات من منظور الشخص الأول لكل لاعب، وليس المرئيات من منظور الشخص الثالث عندما ينظر اللاعبون إلى لاعبين آخرين. تحتفظ المرئيات من منظور الشخص الثالث بالمظهر الافتراضي لـ Roblox. لمزيد من المعلومات حول تعديل مجالات القوة، انظر ForceField.Visible.


تعتبر مجالات القوة مفيدة لأنها توفر للاعبين الوقت الكافي بين الظهور وإعادة الظهور دون الحاجة إلى القلق بشأن اللاعبين الأعداء، ولكن في النهاية تحتاج إلى الاختفاء من أجل طريقة اللعب الرئيسية في وسم الليزر. السكربت الذي يتعامل مع إزالة مجال القوة موجود في ReplicatedStorage ⟩ scheduleDestroyForceField، ويتحقق من ثلاثة شروط فريدة:
- بعد أن يختار اللاعبون مسدسًا، تحتاج مجالات القوة إلى أن تدوم لفترة كافية للسماح للاعبين بالتكيف مع محيطهم.
- خلال هذه الفترة من التكيف، لا يمكن أن تكون مجالات القوة ميزة، لذا تحتاج إلى الاختفاء في اللحظة التي يطلق فيها اللاعب مسدسه.
- تحتاج مجالات القوة إلى الاختفاء عندما يعيد اللاعبون تعيين شخصياتهم إما قبل إطلاق النار أو قبل انتهاء وقت مجال القوة.
تستدعي كل من هذه الفحوصات في سكربت scheduleDestroyForceField دالة endForceField() لهذه الشروط.
-- إنهاء مجال القوة إذا أطلق اللاعب
local blasterStateAttribute = getBlasterStateAttribute()
attributeChangedConnection = player:GetAttributeChangedSignal(blasterStateAttribute):Connect(function()
local currentBlasterState = player:GetAttribute(blasterStateAttribute)
if currentBlasterState == BlasterState.Blasting then
endForceField()
end
end)
-- إنهاء مجال القوة إذا أعاد اللاعب تعيين شخصيته
characterRespawnedConnection = player.CharacterRemoving:Connect(endForceField)
-- إنهاء مجال القوة بعد 8 ثوانٍ
task.delay(MAX_FORCE_FIELD_TIME, endForceField)تتضمن endForceField() جملة if تبدو غريبة حول المتغير forceFieldEnded. نظرًا لأن الفحوصات تعمل بالتتابع، يمكن للسكربت استدعاء دالة endForceField() مرتين أو حتى ثلاث مرات. يضمن المتغير forceFieldEnded أن الدالة تحاول تدمير مجال القوة مرة واحدة فقط.
local function endForceField()
if forceFieldEnded then
return
end
forceFieldEnded = true
attributeChangedConnection:Disconnect()
characterRespawnedConnection:Disconnect()
destroyForceField(player)
endالتعامل مع حالة العميل
بينما يركز معظم هذا القسم على ServerScriptService ⟩ PlayerStateHandler، هناك سكربت آخر بنفس الاسم في ReplicatedStorage. السبب في الانقسام هو بنية العميل-الخادم:
يحتاج العميل إلى فهم معلومات حالة اللاعب حتى يتمكن من الاستجابة بشكل مناسب في الوقت الحقيقي، مثل عرض عناصر واجهة المستخدم الصحيحة، أو تمكين اللاعبين من الحركة وإطلاق النار.
يحتاج الخادم إلى كل هذه المعلومات نفسها حتى يتمكن من منع الاستغلال. على سبيل المثال، يحتاج الخادم أيضًا إلى حالة اللاعب لأداء إجراءات مثل الظهور وتجهيز الشخصيات، وتعطيل مجالات القوة، وعرض لوحة المتصدرين. لهذا السبب يوجد هذا السكربت في ReplicatedStorage وليس في موقع عميل بحت.
لمشاهدة هذه المنطق الأساسية، راجع السكربت التالي في ReplicatedStorage ⟩ PlayerStateHandler الذي يتحقق من حالة المستخدم الحالية، ثم يستدعي الدالة المناسبة التي تتعامل مع الإجراءات المقابلة لتلك الحالة.
local function onPlayerStateChanged(newPlayerState: string)
if newPlayerState == PlayerState.SelectingBlaster then
onSelectingBlaster()
elseif newPlayerState == PlayerState.Playing then
onPlaying()
elseif newPlayerState == PlayerState.TaggedOut then
onTaggedOut()
elseif newPlayerState == PlayerState.InLobby then
onInLobby()
else
warn(`حالة اللاعب غير صالحة ({newPlayerState})`)
end
endتكون جميع استجابات الأحداث مجمعة منطقيًا معًا في هذا السكربت لأنها تتطلب سلوكًا مشابهًا لتمكين أو تعطيل عناصر التحكم في اللاعب، وحركة الكاميرا، وواجهة المستخدم التي تكون مرئية. على سبيل المثال، خلال اختيار المسدس، يحتاج اللاعبون إلى أن يكونوا غير قابلين للتأثر وغير قادرين على الحركة. يتعامل الخادم بالفعل مع مجال القوة، لكن العميل يتعامل مع الحركة. لتوضيح ذلك، إذا قمت بالتحقق من المنطق لدالة onSelectingBlaster()، يمكنك أن ترى أن العميل يعطل حركة اللاعب أثناء اختيارهم لمسدس.
local function onSelectingBlaster()
togglePlayerCamera(true)
togglePlayerMovement(false)
setGuiExclusivelyEnabled(playerGui.PickABlasterGui)
localPlayer:SetAttribute(PlayerAttribute.blasterStateClient, BlasterState.Disabled)
endتكون دالة onPlaying() بسيطة بالمثل. تقوم بتمكين الحركة، والانتقال إلى واجهة العرض الرئيسية (HUD)، وتمكين المسدس، واستدعاء نفس دالة مجال القوة كما في الخادم.
local function onPlaying()
togglePlayerMovement(true)
setGuiExclusivelyEnabled(playerGui.HUDGui)
localPlayer:SetAttribute(PlayerAttribute.blasterStateClient, BlasterState.Ready)
scheduleDestroyForceField()
endإعادة ظهور الشخصيات
تتعامل لعبة وسم الليزر النموذجية مع إعادة ظهور الشخصيات في الجولة من خلال حالة onTaggedOut() في ReplicatedStorage ⟩ PlayerStateHandler. مثل حالة onSelectingBlaster() و onPlaying()، تقوم onTaggedOut() بتحفيز سلوك فريد وفقًا للتغييرات في خاصية playerState. على وجه التحديد، تعطل حركة اللاعب، وتعرض واجهة إعادة الظهور، وتعطل المسدس.
local function onTaggedOut()
-- تعطيل التحكم أثناء وسمه خارجًا
togglePlayerMovement(false)
togglePlayerCamera(false)
setGuiExclusivelyEnabled(playerGui.OutStateGui)
-- تعطيل المسدس أثناء وسمه خارجًا
localPlayer:SetAttribute(PlayerAttribute.blasterStateClient, BlasterState.Disabled)
endإذا كنت ترغب في اختبار هذا السلوك، يمكنك الضغط على Esc، والتنقل إلى علامة التبويب الإعدادات، ثم النقر على زر إعادة تعيين الشخصية. لاحظ أنه عند تشغيل شاشة إعادة الظهور، لا يمكنك الحركة، أو تدوير الكاميرا، أو إطلاق مسدسك.


من المهم ملاحظة أن هذا السكربت لا يعيد فعليًا ظهور الشخصيات، بل يوقفهم عن التصرف ويقدم ملاحظات مرئية للاعبين بأن الخادم يعيد ظهور شخصياتهم. لتوضيح ذلك، إذا قمت بفحص ServerScriptService ⟩ SetupHumanoid ⟩ setupHumanoidAsync ⟩ onHumanoidDied، يقوم السكربت بضبط PlayerState على TaggedOut (بشكل أساسي notifying ReplicatedStorage ⟩ PlayerStateHandler)، ويضيف بعض المؤشرات المرئية. المنطق الفعلي لإعادة الظهور هو سلوك مدمج في Roblox.
عندما يعيد اللاعبون الظهور في الجولة، يظهرون في موقع ظهور فريقهم وفقًا لخاصية SpawnLocation.TeamColor. لتخصيص وقت إعادة الظهور، يمكنك إضافة السطر التالي إلى أعلى SetupHumanoid. لمعرفة المزيد حول هذه التقنية، انظر Players.RespawnTime.
local Players = game:GetService("Players")
Players.RespawnTime = 10 -- سطر جديد، بالثوانيإعدادات متنوعة
كجزء من الإعداد الأولي، تقوم لعبة وسم الليزر النموذجية أيضًا بتنفيذ بعض الخطوات الصغيرة، ولكن الحرجة:
تتضمن اللعبة سكربتًا فارغًا يسمى StarterPlayer ⟩ StarterCharacterScripts ⟩ Health الذي يعطل تجديد الصحة الافتراضي في Roblox. للحصول على شرح لسلوك هذه الخاصية، انظر Humanoid.Health.
تستخدم اللعبة كاميرا من منظور الشخص الأول عن طريق ضبط خاصية StarterPlayer.CameraMode.LockFirstPerson. لاحظ أنه إذا كنت ترغب في السماح للمستخدمين بالتغيير بين كاميرات الشخص الأول والثالث، يجب عليك تغيير الخاصية برمجيًا بدلاً من ضبطها مرة واحدة في الاستوديو، وتعديل عناصر التحكم وواجهة المستخدم لتعويض التغيير في المنظور.
تستخدم اللعبة لوحة المتصدرين المدمجة في Roblox مع وحدة "النقاط"، والتي يكسبها اللاعبون في كل مرة يوسمون لاعبًا آخر. يمكنك رؤية التكوين في ServerScriptService ⟩ SetupLeaderboard، ولكن لوحات المتصدرين داخل اللعبة تقدم نظرة شاملة. لاحظ أن onPlayerTagged يضيف نقاطًا إلى لوحة المتصدرين، والتي ستتعلم عنها في إضافة جولات و كشف الضربات.
الآن بعد أن أصبح بإمكان اللاعبين الظهور، واختيار مسدس، واستهدافه من منظور الشخص الأول، يعلمك القسم التالي عن السكربتات وراء إنشاء طريقة لعب قائمة على الجولات.