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

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

في أعلى السكربت، أعلن عن ثابت يسمى MAX_MOUSE_DISTANCE بقيمة 1000.
أنشئ دالة تسمى getWorldMousePosition.
local tool = script.Parentlocal MAX_MOUSE_DISTANCE = 1000local function getWorldMousePosition()endlocal function toolEquipped()tool.Handle.Equip:Play()endlocal function toolActivated()tool.Handle.Activate:Play()end-- ربط الأحداث بالوظائف المناسبةtool.Equipped:Connect(toolEquipped)tool.Activated:Connect(toolActivated)استخدم دالة GetMouseLocation من UserInputService للحصول على موقع الماوس ثنائي الأبعاد على الشاشة. خصص ذلك لمتغير يسمى mouseLocation.
local UserInputService = game:GetService("UserInputService")local tool = script.Parentlocal MAX_MOUSE_DISTANCE = 1000local function getWorldMousePosition()local mouseLocation = UserInputService:GetMouseLocation()end
الآن بعد أن تمت معرفة موقع الماوس ثنائي الأبعاد، يمكن استخدام خصائصه X وY كمعلمات لدالة Camera:ViewportPointToRay()، التي تنشئ Ray من الشاشة إلى العالم ثلاثي الأبعاد.
استخدم خصائص X وY من mouseLocation كوسائط لدالة ViewportPointToRay(). خصص ذلك لمتغير يسمى screenToWorldRay.
local function getWorldMousePosition()local mouseLocation = UserInputService:GetMouseLocation()-- إنشاء شعاع من موقع الماوس ثنائي الأبعادlocal screenToWorldRay = workspace.CurrentCamera:ViewportPointToRay(mouseLocation.X, mouseLocation.Y)end
حان الوقت لاستخدام دالة Raycast للتحقق مما إذا كان الشعاع يصطدم بكائن. هذا يتطلب نقطة البداية واتجاهًا: في هذا المثال، ستستخدم خصائص الأصل والاتجاه من screenToWorldRay.
طول متجه الاتجاه يحدد مدى سفر الشعاع. يحتاج الشعاع إلى أن يكون بطول MAX_MOUSE_DISTANCE، لذلك سيتعين عليك ضرب متجه الاتجاه بـ MAX_MOUSE_DISTANCE.
أعلن عن متغير يسمى directionVector وخصص له قيمة screenToWorldRay.Direction مضروبة في MAX_MOUSE_DISTANCE.
local function getWorldMousePosition()local mouseLocation = UserInputService:GetMouseLocation()-- إنشاء شعاع من موقع الماوس ثنائي الأبعادlocal screenToWorldRay = workspace.CurrentCamera:ViewportPointToRay(mouseLocation.X, mouseLocation.Y)-- متجه الاتجاه الوحدوي للشعاع مضروبًا في مسافة قصوىlocal directionVector = screenToWorldRay.Direction * MAX_MOUSE_DISTANCEاستدعِ دالة Raycast من workspace، مع تمرير خاصية Origin من screenToWorldRay كأول وسيلة وdirectionVector كالثانية. خصص ذلك لمتغير يسمى raycastResult.
local function getWorldMousePosition()local mouseLocation = UserInputService:GetMouseLocation()-- إنشاء شعاع من موقع الماوس ثنائي الأبعادlocal screenToWorldRay = workspace.CurrentCamera:ViewportPointToRay(mouseLocation.X, mouseLocation.Y)-- متجه الاتجاه الوحدوي للشعاع مضروبًا في مسافة قصوىlocal directionVector = screenToWorldRay.Direction * MAX_MOUSE_DISTANCE-- إجراء شعاع من أصل الشعاع نحو اتجاههlocal raycastResult = workspace:Raycast(screenToWorldRay.Origin, directionVector)
معلومات الاصطدام
إذا وجدت عملية الشعاع كائنًا تم إصابته بالشعاع، ستعيد RaycastResult، والتي تحتوي على معلومات حول الاصطدام بين الشعاع والجسم.
| خاصية RaycastResult | الوصف |
|---|---|
| Instance | الـ BasePart أو خلية Terrain التي تقاطع معها الشعاع. |
| Position | حيث حدثت تقاطع؛ عادةً نقطة مباشرة على سطح جزء أو تضاريس. |
| Material | المادة عند نقطة الاصطدام. |
| Normal | متجه الوحدة للوجه الذي تم تقاطعه. يمكن استخدام ذلك لتحديد الاتجاه الذي يشير إليه الوجه. |
ستكون خاصية Position هي موقع الجسم الذي يطفو الماوس فوقه. إذا لم يكن الماوس يطفو فوق أي جسم ضمن مسافة MAX_MOUSE_DISTANCE، ستكون raycastResult هي nil.
أنشئ عبارة if للتحقق مما إذا كانت raycastResult موجودة.
إذا كانت raycastResult تحتوي على قيمة، ارجع خاصية Position الخاصة بها.
إذا كانت raycastResult هي nil، فهنا ابحث عن نهاية الشعاع. احسب الموقع ثلاثي الأبعاد للماوس من خلال إضافة screenToWorldRay.Origin وdirectionVector معًا.
local function getWorldMousePosition()
local mouseLocation = UserInputService:GetMouseLocation()
-- إنشاء شعاع من موقع الماوس ثنائي الأبعاد
local screenToWorldRay = workspace.CurrentCamera:ViewportPointToRay(mouseLocation.X, mouseLocation.Y)
-- متجه الاتجاه الوحدوي للشعاع مضروبًا في مسافة قصوى
local directionVector = screenToWorldRay.Direction * MAX_MOUSE_DISTANCE
-- إجراء شعاع من أصل الشعاع نحو اتجاهه
local raycastResult = workspace:Raycast(screenToWorldRay.Origin, directionVector)
if raycastResult then
-- ارجع نقطة التقاطع ثلاثية الأبعاد
return raycastResult.Position
else
-- لم يتم إصابة أي جسم لذا احسب الموضع في نهاية الشعاع
return screenToWorldRay.Origin + directionVector
end
end
إطلاق النار نحو الهدف
الآن بعد أن عرفنا موقع الماوس ثلاثي الأبعاد، يمكن استخدامه كنقطة هدف لإطلاق الليزر نحوها. يمكن إنشاء شعاع ثاني بين سلاح اللاعب ونقطة الهدف باستخدام دالة Raycast.
أعلن عن ثابت يسمى MAX_LASER_DISTANCE في أعلى السكربت وخصصه لقيمة 500، أو المدى الذي اخترته لسلاح الليزر.
local UserInputService = game:GetService("UserInputService")local tool = script.Parentlocal MAX_MOUSE_DISTANCE = 1000local MAX_LASER_DISTANCE = 500أنشئ دالة تسمى fireWeapon تحت دالة getWorldMousePosition.
استدعِ getWorldMousePosition وخصص المردود لمتغير يسمى mousePosition. ستكون هذه هي نقطة الهدف لإطلاق الشعاع.
-- لم يتم إصابة أي جسم لذا احسب الموضع في نهاية الشعاعreturn screenToWorldRay.Origin + directionVectorendendlocal function fireWeapon()local mouseLocation = getWorldMousePosition()endlocal function toolEquipped()tool.Handle.Equip:Play()end
هذه المرة، سيكون متجه الاتجاه لوظيفة الشعاع يمثل الاتجاه من موقع أداة اللاعب إلى موقع الهدف.
أعلن عن متغير يسمى targetDirection واحسب متجه الاتجاه من خلال طرح موقع الأداة من mouseLocation.
انظم المتجه من خلال استخدام خاصيته Unit. هذا يعطيه مقدارًا 1، مما يجعل من السهل ضربه بطول لاحقًا.
local function fireWeapon()local mouseLocation = getWorldMousePosition()-- احسب متجه اتجاه موحد واضربه في مسافة الليزرlocal targetDirection = (mouseLocation - tool.Handle.Position).Unitendأعلن عن متغير يسمى directionVector وخصص له القيمة targetDirection مضروبة في MAX_LASER_DISTANCE.
local targetDirection = (mouseLocation - tool.Handle.Position).Unit-- الاتجاه لإطلاق السلاح، مضروبًا في مسافة قصوىlocal directionVector = targetDirection * MAX_LASER_DISTANCEend
يمكن استخدام كائن RaycastParams لتخزين معلمات إضافية لوظيفة الشعاع. سيتم استخدامه في سلاح الليزر للتأكد من أن الشعاع لا يصطدم بالصدفة باللاعب الذي يقوم بإطلاق السلاح. سيتم تجاهل أي أجزاء مدرجة في خاصية FilterDescendantsInstances لكائن RaycastParams في الشعاع.
استمر في دالة fireWeapon وDeclare متغيرًا يسمى weaponRaycastParams. خصص له كائن RaycastParams جديد.
أنشئ جدولًا يحتوي على character المحلية للاعب وخصّصه لخاصة weaponRaycastParams.FilterDescendantsInstances.
اقضِ جولة من موقع مقبض أداة اللاعب، في اتجاه directionVector. تذكر أن تضيف weaponRaycastParams كوسيط هذه المرة. خصص ذلك لمتغير يسمى weaponRaycastResult.
local UserInputService = game:GetService("UserInputService")
local Players = game:GetService("Players")
local tool = script.Parent
local MAX_MOUSE_DISTANCE = 1000
local MAX_LASER_DISTANCE = 500
local function getWorldMousePosition()
local function fireWeapon()
local mouseLocation = getWorldMousePosition()
-- احسب متجهاً متجهاً موحداً واضربه بمسافة الليزر
local targetDirection = (mouseLocation - tool.Handle.Position).Unit
-- الاتجاه لإطلاق السلاح مضروبًا بمسافة قصوى
local directionVector = targetDirection * MAX_LASER_DISTANCE
-- تجاهل شخصية اللاعب لتجنب إلحاق الضرر بأنفسهم
local weaponRaycastParams = RaycastParams.new()
weaponRaycastParams.FilterDescendantsInstances = {Players.LocalPlayer.Character}
local weaponRaycastResult = workspace:Raycast(tool.Handle.Position, directionVector, weaponRaycastParams)
end
أخيرًا، سيتعين عليك التحقق مما إذا كانت عملية الشعاع قد أعادت قيمة. إذا تمت إعادة قيمة، فقد تم إصابة كائن بواسطة الشعاع ويمكن إنشاء ليزر بين السلاح وموقع الإصابة. إذا لم يتم إعادة أي شيء، فإن الموضع النهائي يحتاج إلى أن يُحسب من أجل إنشاء الليزر.
أعلن عن متغير فارغ يسمى hitPosition.
استخدم عبارة if للتحقق مما إذا كانت weaponRaycastResult تحتوي على قيمة. إذا تم إصابة كائن، خصص weaponRaycastResult.Position لـ hitPosition.
local weaponRaycastResult = workspace:Raycast(tool.Handle.Position, directionVector, weaponRaycastParams)-- تحقق مما إذا كانت هناك أي كائنات تم إصابتها بين البداية والنهايةlocal hitPositionif weaponRaycastResult thenhitPosition = weaponRaycastResult.Positionendإذا كانت weaponRaycastResult بلا قيمة، احسب الموضع النهائي لنقطة الشعاع من خلال جمع الخاصية position لمقبض الأداة مع directionVector. خصص ذلك لـ hitPosition.
local weaponRaycastResult = workspace:Raycast(tool.Handle.Position, directionVector, weaponRaycastParams)-- تحقق مما إذا كانت هناك أي كائنات تم إصابتها بين البداية والنهايةlocal hitPositionif weaponRaycastResult thenhitPosition = weaponRaycastResult.Positionelse-- احسب الموضع النهائي بناءً على المسافة القصوى لليزرhitPosition = tool.Handle.Position + directionVectorendendانتقل إلى دالة toolActivated واستدعِ دالة fireWeapon حتى يتم إطلاق الليزر في كل مرة يتم فيها تفعيل الأداة.
local function toolActivated()tool.Handle.Activate:Play()fireWeapon()end
تحقق من الكائن المصاب
لإيجاد ما إذا كان الكائن المصاب بالليزر هو جزء من شخصية اللاعب أم مجرد قطعة من المنظر، ستحتاج إلى البحث عن Humanoid، حيث تمتلك كل شخصية واحدة.
أولاً، ستحتاج إلى العثور على نموذج الشخصية. إذا أصيب جزء من الشخصية، لا يمكنك افتراض أن الوالد الجذر للكائن المصاب سيكون الشخصية. قد يكون الليزر قد أصاب جزءًا من الجسم، أو ملحقًا، أو أداة، وكل واحدة من هذه تقع في أجزاء مختلفة من هيكل الشخصية.

يمكنك استخدام FindFirstAncestorOfClass للعثور على نموذج شخصية كمؤشر لكائن hit بواسطة الليزر، إذا كان موجودًا. إذا وجدت نموذجًا واحتوى على إنسان، يمكنك في معظم الحالات افتراض أنه شخصية.
أضف الشيفرة المميزة أدناه إلى عبارة if في weaponRaycastResult للتحقق مما إذا كانت شخصية مصابة.
-- تحقق مما إذا كانت هناك أي كائنات تم إصابتها بين البداية والنهايةlocal hitPositionif weaponRaycastResult thenhitPosition = weaponRaycastResult.Position-- سيكون الكائن المصاب طفلاً لنموذج شخصية-- إذا تم العثور على إنسان في النموذج، فمن المحتمل أن يكون شخصية لاعبlocal characterModel = weaponRaycastResult.Instance:FindFirstAncestorOfClass("Model")if characterModel thenlocal humanoid = characterModel:FindFirstChildWhichIsA("Humanoid")if humanoid thenprint("تم إصابة لاعب")endendelse-- احسب الموضع النهائي بناءً على المسافة القصوى لليزرhitPosition = tool.Handle.Position + directionVectorend
الآن يجب أن يطبع سلاح الليزر "تم إصابة لاعب" في نافذة الإخراج في كل مرة تصيب فيها عملية الشعاع لاعبًا آخر.
اختبار مع لاعبين متعددين
هناك حاجة إلى لاعبين اثنين لاختبار ما إذا كانت عملية الشعاع تستطيع العثور على لاعبين آخرين، لذا ستحتاج إلى بدء خادم محلي. راجع محاكاة متعددة العملاء للحصول على التعليمات.
في أحد العملاء، اختبر إطلاق النار على اللاعب الآخر باستخدام السلاح بالنقر عليه. يجب أن يتم عرض "تم إصابة لاعب" في الإخراج في كل مرة يتم فيها إطلاق النار على لاعب.
إيجاد موضع الليزر
يجب أن يطلق البلاستر شعاعًا أحمر من الضوء نحو هدفه. ستوجد الدالة الخاصة بذلك داخل ModuleScript بحيث يمكن إعادة استخدامها في سكربتات أخرى لاحقًا. أولاً، يتعين على السكربت العثور على الموضع الذي ينبغي أن يتم فيه عرض شعاع الليزر.
أنشئ ModuleScript يسمى LaserRenderer، متفرع إلى StarterPlayerScripts تحت StarterPlayer.

افتح السكربت وقم بإعادة تسمية جدول الوحدة إلى اسم السكربت LaserRenderer.
أعلن عن متغير يسمى SHOT_DURATION بقيمة 0.15. ستكون هذه الفترة الزمنية (بالثواني) التي يكون فيها الليزر مرئيًا.
أنشئ دالة لـ LaserRenderer باسم createLaser مع معنيين هما toolHandle وendPosition.
local LaserRenderer = {}local SHOT_DURATION = 0.15 -- الوقت الذي يكون فيه الليزر مرئيًا-- إنشاء شعاع ليزر من نقطة البداية نحو نقطة النهايةfunction LaserRenderer.createLaser(toolHandle, endPosition)endreturn LaserRendererأعلن عن متغير يسمى startPosition وخصص خاصية Position من toolHandle كقيمته. سيكون هذا هو موقع مسدس الليزر الخاص باللاعب.
أعلن عن متغير يسمى laserDistance واطرح endPosition من startPosition للعثور على الفرق بين المتجهين. استخدم خاصية Magnitude لهذا للحصول على طول شعاع الليزر.
function LaserRenderer.createLaser(toolHandle, endPosition)local startPosition = toolHandle.Positionlocal laserDistance = (startPosition - endPosition).Magnitudeendأعلن عن متغير laserCFrame لتخزين الموضع والتوجه لشعاع الليزر. يجب أن تكون النقطة هي منتصف الشعاع من البداية إلى النهاية. استخدم CFrame.lookAt لإنشاء CFrame يقع عند startPosition ويواجه اتجاه endPosition. اضرب ذلك في CFrame الجديدة مع قيمة المحور Z بنصف السالب laserDistance للحصول على المنتصف.
function LaserRenderer.createLaser(toolHandle, endPosition)local startPosition = toolHandle.Positionlocal laserDistance = (startPosition - endPosition).Magnitudelocal laserCFrame = CFrame.lookAt(startPosition, endPosition) * CFrame.new(0, 0, -laserDistance / 2)end
إنشاء جزء الليزر
الآن بعد أن تعرفت على المكان الذي يجب إنشاء شعاع الليزر فيه، تحتاج إلى إضافة الشعاع نفسه. يمكن القيام بذلك بسهولة باستخدام جزء Neon.
أعلن عن متغير laserPart وخصص له كائن Part جديد.
قم بتعيين الخصائص التالية لـ laserPart:
- الحجم: Vector3.new(0.2, 0.2, laserDistance)
- CFrame: laserCFrame
- مثبت: صحيح
- يمكن التصادم: خطأ
- اللون: Color3.fromRGB(225, 0, 0) (لون أحمر قوي)
- المادة: Enum.Material.Neon
أضف laserPart إلى Workspace.
أضف الجزء إلى خدمة Debris بحيث يتم إزالته بعد عدد الثواني الموجودة في متغير SHOT_DURATION.
function LaserRenderer.createLaser(toolHandle, endPosition)local startPosition = toolHandle.Positionlocal laserDistance = (startPosition - endPosition).Magnitudelocal laserCFrame = CFrame.lookAt(startPosition, endPosition) * CFrame.new(0, 0, -laserDistance / 2)local laserPart = Instance.new("Part")laserPart.Size = Vector3.new(0.2, 0.2, laserDistance)laserPart.CFrame = laserCFramelaserPart.Anchored = truelaserPart.CanCollide = falselaserPart.Color = Color3.fromRGB(225, 0, 0)laserPart.Material = Enum.Material.NeonlaserPart.Parent = workspace-- إضافة شعاع الليزر إلى خدمة Debris ليتم إزالته وتنظيفهDebris:AddItem(laserPart, SHOT_DURATION)end
الآن الدالة الخاصة بعرض شعاع الليزر مكتملة، يمكن استدعاؤها بواسطة ToolController.
في أعلى سكربت ToolController، أعلن عن متغير يسمى LaserRenderer واستدعي نموذج LaserRenderer الموجود في PlayerScripts.
local UserInputService = game:GetService("UserInputService")local Players = game:GetService("Players")local LaserRenderer = require(Players.LocalPlayer.PlayerScripts.LaserRenderer)local tool = script.Parentفي أسفل دالة fireWeapon، استدعِ دالة LaserRenderer createLaser باستخدام مقبض الأداة وhitPosition كوسائط.
-- احسب الموضع النهائي بناءً على المسافة القصوى لليزرhitPosition = tool.Handle.Position + directionVectorendLaserRenderer.createLaser(tool.Handle, hitPosition)endاختبر السلاح بالضغط على زر التشغيل. يجب أن يكون شعاع الليزر مرئيًا بين السلاح والماوس عند تفعيل الأداة.
التحكم في معدل إطلاق النار للسلاح
تحتاج الأسلحة إلى تأخير بين كل إطلاق نار لإيقاف اللاعبين عن إلحاق ضرر كبير خلال فترة زمنية قصيرة. يمكن التحكم في ذلك من خلال التحقق مما إذا كانت فترة كافية قد مرت منذ آخر مرة تم فيها إطلاق النار.
أعلن عن متغير في أعلى ToolController يسمى FIRE_RATE. سيكون هذا أدنى وقت بين كل إطلاق نار. امنحه قيمة من اختيارك؛ يستخدم هذا المثال 0.3 ثانية.
أعلن عن متغير آخر أدناه يسمى timeOfPreviousShot بقيمة 0. هذا يخزن آخر مرة أطلق فيها اللاعب وسيتم تحديثه مع كل إطلاق نار.
local MAX_MOUSE_DISTANCE = 1000local MAX_LASER_DISTANCE = 300local FIRE_RATE = 0.3local timeOfPreviousShot = 0أنشئ دالة تسمى canShootWeapon بدون معلمات. ستنظر هذه الدالة في مقدار الوقت الذي مضى منذ الإطلاق السابق وتعيد صحيح أو خطأ.
local FIRE_RATE = 0.3local timeOfPreviousShot = 0-- تحقق مما إذا كان قد مرت فترة كافية منذ إطلاق النار السابقlocal function canShootWeapon()endlocal function getWorldMousePosition()داخل الدالة، أعلن عن متغير يسمى currentTime؛ خصص له نتيجة استدعاء دالة tick() . تُعيد هذه كيفية مرور الوقت، بوحدات الثواني منذ 1 يناير 1970 (تاريخ عشوائي يُستخدم على نطاق واسع لحساب الوقت).
اطرح timeOfPreviousShot من currentTime وأعد false إذا كانت النتيجة أقل من FIRE_RATE؛ بخلاف ذلك، أعد true.
-- تحقق مما إذا كان قد مرت فترة كافية منذ إطلاق النار السابقlocal function canShootWeapon()local currentTime = tick()if currentTime - timeOfPreviousShot < FIRE_RATE thenreturn falseendreturn trueendفي نهاية دالة fireWeapon، حدث timeOfPreviousShot كلما تم إطلاق النار باستخدام tick.
hitPosition = tool.Handle.Position + directionVectorendtimeOfPreviousShot = tick()LaserRenderer.createLaser(tool.Handle, hitPosition)endداخل دالة toolActivated، أنشئ عبارة if واستدعِ canShootWeapon للتحقق مما إذا كان يمكن إطلاق النار.
local function toolActivated()if canShootWeapon() thentool.Handle.Activate:Play()fireWeapon()endend
عند اختبار البلاستر، يجب أن تجد أنه بغض النظر عن مدى سرعة نقرك، سيكون دائمًا هناك تأخير قصير يبلغ 0.3 ثانية بين كل إطلاق.
إلحاق الضرر باللاعب
لا يمكن للعملاء إلحاق الضرر بالعملاء الآخرين مباشرة؛ يجب أن يكون الخادم مسؤولًا عن إصدار الضرر عندما يتم إصابة لاعب.
يمكن للعملاء استخدام RemoteEvent لإخبار الخادم بأن شخصية قد تم إصابتها. يجب تخزين هذه في ReplicatedStorage، حيث يمكن رؤيتها لكل من العميل والخادم.
أنشئ مجلد في ReplicatedStorage باسم Events.

أدخل RemoteEvent في مجلد Events وأطلق عليه اسم DamageCharacter.

في ToolController، أنشئ متغيرات في بداية السكربت لـ ReplicatedStorage ومجلد Events.
local UserInputService = game:GetService("UserInputService")local Players = game:GetService("Players")local ReplicatedStorage = game:GetService("ReplicatedStorage")local LaserRenderer = require(Players.LocalPlayer.PlayerScripts.LaserRenderer)local tool = script.Parentlocal eventsFolder = ReplicatedStorage.Eventslocal MAX_MOUSE_DISTANCE = 1000local MAX_LASER_DISTANCE = 500استبدل بيان الطباعة "تم إصابة لاعب" في fireWeapon بخط لولاء لإطلاق حدث DamageCharacter باستخدام متغير characterModel كوسيط.
local characterModel = weaponRaycastResult.Instance:FindFirstAncestorOfClass("Model")if characterModel thenlocal humanoid = characterModel:FindFirstChildWhichIsA("Humanoid")if humanoid theneventsFolder.DamageCharacter:FireServer(characterModel, hitPosition)endendelse-- احسب الموضع النهائي بناءً على المسافة القصوى لليزرhitPosition = tool.Handle.Position + directionVectorend
يحتاج الخادم للتعامل مع الضرر الذي يتلقاه اللاعب الذي أصيب عند إطلاق الحدث.
أدخل Script في ServerScriptService واسمها ServerLaserManager.

أعلن عن متغير يسمى LASER_DAMAGE واجعله 10، أو قيمة من اختيارك.
أنشئ دالة باسم damageCharacter مع معنيين هما playerFired وcharacterToDamage.
داخل الدالة، ابحث عن إنسان الشخصية واطرح LASER_DAMAGE من صحته.
اربط دالة damageCharacter بحدث DamageCharacter في مجلد Events.
local ReplicatedStorage = game:GetService("ReplicatedStorage")local eventsFolder = ReplicatedStorage.Eventslocal LASER_DAMAGE = 10function damageCharacter(playerFired, characterToDamage)local humanoid = characterToDamage:FindFirstChildWhichIsA("Humanoid")if humanoid then-- أزل الصحة من الشخصيةhumanoid.Health -= LASER_DAMAGEendend-- ارتباط الأحداث بالوظائف المناسبةeventsFolder.DamageCharacter.OnServerEvent:Connect(damageCharacter)اختبر البلاستر مع لاعبين اثنين عن طريق بدء خادم محلي. عندما تطلق على اللاعب الآخر، ستنقص صحته بمقدار القيمة المعينة لـ LASER_DAMAGE.
عرض أشعة الليزر للاعبين الآخرين
حاليًا، يتم إنشاء شعاع الليزر بواسطة العميل الذي يطلق السلاح، لذلك فقط سيكون بإمكانهم رؤية شعاع الليزر.
إذا تم إنشاء شعاع الليزر على الخادم، سيكون بإمكان الجميع رؤيته. ومع ذلك، سيكون هناك تَأخر صغير بين قيام العميل بإطلاق السلاح واستلام الخادم للمعلومات حول الإطلاق. هذا يعني أن العميل الذي يطلق السلاح سيرى تأخيرًا بين وقت تنشيطهم للسلاح ووقتهم لرؤية شعاع الليزر؛ سيكون السلاح بسبب ذلك يبدو متأخرا.
لحل هذه المشكلة، سيقوم كل عميل بإنشاء أشعة ليزر خاصة بهم. هذا يعني أن العميل الذي يقوم بإطلاق السلاح سيرى شعاع الليزر على الفور. سيعاني العملاء الآخرون من تأخير صغير بين الوقت الذي يطلق فيه لاعب آخر وشعاع ليظهر. هذا هو أفضل سيناريو: لا توجد طريقة لإيصالات شعاع أحد العملاء إلى عملاء آخرين بشكل أسرع.
عميل المُطلق
أولاً، يحتاج العميل إلى إخبار الخادم أنه قد أطلق شعاعًا وتقديم موضع النهاية.
أدخل RemoteEvent في مجلد Events في ReplicatedStorage واسمها LaserFired.

ابحث عن دالة fireWeapon في سكربت ToolController. في نهاية الدالة، أطلق حدث LaserFired باستخدام hitPosition كوسيط.
hitPosition = tool.Handle.Position + directionVectorendtimeOfPreviousShot = tick()eventsFolder.LaserFired:FireServer(hitPosition)LaserRenderer.createLaser(tool.Handle, hitPosition)end
الخادم
الآن يجب على الخادم استلام الحدث الذي أطلقه العميل وإخبار جميع العملاء بموقع البداية والنهاية لشعاع الليزر حتى يتمكنوا من عرضه أيضًا.
في السكربت ServerLaserManager، أنشئ دالة باسم playerFiredLaser فوق damageCharacter مع معنيين هما playerFired وendPosition.
اربط الدالة بحدث LaserFired.
-- إعلام جميع العملاء بأنه تم إطلاق ليزر حتى يتمكنوا من عرض الليزرlocal function playerFiredLaser(playerFired, endPosition)end-- ربط الأحداث بالوظائف المناسبةeventsFolder.DamageCharacter.OnServerEvent:Connect(damageCharacter)eventsFolder.LaserFired.OnServerEvent:Connect(playerFiredLaser)
يحتاج الخادم إلى نقطة البداية للليزر. يمكن إرسال ذلك من العميل، ولكن من الأفضل تجنب الثقة بالعميل كلما كان ذلك ممكنًا. ستكون نقطة مقبض سلاح الشخص هو نقطة البداية، لذا يمكن للخادم العثور عليها من هناك.
أنشئ دالة getPlayerToolHandle فوق الدالة playerFiredLaser مع معلمة تُدعى player.
استخدم الشيفرة التالية للبحث عن سلاح اللاعب للعثور على المقبض الكائن.
local LASER_DAMAGE = 10-- العثور على مقبض الأداة التي يحملها اللاعبlocal function getPlayerToolHandle(player)local weapon = player.Character:FindFirstChildOfClass("Tool")if weapon thenreturn weapon:FindFirstChild("Handle")endend-- إعلام جميع العملاء بأنه تم إطلاق ليزر حتى يتمكنوا من عرض الليزرlocal function playerFiredLaser(playerFired, endPosition)
يمكن الآن للخادم استدعاء FireAllClients بواسطة حدث LaserFired لإرسال المعلومات المطلوبة لعرض الليزر للعملاء. يشمل ذلك اللاعب الذي أطلق الليزر (لذا لا يقوم عميل ذلك اللاعب بعرض الليزر مرتين)، المقبض الخاص بسلاح الليزر (الذي يعمل كنقطة بداية للشعاع) وموقع النهاية للشعاع.
في دالة playerFiredLaser، استدعِ دالة getPlayerToolHandle مع playerFired كوسيط وخصص القيمة لمتغير يسمى toolHandle.
إذا كانت toolHandle موجودة، فأطلق الحدث LaserFired على جميع العملاء باستخدام playerFired وtoolHandle وendPosition كوسائط.
-- إعلام جميع العملاء بأنه تم إطلاق ليزر حتى يتمكنوا من عرض الليزرlocal function playerFiredLaser(playerFired, endPosition)local toolHandle = getPlayerToolHandle(playerFired)if toolHandle theneventsFolder.LaserFired:FireAllClients(playerFired, toolHandle, endPosition)endend
عرض على العملاء
الآن بعد أن تم استدعاء FireAllClients، سيتلقى كل عميل حدثًا من الخادم لعرض شعاع الليزر. يمكن لكل عميل إعادة استخدام وحدة LaserRenderer من السابق لعرض شعاع الليزر باستخدام موضع المقبض وموضع النهاية الذي أرسله الخادم. يجب أن يتجاهل اللاعب الذي أطلق شعاع الليزر في الأصل هذا الحدث، وإلا فسيرون 2 شعاع.
أنشئ LocalScript في StarterPlayerScripts يسمى ClientLaserManager.

داخل السكربت، قم باستدعاء وحدة LaserRenderer.
أنشئ دالة باسم createPlayerLaser مع المعاني playerWhoShot وtoolHandle وendPosition.
اربط الدالة بحدث LaserFired في مجلد Events.
في الدالة، استخدم عبارة if للتحقق مما إذا كان playerWhoShot لا يساوي Players.LocalPlayer.
داخل عبارة if، استدعِ دالة createLaser من وحدة LaserRenderer باستخدام toolHandle وendPosition كوسائط.
local Players = game:GetService("Players")local ReplicatedStorage = game:GetService("ReplicatedStorage")local LaserRenderer = require(script.Parent:WaitForChild("LaserRenderer"))local eventsFolder = ReplicatedStorage.Events-- عرض ليزر لاعب آخرlocal function createPlayerLaser(playerWhoShot, toolHandle, endPosition)if playerWhoShot ~= Players.LocalPlayer thenLaserRenderer.createLaser(toolHandle, endPosition)endendeventsFolder.LaserFired.OnClientEvent:Connect(createPlayerLaser)اختبر البلاستر مع لاعبين اثنين عن طريق بدء خادم محلي. ضع كل عميل على جوانب مختلفة من شاشتك حتى تتمكن من رؤية كلتا النافذتين في نفس الوقت. عند إطلاق النار في عميل واحد، يجب أن ترى شعاع الليزر في العميل الآخر.
تأثيرات الصوت
حاليًا، يتم تشغيل تأثير صوت الإطلاق حاليًا فقط على العميل الذي يطلق المقذوف. تحتاج إلى نقل الشيفرة لتشغيل الصوت حتى يتمكن الآخرون من سماعه أيضًا.
في سكربت ToolController، انتقل إلى دالة toolActivated وقم بإزالة السطر الذي يُشغل صوت التفعيل.
local function toolActivated()if canShootWeapon() thenfireWeapon()endendفي أسفل دالة createLaser في LaserRenderer، أعلن عن متغير يسمى shootingSound واستخدم طريقة FindFirstChild() الخاصة بـtoolHandle للتحقق من صوت Activate.
استخدم عبارة if للتحقق مما إذا كانت shootingSound موجودة؛ إذا كانت كذلك، استدعِ دالة Play() لها.
laserPart.Parent = workspace-- إضافة شعاع الليزر إلى خدمة Debris ليتم إزالته وتنظيفهDebris:AddItem(laserPart, SHOT_DURATION)-- تشغيل صوت إطلاق السلاحlocal shootingSound = toolHandle:FindFirstChild("Activate")if shootingSound thenshootingSound:Play()endend
تأمين الأحداث باستخدام التحقق
إذا لم يقم الخادم بالتحقق من البيانات الواردة من الطلبات القادمة، فيمكن لمخترق استغلال هذه الوظائف والفعاليات واستخدامها لإرسال قيم زائفة إلى الخادم. من المهم استخدام التحقق على الجهة الخادمة لمنع ذلك.
في حالتها الحالية، تعتبر حدث DamageCharacter عرضة جدًا للهجوم. يمكن للمخترق استخدام هذا الحدث لإلحاق الضرر بأي لاعب يريد بدون إصابته.
التحقق هو عملية التحقق من أن القيم المرسلة إلى الخادم واقعية. في هذه الحالة، سيتعين على الخادم:
- التحقق من المسافة بين اللاعب وموضع الإصابة بواسطة الليزر ضمن حدود معينة.
- إجراء شعاع بين السلاح الذي أطلق الليزر وموضع الإصابة للتأكد من أن الإطلاق كان ممكنًا ولم يتجاوز أي جدران.
العميل
يحتاج العميل إلى إرسال موضع الإصابة بواسطة الشعاع إلى الخادم حتى يتسنى له التحقق من أن المسافة واقعية.
في ToolController، انتقل إلى السطر حيث يتم إطلاق حدث DamageCharacter في دالة fireWeapon.
أضف hitPosition كوسيط.
if characterModel thenlocal humanoid = characterModel:FindFirstChildWhichIsA("Humanoid")if humanoid theneventsFolder.DamageCharacter:FireServer(characterModel, hitPosition)endend
الخادم
العميل الآن يرسل معلمة إضافية من خلال حدث DamageCharacter، لذا يحتاج ServerLaserManager إلى ضبطها لتقبلها.
في سكربت ServerLaserManager، أضف معلمة hitPosition إلى دالة damageCharacter.
function damageCharacter(playerFired, characterToDamage, hitPosition)local humanoid = characterToDamage:FindFirstChildWhichIsA("Humanoid")if humanoid then-- إزالة الصحة من الشخصيةhumanoid.Health -= LASER_DAMAGEendendأسفل دالة getPlayerToolHandle، أنشئ دالة باسم isHitValid مع ثلاث معلمات: playerFired، characterToDamage وhitPosition.
endlocal function isHitValid(playerFired, characterToDamage, hitPosition)end
سوف تكون أول عملية فحص هي المسافة بين موضع الإصابة والشخصية المصابة.
أعلن عن متغير يسمى MAX_HIT_PROXIMITY في أعلى السكربت وخصصه قيمة 10. ستكون هذه هي أقصى المسافة المسموح بها بين الإصابة والشخصية. تحتاج إلى وجود هوامش لإتاحة حركة الشخصية قليلاً منذ أن أطلق العميل الحدث.
local ReplicatedStorage = game:GetService("ReplicatedStorage")local eventsFolder = ReplicatedStorage.Eventslocal LASER_DAMAGE = 10local MAX_HIT_PROXIMITY = 10في دالة isHitValid، احسب المسافة بين الشخصية وموضع الإصابة. إذا كانت المسافة أكبر من MAX_HIT_PROXIMITY، فأعد false.
local function isHitValid(playerFired, characterToDamage, hitPosition)-- التحقق من المسافة بين الشخصية المصابة وموضع الإصابةlocal characterHitProximity = (characterToDamage.HumanoidRootPart.Position - hitPosition).Magnitudeif characterHitProximity > MAX_HIT_PROXIMITY thenreturn falseendend
سوف تتضمن العملية الثانية تنفيذ شعاع بين السلاح المُطلق وموضع الإصابة. إذا أعادت الدالة شعاعًا يضرب كائنًا ليس الشخصية، يمكنك أن تفترض أن الإصابة لم تكن صالحة حيث أن شيئًا ما كان يحجب الإطلاق.
انسخ الشيفرة أدناه لإجراء هذا الفحص. أعد true في نهاية الدالة: إذا تم الوصول إلى النهاية، فقد تم اجتياز جميع التحققات.
local function isHitValid(playerFired, characterToDamage, hitPosition)-- التحقق من المسافة بين الشخصية المصابة وموضع الإصابةlocal characterHitProximity = (characterToDamage.HumanoidRootPart.Position - hitPosition).Magnitudeif characterHitProximity > 10 thenreturn falseend-- تحقق من تصويب خلال الجدرانlocal toolHandle = getPlayerToolHandle(playerFired)if toolHandle thenlocal rayLength = (hitPosition - toolHandle.Position).Magnitudelocal rayDirection = (hitPosition - toolHandle.Position).Unitlocal raycastParams = RaycastParams.new()raycastParams.FilterDescendantsInstances = {playerFired.Character}local rayResult = workspace:Raycast(toolHandle.Position, rayDirection * rayLength, raycastParams)-- إذا تم إصابة كائن لم يكن من الشخصية، فتجاهل الإصابةif rayResult and not rayResult.Instance:IsDescendantOf(characterToDamage) thenreturn falseendendreturn trueendأعلن عن متغير في دالة damageCharacter يسمى validShot. خصص له نتيجة الاستدعاء إلى دالة isHitValid مع ثلاث معلمات: playerFired وcharacterToDamage وhitPosition.
في عبارة if أدناه، أضف عامل and للتحقق مما إذا كانت validShot صحيحة.
function damageCharacter(playerFired, characterToDamage, hitPosition)local humanoid = characterToDamage:FindFirstChildWhichIsA("Humanoid")local validShot = isHitValid(playerFired, characterToDamage, hitPosition)if humanoid and validShot then-- إزالة الصحة من الشخصيةhumanoid.Health -= LASER_DAMAGEendend
الآن أصبح حدث damageCharacter أكثر أمانًا وسيمنع معظم اللاعبين من استغلاله. تجدر الإشارة إلى أن بعض اللاعبين المتعمدين سيجدون غالبًا طرقًا لتجاوز التحقق؛ فإن الحفاظ على أمن الأحداث البشرية هو جهد مستمر.
لقد اكتملت الآن بندقيتك الليزرية، مع نظام بسيط للكشف عن الإصابة باستخدام الأشعة. جرب درس كشف إدخال المستخدم لمعرفة كيف يمكنك إضافة إجراء إعادة تحميل إلى مسدس الليزر الخاص بك، أو أنشئ خريطة ممتعة وجرب مسدس الليزر الخاص بك مع لاعبين آخرين!
الكود النهائي
ToolController
local UserInputService = game:GetService("UserInputService")
local Players = game:GetService("Players")
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local LaserRenderer = require(Players.LocalPlayer.PlayerScripts.LaserRenderer)
local tool = script.Parent
local eventsFolder = ReplicatedStorage.Events
local MAX_MOUSE_DISTANCE = 1000
local MAX_LASER_DISTANCE = 500
local FIRE_RATE = 0.3
local timeOfPreviousShot = 0
-- تحقق مما إذا كانت فترة كافية قد مرت منذ آخر إطلاق نار
local function canShootWeapon()
local currentTime = tick()
if currentTime - timeOfPreviousShot < FIRE_RATE then
return false
end
return true
end
local function getWorldMousePosition()
local mouseLocation = UserInputService:GetMouseLocation()
-- إنشاء شعاع من موقع الماوس ثنائي الأبعاد
local screenToWorldRay = workspace.CurrentCamera:ViewportPointToRay(mouseLocation.X, mouseLocation.Y)
-- متجه الاتجاه الوحدوي للشعاع مضروبًا في المسافة القصوى
local directionVector = screenToWorldRay.Direction * MAX_MOUSE_DISTANCE
-- إجراء شعاع من أصل الشعاع نحو اتجاهه
local raycastResult = workspace:Raycast(screenToWorldRay.Origin, directionVector)
if raycastResult then
-- ارجع نقطة التقاطع ثلاثية الأبعاد
return raycastResult.Position
else
-- لم يتم إصابة أي جسم لذا احسب الموضع في نهاية الشعاع
return screenToWorldRay.Origin + directionVector
end
end
local function fireWeapon()
local mouseLocation = getWorldMousePosition()
-- احسب متجه اتجاه موحد واضربه في مسافة الليزر
local targetDirection = (mouseLocation - tool.Handle.Position).Unit
-- الاتجاه لإطلاق السلاح، مضروبًا في المسافة القصوى
local directionVector = targetDirection * MAX_LASER_DISTANCE
-- تجاهل شخصية اللاعب لتجنب إلحاق الضرر بأنفسهم
local weaponRaycastParams = RaycastParams.new()
weaponRaycastParams.FilterDescendantsInstances = {Players.LocalPlayer.Character}
local weaponRaycastResult = workspace:Raycast(tool.Handle.Position, directionVector, weaponRaycastParams)
-- تحقق مما إذا كانت هناك أي كائنات تم إصابتها بين البداية والنهاية
local hitPosition
if weaponRaycastResult then
hitPosition = weaponRaycastResult.Position
-- سيكون الكائن المصاب طفلاً لنموذج شخصية
-- إذا تم العثور على إنسان في النموذج، فمن المحتمل أن يكون شخصية لاعب
local characterModel = weaponRaycastResult.Instance:FindFirstAncestorOfClass("Model")
if characterModel then
local humanoid = characterModel:FindFirstChildWhichIsA("Humanoid")
if humanoid then
eventsFolder.DamageCharacter:FireServer(characterModel, hitPosition)
end
end
else
-- احسب الموضع النهائي بناءً على المسافة القصوى لليزر
hitPosition = tool.Handle.Position + directionVector
end
timeOfPreviousShot = tick()
eventsFolder.LaserFired:FireServer(hitPosition)
LaserRenderer.createLaser(tool.Handle, hitPosition)
end
local function toolEquipped()
tool.Handle.Equip:Play()
end
local function toolActivated()
if canShootWeapon() then
fireWeapon()
end
end
tool.Equipped:Connect(toolEquipped)
tool.Activated:Connect(toolActivated)
LaserRenderer
local LaserRenderer = {}
local Debris = game:GetService("Debris")
local SHOT_DURATION = 0.15 -- الوقت الذي يكون فيه الليزر مرئيًا
-- إنشاء شعاع ليزر من نقطة البداية نحو نقطة النهاية
function LaserRenderer.createLaser(toolHandle, endPosition)
local startPosition = toolHandle.Position
local laserDistance = (startPosition - endPosition).Magnitude
local laserCFrame = CFrame.lookAt(startPosition, endPosition) * CFrame.new(0, 0, -laserDistance / 2)
local laserPart = Instance.new("Part")
laserPart.Size = Vector3.new(0.2, 0.2, laserDistance)
laserPart.CFrame = laserCFrame
laserPart.Anchored = true
laserPart.CanCollide = false
laserPart.Color = Color3.fromRGB(255, 0, 0)
laserPart.Material = Enum.Material.Neon
laserPart.Parent = workspace
-- إضافة شعاع الليزر إلى خدمة Debris ليتم إزالته وتنظيفه
Debris:AddItem(laserPart, SHOT_DURATION)
-- تشغيل صوت إطلاق السلاح
local shootingSound = toolHandle:FindFirstChild("Activate")
if shootingSound then
shootingSound:Play()
end
end
return LaserRenderer
ServerLaserManager
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local eventsFolder = ReplicatedStorage.Events
local LASER_DAMAGE = 10
local MAX_HIT_PROXIMITY = 10
-- العثور على مقبض الأداة التي يحملها اللاعب
local function getPlayerToolHandle(player)
local weapon = player.Character:FindFirstChildOfClass("Tool")
if weapon then
return weapon:FindFirstChild("Handle")
end
end
local function isHitValid(playerFired, characterToDamage, hitPosition)
-- التحقق من المسافة بين الشخصية المصابة وموضع الإصابة
local characterHitProximity = (characterToDamage.HumanoidRootPart.Position - hitPosition).Magnitude
if characterHitProximity > MAX_HIT_PROXIMITY then
return false
end
-- تحقق من تصويب خلال الجدران
local toolHandle = getPlayerToolHandle(playerFired)
if toolHandle then
local rayLength = (hitPosition - toolHandle.Position).Magnitude
local rayDirection = (hitPosition - toolHandle.Position).Unit
local raycastParams = RaycastParams.new()
raycastParams.FilterDescendantsInstances = {playerFired.Character}
local rayResult = workspace:Raycast(toolHandle.Position, rayDirection * rayLength, raycastParams)
-- إذا تم إصابة كائن لم يكن من الشخصية، فتجاهل الإصابة
if rayResult and not rayResult.Instance:IsDescendantOf(characterToDamage) then
return false
end
end
return true
end
-- إعلام جميع العملاء بأنه تم إطلاق ليزر حتى يتمكنوا من عرض الليزر
local function playerFiredLaser(playerFired, endPosition)
local toolHandle = getPlayerToolHandle(playerFired)
if toolHandle then
eventsFolder.LaserFired:FireAllClients(playerFired, toolHandle, endPosition)
end
end
function damageCharacter(playerFired, characterToDamage, hitPosition)
local humanoid = characterToDamage:FindFirstChildWhichIsA("Humanoid")
local validShot = isHitValid(playerFired, characterToDamage, hitPosition)
if humanoid and validShot then
-- إزالة الصحة من الشخصية
humanoid.Health -= LASER_DAMAGE
end
end
-- ربط الأحداث بالوظائف المناسبة
eventsFolder.DamageCharacter.OnServerEvent:Connect(damageCharacter)
eventsFolder.LaserFired.OnServerEvent:Connect(playerFiredLaser)
ClientLaserManager
local Players = game:GetService("Players")
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local LaserRenderer = require(Players.LocalPlayer.PlayerScripts:WaitForChild("LaserRenderer"))
local eventsFolder = ReplicatedStorage.Events
-- عرض ليزر لاعب آخر
local function createPlayerLaser(playerWhoShot, toolHandle, endPosition)
if playerWhoShot ~= Players.LocalPlayer then
LaserRenderer.createLaser(toolHandle, endPosition)
end
end
eventsFolder.LaserFired.OnClientEvent:Connect(createPlayerLaser)