تنفيذ سلوك البازوكا هي عملية برمجة ميكانيك الانفجار في تجارب مطلق النار من منظور الشخص الأول. بينما يمكن للاعبين أن يقوموا بالانفجار بنقرة واحدة أو ضغطة على زر، فإن إنشاء سلوك انفجار مرضي ودقيق أمر مهم لأنه يعزز استمتاع اللاعبين باللعبة بشكل عام.
باستخدام تجربة الوسم بالليزر النموذجية كمرجع، يُعلمك هذا القسم من الدليل عن السكربتات وراء تنفيذ سلوك البازوكا لنوعين مختلفين من البازوكا، بما في ذلك إرشادات حول:
- اكتشاف متى يضغط اللاعبون على زر الانفجار.
- التحقق مما إذا كان اللاعب يمكنه استخدام بازوكته إذا ضغط مؤخرًا على زر الانفجار.
- إنشاء بيانات الانفجار التي تخبر الخادم بمن بدأ الانفجار، من أين جاء، وما كانت الوجهة النهائية لكل شعاع ليزر.
- إبلاغ الخادم ببيانات الانفجار حتى يمكنه القيام بالإجراءات المناسبة إذا تصادم الانفجار مع لاعب آخر.
- إعادة ضبط البازوكا بين كل انفجار لمنح البازوكا وقتًا كافيًا لتبرد قبل أن تتمكن من الانفجار مرة أخرى.
بعد إكمال هذا القسم، ستتعلم عن السكربتات التي تتيح للبازوكا اكتشاف متى تتصادم إنفجاراتها مع لاعبين آخرين، ثم خصم الكمية المناسبة من الصحة وفقًا لكل نوع من البازوكا.
اكتشاف إدخال اللاعب
الخطوة الأولى لتنفيذ سلوك البازوكا هي الاستماع عندما يضغط لاعب على زر الانفجار. يعتمد نوع الإدخال الذي يستخدمه اللاعبون للضغط على زر الانفجار على الجهاز الذي يستخدمونه للوصول إلى التجربة. على سبيل المثال، تدعم تجربة الوسم بالليزر النموذجية الضوابط باستخدام الماوس ولوحة المفاتيح، وأجهزة التحكم، والضوابط اللمسية. يمكنك رؤية كل من هذه الأنواع من الإدخال في ReplicatedStorage ⟩ UserInputHandler.
يستخدم هذا السكربت العميل ContextActionService لربط MouseButton1 و ButtonR2 للعملية الانفجارية. وهذا يعني أنه في كل مرة يضغط فيها اللاعب على زر الماوس الأيسر أو زر R2 في جهاز التحكم، يتم تشغيل شعاع الليزر للخروج من البازوكا. لاحظ أن HUDGui يحتوي على زر للانفجار على الأجهزة المحمولة، الذي يتصل به لاحقًا في السكربت.
UserInputHandlerContextActionService:BindAction("_", onBlasterActivated, false,Enum.UserInputType.MouseButton1,Enum.KeyCode.ButtonR2)
ملاحظة أخرى مهمة هي استخدام Enum.UserInputState.Begin في تعريف onBlasterActivated(). لا تحدث العديد من تفاعلات واجهة المستخدم، مثل اختيار بازوكا في هذا المثال، حتى بعد أن يرتفع زر الماوس (Enum.UserInputState.End)، مما يمنح المستخدمين فرصة أخيرة لتجنب التفاعل. ومع ذلك، لا تشعر ميكانيكية الانفجار بالاستجابة ما لم تحدث في اللحظة التي ينخفض فيها الزر.
لتوضيح، يمكنك تغيير Enum.UserInputState.Begin إلى Enum.UserInputState.End، ثم قم بتجربة اللعب لترى كيف يؤثر استجابة الانفجار على تجربة اللعبة. على سبيل المثال، إذا كان بإمكان اللاعبين الضغط على الزر دون تنشيط الانفجار، كيف قد يغير ذلك تجربتهم أثناء الوسم للاعبين آخرين؟
UserInputHandler
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:
canLocalPlayerBlast
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، يمكنك انفجار بازوكتك بسرعة دون أي تأخير، وهو أمر غير واقعي لتجربة الوسم بالليزر.
canLocalPlayerBlast
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، والتي يمكنك مراجعتها أدناه.
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 بيانات كاملة عن الانفجار، تقوم بإطلاق حدثين:
blastClientlocal laserBlastedBindableEvent = ReplicatedStorage.Instances.LaserBlastedBindableEventlocal laserBlastedEvent = ReplicatedStorage.Instances.LaserBlastedEventlaserBlastedBindableEvent:Fire(blastData)laserBlastedEvent:FireServer(blastData)
يبلغ BindableEvent السكربتات العميلة الأخرى عن الانفجار. على سبيل المثال، تستخدم ReplicatedStorage ⟩ FirstPersonBlasterVisuals هذا الحدث لمعرفة متى تعرض التأثيرات البصرية، مثل الرسوم المتحركة للاحتراق وشريط التهدئة. بالمثل، يقوم RemoteEvent بإبلاغ سكربتات الخادم عن الانفجار، والذي يبدأ معالجة الانفجار في ServerScriptService ⟩ LaserBlastHandler.
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 وتجربة اللعبة.
getValidatedBlastDatalocal distanceFromCharacterToOrigin = blastData.originCFrame.Position - rootPartCFrame.Positionprint(distanceFromCharacterToOrigin.Magnitude) -- updated line, be sure to removeif distanceFromCharacterToOrigin.Magnitude > ToleranceValues.DISTANCE_SANITY_CHECK_TOLERANCE_STUDS thenwarn(`Player {player.Name} failed an origin sanity check while blasting`)returnend
بينما تتحرك وتنفجر، لاحظ الإخراج. قد يبدو مثل هذا:
1.90196299552917483.15495586395263672.57428836822509774.80445861816406252.6434271335601807
إذا زدت من سرعة الحركة للاعبين في ReplicatedStorage ⟩ PlayerStateHandler ⟩ togglePlayerMovement، ثم اختبرت اللعبة مرة أخرى، ستواجه على الأرجح العديد من الفحوصات الفاشلة بسبب الحركة الزائدة بين الانفجارات.
togglePlayerMovementlocal ENABLED_WALK_SPEED = 60 -- updated line, be sure to change back
ثم يقوم الخادم بالتالي:
- التحقق من صحة rayResults.
- التحقق مما إذا كان بإمكان اللاعب الانفجار.
- إعادة ضبط حالة البازوكا.
- تقليل الصحة لأي لاعبين تم وسمهم.
- تكرار الانفجار لجميع اللاعبين الآخرين حتى يتمكنوا من رؤية التأثيرات البصرية من منظور الشخص الثالث.
لمزيد من المعلومات حول هذه العمليات على الخادم، انظر قسم كشف الاصطدامات من الدليل.
إعادة ضبط البازوكا
في تجربة الوسم بالليزر النموذجية، تستخدم البازوكا ميكانيكية الحرارة. بدلاً من إعادة التحميل بعد عدد معين من الانفجارات، فهي تحتاج إلى وقت "لتبرد" بين كل انفجار. يحدث نفس التأخير في التهدئة على كل من العميل (blastClient) والخادم (blastServer)، مع كون الخادم هو مصدر الحقيقة.
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، يمكن للاعب الانفجار مرة أخرى، وتكرر العملية بأكملها. لمساعدة اللاعب على فهم متى يمكنه الانفجار مرة أخرى، تتضمن التجربة شريط تهدئة.
في هذه المرحلة، يمكن للاعبين الظهور وإعادة الظهور، والتصويب والانفجار، ولكن لا تزال التجربة بحاجة إلى تحديد نتائج كل انفجار. في القسم التالي من الدليل، ستتعلم كيفية برمجة قدرة البازوكا لاكتشاف متى يصطدم الانفجار بلاعب آخر، ثم تقليل الكمية المناسبة من صحة اللاعب وفقًا لإعدادات البازوكا.