使用激光進行擊中檢測

*此內容是使用 AI(Beta 測試版)翻譯,可能含有錯誤。若要以英文檢視此頁面,請按一下這裡

在本教程中,您將學習如何從創建玩家工具中的槍械發射激光並檢測它是否擊中玩家。

射線檢測以查找碰撞

射線檢測從起始位置向給定方向創建一條不可見的射線,並具有定義的長度。如果射線與其路徑中的物體或地形發生碰撞,它將返回有關碰撞的信息,例如位置和碰撞的物體。

從 A 指向 B 的射線與牆壁發生碰撞

查找鼠標位置

在發射激光之前,您必須首先知道玩家的瞄準位置。這可以通過從玩家的 2D 鼠標位置直接從相機向前射線檢測到 3D 世界來找到。射線將與玩家用鼠標瞄準的任何物體發生碰撞。

  1. 打開創建玩家工具中的槍械工具內的 ToolController 腳本。如果您尚未完成該教程,您可以下載槍械模型並將其插入到 StarterPack 中。

  2. 在腳本的頂部聲明一個名為 MAX_MOUSE_DISTANCE 的常量,值為 1000

  3. 創建一個名為 getWorldMousePosition 的函數。

    local tool = script.Parent
    local MAX_MOUSE_DISTANCE = 1000
    local function getWorldMousePosition()
    end
    local function toolEquipped()
    tool.Handle.Equip:Play()
    end
    local function toolActivated()
    tool.Handle.Activate:Play()
    end
    -- 將事件連接到適當的函數
    tool.Equipped:Connect(toolEquipped)
    tool.Activated:Connect(toolActivated)
  4. 使用 UserInputService 的 GetMouseLocation 函數獲取玩家在屏幕上的 2D 鼠標位置。將其分配給名為 mouseLocation 的變量。

    local UserInputService = game:GetService("UserInputService")
    local tool = script.Parent
    local MAX_MOUSE_DISTANCE = 1000
    local function getWorldMousePosition()
    local mouseLocation = UserInputService:GetMouseLocation()
    end

現在已知 2D 鼠標位置,其 XY 屬性可以用作 Camera:ViewportPointToRay() 函數的參數,該函數從屏幕創建一個 Ray 進入 3D 世界。

  1. 使用 mouseLocationXY 屬性作為 Camera:ViewportPointToRay() 函數的參數。將其分配給名為 screenToWorldRay 的變量。

    local function getWorldMousePosition()
    local mouseLocation = UserInputService:GetMouseLocation()
    -- 從 2D 鼠標位置創建一條射線
    local screenToWorldRay = workspace.CurrentCamera:ViewportPointToRay(mouseLocation.X, mouseLocation.Y)
    end

現在是時候使用 WorldRoot:Raycast() 函數檢查射線是否擊中物體。這需要一個起始位置和方向向量:在此示例中,您將使用 screenToWorldRay 的原點和方向屬性。

方向向量的長度決定了射線將行進多遠。射線需要與 MAX_MOUSE_DISTANCE 一樣長,因此您必須將方向向量乘以 MAX_MOUSE_DISTANCE

  1. 聲明一個名為 directionVector 的變量,並將其賦值為 screenToWorldRay.Direction 乘以 MAX_MOUSE_DISTANCE

    local function getWorldMousePosition()
    local mouseLocation = UserInputService:GetMouseLocation()
    -- 從 2D 鼠標位置創建一條射線
    local screenToWorldRay = workspace.CurrentCamera:ViewportPointToRay(mouseLocation.X, mouseLocation.Y)
    -- 射線的單位方向向量乘以最大距離
    local directionVector = screenToWorldRay.Direction * MAX_MOUSE_DISTANCE
  2. 調用工作區的 WorldRoot:Raycast() 函數,將 screenToWorldRayOrigin 屬性作為第一個參數,將 directionVector 作為第二個參數。將其分配給名為 raycastResult 的變量。

    local function getWorldMousePosition()
    local mouseLocation = UserInputService:GetMouseLocation()
    -- 從 2D 鼠標位置創建一條射線
    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射線相交的 BasePartTerrain 單元。
Position交點發生的地方;通常是直接在部件或地形表面上的一個點。
Material碰撞點的材料。
Normal相交面的法向量。這可以用來確定面朝向的方向。

Position 屬性將是鼠標懸停的物體的位置。如果鼠標沒有懸停在距離 MAX_MOUSE_DISTANCE 內的任何物體上,則 raycastResult 將為 nil

  1. 創建一個 if 語句來檢查 raycastResult 是否存在。

  2. 如果 raycastResult 有值,則返回其 Position 屬性。

  3. 如果 raycastResultnil,則查找射線檢測的結束位置。通過將 screenToWorldRay.OrigindirectionVector 相加來計算鼠標的 3D 位置。

local function getWorldMousePosition()
local mouseLocation = UserInputService:GetMouseLocation()
-- 從 2D 鼠標位置創建一條射線
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
-- 返回交點的 3D 點
return raycastResult.Position
else
-- 沒有物體被擊中,因此計算射線的結束位置
return screenToWorldRay.Origin + directionVector
end
end

向目標發射

現在已知 3D 鼠標位置,可以將其用作發射激光的 目標位置。可以使用 Raycast 函數在玩家的武器和目標位置之間發射第二條射線。

  1. 在腳本的頂部聲明一個名為 MAX_LASER_DISTANCE 的常量,並將其賦值為 500,或您選擇的激光槍的範圍。

    local UserInputService = game:GetService("UserInputService")
    local tool = script.Parent
    local MAX_MOUSE_DISTANCE = 1000
    local MAX_LASER_DISTANCE = 500
  2. getWorldMousePosition 函數下創建一個名為 fireWeapon 的函數。

  3. 調用 getWorldMousePosition 並將結果分配給名為 mousePosition 的變量。這將是射線檢測的目標位置。

    -- 沒有物體被擊中,因此計算射線的結束位置
    return screenToWorldRay.Origin + directionVector
    end
    end
    local function fireWeapon()
    local mouseLocation = getWorldMousePosition()
    end
    local function toolEquipped()
    tool.Handle.Equip:Play()
    end

這次,射線檢測函數的方向向量將表示從玩家的工具位置到目標位置的方向。

  1. 聲明一個名為 targetDirection 的變量,通過從 mouseLocation 中減去工具位置來計算方向向量。

  2. 使用其 Unit 屬性對向量進行歸一化。這使其大小為 1,便於稍後乘以長度。

    local function fireWeapon()
    local mouseLocation = getWorldMousePosition()
    -- 計算歸一化的方向向量並乘以激光距離
    local targetDirection = (mouseLocation - tool.Handle.Position).Unit
    end
  3. 聲明一個名為 directionVector 的變量,並將其賦值為 targetDirection 乘以 MAX_LASER_DISTANCE

    local targetDirection = (mouseLocation - tool.Handle.Position).Unit
    -- 發射武器的方向,乘以最大距離
    local directionVector = targetDirection * MAX_LASER_DISTANCE
    end

可以使用 RaycastParams 對象來存儲射線檢測函數的其他參數。它將在您的激光槍中使用,以確保射線檢測不會意外地與發射武器的玩家發生碰撞。任何包含在 RaycastParams 對象的 FilterDescendantsInstances 屬性中的部件將在射線檢測中被 忽略

  1. 繼續 fireWeapon 函數,聲明一個名為 weaponRaycastParams 的變量。將一個新的 RaycastParams 對象賦值給它。

  2. 創建一個包含玩家本地 character 的表,並將其分配給 weaponRaycastParams.FilterDescendantsInstances 屬性。

  3. 從玩家的工具手柄位置進行射線檢測,朝向 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

最後,您需要檢查射線檢測操作是否返回了值。如果返回了一個值,則表示射線擊中了物體,可以在武器和擊中位置之間創建激光。如果沒有返回任何內容,則需要計算最終位置以創建激光。

  1. 聲明一個名為 hitPosition 的空變量。

  2. 使用 if 語句檢查 weaponRaycastResult 是否有值。如果擊中了一個物體,則將 weaponRaycastResult.Position 賦值給 hitPosition

    local weaponRaycastResult = workspace:Raycast(tool.Handle.Position, directionVector, weaponRaycastParams)
    -- 檢查在起始位置和結束位置之間是否擊中了任何物體
    local hitPosition
    if weaponRaycastResult then
    hitPosition = weaponRaycastResult.Position
    end
  3. 如果 weaponRaycastResult 沒有值,則通過將工具手柄的 positiondirectionVector 相加來計算射線檢測的結束位置。將其賦值給 hitPosition

    local weaponRaycastResult = workspace:Raycast(tool.Handle.Position, directionVector, weaponRaycastParams)
    -- 檢查在起始位置和結束位置之間是否擊中了任何物體
    local hitPosition
    if weaponRaycastResult then
    hitPosition = weaponRaycastResult.Position
    else
    -- 根據最大激光距離計算結束位置
    hitPosition = tool.Handle.Position + directionVector
    end
    end
  4. 導航到 toolActivated 函數並調用 fireWeapon 函數,以便每次激活工具時都能發射激光。

    local function toolActivated()
    tool.Handle.Activate:Play()
    fireWeapon()
    end

檢查擊中的物體

要查找激光擊中的物體是否是玩家角色的一部分或僅僅是場景的一部分,您需要查找 Humanoid,因為每個角色都有一個。

首先,您需要找到 角色模型。如果擊中角色的一部分,則不能假設擊中物體的父級是角色。激光可能擊中了一個身體部位、一個配件或一個工具,這些都位於角色層次結構的不同部分。

您可以使用 FindFirstAncestorOfClass 查找激光擊中的物體的角色模型祖先(如果存在)。如果找到一個模型並且它包含一個 humanoid,在大多數情況下,您可以假設它是一個角色。

  1. 將下面的高亮代碼添加到 weaponRaycastResult if 語句中,以檢查是否擊中了一個角色。

    -- 檢查在起始位置和結束位置之間是否擊中了任何物體
    local hitPosition
    if weaponRaycastResult then
    hitPosition = weaponRaycastResult.Position
    -- 被擊中的實例將是角色模型的子級
    -- 如果在模型中找到 humanoid,那麼它很可能是玩家的角色
    local characterModel = weaponRaycastResult.Instance:FindFirstAncestorOfClass("Model")
    if characterModel then
    local humanoid = characterModel:FindFirstChildWhichIsA("Humanoid")
    if humanoid then
    print("玩家被擊中")
    end
    end
    else
    -- 根據最大激光距離計算結束位置
    hitPosition = tool.Handle.Position + directionVector
    end

現在,激光槍應該在每次射線檢測操作擊中另一個玩家時,將 玩家被擊中 打印到 輸出 窗口。

使用多個玩家進行測試

需要兩個玩家來測試武器射線檢測是否找到其他玩家,因此您需要啟動本地服務器。請參見多客戶端模擬以獲取說明。

在一個客戶端上,測試用武器射擊另一個玩家,通過單擊他們。每次射擊玩家時,應在輸出中顯示 "玩家被擊中"。

查找激光位置

槍械應該向其目標發射一束紅光。這個功能將放在一個 ModuleScript 中,以便將來可以在其他腳本中重用。首先,腳本需要找到激光束應該渲染的位置。

  1. 創建一個名為 LaserRendererModuleScript,並將其作為子項放在 StarterPlayerScripts 下的 StarterPlayer 中。

  2. 打開腳本並將模塊表重命名為腳本的名稱 LaserRenderer

  3. 聲明一個名為 SHOT_DURATION 的變量,值為 0.15。這將是激光可見的時間(以秒為單位)。

  4. 創建一個名為 createLaser 的 LaserRenderer 函數,帶有兩個參數,分別為 toolHandleendPosition

    local LaserRenderer = {}
    local SHOT_DURATION = 0.15 -- 激光可見的時間
    -- 從起始位置朝向結束位置創建激光束
    function LaserRenderer.createLaser(toolHandle, endPosition)
    end
    return LaserRenderer
  5. 聲明一個名為 startPosition 的變量,並將 toolHandlePosition 屬性設置為其值。這將是玩家的激光槍的位置。

  6. 聲明一個名為 laserDistance 的變量,並從 startPosition 中減去 endPosition 以查找兩個向量之間的差異。使用 Magnitude 屬性來獲取激光束的長度。

    function LaserRenderer.createLaser(toolHandle, endPosition)
    local startPosition = toolHandle.Position
    local laserDistance = (startPosition - endPosition).Magnitude
    end
  7. 聲明一個 laserCFrame 變量以存儲激光束的位置和方向。位置需要是激光束的起始和結束之間的中點。使用 CFrame.lookAt 創建一個位於 startPosition 並面向 endPosition 的新 CFrame。將其乘以一個新的 CFrame,Z 軸值為負的 laserDistance 一半,以獲得中點。

    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)
    end

創建激光部件

現在您知道要創建激光束的位置,您需要添加激光束本身。這可以通過 Neon 部件輕鬆完成。

  1. 聲明一個變量 laserPart,並將其賦值為一個新的 Part 實例。

  2. 設置 laserPart 的以下屬性:

    1. Size: Vector3.new(0.2, 0.2, laserDistance)
    2. CFrame: laserCFrame
    3. Anchored: true
    4. CanCollide: false
    5. Color: Color3.fromRGB(225, 0, 0)(強紅色)
    6. Material: Enum.Material.Neon
  3. laserPart 作為子項放入 Workspace 中。

  4. 將該部件添加到 Debris 服務中,以便在 SHOT_DURATION 變量中的秒數後將其移除。

    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(225, 0, 0)
    laserPart.Material = Enum.Material.Neon
    laserPart.Parent = workspace
    -- 將激光束添加到 Debris 服務中以便移除和清理
    Debris:AddItem(laserPart, SHOT_DURATION)
    end

現在渲染激光束的函數已完成,可以由 ToolController 調用。

  1. ToolController 腳本的頂部聲明一個名為 LaserRenderer 的變量,並要求 PlayerScripts 中的 LaserRenderer ModuleScript。

    local UserInputService = game:GetService("UserInputService")
    local Players = game:GetService("Players")
    local LaserRenderer = require(Players.LocalPlayer.PlayerScripts.LaserRenderer)
    local tool = script.Parent
  2. fireWeapon 函數的底部,使用工具手柄和 hitPosition 作為參數調用 LaserRenderer 的 createLaser 函數。

    -- 根據最大激光距離計算結束位置
    hitPosition = tool.Handle.Position + directionVector
    end
    LaserRenderer.createLaser(tool.Handle, hitPosition)
    end
  3. 通過單擊播放按鈕測試武器。當激活工具時,武器和鼠標之間應可見激光束。

控制武器射擊速率

武器需要在每次射擊之間有延遲,以防止玩家在短時間內造成過多傷害。這可以通過檢查自上次射擊以來是否已經過了足夠的時間來控制。

  1. ToolController 的頂部聲明一個名為 FIRE_RATE 的變量。這將是每次射擊之間的最小時間。給它一個您選擇的值;此示例使用 0.3 秒。

  2. 在下面聲明另一個變量,名為 timeOfPreviousShot,值為 0。這將存儲玩家最後一次射擊的時間,並將在每次射擊時更新。

    local MAX_MOUSE_DISTANCE = 1000
    local MAX_LASER_DISTANCE = 300
    local FIRE_RATE = 0.3
    local timeOfPreviousShot = 0
  3. 創建一個名為 canShootWeapon 的函數,無參數。此函數將查看自上次射擊以來經過了多少時間並返回 true 或 false。

    local FIRE_RATE = 0.3
    local timeOfPreviousShot = 0
    -- 檢查自上次射擊以來是否已經過了足夠的時間
    local function canShootWeapon()
    end
    local function getWorldMousePosition()
  4. 在函數內部聲明一個名為 currentTime 的變量;將其賦值為調用 tick() 函數的結果。這將返回自 1970 年 1 月 1 日(廣泛用於計算時間的任意日期)以來經過的時間(以秒為單位)。

  5. currentTime 中減去 timeOfPreviousShot,如果結果小於 FIRE_RATE,則返回 false;否則返回 true

    -- 檢查自上次射擊以來是否已經過了足夠的時間
    local function canShootWeapon()
    local currentTime = tick()
    if currentTime - timeOfPreviousShot < FIRE_RATE then
    return false
    end
    return true
    end
  6. fireWeapon 函數的末尾,每次武器射擊時使用 tick 更新 timeOfPreviousShot

    hitPosition = tool.Handle.Position + directionVector
    end
    timeOfPreviousShot = tick()
    LaserRenderer.createLaser(tool.Handle, hitPosition)
    end
  7. toolActivated 函數內部,創建一個 if 語句並調用 canShootWeapon 以檢查武器是否可以發射。

    local function toolActivated()
    if canShootWeapon() then
    tool.Handle.Activate:Play()
    fireWeapon()
    end
    end

當您測試槍械時,您應該發現無論您點擊多快,每次射擊之間始終會有短暫的 0.3 秒延遲。

對玩家造成傷害

客戶端無法直接對其他客戶端造成傷害;服務器需要負責在玩家被擊中時發出傷害。

客戶端可以使用 RemoteEvent 告訴服務器角色已被擊中。這些應存儲在 ReplicatedStorage 中,客戶端和服務器都可以看到它們。

  1. 在 ReplicatedStorage 中創建一個名為 EventsFolder

  2. 在 Events 文件夾中插入一個 RemoteEvent,並將其命名為 DamageCharacter

  3. 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.Parent
    local eventsFolder = ReplicatedStorage.Events
    local MAX_MOUSE_DISTANCE = 1000
    local MAX_LASER_DISTANCE = 500
  4. 用一行 Luau 代碼替換 fireWeapon 中的 "玩家被擊中" 打印語句,以使用 characterModel 變量作為參數觸發 DamageCharacter 遠程事件。

    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

服務器需要在事件觸發時對被擊中的玩家造成傷害。

  1. 在 ServerScriptService 中插入一個 Script,並將其命名為 ServerLaserManager

  2. 聲明一個名為 LASER_DAMAGE 的變量,並將其設置為 10,或您選擇的值。

  3. 創建一個名為 damageCharacter 的函數,帶有兩個參數,分別為 playerFiredcharacterToDamage

  4. 在函數內部,查找角色的 Humanoid 並從其生命值中減去 LASER_DAMAGE

  5. damageCharacter 函數連接到 Events 文件夾中的 DamageCharacter 遠程事件。

    local ReplicatedStorage = game:GetService("ReplicatedStorage")
    local eventsFolder = ReplicatedStorage.Events
    local LASER_DAMAGE = 10
    function damageCharacter(playerFired, characterToDamage)
    local humanoid = characterToDamage:FindFirstChildWhichIsA("Humanoid")
    if humanoid then
    -- 從角色中移除生命值
    humanoid.Health -= LASER_DAMAGE
    end
    end
    -- 將事件連接到適當的函數
    eventsFolder.DamageCharacter.OnServerEvent:Connect(damageCharacter)
  6. 通過啟動本地服務器測試兩個玩家的槍械。當您射擊另一個玩家時,他們的生命值將減少 LASER_DAMAGE 中分配的數量。

渲染其他玩家的激光束

目前,激光束是由發射武器的客戶端創建的,因此只有他們能看到激光束。

如果激光束是在服務器上創建的,那麼每個人都能看到它。然而,客戶端射擊武器和服務器接收有關射擊的信息之間會有一小段 延遲。這意味著發射武器的客戶端將看到激活武器和看到激光束之間的延遲;因此,武器會感覺到延遲。

為了解決這個問題,每個客戶端將創建自己的激光束。這意味著發射武器的客戶端將立即看到激光束。其他客戶端將在另一個玩家射擊和激光束出現之間經歷一小段延遲。這是最佳情況:沒有辦法更快地將一個客戶端的激光傳達給其他客戶端。

射手的客戶端

首先,客戶端需要告訴服務器它已經發射了激光並提供結束位置。

  1. 在 ReplicatedStorage 的 Events 文件夾中插入一個 RemoteEvent,並將其命名為 LaserFired

  2. ToolController 腳本中找到 fireWeapon 函數。在函數的末尾,使用 hitPosition 作為參數觸發 LaserFired 遠程事件。

    hitPosition = tool.Handle.Position + directionVector
    end
    timeOfPreviousShot = tick()
    eventsFolder.LaserFired:FireServer(hitPosition)
    LaserRenderer.createLaser(tool.Handle, hitPosition)
    end

服務器

服務器現在必須接收客戶端發射的事件,並告訴所有客戶端激光束的起始和結束位置,以便它們也能渲染它。

  1. ServerLaserManager 腳本中,創建一個名為 playerFiredLaser 的函數,帶有兩個參數,分別為 playerFiredendPosition

  2. 將該函數連接到 LaserFired 遠程事件。

    -- 通知所有客戶端激光已發射,以便它們可以顯示激光
    local function playerFiredLaser(playerFired, endPosition)
    end
    -- 將事件連接到適當的函數
    eventsFolder.DamageCharacter.OnServerEvent:Connect(damageCharacter)
    eventsFolder.LaserFired.OnServerEvent:Connect(playerFiredLaser)

服務器需要激光的起始位置。這可以從客戶端發送,但最好避免信任客戶端。角色的武器手柄位置將是起始位置,因此服務器可以從那裡找到它。

  1. playerFiredLaser 函數上方創建一個名為 getPlayerToolHandle 的函數,帶有一個參數 player

  2. 使用以下代碼在玩家的角色中查找武器並返回手柄對象。

    local LASER_DAMAGE = 10
    -- 查找玩家持有的工具的手柄
    local function getPlayerToolHandle(player)
    local weapon = player.Character:FindFirstChildOfClass("Tool")
    if weapon then
    return weapon:FindFirstChild("Handle")
    end
    end
    -- 通知所有客戶端激光已發射,以便它們可以顯示激光
    local function playerFiredLaser(playerFired, endPosition)

現在,服務器可以在 LaserFired 遠程事件上調用 FireAllClients,以將渲染激光所需的信息發送給客戶端。這包括 player(發射激光的玩家,以便該玩家的客戶端不會重複渲染激光)、手柄(作為激光的起始位置)和激光的結束位置。

  1. playerFiredLaser 函數中,使用 getPlayerToolHandle 函數,將 playerFired 作為參數,並將值分配給名為 toolHandle 的變量。

  2. 如果 toolHandle 存在,則使用 playerFiredtoolHandleendPosition 作為參數觸發 LaserFired 事件以通知所有客戶端。

    -- 通知所有客戶端激光已發射,以便它們可以顯示激光
    local function playerFiredLaser(playerFired, endPosition)
    local toolHandle = getPlayerToolHandle(playerFired)
    if toolHandle then
    eventsFolder.LaserFired:FireAllClients(playerFired, toolHandle, endPosition)
    end
    end

在客戶端渲染

現在 FireAllClients 被調用,每個客戶端將從服務器接收到一個事件以渲染激光束。每個客戶端可以重用之前的 LaserRenderer 模塊來使用服務器發送的工具手柄位置和結束位置值渲染激光束。最初發射激光束的玩家應該忽略此事件,否則他們將看到兩條激光。

  1. 在 StarterPlayerScripts 中創建一個名為 ClientLaserManagerLocalScript

  2. 在腳本中要求 LaserRenderer 模塊。

  3. 創建一個名為 createPlayerLaser 的函數,帶有參數 playerWhoShottoolHandleendPosition

  4. 將該函數連接到 Events 文件夾中的 LaserFired 遠程事件。

  5. 在函數中,使用 if 語句檢查 playerWhoShot 是否 不等於 LocalPlayer。

  6. 在 if 語句內部,使用 toolHandleendPosition 作為參數調用 LaserRenderer 模塊的 createLaser 函數。

    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 then
    LaserRenderer.createLaser(toolHandle, endPosition)
    end
    end
    eventsFolder.LaserFired.OnClientEvent:Connect(createPlayerLaser)
  7. 通過啟動本地服務器測試兩個玩家的槍械。將每個客戶端放置在顯示器的不同側面,以便您可以同時看到兩個窗口。當您在一個客戶端上射擊時,您應該在另一個客戶端上看到激光。

音效

目前,射擊音效僅在發射彈丸的客戶端上播放。您需要將播放聲音的代碼移動,以便其他玩家也能聽到它。

  1. ToolController 腳本中,導航到 toolActivated 函數,並刪除播放 Activate 音效的行。

    local function toolActivated()
    if canShootWeapon() then
    fireWeapon()
    end
    end
  2. LaserRenderer 中的 createLaser 函數底部,聲明一個名為 shootingSound 的變量,並使用 FindFirstChild() 方法檢查 toolHandle 中的 Activate 音效。

  3. 使用 if 語句檢查 shootingSound 是否存在;如果存在,則調用其 Play() 函數。

    laserPart.Parent = workspace
    -- 將激光束添加到 Debris 服務中以便移除和清理
    Debris:AddItem(laserPart, SHOT_DURATION)
    -- 播放武器的射擊聲音
    local shootingSound = toolHandle:FindFirstChild("Activate")
    if shootingSound then
    shootingSound:Play()
    end
    end

使用驗證保護遠程事件

如果服務器不檢查來自傳入請求的數據,則黑客可以濫用遠程函數和事件,並使用它們向服務器發送虛假值。使用 服務器端驗證 來防止這種情況是很重要的。

在當前形式下,DamageCharacter 遠程事件非常容易受到攻擊。黑客可以使用此事件對任何他們想要的玩家造成傷害,而無需射擊他們。

驗證是檢查發送到服務器的值是否現實的過程。在這種情況下,服務器需要:

  • 檢查玩家與激光擊中位置之間的距離是否在某個邊界內。
  • 在發射激光的武器和擊中位置之間進行射線檢測,以確保射擊是可能的,並且沒有穿過任何牆壁。

客戶端

客戶端需要將射線檢測擊中的位置發送給服務器,以便它可以檢查距離是否現實。

  1. ToolController 中,導航到 fireWeapon 函數中觸發 DamageCharacter 遠程事件的行。

  2. hitPosition 添加為參數。

    if characterModel then
    local humanoid = characterModel:FindFirstChildWhichIsA("Humanoid")
    if humanoid then
    eventsFolder.DamageCharacter:FireServer(characterModel, hitPosition)
    end
    end

服務器

客戶端現在通過 DamageCharacter 遠程事件發送了一個額外的參數,因此 ServerLaserManager 需要進行調整以接受它。

  1. ServerLaserManager 腳本中,將 hitPosition 參數添加到 damageCharacter 函數中。

    function damageCharacter(playerFired, characterToDamage, hitPosition)
    local humanoid = characterToDamage:FindFirstChildWhichIsA("Humanoid")
    if humanoid then
    -- 從角色中移除生命值
    humanoid.Health -= LASER_DAMAGE
    end
    end
  2. getPlayerToolHandle 函數下方創建一個名為 isHitValid 的函數,帶有三個參數:playerFiredcharacterToDamagehitPosition

    end
    local function isHitValid(playerFired, characterToDamage, hitPosition)
    end

第一個檢查將是擊中位置和角色之間的距離。

  1. 在腳本的頂部聲明一個名為 MAX_HIT_PROXIMITY 的變量,並將其賦值為 10。這將是擊中位置和角色之間允許的最大距離。需要容差,因為角色可能在客戶端發射事件後稍微移動。

    local ReplicatedStorage = game:GetService("ReplicatedStorage")
    local eventsFolder = ReplicatedStorage.Events
    local LASER_DAMAGE = 10
    local MAX_HIT_PROXIMITY = 10
  2. isHitValid 函數中,計算角色和擊中位置之間的距離。如果距離大於 MAX_HIT_PROXIMITY,則返回 false

    local function isHitValid(playerFired, characterToDamage, hitPosition)
    -- 驗證擊中角色和擊中位置之間的距離
    local characterHitProximity = (characterToDamage.HumanoidRootPart.Position - hitPosition).Magnitude
    if characterHitProximity > MAX_HIT_PROXIMITY then
    return false
    end
    end

第二個檢查將涉及在發射的武器和擊中位置之間進行射線檢測。如果射線檢測返回的物體不是角色,則可以假設射擊無效,因為有東西阻擋了射擊。

  1. 複製以下代碼以執行此檢查。返回 true 在函數的末尾:如果到達結尾,則所有檢查都已通過。

    local function isHitValid(playerFired, characterToDamage, hitPosition)
    -- 驗證擊中角色和擊中位置之間的距離
    local characterHitProximity = (characterToDamage.HumanoidRootPart.Position - hitPosition).Magnitude
    if characterHitProximity > 10 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
  2. damageCharacter 函數中聲明一個名為 validShot 的變量。將其賦值為調用 isHitValid 函數的結果,參數為 playerFiredcharacterToDamagehitPosition

  3. 在下面的 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_DAMAGE
    end
    end

現在,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()
-- 從 2D 鼠標位置創建一條射線
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
-- 返回交點的 3D 點
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
-- 被擊中的實例將是角色模型的子級
-- 如果在模型中找到 humanoid,那麼它很可能是玩家的角色
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)
©2026 Roblox Corporation、Roblox、Roblox 標誌及 Powering Imagination 是我們在美國及其他國家地區的部分註冊與未註冊商標。