激光击杀检测

*此内容使用人工智能(Beta)翻译,可能包含错误。若要查看英文页面,请点按 此处

在本教程中,您将学习如何从激光发射器中投射激光,并且检查它是否击中玩家。

射线投射以检测碰撞

射线投射 从起始位置向目标方向射出一个隐形射线,并且在定义长度的基础上。如果射线与路径上的对象或地形碰撞,它将返回与碰撞相关的信息,例如位置和碰撞的对象。

射线从 A 向 B 碰撞墙壁

寻找鼠标位置

在激光可以射击之前,您必须先知道玩家正在瞄准。 这可以通过从屏幕上的玩家 2D 鼠标位置直接将激光射向游戏世界的前方从相机到游戏世界的前方的射线投射到玩家。 激光会与玩家所瞄准的任何东西进行碰撞。

  1. 从创建玩家工具中打开工具控制器脚本。如果您尚未完成该教程,您可以下载Blaster模型并将其插入到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() 函数,该函数从屏幕上生成一个 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 乘以 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. 调用 Raycast 工作区的函数,将 Origin 属性的 screenToWorldRay 作为第一个参数和 2> directionVector2> 作为第二个参数分别作为第一个参数和第二个参数的标签。 将此分配到变量名为 5>raycastResult5> 。


    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 ,该操作包含与射线和对象之间的碰撞有关的信息。

射线投射结果描述
实例Class.BasePart 或 Terrain 牢固体,射线斜过。
位置交叉点发生在哪里;通常是在零件或地形表面上直接。
材质在碰撞点的材料。
普通交叉面的普通向量。这可以用来确定面部指向哪个方向。

位置属性将是鼠标所指针对的对象的位置。如果鼠标在MAX_MOUSE_DISTANCE范围内,而不是对任何对象,raycastResult 将为零。

  1. 创建一个 if 声明来检查 raycastResult 是否存在。

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

  3. 如果 raycastResult 为空,则找到射线投射结果的结束。 使用 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 鼠标位置已知,它可以用作一个 目标位置 来向目标发射激光。 一个第二射线可以在玩家的武器和目标位置之间使用 射线投射 函数进行纵向射击。

  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. 创建一个名为 fireWeapon 的函数,在 getWorldMousePosition 函数下。

  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. 使用它的 单位 属性来 normalize 它。这使它的尺寸变得 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 对象可用于存储额外的参数为射线投射功能。它将在您的激光冲击波上使用,以确保射线投射不会意外地与玩家发射武器碰撞。 任何包含在 FilterDescendantsInstances 属性的

  1. 继续 fireWeapon 函数,并且声明一个名为 weaponRaycastParams 的变量。 为它分配一个新的 RaycastParams 对象。

  2. 创建一个包含玩家本地角色 角色 和分配到 weaponRaycastParams.FilterDescendantsInstances 属性。

  3. 从玩家的工具处理位置射出射线,向 directionVector 。记得这次添加 weaponRaycastParams 作为参数。将其分配到名为 武器射击结果 的变量。


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 无值,请通过将工具处理器的位置与 duration Vector 结合来计算射线投射的最终位置。将其分配到 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

现在激光冲击枪每次激光投射到另一个玩家时,应该打印Player hit到输出窗口。

与多个玩家测试

需要两个玩家来测试武器射线投射是否会发现其他玩家,因此您需要启动一个本地服务器。

  1. 选择 Studio 的 测试 选项卡。

  2. 确保玩家下拉菜单设置为“2 名玩家”,然后单击“开始”按钮为 开始 一个本地服务器,有 3 个窗口。第一个窗口将是本地服务器,其他窗口将是玩家 1 和玩家 2 的客户。

  3. 在一个客户端上,测试其他玩家使用武器点击他们。 显示“玩家被击中” 每次玩家被击中时。

您可以在这里了解测试选项卡。

寻找激光位置

激光发射器应该向目标射出红色光束。该功能将在 ModuleScript 内发生,因此它可以在其他脚本中稍后重用。首先,脚本需要找到激光发射器的位置,以便在后续脚本中重用。

  1. 创建一个 ModuleScript 名为 LaserRender,父级是 StarterPlayerScripts 下的 StarterPlayer。

  2. 打开脚本,将模块表命名为脚本的名称 LaserRender

  3. 声明一个名为 SHOT_DURATION 的变量,其值为 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, 激光距离)
    2. CFrame : 激光CFrame
    3. 锚定 : 是
    4. 可以碰撞吗 : false
    5. 颜色 :Color3.从RGB(225, 0, 0) (一个强烈的红色)
    6. 材质 : Enum.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

现在激光照射的功能已完成,它可以由 工具控制器 调用。

  1. ToolController 脚本的顶部,声明一个名为 LaserRender 的变量,并要求位于 PlayerScripts 中的激光渲染模块脚本。


    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 的函数,无参数。 此函数将看到从上一次射击开始的时间过去的时间,并且返回 true 或 false。


    local FIRE_RATE = 0.3
    local timeOfPreviousShot = 0
    -- 检查自从上次射击开始时间起是否已过足的时间
    local function canShootWeapon()
    end
    local function getWorldMousePosition()
  4. 在 function 声明一个名为 currentTime 的变量;将其作为调用 tick() 函数的结果分配给它。这将返回自从 1970 年 1 月 1 日起计时的时间,以秒为单位显示。

  5. currentTime中减去 timeOfPreviousShot ,并且在1> FIRE_LETE1> 结果小于4> FIRE_RATE4> 时返回7> 否定7> ;否则,返回timeOfPreviousShot0>。


    -- 检查自从上次射击开始时间起是否已过足的时间
    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

当你测试 blaster 时,你应该发现,无论你点击多快,每次射击之间总会有一个 0.3 秒的短暂延迟。

伤害玩家

客户端无法直接伤害其他客户端;服务器需要负责发出伤害,当玩家被击中时。

客户可以使用 RemoteEvent 向服务器告知角色已被击中。这些应该存储在 ReplicatedStorage 中,客户端和服务器都可以看到。

  1. 在 ReplicatedStorage 中创建一个名为 Events文件夹

  2. 将一个远程事件放入 伤害角色 文件夹中,并命名为

  3. 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
  4. fireWeapon 中,将打印声明 DamageCharacter 远程事件使用 Lua 发射线路 1> CharacterModel1> 变量作为参数。


    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. 使用 2 个玩家启动一个本地服务器。当你向另一个玩家射击时,他们的生命值会降低 LASER_DAMAGE

渲染其他玩家的激光

目前,激光光束是由客户发射武器创建的,因此,只有他们能够看到激光光束。

如果激光柱在服务器上创建,那么所有人都能看到它。 但是,激光柱之间的小 延迟 会使服务器收到有关射击武器的信息。 这意味着客户使用武器时会在激活武器后看到一种延迟,这是客户使用武器时会看到的延迟。 这意味着激光柱将在激活后会有一种延迟。

为了解决这个问题,每个客户都将创建自己的激光柱。这意味着客户射击武器时会立即看到激光柱。其他客户将体验激光柱之间的小延迟。 这是最佳的情景:无法在任何一名玩家射击时通信到另一个玩家的激光。

射击客户

首先,客户端需要告诉服务器它发射了激光,并提供末端位置。

  1. RemoteEvent 插入 ReplicatedStorage 中的 Events 文件夹,并命名为 LaserFired。

  2. ToolController 脚本中找到 fireWeapon 函数。在功能的结束部分,使用 1> hitPosition1> 作为参数,使用 4> LaserFired4> 远程事件。


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

服务器

服务器现在必须接受客户端发射的事件,并告诉所有客户端激光光束的开始和结束位置,以便它们也可以渲染它。

  1. 在 ServerLaserManager 脚本中,创建一个名为 playerFiredLaser 的函数,上 damageCharacter 两个参数,分别为 2> playerFired2> 和 5> endPosition5>。

  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 在 LaserFired 远程事件上,发送需要用于渲染激光到客户端的信息。这包括发射激光的 玩家 (因此,客户端的玩家不会 rend染激光两次),手柄 blaster (act as a starting position for the laser) 和激光的 end position。

  1. playerFiredLaser 函数中,调用 getPlayerToolHandle 函数,并将 playerFired 作为参数,并将值分配到 1> toolHandle1> 变量。

  2. 如果 toolHandle 存在,为所有使用 playerFiredtoolHandle 和 1> endPosition1> 作为参数的客户端发射激光轨道。


    -- 告诉所有客户,激光已发射,以便他们显示激光
    local function playerFiredLaser(playerFired, endPosition)
    local toolHandle = getPlayerToolHandle(playerFired)
    if toolHandle then
    eventsFolder.LaserFired:FireAllClients(playerFired, toolHandle, endPosition)
    end
    end

在客户端上渲染

现在 FireAllClients 已被调用,每个客户端都会从服务器获得一个事件来渲染激光。每个客户端可以重用 LaserRender 模块从以前版本的工具的处理位置和端位值发送服务器发出的激光来渲染激光。首先发射激光的玩家应该忽略此事件,否则他们会看到 2 个激光。

  1. 在 StarterPlayerScripts 中创建一个 本地脚本 称为 ClientLaserManager

  2. 在脚本中,需要 激光渲染器 模块。

  3. 创建一个名为 createPlayerLaser 的函数,该参数为 playerWhoShottoolHandle 和 1> endPosition1> 。

  4. 将函数连接到 LaserFired 远程事件在 事件 文件夹中。

  5. 在函数中,使用 如果 声明检查 playerWhoShot 是否 不等同 本地玩家。

  6. 在 if 声明中,使用 createLasertoolHandle 作为参数,从 LaserRender 模块调用 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. 测试 blaster 用 2 位玩家,开始一个本地服务器。将每个客户端放置在不同侧面的屏幕上,以便您可以同时看到两个窗口。当你在一个客户端上射击时,你应该看到激光在另一个客户端上。

音效

目前,射击声效只会在发射弹道的客户端播放。你需要将代码移动到播放声效的客户端,以便其他玩家也能听到。

  1. ToolController 脚本中,导航到 工具激活 函数,并移除播放激活声音的行。


    local function toolActivated()
    if canShootWeapon() then
    fireWeapon()
    end
    end
  2. createLaser 函数的底部,声称一个名为 shootingSound 的变量,并使用 Class.Instance:FindFirstChild()|FindFirstChild() 方法的 1> toolHandle1> 检查声音 4>激活4>。

  3. 使用 如果 声明检查是否存在 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 中,导航到 DamageCharacter 远程事件在 fireWeapon 函数中发生的行。

  2. hitPosition 作为参数。


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

服务器

客户端现在正在通过伤害角色远程事件发送额外的参数,因此 服务器激光管理器 需要调整才能接受它。

  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. 复制下面的代码执行此检查。在函数结束时返回 :如果它到达结束,所有检查都通过了。


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

客户端激光管理器


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)