实现光束行为

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

实现光束行为 是在第一人称射击体验中编程光束机制的过程。尽管玩家可以通过单击或按下按钮来发射光束,但创建令人满意和准确的光束行为非常重要,因为这增强了玩家对整体游戏玩法的享受。

使用 示例激光标记体验 作为参考,本教程的这一部分将教你关于实现两种不同类型光束行为的脚本,包括以下指导:

  • 检测玩家何时按下光束按钮。
  • 检查玩家是否可以使用光束,如果他们最近按下了光束按钮。
  • 生成光束数据,告诉服务器谁发起了光束,光束来自哪里,以及每个激光束的最终目的地。
  • 通知服务器光束数据,以便它在光束与其他玩家碰撞时执行适当操作。
  • 在每次光束之间重置光束,以给光束足够的冷却时间,然后才能再次发射。

完成这一部分后,你将了解允许光束检测其光束何时与其他玩家碰撞的脚本,然后根据每种光束类型扣除相应的生命值。

检测玩家输入

实现光束行为的第一步是监听玩家何时按下光束按钮。玩家用于按下光束按钮的输入类型取决于他们用于访问体验的设备。例如,示例激光标记体验支持鼠标和键盘控制、游戏手柄和触控控制。你可以在 ReplicatedStorageUserInputHandler 中看到这些输入类型。

该客户端脚本使用 ContextActionService 绑定 MouseButton1ButtonR2 到发射动作。这意味着每当玩家按下左鼠标按钮或游戏手柄的 R2 按钮时,它会触发激光束从光束中发射。请注意,HUDGui 包含一个移动设备上的发射按钮,稍后将在脚本中连接到它。

UserInputHandler

ContextActionService:BindAction("_", onBlasterActivated, false,
Enum.UserInputType.MouseButton1,
Enum.KeyCode.ButtonR2
)

另一个重要的注意事项是在 onBlasterActivated() 定义中使用 Enum.UserInputState.Begin。许多用户界面交互,例如在此示例中选择光束,直到鼠标按钮弹起(Enum.UserInputState.End)后才会发生,这给用户最后一刻避免交互的机会。然而,光束机制不会感觉到响应,除非在按钮按下的瞬间发生。

为了演示,你可以将 Enum.UserInputState.Begin 更改为 Enum.UserInputState.End,然后进行游戏测试,看看光束的响应性如何影响体验的游戏性。例如,如果玩家可以按住按钮而不触发光束,这将如何改变他们在标记其他玩家时的体验?

UserInputHandler

local function onBlasterActivated(_actionName: string,
inputState: Enum.UserInputState, _inputObject: InputObject)
if inputState == Enum.UserInputState.End then -- 更新后行,确保更改回来
attemptBlastClient()
end
end

检查玩家是否可以发射

UserInputHandler 检测到按钮按下或屏幕点击后,它调用 ReplicatedStorageBlasterattemptBlastClient 来检查玩家是否可以发射。如同在示例激光标记体验中的大多数检查一样,它会发生两次:第一次在客户端,稍后在服务器上。attemptBlastClient 然后调用 ReplicatedStorageBlastercanLocalPlayerBlast 来对 blasterStateClient 玩家属性进行简单检查:

canLocalPlayerBlast

local function canLocalPlayerBlast(): boolean
return localPlayer:GetAttribute(PlayerAttribute.blasterStateClient) == BlasterState.Ready
end

如果你检查 ReplicatedStorageBlasterBlasterState,你可以看到体验有三种光束状态:ReadyBlastingDisabled。要查看每种状态的效果,你可以进行体验测试,选择你在 Players 服务下的玩家,然后在 Properties 窗口中观察 blasterStateClient 属性。注意它在你选择光束时显示为 Disabled,大多数时候为 Ready,在你按下按钮后的不到一秒内显示为 Blasting

这一瞬间的暂停阻止你以尽可能快的速度发射光束。例如,如果你将函数更改为始终返回 true,则可以快速发射光束而没有任何延迟,这在激光标记游戏中是不现实的。

canLocalPlayerBlast

local function canLocalPlayerBlast(): boolean
return true -- 更新后行,确保更改回来
end

生成光束数据

在确认玩家的光束处于 Ready 状态后,attemptBlastClient 调用 ReplicatedStorageattemptBlastClientblastClientblastClient 的第一步是将 blasterStateClient 玩家属性设置为 Blasting,这可以避免之前提到的快速发射情况。

下一步是生成光束数据。如果你查看 ReplicatedStorageBlasterBlastData,你可以看到每次发射都有三项信息:

  • 发起光束的玩家。
  • 一个 DataType.CFrame,表示光束的起点。
  • 一个 RayResult 表,包含每个激光束的最终目的地和被击中的玩家(如果击中其他玩家)。

为了生成这些数据,blastClient 调用 ReplicatedStorageattemptBlastClientblastClientgenerateBlastData,你可以在下面查看。

generateBlastData

local function generateBlastData(): BlastData.Type
local blasterConfig = getBlasterConfig()
local rayDirections = getDirectionsForBlast(
currentCamera.CFrame, blasterConfig)
local rayResults = castLaserRay(
localPlayer, currentCamera.CFrame.Position, rayDirections)
local blastData: BlastData.Type = {
player = localPlayer,
originCFrame = currentCamera.CFrame,
rayResults = rayResults,
}
return blastData
end

此函数开始时使用 getBlasterConfig 获取玩家的光束类型。示例提供了两种类型的光束:一种产生几条宽水平散布的光束,另一种产生单条光束。你可以在 ReplicatedStorageInstancesLaserBlastersFolder 中找到它们的配置。

然后,该函数使用 currentCamera.CFrame 作为光束的起点,传递给 getDirectionsForBlast。在这一点上,代码不再关于光束,而是关于激光束,关于这一点你将在本教程的 检测击中 部分中了解更多。最后,在创建了 rayResults 表后,generateBlastData 拥有返回光束数据到 blastClient 所需的所有信息。

通知服务器

一旦 blastClient 拥有完整的光束数据,它将触发两个事件:

blastClient

local laserBlastedBindableEvent = ReplicatedStorage.Instances.LaserBlastedBindableEvent
local laserBlastedEvent = ReplicatedStorage.Instances.LaserBlastedEvent
laserBlastedBindableEvent:Fire(blastData)
laserBlastedEvent:FireServer(blastData)

BindableEvent 通知其他客户端脚本光束的发射。例如,ReplicatedStorageFirstPersonBlasterVisuals 使用该事件了解何时显示视觉效果,例如光束动画和冷却条。类似地,RemoteEvent 通知服务器脚本光束,服务器开始在 ServerScriptServiceLaserBlastHandler 中处理光束。

LaserBlastHandler

local function onLaserBlastedEvent(playerBlasted: Player, blastData: BlastData.Type)
local validatedBlastData = getValidatedBlastData(playerBlasted, blastData)
if not validatedBlastData then
return
end
if not canPlayerBlast(playerBlasted) then
return
end
blastServer(playerBlasted)
processTaggedPlayers(playerBlasted, blastData)
for _, replicateToPlayer in Players:GetPlayers() do
if playerBlasted == replicateToPlayer then
continue
end
replicateBlastEvent:FireClient(replicateToPlayer, playerBlasted, blastData)
end
end

为了帮助防止作弊,服务器必须验证每个客户端发送的所有数据。这些检查包括:

  1. BlastData 是一个表吗?它是否包含一个 Class.CFrame 和另一个名为 rayResults 的表?
  2. 玩家是否装备了光束?
  3. 玩家是否有角色并且处于游戏世界中?
  4. 发送光束数据后,玩家是否移动超过了一定的距离?

最后一个检查涉及到判断,并根据服务器延迟和玩家移动速度,你可能会决定不同的值对于你的体验而言是过高的。为了演示如何进行这种判断,你可以在 getValidatedBlastData 中添加一个打印语句并进行测试。

getValidatedBlastData

local distanceFromCharacterToOrigin = blastData.originCFrame.Position - rootPartCFrame.Position
print(distanceFromCharacterToOrigin.Magnitude) -- 更新后行,确保移除
if distanceFromCharacterToOrigin.Magnitude > ToleranceValues.DISTANCE_SANITY_CHECK_TOLERANCE_STUDS then
warn(`玩家 {player.Name} 在发射光束时未通过起源验证检查`)
return
end

当你移动并发射时,注意输出。它可能类似于:


1.9019629955291748
3.1549558639526367
2.5742883682250977
4.8044586181640625
2.6434271335601807

如果你在 ReplicatedStoragePlayerStateHandlertogglePlayerMovement 中增加玩家的移动速度,然后再次测试,你可能会因为发射之间的过度移动而遇到许多检查失败。

togglePlayerMovement

local ENABLED_WALK_SPEED = 60 -- 更新后行,确保更改回来

然后,服务器将执行以下操作:

  • 验证 rayResults
  • 检查玩家是否可以发射。
  • 重置光束状态。
  • 为任何被标记的玩家减少生命值。
  • 将光束复刻到所有其他玩家,以便他们可以看到第三人称的视觉效果。

有关这些服务器操作的更多信息,请参阅教程的 检测击中 部分。

重置光束

在示例激光标记体验中,光束使用热量机制。它们在发射一定数量的光束后不进行重新加载,而是在每次发射之间需要时间“冷却”。这种冷却延迟同时发生在客户端(blastClient)和服务器(blastServer),服务器作为真实性的来源。

blastServer

local blasterConfig = getBlasterConfig(player)
local secondsBetweenBlasts = blasterConfig:GetAttribute("secondsBetweenBlasts")
task.delay(secondsBetweenBlasts, function()
local currentState = player:GetAttribute(PlayerAttribute.blasterStateServer)
if currentState == BlasterState.Blasting then
player:SetAttribute(PlayerAttribute.blasterStateServer, BlasterState.Ready)
end
end)

secondsBetweenBlasts 属性是 ReplicatedStorageInstancesLaserBlastersFolder 中光束配置的一部分。在 secondsBetweenBlasts 延迟过去后,玩家可以再次发射,整个过程重复。为了帮助玩家了解何时可以再次发射,体验中添加了一个冷却条。

此时,玩家可以生成和重生,瞄准和发射,但体验仍然需要确定每次发射的结果。在教程的下一部分中,你将学习如何编程光束检测光束何时击中另一个玩家,并根据光束设置减少对应的玩家生命值。

©2026 Roblox Corporation、Roblox、Roblox 标志及 Powering Imagination 是我们在美国及其他国家或地区的注册与未注册商标。