在這個教程中,您將學習如何從創建玩家工具中的強射器發射激光並檢測它是否擊中玩家。
射線檢測以找到碰撞
射線檢測從起始位置向給定方向創建一條看不見的射線,並具有定義的長度。如果射線與其路徑中的物體或地形發生碰撞,則會返回有關碰撞的信息,例如位置和碰撞的物體。

查找滑鼠位置
在發射激光之前,您必須先知道玩家的瞄準方向。這可以通過從玩家的 2D 滑鼠位置直接從相機向 3D 世界前進進行射線檢測來找到。射線會與玩家用滑鼠瞄準的物體發生碰撞。
在腳本頂部聲明一個名為 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)使用 UserInputService 的 GetMouseLocation 函數來獲取玩家在螢幕上的 2D 滑鼠位置。將其分配給名為 mouseLocation 的變量。
local UserInputService = game:GetService("UserInputService")local tool = script.Parentlocal MAX_MOUSE_DISTANCE = 1000local function getWorldMousePosition()local mouseLocation = UserInputService:GetMouseLocation()end
現在已知了 2D 的滑鼠位置,其 X 和 Y 屬性可以用作 Camera:ViewportPointToRay() 函數的參數,該函數從螢幕創建 Ray 進入 3D 世界。
使用 mouseLocation 的 X 和 Y 屬性作為 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。
聲明一個名為 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調用工作區的 WorldRoot:Raycast() 函數,將 screenToWorldRay 的 Origin 屬性作為第一個參數,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 | 射線相交的 BasePart 或 Terrain 單元。 |
| Position | 交集發生的地方;通常位於部件或地形表面上的一個點。 |
| Material | 碰撞點的材料。 |
| Normal | 相交面的法向量。這可以用來確定面的指向。 |
Position 屬性將是滑鼠懸停的物體的位置。如果滑鼠在 MAX_MOUSE_DISTANCE 的距離內沒有懸停在任何物體上,則 raycastResult 將為 nil。
編寫一個 if 語句來檢查 raycastResult 是否存在。
如果 raycastResult 有值,則返回其 Position 屬性。
如果 raycastResult 為 nil,則查找射線的末端。通過將 screenToWorldRay.Origin 和 directionVector 相加來計算滑鼠的 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 的滑鼠位置,可以將其用作向目標位置發射激光的 target position。可以使用 Raycast 函數在玩家的武器和目標位置之間發射第二條射線。
在腳本頂部宣告一個稱為 MAX_LASER_DISTANCE 的常量,並將其指派為 500,或您選擇的激光強射器範圍。
local UserInputService = game:GetService("UserInputService")local tool = script.Parentlocal MAX_MOUSE_DISTANCE = 1000local MAX_LASER_DISTANCE = 500在 getWorldMousePosition 函數下創建一個名為 fireWeapon 的函數。
調用 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 物件來儲存額外的參數,以用於射線檢測函數。它將在您的激光強射器中使用,以確保射線檢測不會意外地與發射武器的玩家相碰撞。任何包含在 RaycastParams 物件的 FilterDescendantsInstances 屬性中的部件都將在射線檢測中被 忽略。
繼續 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 沒有值,則通過將工具手柄的 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 來查找由激光擊中的物體的角色模型祖先(如果存在)。如果找到一個模型並且它包含一個 humanoid,在大多數情況下,您可以假設它是角色。
在 weaponRaycastResult 的 if 語句中添加以下突出顯示的代碼,以檢查是否擊中了角色。
-- 檢查在起始和結束位置之間是否有物體被擊中local hitPositionif weaponRaycastResult thenhitPosition = weaponRaycastResult.Position-- 被擊中的實例將是角色模型的子物件-- 如果在模型中找到 humanoid,那麼它很可能是玩家的角色local characterModel = weaponRaycastResult.Instance:FindFirstAncestorOfClass("Model")if characterModel thenlocal humanoid = characterModel:FindFirstChildWhichIsA("Humanoid")if humanoid thenprint("玩家被擊中")endendelse-- 根據最大激光距離計算結束位置hitPosition = tool.Handle.Position + directionVectorend
現在,激光強射器應在每次射線檢測擊中另一個玩家時將 玩家被擊中 打印到輸出窗口。
測試多位玩家
需要兩名玩家來測試武器射線檢測是否找到其他玩家,因此您需要啟動本地伺服器。請參見多客戶端模擬以獲取說明。
在一個客戶端上,測試用武器射擊另一個玩家,方法是點擊他們。每次射擊玩家時,"玩家被擊中" 應在輸出中顯示。
查找激光位置
強射器應該向其目標發射紅色光束。這方面的功能將放在 ModuleScript 中,以便在其他腳本中重複使用。首先,該腳本需要找到激光束應渲染的位置。
創建一個名為 LaserRenderer 的 ModuleScript,將其父項設為 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 的變量,並將 toolHandle 的 Position 屬性設置為其值。這將是玩家的激光強射器位置。
宣告一個名為 laserDistance 的變量,並將 endPosition 減去 startPosition 以找到兩個向量之間的差。使用其 Magnitude 屬性來獲取激光束的長度。
function LaserRenderer.createLaser(toolHandle, endPosition)local startPosition = toolHandle.Positionlocal laserDistance = (startPosition - endPosition).Magnitudeend聲明一個 laserCFrame 變量,用於存儲激光束的位置和方向。位置需要是激光束起始和結束點的中點。使用 CFrame.lookAt 創建一個位於 startPosition並面向 endPosition 的新 CFrame。將其乘以 Z 軸值為負的 laserDistance 的新 CFrame,以獲得中點。
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 的變量,並引用位於 PlayerScripts 的 LaserRenderer 模塊。
local UserInputService = game:GetService("UserInputService")local Players = game:GetService("Players")local LaserRenderer = require(Players.LocalPlayer.PlayerScripts.LaserRenderer)local tool = script.Parent在 fireWeapon 函數的底部,使用工具手柄和 hitPosition 作為參數來調用 LaserRenderer createLaser 函數。
-- 根據最大激光距離計算結束位置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() 函數的結果。這返回自 1970 年 1 月 1 日(從廣泛使用的任意日期返回的秒數)以來的經過時間。
從 currentTime 中減去 timeOfPreviousShot,如果結果小於 FIRE_RATE,則返回 false;否則,返回 true。
-- 檢查自上次發射以來是否已過足夠時間local function canShootWeapon()local currentTime = tick()if currentTime - timeOfPreviousShot < FIRE_RATE thenreturn falseendreturn trueend在 fireWeapon 函數的末尾,每次發射武器時使用 tick 更新 timeOfPreviousShot。
hitPosition = tool.Handle.Position + directionVectorendtimeOfPreviousShot = tick()LaserRenderer.createLaser(tool.Handle, hitPosition)end在 toolActivated 函數內部創建一個 if 語句並調用 canShootWeapon 來檢查武器是否可以發射。
local function toolActivated()if canShootWeapon() thenfireWeapon()endend
當您測試強射器時,您應找到無論您點擊多快,每次發射之間都會有短短的 0.3 秒延遲。
傷害玩家
客戶端無法直接傷害其他客戶端;伺服器需要負責在播放器被擊中時發出傷害。
客戶端可以使用 RemoteEvent 來告訴伺服器角色已被擊中。這些應存儲在 ReplicatedStorage 中,以便客戶端和伺服器都能看到。
創建一個名為 Events 的 Folder 在 ReplicatedStorage 中。

在 Events 文件夾中插入一個 RemoteEvent,並將其命名為 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用一行 Luau 替換 fireWeapon 中的 "玩家被擊中" 輸出語句,以使用 characterModel 變量作為參數發送 DamageCharacter 遙控事件。
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 中插入一個 Script,並將其命名為 ServerLaserManager。

宣告一個名為 LASER_DAMAGE 的變量,並將其設置為 10,或您選擇的其他值。
創建一個名為 damageCharacter 的函數,具有兩個參數,分別為 playerFired 和 characterToDamage。
在函數內部,查找角色的 Humanoid,並從其生命值中減去 LASER_DAMAGE。
將 damageCharacter 函數與 Events 文件夾中的 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)通過啟動本地伺服器,使用 2 位玩家測試強射器。當您射擊另一個玩家時,他們的生命值將減少 LASER_DAMAGE 中設定的值。
渲染其他玩家的激光束
目前,激光束是由發射武器的客戶端創建的,因此只有他們能看到激光束。
如果激光束是在伺服器上創建的,那麼每個人都能看到它。然而,客戶端發射武器與伺服器接收到有關射擊的信息之間會有一小段 延遲。這意味著發射武器的客戶端會看到他們啟用武器與看到激光束之間的延遲;因此武器會感覺滯後。
為了解決這個問題,每個客戶端將創建自己的激光束。這意味著發射武器的客戶端會立即看到激光束。其他客戶端將經歷另一玩家射擊後激光束出現的小延遲。這是最佳情況:沒有辦法比這更快地將一個客戶端的激光傳達給其他客戶端。
射手的客戶端
首先,客戶端需要告訴伺服器它已發射激光並提供結束位置。
在 ReplicatedStorage 的 Events 文件夾中插入一個 RemoteEvent,並將其命名為 LaserFired。

在 ToolController 腳本中找到 fireWeapon 函數。在函數末尾,使用 hitPosition 作為參數觸發 LaserFired 遙控事件。
hitPosition = tool.Handle.Position + directionVectorendtimeOfPreviousShot = tick()eventsFolder.LaserFired:FireServer(hitPosition)LaserRenderer.createLaser(tool.Handle, hitPosition)end
伺服器
現在,伺服器必須接收客戶端發射的事件,並告訴所有客戶端激光束的開始和結束位置,以便它們也能渲染。
在 ServerLaserManager 腳本中,創建一個名為 playerFiredLaser 的函數,帶有兩個參數,分別為 playerFired 和 endPosition。
將該函數連接到 LaserFired 遙控事件。
-- 通知所有客戶端激光已發射,以便他們可以顯示激光local function playerFiredLaser(playerFired, endPosition)end-- 將事件連接到適當的函數eventsFolder.DamageCharacter.OnServerEvent:Connect(damageCharacter)eventsFolder.LaserFired.OnServerEvent:Connect(playerFiredLaser)
伺服器需要激光的起始位置。這可以由客戶端發送,但是最好的方法是在可能的情況下避免相信客戶端。角色的武器手柄位置將是起始位置,因此伺服器可以從這裏找到它。
在 playerFiredLaser 函數上方創建一個名為 getPlayerToolHandle 的函數,帶有一個參數稱為 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)
現在伺服器可以在 LaserFired 遙控事件上調用 FireAllClients,以向客戶端發送渲染激光所需的信息。這包括 發射激光的玩家(以免該玩家的客戶端重複渲染激光),手柄(作為激光的起始位置)、以及激光的結束位置。
在 playerFiredLaser 函數中,將 getPlayerToolHandle 函數與 playerFired 作為參數調用,並將值指派給變量 toolHandle。
如果 toolHandle 存在,則使用 playerFired、toolHandle 和 endPosition 作為參數觸發 LaserFired 事件給所有客戶端。
-- 通知所有客戶端激光已發射,以便他們可以顯示激光local function playerFiredLaser(playerFired, endPosition)local toolHandle = getPlayerToolHandle(playerFired)if toolHandle theneventsFolder.LaserFired:FireAllClients(playerFired, toolHandle, endPosition)endend
在客戶端上渲染
現在 FireAllClients 已被調用,每個客戶端將從伺服器收到事件以渲染激光束。每個客戶端可以重複使用之前的 LaserRenderer 模塊來渲染激光束,使用角色的手柄位置和伺服器發送的結束位置值。最初發射激光束的玩家應忽略此事件,否則他們會看到兩束激光。
在 StarterPlayerScripts 中創建一個名為 ClientLaserManager 的 LocalScript。

在腳本中引用 LaserRenderer 模塊。
創建一個名為 createPlayerLaser 的函數,具有參數 playerWhoShot、toolHandle 和 endPosition。
將這個函數與 Events 文件夾中的 LaserFired 遙控事件連接。
在函數中使用 if 語句檢查 playerWhoShot 是否 不等於 LocalPlayer。
在 if 語句中,使用 toolHandle 和 endPosition 作為參數調用 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 thenLaserRenderer.createLaser(toolHandle, endPosition)endendeventsFolder.LaserFired.OnClientEvent:Connect(createPlayerLaser)使用 2 位玩家啟動本地伺服器測試強射器。將每個客戶端的位置放在顯示器的不同側,以便同時看到兩個窗口。當您在一個客戶端上射擊時,另一個客戶端上應該能看到激光束。
音效
目前,發射音效僅在發射項目的客戶端上播放。您需要將播放聲音的代碼移動,以便其他玩家也能聽到。
在 ToolController 腳本中,轉到 toolActivated 函數並移除其播放音效的行。
local function toolActivated()if canShootWeapon() thenfireWeapon()endend在 LaserRenderer 中的 createLaser 函數底部,宣告一個名為 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 中,轉到 fireWeapon 函數中發送 DamageCharacter 遙控事件的行。
添加 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()
-- 從 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)
