تنفيذ سلوك البازوكا هو عملية برمجة ميكانيك الانفجار في ألعاب إطلاق النار من منظور الشخص الأول. بينما يمكن للاعبين إطلاق النار بنقرة واحدة أو ضغط على زر، فإن إنشاء سلوك انفجار مرضي ودقيق أمر مهم لأنه يعزز استمتاع اللاعبين باللعبة بشكل عام.
باستخدام لعبة الليزر تاج النموذجية كمرجع، يعلمك هذا القسم من البرنامج التعليمي عن السكربتات وراء تنفيذ سلوك البازوكا لنوعين مختلفين من البازوكا، بما في ذلك الإرشادات حول:
- اكتشاف متى يضغط اللاعبون على زر الانفجار.
- التحقق مما إذا كان بإمكان اللاعب استخدام بازوكته إذا ضغط مؤخرًا على زر الانفجار.
- توليد بيانات الانفجار التي تخبر الخادم من الذي بدأ الانفجار، ومن أين جاء، وما كانت الوجهة النهائية لكل شعاع ليزر.
- إبلاغ الخادم ببيانات الانفجار حتى يتمكن من تنفيذ الإجراءات المناسبة إذا تصادم الانفجار مع لاعب آخر.
- إعادة تعيين البازوكا بين كل انفجار لمنح البازوكا الوقت الكافي لتبرد قبل أن تتمكن من الانفجار مرة أخرى.
بعد الانتهاء من هذا القسم، ستتعلم عن السكربتات التي تسمح للبازوكا باكتشاف متى تصطدم انفجاراتها بلاعبين آخرين، ثم خصم الكمية المناسبة من الصحة وفقًا لكل نوع من البازوكا.
اكتشاف إدخال اللاعب
الخطوة الأولى لتنفيذ سلوك البازوكا هي الاستماع لمتى يضغط اللاعب على زر الانفجار. يعتمد نوع الإدخال الذي يستخدمه اللاعبون للضغط على زر الانفجار على الجهاز الذي يستخدمونه للوصول إلى اللعبة. على سبيل المثال، تدعم لعبة الليزر تاج النموذجية التحكم بالماوس ولوحة المفاتيح، وأجهزة التحكم، والتحكم باللمس. يمكنك رؤية كل هذه الأنواع من الإدخال في ReplicatedStorage ⟩ UserInputHandler.
يستخدم هذا السكربت العميل ContextActionService لربط MouseButton1 و ButtonR2 بإجراء الانفجار. هذا يعني أنه في كل مرة يضغط فيها اللاعب على زر الماوس الأيسر أو زر R2 في جهاز التحكم، يتم تفعيل شعاع ليزر ينفجر من البازوكا. لاحظ أن HUDGui يحتوي على زر للانفجار على الأجهزة المحمولة، والذي يتم توصيله لاحقًا في السكربت.
ContextActionService:BindAction("_", onBlasterActivated, false,
Enum.UserInputType.MouseButton1,
Enum.KeyCode.ButtonR2
)ملاحظة أخرى مهمة هي استخدام Enum.UserInputState.Begin في تعريف onBlasterActivated(). العديد من تفاعلات واجهة المستخدم، مثل اختيار بازوكة في هذا المثال، لا تحدث حتى بعد أن يرتفع زر الماوس (Enum.UserInputState.End)، مما يمنح المستخدمين فرصة أخيرة لتجنب التفاعل. ومع ذلك، لا يشعر ميكانيك الانفجار بالاستجابة ما لم يحدث في اللحظة التي ينخفض فيها الزر.
لتوضيح ذلك، يمكنك تغيير Enum.UserInputState.Begin إلى Enum.UserInputState.End، ثم اختبار اللعبة لترى كيف يؤثر استجابة الانفجار على طريقة اللعب. على سبيل المثال، إذا كان بإمكان اللاعبين الضغط على الزر دون تفعيل الانفجار، كيف قد يغير ذلك تجربتهم أثناء وسم لاعبين آخرين؟
local function onBlasterActivated(_actionName: string,
inputState: Enum.UserInputState, _inputObject: InputObject)
if inputState == Enum.UserInputState.End then -- updated line, be sure to change back
attemptBlastClient()
end
endتحقق مما إذا كان بإمكان اللاعب الانفجار
بعد أن يكتشف UserInputHandler ضغط الزر أو لمس الشاشة، يستدعي ReplicatedStorage ⟩ Blaster ⟩ attemptBlastClient للتحقق مما إذا كان بإمكان اللاعب الانفجار أم لا. مثل معظم الفحوصات في لعبة الليزر تاج النموذجية، يحدث ذلك مرتين: أولاً على العميل، ثم لاحقًا على الخادم. ثم يستدعي attemptBlastClient ReplicatedStorage ⟩ Blaster ⟩ canLocalPlayerBlast لإجراء فحص بسيط لخاصية اللاعب blasterStateClient:
local function canLocalPlayerBlast(): boolean
return localPlayer:GetAttribute(PlayerAttribute.blasterStateClient) == BlasterState.Ready
endإذا قمت بفحص ReplicatedStorage ⟩ Blaster ⟩ BlasterState، يمكنك أن ترى أن اللعبة تحتوي على ثلاث حالات بازوكا: Ready، Blasting، و Disabled. لرؤية تأثير كل من هذه الحالات، يمكنك اختبار اللعبة، واختيار لاعبك تحت خدمة Players، ثم ملاحظة خاصية blasterStateClient في نافذة Properties. لاحظ كيف تعرض Disabled أثناء اختيار بازوكتك، و Ready معظم الوقت، و Blasting لأقل من ثانية بعد الضغط على الزر.
تمنع هذه الفاصلة الطفيفة من قدرتك على الانفجار بسرعة كما يمكنك النقر. على سبيل المثال، إذا قمت بتغيير الوظيفة لتعيد دائمًا true، يمكنك الانفجار بسرعة دون أي تأخير، وهو ما لا يتناسب مع طريقة لعب الليزر تاج.
local function canLocalPlayerBlast(): boolean
return true -- updated line, be sure to change back
endتوليد بيانات الانفجار
بعد التحقق من أن بازوكة اللاعب في حالة Ready، يستدعي attemptBlastClient ReplicatedStorage ⟩ attemptBlastClient ⟩ blastClient. الخطوة الأولى التي تتخذها blastClient هي تعيين خاصية اللاعب blasterStateClient إلى Blasting، مما يتجنب حالة إطلاق النار السريع السابقة.
الخطوة التالية هي توليد بيانات الانفجار. إذا قمت بمراجعة ReplicatedStorage ⟩ Blaster ⟩ BlastData، يمكنك أن ترى أن كل انفجار يتكون من ثلاث قطع من المعلومات:
- اللاعب الذي يبدأ الانفجار.
- DataType.CFrame التي تمثل نقطة انطلاق الانفجار.
- جدول RayResult الذي يحتوي على الوجهة النهائية لكل شعاع ليزر واللاعب المصاب، إذا أصاب لاعبًا آخر.
لتوليد هذه البيانات، تستدعي blastClient ReplicatedStorage ⟩ attemptBlastClient ⟩ blastClient ⟩ generateBlastData، والتي يمكنك مراجعتها أدناه.
local function generateBlastData(): BlastData.Type
local blasterConfig = getBlasterConfig()
local rayDirections = getDirectionsForBlast(
currentCamera.CFrame, blasterConfig)
local rayResults = castLaserRay(
localPlayer, currentCamera.CFrame.Position, rayDirections)
local blastData: BlastData.Type = {
player = localPlayer,
originCFrame = currentCamera.CFrame,
rayResults = rayResults,
}
return blastData
endتبدأ هذه الوظيفة باستخدام getBlasterConfig لاسترداد نوع بازوكة اللاعب. يوفر النموذج نوعين من البازوكا: واحدة تنتج عدة أشعة مع انتشار أفقي واسع، وأخرى تنتج شعاعًا واحدًا. يمكنك العثور على تكويناتهما في ReplicatedStorage ⟩ Instances ⟩ LaserBlastersFolder.
ثم تستخدم الوظيفة currentCamera.CFrame كنقطة انطلاق للانفجار، وتمريرها إلى getDirectionsForBlast. في هذه المرحلة، لم يعد الأمر يتعلق بالبازوكا، بل بالشعاع الليزري، الذي ستتعلم المزيد عنه في قسم اكتشاف الاصطدامات من البرنامج التعليمي. أخيرًا، بعد إنشاء جدول rayResults، تمتلك generateBlastData كل المعلومات التي تحتاجها لإرجاع بيانات الانفجار إلى blastClient.
إبلاغ الخادم
بمجرد أن يكون لدى blastClient بيانات كاملة للانفجار، يطلق حدثين:
local laserBlastedBindableEvent = ReplicatedStorage.Instances.LaserBlastedBindableEvent
local laserBlastedEvent = ReplicatedStorage.Instances.LaserBlastedEvent
laserBlastedBindableEvent:Fire(blastData)
laserBlastedEvent:FireServer(blastData)يُعلم BindableEvent السكربتات العميلة الأخرى بالانفجار. على سبيل المثال، يستخدم ReplicatedStorage ⟩ FirstPersonBlasterVisuals هذا الحدث لمعرفة متى يجب عرض التأثيرات البصرية، مثل رسوم الانفجار وشريط التبريد. بالمثل، يُعلم RemoteEvent السكربتات على الخادم بالانفجار، مما يبدأ في معالجة الانفجار في ServerScriptService ⟩ LaserBlastHandler.
local function onLaserBlastedEvent(playerBlasted: Player, blastData: BlastData.Type)
local validatedBlastData = getValidatedBlastData(playerBlasted, blastData)
if not validatedBlastData then
return
end
if not canPlayerBlast(playerBlasted) then
return
end
blastServer(playerBlasted)
processTaggedPlayers(playerBlasted, blastData)
for _, replicateToPlayer in Players:GetPlayers() do
if playerBlasted == replicateToPlayer then
continue
end
replicateBlastEvent:FireClient(replicateToPlayer, playerBlasted, blastData)
end
endللمساعدة في منع الغش، يجب على الخادم التحقق من جميع البيانات التي يرسلها كل عميل. تشمل هذه الفحوصات:
- هل BlastData جدول؟ هل يحتوي على Class.CFrame وجدول آخر يسمى rayResults؟
- هل اللاعب لديه بازوكة مزودة؟
- هل لدى اللاعب شخصية وموقع داخل العالم؟
- بعد إرسال بيانات الانفجار، هل انتقل اللاعب مسافة مفرطة بعيدًا عن المكان الذي انفجر فيه شعاع الليزر؟
تشمل هذه الفحص الأخير قرارًا قضائيًا، ووفقًا لزمن تأخير الخادم وسرعة حركة اللاعب، قد تقرر أن القيم المختلفة مفرطة بالنسبة للعبتك الخاصة. لتوضيح كيفية اتخاذ هذا القرار، يمكنك الحصول على فكرة عن مقدار التغيير في الموضع النموذجي عن طريق إضافة عبارة طباعة في getValidatedBlastData واختبار اللعبة.
local distanceFromCharacterToOrigin = blastData.originCFrame.Position - rootPartCFrame.Position
print(distanceFromCharacterToOrigin.Magnitude) -- updated line, be sure to remove
if distanceFromCharacterToOrigin.Magnitude > ToleranceValues.DISTANCE_SANITY_CHECK_TOLERANCE_STUDS then
warn(`Player {player.Name} failed an origin sanity check while blasting`)
return
endبينما تتحرك وتنفجر، لاحظ المخرجات. قد تبدو شيئًا مثل هذا:
1.9019629955291748
3.1549558639526367
2.5742883682250977
4.8044586181640625
2.6434271335601807إذا قمت بزيادة سرعة الحركة للاعبين في ReplicatedStorage ⟩ PlayerStateHandler ⟩ togglePlayerMovement، ثم اختبرت مرة أخرى، من المحتمل أن تواجه العديد من الفحوصات الفاشلة بسبب الحركة المفرطة بين الانفجارات.
local ENABLED_WALK_SPEED = 60 -- updated line, be sure to change backثم يقوم الخادم بما يلي:
- يتحقق من صحة rayResults.
- يتحقق مما إذا كان بإمكان اللاعب الانفجار.
- يعيد تعيين حالة البازوكا.
- يقلل الصحة لأي لاعبين تم وسمهم.
- يكرر الانفجار لجميع اللاعبين الآخرين حتى يتمكنوا من رؤية التأثيرات البصرية من منظور الشخص الثالث.
لمزيد من المعلومات حول هذه العمليات على الخادم، انظر قسم اكتشاف الاصطدامات من البرنامج التعليمي.
إعادة تعيين البازوكا
في لعبة الليزر تاج النموذجية، تستخدم البازوكا ميكانيك الحرارة. بدلاً من إعادة التحميل بعد عدد محدد من الانفجارات، تحتاج إلى وقت "لتبرد" بين كل انفجار. يحدث نفس تأخير التبريد هذا على كل من العميل (blastClient) والخادم (blastServer)، مع عمل الخادم كمصدر للحقيقة.
local blasterConfig = getBlasterConfig(player)
local secondsBetweenBlasts = blasterConfig:GetAttribute("secondsBetweenBlasts")
task.delay(secondsBetweenBlasts, function()
local currentState = player:GetAttribute(PlayerAttribute.blasterStateServer)
if currentState == BlasterState.Blasting then
player:SetAttribute(PlayerAttribute.blasterStateServer, BlasterState.Ready)
end
end)تعتبر خاصية secondsBetweenBlasts جزءًا من تكوين البازوكا في ReplicatedStorage ⟩ Instances ⟩ LaserBlastersFolder. بعد مرور تأخير secondsBetweenBlasts، يمكن للاعب الانفجار مرة أخرى، وتكرر العملية بالكامل. لمساعدة اللاعب على فهم متى يمكنه الانفجار مرة أخرى، تتضمن اللعبة شريط تبريد.
في هذه المرحلة، يمكن للاعبين الظهور وإعادة الظهور، والتصويب والانفجار، ولكن لا يزال يتعين على اللعبة تحديد نتائج كل انفجار. في القسم التالي من البرنامج التعليمي، ستتعلم كيفية برمجة القدرة للبازوكا لاكتشاف متى يصطدم الانفجار بلاعب آخر، ثم تقليل الكمية المناسبة من صحة اللاعب وفقًا لإعدادات البازوكا.