雷射檢測器檢測到的擊中行為

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

在這個教學中,你會學會從雷射器中發射雷射並在 創建玩家工具 中檢查是否擊中玩家。

使用雷射投射找到衝突

雷射投射 從開始位置移動一個隱形的雷射到指定方向,並且以一定長度定義。如果雷射撞到路徑上的物體或地形,會返回衝突相關資訊,例如位置和物體。

向 A 射線撞擊 B 牆壁

尋找滑鼠位置

在雷射可以射擊之前,你必須先知道玩家正在瞄準哪裡。這可以通過從玩家 2D 滑鼠位置直接雷射前方的屏幕上找到。雷會撞擊玩家正在瞄準的任何東西。

  1. 創建玩家工具 中開啟 Blaster 工具控制器內的 工具控制器 指令。如果您尚未完成該教學,您可以下載 1>Blaster1> 模型並將其插入到 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 屬性可以用作參數 for the Camera:ViewportPointToRay() 函數,它從屏幕上創建一個 1> Datatype.Ray1> 從屏幕進入 3D 遊戲世界。

  1. 使用 XY 屬性的 mouseLocation 作為 1> Class.Camera:ViewportPointToRay()|ViewportPointToRay()1> 函數的參數。將此分配到名為 4> screenToWorldRay4> 的變量。


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

是時候使用 Raycast 函數來檢查雷射是否擊中對物件。這需要一個開始位置和方向向向量力:在此示例中,您將使用 screenToWorldRay 的原始和方向屬性。

方向向量的長度決定向射線的旅程範圍。射線必須為 MAX_MOUSE_DISTANCE ,因此您必須將方向向量乘以 MAX_MOUSE_DISTANCE

  1. 宣告一個名為 directionVector 的變數,並且將其值乘以 screenToWorldRay.Direction


    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. 呼叫 Raycast 工作區的 起始 屬性,將 screenToWorldRay 的第一個參數和 1> durationVector1> 的第二個參數傳為第一個參數,並將 4> raycastResult4> 變更為變量名 7>raycastResult7>。


    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,其中包含對碰兩個射線和對物件之間的衝突的信息。

射線投射結果說明
實例射線擋道的 BasePartTerrain 牢獄。
位置發生交叉點的地方;通常是在零件或地形的表面上。
材料在碰撞點的材料。
普通交叉面的正常向量。這可以用來決定面是否指向哪個方向。

位置 屬性將是鼠標所指向的對象的位置。如果鼠標不在MAX_MOUSE_DISTANCE 內的任何對象上,raycastResult 將為空。

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

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

  3. 如果 raycastResult 為零,則找到射線投射結果的終點。計算鼠標的 3D 位置,請將 screenToWorldRay.OrigindirectionVector 加入一起。


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 滑鼠位置,它可以用作 目標位置 發射雷射向前射擊。一個第二個射線可以在玩家的武器和目標位置之間使用 射線投射 功能來拋射。

  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. 使用它的 單位 屬性來普通化它。這使它的乘數變得簡單,因此可以稍後用長度來乘以。


    local function fireWeapon()
    local mouseLocation = getWorldMousePosition()
    -- 計算一個普通方向量量,並乘以雷射距離
    local targetDirection = (mouseLocation - tool.Handle.Position).Unit
    end
  3. 宣告一個名為 directionVector 的變數,並且將它的值乘以 targetDirection


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

一個 RaycastParams 對象可用來儲存射線投射功能的額外參數。它會在雷射發射器上使用,以確保雷射不會意外與玩具發射器碰撞。任何包含在 FilterDescendantsInstances 屬性的雷

  1. 繼續 fireWeapon 函數並宣告一個變量名為 weaponRaycastParams 。為它設定新的 RaycastParams 對象。

  2. 建立包含玩家本地 角色 的桌子,並將其指定為 weaponRaycastParams.FilterDescendantsInstances 屬性。

  3. 從玩家的工具處位置射線投射到 directionVector 。記住,這次要從 weaponRaycastParams 作為參數添加。這次從 waponRaycastResult 變量命名。


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. 使用 如果 句話來檢查 weaponRaycastResult 是否有值。如果對象被擊中,將 weaponRaycastResult.Position 指派給 1> hitPosition1> 。


    local weaponRaycastResult = workspace:Raycast(tool.Handle.Position, directionVector, weaponRaycastParams)
    -- 檢查任何物體是否在開始和結束位置之間被擊中
    local hitPosition
    if weaponRaycastResult then
    hitPosition = weaponRaycastResult.Position
    end
  3. 如果 weaponRaycastResult 沒有值,計算射線投射的終點位置,將工具處理器的位置與 numberDirectionVector 添加到一起,並將其分配到 directionVector


    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 來尋找雷射擊中的物體的人物模型祖先,如果存在。如果您找到模型並且它包含人形,在大多數情況下您可以假設它是一個人物。

  1. weaponRaycastResult 下將以下標示的代碼添加到 if 句話來檢查角色是否被擊中。


    -- 檢查任何物體是否在開始和結束位置之間被擊中
    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
    print("Player hit")
    end
    end
    else
    -- 根據最大雷射距離計算端位
    hitPosition = tool.Handle.Position + directionVector
    end

現在雷射激光器每次雷cast操作打印Player hit到輸出窗口。

與多個玩家測試

需要兩個玩家才能測試武器射線投射是否尋找其他玩家,因此您需要開啟本地服務伺服器。

  1. 選擇 Studio 的 測試 標籤。

  2. 確認玩家選單已設為「2 位玩家」,然後按一下開始按鈕來 開始 一個本地伺服器,並有 3 個視窗會出現。第一視窗會是本地伺服器,其他視窗會是玩家1 和玩家 2 的客戶。

  3. 在一個客戶端上,測試射擊其他玩家使用武器,點擊他們。 "玩家被擊中" 應該顯示在輸出每次玩家被擊中時。

您可以在此了解更多關於測試標籤

尋找雷射位置

發射器應該發射紅色光束至其目標。 發射器的功能將在 ModuleScript 內,因此它可以在其他腳本中重用。 首先,腳本需要找到雷射光束的位置。

  1. 創建名為 LaserRendererModuleScript ,在 StarterPlayer 下的 StarterPlayer 下。

  2. 開啟指令碼,並將模組表重命名為指令碼名 LaserRenderer

  3. 射擊時間 變更為 0.15 的變數。這將是雷射可見時間的數量 (以秒計)。

  4. 创建名为 createLaser 的激光渲染器函数,拥有两个参数: toolHandleendPosition


    local LaserRenderer = {}
    local SHOT_DURATION = 0.15 -- 雷射可見時間
    -- 從開始位置創建雷射光束
    function LaserRenderer.createLaser(toolHandle, endPosition)
    end
    return LaserRenderer
  5. 宣告一個名為 startPosition 的變數,並將 位置 屬性的 toolHandle 設為其值。這將會是玩家雷射器的位置。

  6. 宣告名為 laserDistance 的變數,並且從 endPosition 減去 startPosition 以找到兩個向量之間的差。使用此 1> 強度1> 屬性來取得雷射光束的長度。


    function LaserRenderer.createLaser(toolHandle, endPosition)
    local startPosition = toolHandle.Position
    local laserDistance = (startPosition - endPosition).Magnitude
    end
  7. 宣稱一個 laserCFrame 變數來存儲雷射光束的位置和方向。位置必須是開始和結束光束的中間點。使用 CFrame.lookAt 來創建新的 Datatype.CFrame


    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. 大小 : Vector3.new(0.2, 0.2, laserDistance)
    2. CFrame : laserCFrame
    3. 錨定 : true
    4. CanCollide : false
    5. 顏色 : Color3.fromRGB(225, 0, 0) (一種強烈的紅色)
    6. 材質 : 枚數.Material.Neon
  3. laserPart工作區

  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 指令碼的上方,宣告一個名為 LaserRender 的變數,並且要求位於 PlayerScripts 中的 LaserRender 模組指定為需要。


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


    -- 根據最大雷射距離計算端位
    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 的函數。這個函數會看看自從上一次射擊開始的時間,並且以真或假的方式返回。


    local FIRE_RATE = 0.3
    local timeOfPreviousShot = 0
    -- 檢查從上一次發射彈藥時至少過去了多久
    local function canShootWeapon()
    end
    local function getWorldMousePosition()
  4. 內部 functions 一個名為 currentTime 的變數;將其結果分配給 tick() 的函數。這返回從 1 月 1 日 1970 年 (一般用於計算時間) 起到的時間,以秒計時。

  5. timeOfPreviousShotcurrentTime 中控制 timeOfPreviousShot ,並且將結果小於 1>FIRE_RATE1> 時,返回 4> 否則返回 7> 真的7> 。


    -- 檢查從上一次發射彈藥時至少過去了多久
    local function canShootWeapon()
    local currentTime = tick()
    if currentTime - timeOfPreviousShot < FIRE_RATE then
    return false
    end
    return true
    end
  6. fireWeapon 函數的結束時 ,每次使用 timeOfPreviousShot 發射武器時更新 tick


    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 中創建名為 Events文件夾

  2. 將一個遠端事件放入 傷害角色 夾中。

  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. "Player hit" 列印說明在 fireWeapon 中用 Lua 列印傷害角色遠端事件,並將 characterModel 變量作為參引數。


    local characterModel = weaponRaycastResult.Instance:FindFirstAncestorOfClass("Model")
    if characterModel then
    local humanoid = characterModel:FindFirstChildWhichIsA("Humanoid")
    if humanoid then
    eventsFolder.DamageCharacter:FireServer(characterModel)
    end
    end
    else
    -- 根據最大雷射距離計算端位
    hitPosition = tool.Handle.Position + directionVector
    end

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

  1. 將 指令碼 插入 ServerScriptService 並命名為 ServerLaserManager。

  2. 宣告變量名 LASER_DAMAGE 並將其設為 10 或您選擇的任何值。

  3. 創建名為 damageCharacter 的函數,並且有兩個參數為 playerFiredcharacterToDamage

  4. 在函數內,找到角色的人形並從其生命值中 LASER_DAMAGE

  5. 連接 damageCharacter 函數到 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. RemoteEvent 放入 ReplicatedStorage 的 事件 夾中,並命名它為 LaserFired。

  2. fireWeapon 功能在 ToolController 指令碼中找到。在功能的結束,使用 LaserFired 遠端事件使用 1>hitPosition1> 作為參引數來發射。


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

服務器

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

  1. ServerLaserManager 指令碼中,創建名為 playerFiredLaser 的函數,上 damageCharacter 與兩個參數名為 1> playerFired1> 和 4> endPosition4>。

  2. 連接功能到 LaserFired 遠端事件。


    -- 通知所有客戶,雷射已發射,因此他們可以顯示雷射
    local function playerFiredLaser(playerFired, endPosition)
    end

    -- 連接事件到相應的函數
    eventsFolder.DamageCharacter.OnServerEvent:Connect(damageCharacter)
    eventsFolder.LaserFired.OnServerEvent:Connect(playerFiredLaser)

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

  1. playerFiredLaser 函數上創建一個名為 playerFiredLaser 的函數,並且在參數上使用 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)

服務器現在可以在 雷射發射器 遠端事件上呼叫 FireAllClients 來傳送必要的資訊給客戶,以便為客戶渲染雷射。這包括 1>player1> 發射雷射 (因此客戶的雷射不會重複) 的起始位置,4> 處理器4> 的 blaster

  1. playerFiredLaser 函數中,將 getPlayerToolHandle 函數與 playerFired 作為參數,並且將值分配到名為 1> toolHandle1> 的變量。

  2. 如果 工具處理器 存在,發射 LaserFired 事件給所有使用 playerFiredtoolHandle 和 1> endPosition1> 作為參數的客戶。


    -- 通知所有客戶,雷射已發射,因此他們可以顯示雷射
    local function playerFiredLaser(playerFired, endPosition)
    local toolHandle = getPlayerToolHandle(playerFired)
    if toolHandle then
    eventsFolder.LaserFired:FireAllClients(playerFired, toolHandle, endPosition)
    end
    end

在客戶端上渲染

現在 FireAllClients 已經呼叫,每個客戶人將從服務器收到一個事件來渲染雷射光束。每個客伺服器人都可以重用 LaserRenderer 模組從以前重用工具的位置和位置值傳送的雷射光束來渲染雷射光束。玩家發射雷射光束的第一個人應該忽略此事件,否則他們會看到 2 個雷射。

  1. 在 StarterPlayerScripts 中的 LocalScript 創建 ClientLaserManager

  2. 內部的脚指令碼,需要 雷射渲染器 模組。

  3. 創建名為 createPlayerLaser 的函數,並且設有 playerWhoShottoolHandle 和 1> endPosition1> 參數。

  4. 將功能連接到 LaserFired 遠端事件在 事件 夾中。

  5. 在 function 中,使用 如果 句子來檢查 playerWhoShot 是否 不等同 本地玩家。

  6. 在 if 語句內,從 LaserRender 模組使用 createLasertoolHandle 作為參數來呼叫 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 then
    LaserRenderer.createLaser(toolHandle, endPosition)
    end
    end
    eventsFolder.LaserFired.OnClientEvent:Connect(createPlayerLaser)
  7. 使用 2 名玩家啟動本地伺服器。將每個客戶端放置在監視器的不同側面,讓您可以同時查看兩個窗口。當您在一個客戶端上射擊時,您應該看到雷射在另一個客戶端上。

音效

目前,射擊聲效果只會在彈道投射到客戶端時播放。您需要將代碼移動到播放聲效,以便其他玩家也能聽到。

  1. ToolController 指令碼中,移動至 工具已啟用 功能,並移除播放啟用聲音的行。


    local function toolActivated()
    if canShootWeapon() then
    fireWeapon()
    end
    end
  2. createLaser 函數的底部,在 LaserRender 中,宣告一個名為 shootingSound 的變數,並使用 1> Class.Instance:FindFirstChild()|FindFirstChild()1> 方法的 4> toolHandle4> 來檢查 7>激活7> 音效。

  3. 使用 如果 句話來檢查是否存在 shootingSound ;如果存在,請呼叫其 Play 函數。


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

使用驗證保安遙控器

如果服務器沒有檢查來自進入的請求的資料,一個駭客可以濫用遠端功能和事件並使用它們來向服務伺服器發送假值。重要的是要使用 服務器側驗證 來防止這種情況。

在目前的形式中, 傷害角色 遠程事件非常脆弱,黑客可以使用此事件對任何玩家造成傷害,而不需要射擊他們。

驗證是檢查傳送到伺服器的值是否實際的過程。在此情況下,伺服器需要:

  • 檢查雷射擊中玩家和位置之間的距離是否在特定的範圍內。
  • 在雷射發射時發射武器和命中位置之間射擊,以確認射擊是否可行,並且不會穿過任何牆壁。

客戶

客戶需要將服務器的位置傳送給服務器,以便確認距離是否實際。

  1. ToolController 中,移動到 DamageCharacter 遠端事件在 fireWeapon 功能中發生的線上。

  2. hitPosition 作為引數。


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

伺服器

客戶端現在正在通過 DamageCharacter 遠端事件傳送額外參數,因此 傷害管理器 需要調整才能接受。

  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 的函數,並且在三個參數上: playerFired、1> characterToDamage1> 和 4> hitPosition4>。


    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 ,則返回 錯誤


    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 函數的 有效射擊 中,為它設定三個參數的結果: isHitValid , 1> CharacterToDamage1> 和 4> hitPosition4> 。

  3. 在下方的 if 句中,加入 操作器來檢查 validShot 是否為


    function damageCharacter(playerFired, characterToDamage, hitPosition)
    local humanoid = characterToDamage:FindFirstChildWhichIsA("Humanoid")
    local validShot = isHitValid(playerFired, characterToDamage, hitPosition)
    if humanoid and validShot then
    -- 從角色中移除生命值
    humanoid.Health -= LASER_DAMAGE
    end
    end

現在,傷害角色遠程事件更安全,並且會防止大多數玩家濫用它。注意一些邪惡的玩家會常常尋找驗證方法;保護遠程事件安全是一個持續的努力。

雷射激光器已完成,具有基本的雷射檢測系統使用射光線投射。試試 偵測用戶輸入輸入 教學,了解如何將重新裝彈操作添加到你的雷射激光器,或創建一個有趣的遊戲地圖並嘗試你的雷射激光器與其他玩家!

最終代碼

工具控制器


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
-- 被擊中的實例將是其子一個角色模型
-- 如果在模型中找到人形,那麼可能是玩家的角色
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)

雷射器


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

服務器雷射管理器


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)

客戶LaserManager


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)