ตรวจจับการโจมตี

*เนื้อหานี้แปลโดยใช้ AI (เวอร์ชัน Beta) และอาจมีข้อผิดพลาด หากต้องการดูหน้านี้เป็นภาษาอังกฤษ ให้คลิกที่นี่

การตรวจจับการโจมตี คือกระบวนการในการระบุเมื่อการยิงชนกับผู้เล่น จากนั้นลดพลังชีวิตของพวกเขาตามนั้น ในระดับสูง คุณสามารถคิดว่าการทำงานนี้เป็น:

  1. การตรวจสอบที่จำลองทางกายภาพว่ากระสุนชนเป้าหมายหรือไม่
  2. การตรวจสอบทันทีว่าปืนเลเซอร์ถูกเล็งไปที่เป้าหมายหรือไม่

ประเภทของการตรวจจับการโจมตีที่คุณใช้ขึ้นอยู่กับความต้องการของเกมของคุณ ตัวอย่างเช่น การตรวจสอบที่จำลองทางกายภาพเหมาะสำหรับเกมดอดจ์บอลที่ลูกบอลต้องออกจากมือด้วยความเร็วที่แน่นอน ตกลงขณะเคลื่อนที่ในอากาศ หรือเปลี่ยนทิศทางจากสภาพอากาศ อย่างไรก็ตาม การตรวจสอบทันทีเหมาะสมกว่าสำหรับเกมเลเซอร์แท็กที่ลำแสงต้องมีความเร็วเกือบไม่จำกัดและไม่สนใจปัจจัยด้านสิ่งแวดล้อมเช่นแรงโน้มถ่วงและความเร็วลม

โดยใช้ เกมเลเซอร์แท็กตัวอย่าง เป็นข้อมูลอ้างอิง ส่วนนี้ของบทเรียนจะสอนคุณเกี่ยวกับสคริปต์ที่อยู่เบื้องหลังการตรวจจับการโจมตีในพื้นที่ 3D รวมถึงแนวทางในการ:

  • รับทิศทางการยิงจากค่ากล้องปัจจุบันและประเภทปืนเลเซอร์ของผู้เล่น
  • ยิงรังสีในเส้นตรงจากปืนเลเซอร์ขณะยิง
  • ตรวจสอบการยิงเพื่อป้องกันการใช้ข้อมูลปืนเลเซอร์ในทางที่ผิด
  • ลดพลังชีวิตของผู้เล่นตามความเสียหายจากการยิงของแต่ละประเภทปืนเลเซอร์และจำนวนรังสีที่ชนผู้เล่น

หลังจากที่คุณทำส่วนนี้เสร็จแล้ว คุณสามารถสำรวจหัวข้อการพัฒนาเพิ่มเติมเพื่อปรับปรุงการเล่นเกมของคุณ เช่น เสียง แสง และเอฟเฟกต์พิเศษ

รับทิศทางการยิง

หลังจากที่ผู้เล่นยิงปืนเลเซอร์ของพวกเขา ReplicatedStorageBlasterattemptBlastClientblastClientgenerateBlastData จะเรียกใช้ฟังก์ชันสองฟังก์ชันเพื่อเริ่มกระบวนการตรวจจับการโจมตี: rayDirections() และ rayResults().

generateBlastData
local rayDirections = getDirectionsForBlast(currentCamera.CFrame, blasterConfig)
local rayResults = castLaserRay(localPlayer, currentCamera.CFrame.Position, rayDirections)

ข้อมูลนำเข้าสำหรับ rayDirections นั้นตรงไปตรงมา: ค่าตำแหน่งและการหมุนของกล้องปัจจุบัน และประเภทปืนเลเซอร์ของผู้เล่น หากเกมเลเซอร์แท็กตัวอย่างให้ผู้เล่นมีปืนเลเซอร์ที่ผลิตลำแสงเลเซอร์เพียงลำแสงเดียว ReplicatedStorageLaserRaygetDirectionsForBlast จะไม่จำเป็นเพราะคุณสามารถใช้ currentCamera.CFrame.LookVector เพื่อคำนวณทิศทางสำหรับการยิง

อย่างไรก็ตาม เนื่องจากตัวอย่างมีประเภทปืนเลเซอร์เพิ่มเติมที่ผลิตลำแสงเลเซอร์หลายลำแสงที่มีการกระจายแนวนอนกว้าง getDirectionsForBlast ต้องคำนวณทิศทางสำหรับลำแสงเลเซอร์แต่ละลำแสงตามมุมภายในการกำหนดค่าปืนเลเซอร์:

getDirectionsForBlast
if numLasers == 1 then
-- สำหรับเลเซอร์เดี่ยว พวกเขาจะเล็งตรง
table.insert(directions, originCFrame.LookVector)
elseif numLasers > 1 then
-- สำหรับเลเซอร์หลายลำแสง ให้กระจายออกอย่างสม่ำเสมอในแนวนอน
-- รอบๆ มุม laserSpreadDegrees ที่อยู่กลาง
local leftAngleBound = laserSpreadDegrees / 2
local rightAngleBound = -leftAngleBound
local degreeInterval = laserSpreadDegrees / (numLasers - 1)
for angle = rightAngleBound, leftAngleBound, degreeInterval do
local direction = (originCFrame * CFrame.Angles(0, math.rad(angle), 0)).LookVector
table.insert(directions, direction)
end
end

เพื่อแสดงแนวคิดนี้เพิ่มเติม หากคุณต้องการรวมประเภทปืนเลเซอร์ที่สามที่มีการกระจาย แนวตั้ง กว้าง คุณสามารถสร้างแอตทริบิวต์ปืนเลเซอร์ใหม่ เช่น spreadDirection จากนั้นปรับการคำนวณ CFrame เพื่อใช้แกนที่แตกต่างกัน ตัวอย่างเช่น สังเกตความแตกต่างในการคำนวณ direction ในสคริปต์ด้านล่างสำหรับประเภทปืนเลเซอร์ที่สามที่สมมติขึ้นนี้

if numLasers == 1 then
table.insert(directions, originCFrame.LookVector)
elseif numLasers > 1 then
local leftAngleBound = laserSpreadDegrees / 2
local rightAngleBound = -leftAngleBound
local degreeInterval = laserSpreadDegrees / (numLasers - 1)
for angle = rightAngleBound, leftAngleBound, degreeInterval do
local direction
if spreadDirection == "vertical" then
direction = (originCFrame * CFrame.Angles(math.rad(angle), 0, 0)).LookVector
else
direction = (originCFrame * CFrame.Angles(0, math.rad(angle), 0)).LookVector
end
table.insert(directions, direction)
end
end
return directions

สุดท้าย ฟังก์ชัน rayDirections() จะส่งคืนตารางของ Vectors ที่แสดงถึงทิศทางของลำแสงเลเซอร์แต่ละลำแสง หากมีประโยชน์ คุณสามารถเพิ่มการบันทึกเพื่อให้เข้าใจว่าข้อมูลนี้มีลักษณะอย่างไร

generateBlastData
local rayDirections = getDirectionsForBlast(currentCamera.CFrame, blasterConfig)
for _, direction in rayDirections do -- new line
print(direction) -- new line
end -- new line
local rayResults = castLaserRay(localPlayer, currentCamera.CFrame.Position, rayDirections)

ยิงรังสี

castLaserRay(), ฟังก์ชันที่สองใน ReplicatedStorageBlasterattemptBlastClientblastClientgenerateBlastData, ทำการดำเนินการที่ซับซ้อนมากขึ้นภายในสคริปต์ มันเริ่มต้นโดยการระบุพารามิเตอร์เพื่อให้สามารถทำการเรียก Workspace:Raycast() สำหรับวัตถุประสงค์ในการยิงรังสี การยิงรังสีคือกระบวนการในการส่งรังสีที่มองไม่เห็นจากจุด Vector3 ในทิศทางเฉพาะด้วยความยาวที่กำหนด จากนั้นตรวจสอบเส้นทางของมันเพื่อดูว่ามันตัดกับวัตถุอื่นๆ ที่ใด

ข้อมูลนี้มีประโยชน์โดยเฉพาะสำหรับเกมยิงมุมมองบุคคลที่หนึ่ง เพราะมันช่วยให้คุณเห็นว่าเมื่อใดและที่ใดที่การยิงตัดกับผู้เล่นหรือสิ่งแวดล้อม ตัวอย่างเช่น รูปภาพต่อไปนี้แสดงให้เห็นถึงรังสีสองรังสีที่ยิงข平ขนานกัน ตามจุดกำเนิดและทิศทางของพวกเขา รังสี A พลาดผนังและดำเนินต่อไปจนถึงระยะสูงสุด ในขณะที่รังสี B ชนกับผนัง สำหรับข้อมูลเพิ่มเติมเกี่ยวกับกระบวนการนี้ โปรดดูที่ Raycasting.

แผนภาพที่รังสี A ผ่านผนังต่อไป และรังสี B ชนกับผนัง.

พารามิเตอร์ของ castLaserRay() ระบุว่าการเรียก Raycast() จะต้องพิจารณาทุกส่วนในพื้นที่ทำงาน ยกเว้น ตัวละครที่ยิง สคริปต์จะยิงรังสีสำหรับแต่ละทิศทางในตาราง directions หากรังสีชนกับบางสิ่ง มันจะสร้าง RaycastResult ซึ่งมีห้าคุณสมบัติ:

  • Distance – ระยะห่างระหว่างจุดกำเนิดรังสีและจุดตัด
  • InstanceBasePart หรือเซลล์ Terrain ที่รังสีตัดกัน
  • MaterialEnum.Material ที่จุดตัด
  • Position – ตำแหน่ง Vector3 ของจุดตัดระหว่างรังสีและ Instance
  • NormalVector3 ของเวกเตอร์ปกติของหน้าที่รังสีตัดกัน

ค่าของ Instance เป็นคุณสมบัติที่สำคัญที่สุดในคุณสมบัติเหล่านี้สำหรับการเล่นเกมของเกมเลเซอร์แท็กตัวอย่าง เพราะมันสื่อสารเมื่อรังสีชนกับผู้เล่นคนอื่น เพื่อดึงข้อมูลนี้ เกมจะใช้ฟังก์ชันช่วย ReplicatedStorageLaserRaycastLaserRaygetPlayerFromDescendant หากมันส่งคืน nil หมายความว่า Instance นั้นไม่ใช่ส่วนหนึ่งของผู้เล่น หมายความว่ารังสีชนกับวัตถุที่ไม่มีชีวิตในสิ่งแวดล้อม

castLaserRay() จะใช้ Position และ Normal เพื่อสร้าง CFrame ใหม่ที่เรียกว่า destination ของรังสี รังสีแต่ละรังสีมีจุดหมายปลายทาง และมันคือจุดที่รังสีชนในพื้นที่ 3D หรือจุดที่ปลายสุดของระยะสูงสุด ขึ้นอยู่กับว่าผู้เล่นของคุณเล็งได้ดีเพียงใด ค่าหลายหรือส่วนใหญ่ของ taggedPlayer จะเป็น nil.

castLaserRay
if result then
-- การยิงชนกับบางสิ่ง ให้ตรวจสอบว่ามันเป็นผู้เล่นหรือไม่
destination = CFrame.lookAt(result.Position, result.Position + result.Normal)
taggedPlayer = getPlayerFromDescendant(result.Instance)
else
-- การยิงไม่ชนกับอะไรเลย ดังนั้นจุดหมายปลายทางของมันคือ
-- จุดที่ระยะสูงสุดของมัน
local distantPosition = origin + rayDirection * MAX_DISTANCE
destination = CFrame.lookAt(distantPosition, distantPosition - rayDirection)
taggedPlayer = nil
end

ตรวจสอบการยิง

เพื่อป้องกันการโกง บทก่อนหน้า การนำปืนเลเซอร์ไปใช้ อธิบายว่า blastClient แจ้งเซิร์ฟเวอร์เกี่ยวกับการยิงโดยใช้ RemoteEvent เพื่อให้สามารถตรวจสอบข้อมูลทั้งหมดที่แต่ละไคลเอนต์ส่ง เช่น พวกเขาได้ทำการติดป้ายผู้เล่นคนอื่นด้วยปืนเลเซอร์ของพวกเขาจริงหรือไม่ กระบวนการตรวจสอบรังสีนี้เกิดขึ้นใน ServerScriptServiceLaserBlastHandlergetValidatedBlastDatagetValidatedRayResults และการตรวจสอบแต่ละครั้งสัมพันธ์กับโมดูลสคริปต์ที่ซ้อนกัน:

  1. ก่อนอื่น getValidatedRayResults จะเรียก validateRayResult เพื่อตรวจสอบว่าทุกรายการในตาราง rayResults จากไคลเอนต์เป็น CFrame และ Player (หรือ nil).

  2. ถัดไป มันจะเรียก isRayAngleFromOriginValid เพื่อตรวจสอบมุมที่คาดหวังของการกระจายเลเซอร์กับมุมจากไคลเอนต์ โค้ดนี้แสดงให้เห็นถึงข้อดีของการใช้ ReplicatedStorage เพราะเซิร์ฟเวอร์สามารถเรียก getDirectionsForBlast เอง เก็บค่าที่ส่งกลับเป็นข้อมูล "ที่คาดหวัง" และเปรียบเทียบกับข้อมูลจากไคลเอนต์

    เช่นเดียวกับการตรวจสอบปืนเลเซอร์จากบทก่อนหน้า isRayAngleFromOriginValid ขึ้นอยู่กับค่าความทนทานเพื่อตัดสินว่าอะไรคือความแตกต่างในมุมที่ "มากเกินไป":

    isRayAngleFromOriginValid
    local claimedDirection = (rayResult.destination.Position - originCFrame.Position).Unit
    local directionErrorDegrees = getAngleBetweenDirections(claimedDirection, expectedDirection)
    return directionErrorDegrees <= ToleranceValues.BLAST_ANGLE_SANITY_CHECK_TOLERANCE_DEGREES

    Roblox ได้ทำให้การคำนวณที่ซับซ้อนที่สุดง่ายขึ้น ดังนั้นผลลัพธ์จึงเป็นฟังก์ชันช่วยที่สั้นและสามารถนำกลับมาใช้ใหม่ได้สูงซึ่งสามารถใช้ได้ในหลายเกม:

    getAngleBetweenDirections
    local function getAngleBetweenDirections(directionA: Vector3, directionB: Vector3)
    local dotProduct = directionA:Dot(directionB)
    local cosAngle = math.clamp(dotProduct, -1, 1)
    local angle = math.acos(cosAngle)
    return math.deg(angle)
    end
  3. การตรวจสอบถัดไปเป็นสิ่งที่เข้าใจได้มากที่สุด ในขณะที่ getValidatedBlastData ใช้ DISTANCE_SANITY_CHECK_TOLERANCE_STUDS เพื่อตรวจสอบว่าผู้เล่นที่ยิงอยู่ใกล้กับจุดกำเนิดของรังสีหรือไม่ isPlayerNearPosition ใช้ตรรกะเดียวกันเพื่อตรวจสอบว่าผู้เล่นที่ถูกติดป้ายอยู่ใกล้กับจุดหมายปลายทางของรังสีหรือไม่:

    isPlayerNearPosition
    local distanceFromCharacterToPosition = position - character:GetPivot().Position
    if distanceFromCharacterToPosition.Magnitude > ToleranceValues.DISTANCE_SANITY_CHECK_TOLERANCE_STUDS then
    return false
    end
  4. การตรวจสอบสุดท้าย isRayPathObstructed ใช้การยิงรังสีที่แตกต่างกันเพื่อตรวจสอบว่าจุดหมายปลายทางของรังสีอยู่หลังผนังหรือสิ่งกีดขวางอื่นจากตำแหน่งของไคลเอนต์หรือไม่ ตัวอย่างเช่น หากผู้เล่นที่ไม่ดีพยายามลบผนังทั้งหมดจากเกมเพื่อทำการติดป้ายผู้เล่นคนอื่น เซิร์ฟเวอร์จะตรวจสอบและยืนยันว่ารังสีไม่ถูกต้องเพราะมันรู้ตำแหน่งของวัตถุทุกชิ้นในสิ่งแวดล้อม

    isRayPathObstructed
    local scaledDirection = (rayResult.destination.Position - blastData.originCFrame.Position)
    scaledDirection *= (scaledDirection.Magnitude - 1) / scaledDirection.Magnitude

ไม่มีกลยุทธ์ป้องกันการโกงใดที่ครอบคลุม แต่สิ่งสำคัญคือต้องพิจารณาว่าผู้เล่นที่ไม่ดีอาจเข้ามาในเกมของคุณอย่างไรเพื่อที่คุณจะได้วางการตรวจสอบที่เซิร์ฟเวอร์สามารถทำงานเพื่อตรวจสอบพฤติกรรมที่น่าสงสัย

ลดพลังชีวิตของผู้เล่น

หลังจากตรวจสอบว่าผู้เล่นติดป้ายผู้เล่นคนอื่นแล้ว ขั้นตอนสุดท้ายในการทำให้วงจรการเล่นเกมหลักในเกมเลเซอร์แท็กตัวอย่างเสร็จสมบูรณ์คือการลดพลังชีวิตของผู้เล่นที่ถูกติดป้าย เพิ่มคะแนนในกระดานคะแนน และทำให้ผู้เล่นกลับมาในรอบ

เริ่มต้นด้วยการลดพลังชีวิตของผู้เล่นที่ถูกติดป้าย การเกิดและการเกิดใหม่ ครอบคลุมความแตกต่างระหว่าง Player และ Player.Character โดยเฉพาะว่าตัวละครคือโมเดล Humanoid โมเดล Humanoid มีคุณสมบัติ Health ที่มีค่าเริ่มต้นเป็น 100 แทนที่จะใช้ระบบของตัวเอง เกมเลเซอร์แท็กตัวอย่างใช้คุณสมบัตินี้เพื่อติดตามความเสียหายที่ผู้เล่นต้องการก่อนที่จะถูกติดป้ายออกจากรอบ

เกมเก็บค่าความเสียหายในแอตทริบิวต์ damagePerHit ของแต่ละปืนเลเซอร์ ตัวอย่างเช่น ปืนเลเซอร์ที่ยิงลำแสงเลเซอร์เดียวทำความเสียหาย 10 คะแนน ดังนั้นจึงต้องใช้การยิงสิบครั้งด้วยปืนเลเซอร์นี้เพื่อติดป้ายผู้เล่นคนอื่น เพื่อเริ่มกระบวนการติดป้ายผู้เล่นออก LaserBlastHandler จะเรียก ServerScriptServiceLaserBlastHandlerprocessTaggedPlayers ซึ่งตรวจสอบตาราง rayResults ที่ได้รับการตรวจสอบแล้วสำหรับผู้เล่นและส่ง damagePerHit ไปยัง onPlayerTagged.

Health ไม่รับค่าลบ ดังนั้น onPlayerTagged จึงมีตรรกะบางอย่างเพื่อรักษาพลังชีวิตของผู้เล่นให้อยู่ที่ศูนย์หรือต่ำกว่า หลังจากตรวจสอบว่าพลังชีวิตของผู้เล่นสูงกว่าศูนย์แล้ว มันจะเปรียบเทียบพลังชีวิตกับ damagePerHit และใช้ค่าที่น้อยกว่าของทั้งสองค่า ตัวอย่างเช่น หากผู้เล่นมีพลังชีวิต 10 และถูกยิงด้วยลำแสงเลเซอร์ที่ทำความเสียหาย 15 ลำแสงเลเซอร์จะทำความเสียหายเพียง 10 คะแนนเท่านั้น

วิธีการนี้อาจดูซับซ้อนเล็กน้อย ตัวอย่างเช่น ทำไมไม่ตั้งค่าพลังชีวิตของผู้เล่นเป็นศูนย์หากมันจะเป็นค่าลบ? สาเหตุคือการตั้งค่าค่าพลังชีวิตจะหลีกเลี่ยงสนามพลัง การใช้วิธี Humanoid:TakeDamage() จะทำให้แน่ใจว่าผู้เล่นจะไม่ถูกทำความเสียหายขณะสนามพลังของพวกเขาทำงานอยู่

onPlayerTagged
local function onPlayerTagged(playerBlasted: Player, playerTagged: Player, damageAmount: number)
local character = playerTagged.Character
local isFriendly = playerBlasted.Team == playerTagged.Team
-- ห้ามยิงเพื่อน
if isFriendly then
return
end
local humanoid = character and character:FindFirstChild("Humanoid")
if humanoid and humanoid.Health > 0 then
-- หลีกเลี่ยงพลังชีวิตติดลบ
local damage = math.min(damageAmount, humanoid.Health)
-- TakeDamage ทำให้แน่ใจว่าพลังชีวิตไม่ลดลงหากสนามพลังทำงานอยู่
humanoid:TakeDamage(damage)
if humanoid.Health <= 0 then
-- มอบคะแนนให้ playerBlasted สำหรับการติดป้าย playerTagged
Scoring.incrementScore(playerBlasted, 1)
end
end
end

ขั้นตอนถัดไปคือการเพิ่มคะแนนในกระดานคะแนน อาจดูเหมือนไม่จำเป็นที่ LaserBlastHandler จะรวมผู้เล่นที่ยิงพร้อมกับข้อมูลการยิง แต่หากไม่มีข้อมูลนั้น เกมจะไม่สามารถให้เครดิตผู้เล่นที่ติดป้ายใครออกได้ สุดท้าย ผู้เล่นที่ถูกติดป้ายจะเกิดใหม่กลับเข้าสู่รอบ ซึ่งคุณสามารถตรวจสอบได้ใน การเกิดและการเกิดใหม่.

ห้าบทในหลักสูตรนี้ครอบคลุมวงจรการเล่นเกมหลักของเกม แต่ยังมีพื้นที่ให้สำรวจอีกมากมาย เช่น:

  • ภาพลักษณ์ของปืนเลเซอร์: ดูที่ ReplicatedStorageFirstPersonBlasterVisuals และ ServerScriptServiceThirdPersonBlasterVisuals.
  • เสียง: ดูที่ ReplicatedStorageSoundHandler.
  • โหมดที่กำหนดเอง: คุณจะปรับเปลี่ยนเกมนี้เพื่อแนะนำประเภทของวัตถุประสงค์ใหม่ๆ เช่น การทำคะแนนให้ได้มากที่สุดก่อนเวลาหมดได้อย่างไร?

สำหรับตรรกะการเล่นเกมที่ขยายสำหรับเกมเลเซอร์แท็ก รวมถึงทรัพย์สินสิ่งแวดล้อมที่มีคุณภาพสูงและนำกลับมาใช้ใหม่ได้ โปรดตรวจสอบ เลเซอร์แท็ก เทมเพลต

©2026 Roblox Corporation. Roblox, โลโก้ Roblox และ Powering Imagination เป็นส่วนหนึ่งของเครื่องหมายการค้าที่จดทะเบียน และไม่ได้จดทะเบียนของเราในสหรัฐฯ และประเทศอื่นๆ