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

العثور على موقع الماوس
قبل أن يمكن إطلاق الليزر، يجب أولاً معرفة أين يستهدف اللاعب. يمكن العثور على ذلك عن طريق استخدام الأشعة من موقع الماوس ثنائي الأبعاد للاعب على الشاشة مباشرة إلى الأمام من الكاميرا إلى العالم ثلاثي الأبعاد. سيصطدم الشعاع بما يستهدفه اللاعب بالماوس.
افتح سكربت ToolController داخل أداة Blaster من إنشاء أدوات اللاعب. إذا لم تكمل هذا الدليل بعد، يمكنك تنزيل نموذج Blaster وإدراجه في 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 وأعلن عن متغير يسمى 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 قيمة، احسب الموقع النهائي لعملية الأشعة عن طريق إضافة موقع مقبض الأداة مع 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 للعثور على نموذج شخصية كجد للجسم المصاب بالليزر، إذا كان موجودًا. إذا وجدت نموذجًا واحتوى على إنسان، في معظم الحالات يمكنك افتراض أنه شخصية.
أضف الكود المميز أدناه إلى عبارة 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:
- Size: Vector3.new(0.2, 0.2, laserDistance)
- CFrame: laserCFrame
- Anchored: true
- CanCollide: false
- Color: Color3.fromRGB(225, 0, 0) (لون أحمر قوي)
- Material: 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، استدعِ دالة createLaser من LaserRenderer باستخدام مقبض الأداة و 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 بدون معلمات. ستنظر هذه الدالة في مقدار الوقت الذي مر منذ الطلقة السابقة وتعيد true أو false.
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 في مجلد الأحداث وسمه DamageCharacter.

في ToolController، أنشئ متغيرات في بداية السكربت لـ ReplicatedStorage ومجلد الأحداث.
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 بسطر من Luau لإطلاق حدث 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
يحتاج الخادم إلى إلحاق الضرر باللاعب الذي تم إصابته عندما يتم إطلاق الحدث.
أدخل سكربت في ServerScriptService وسمه ServerLaserManager.

أعلن عن متغير يسمى LASER_DAMAGE واضبطه على 10، أو قيمة من اختيارك.
أنشئ دالة باسم damageCharacter مع معلمين يسمى playerFired و characterToDamage.
داخل الدالة، ابحث عن إنسان الشخصية واطرح LASER_DAMAGE من صحته.
اربط دالة damageCharacter بحدث DamageCharacter في مجلد الأحداث.
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 في مجلد الأحداث في 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 من وقت سابق لعرض شعاع الليزر باستخدام موقع المقبض ونقطة النهاية المرسلة من الخادم. يجب على اللاعب الذي أطلق شعاع الليزر في المقام الأول تجاهل هذا الحدث وإلا سيرى شعاعين.
أنشئ LocalScript في StarterPlayerScripts يسمى ClientLaserManager.

داخل السكربت، استدعِ وحدة LaserRenderer.
أنشئ دالة باسم createPlayerLaser مع المعلمات playerWhoShot و toolHandle و endPosition.
اربط الدالة بحدث LaserFired في مجلد الأحداث.
في الدالة، استخدم عبارة 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 هو true.
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 LaserRendererServerLaserManager
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)