实现爆破行为 是在第一人称射击游戏中编程爆破机制的过程。虽然玩家可以通过单击或按下按钮来进行爆破,但创建令人满意和准确的爆破行为非常重要,因为它增强了玩家对整体游戏体验的享受。
使用 示例激光标记游戏 作为参考,本教程的这一部分将教您有关实现两种不同类型爆破器的爆破行为的脚本,包括以下指导:
- 检测玩家何时按下爆破按钮。
- 检查玩家在最近按下爆破按钮后是否可以使用他们的爆破器。
- 生成爆破数据,告诉服务器谁发起了爆破,爆破来自哪里,以及每个激光束的最终目的地是什么。
- 通知服务器爆破数据,以便它可以在爆破与其他玩家碰撞时执行适当的操作。
- 在每次爆破之间重置爆破器,以便给爆破器足够的时间冷却,然后才能再次爆破。
完成本节后,您将学习允许爆破器检测其爆破与其他玩家碰撞的脚本,然后根据每种爆破器类型扣除相应的生命值。
检测玩家输入
实现爆破行为的第一步是监听玩家何时按下爆破按钮。玩家用来按下爆破按钮的输入类型取决于他们使用的设备。例如,示例激光标记游戏支持鼠标和键盘控制、游戏手柄和触摸控制。您可以在 ReplicatedStorage ⟩ UserInputHandler 中看到这些输入类型。
此客户端脚本使用 ContextActionService 将 MouseButton1 和 ButtonR2 绑定到爆破动作。这意味着每当玩家按下左鼠标按钮或游戏手柄的 R2 按钮时,就会触发激光束从爆破器中爆发。请注意,HUDGui 包含一个用于移动设备的爆破按钮,稍后将在脚本中连接到它。
ContextActionService:BindAction("_", onBlasterActivated, false,
Enum.UserInputType.MouseButton1,
Enum.KeyCode.ButtonR2
)另一个重要的注意事项是在 onBlasterActivated() 定义中使用 Enum.UserInputState.Begin。许多用户界面交互,例如在此示例中选择爆破器,直到鼠标按钮抬起(Enum.UserInputState.End)后才会发生,这给用户提供了最后一秒的机会来避免交互。然而,爆破机制在按钮按下的瞬间发生时才会感觉响应迅速。
为了演示,您可以将 Enum.UserInputState.Begin 更改为 Enum.UserInputState.End,然后进行游戏测试以查看爆破的响应性如何影响游戏体验。例如,如果玩家可以按住按钮而不触发爆破,这将如何改变他们在标记其他玩家时的体验?
local function onBlasterActivated(_actionName: string,
inputState: Enum.UserInputState, _inputObject: InputObject)
if inputState == Enum.UserInputState.End then -- 更新行,请确保更改回去
attemptBlastClient()
end
end检查玩家是否可以爆破
在 UserInputHandler 检测到按钮按下或屏幕点击后,它调用 ReplicatedStorage ⟩ Blaster ⟩ attemptBlastClient 来检查玩家是否可以爆破。与示例激光标记游戏中的大多数检查一样,它发生两次:第一次在客户端,然后在服务器上。attemptBlastClient 然后调用 ReplicatedStorage ⟩ Blaster ⟩ canLocalPlayerBlast 来执行对 blasterStateClient 玩家属性的简单检查:
local function canLocalPlayerBlast(): boolean
return localPlayer:GetAttribute(PlayerAttribute.blasterStateClient) == BlasterState.Ready
end如果您检查 ReplicatedStorage ⟩ Blaster ⟩ BlasterState,您会看到游戏有三种爆破器状态:Ready、Blasting 和 Disabled。要查看这些状态的效果,您可以进行游戏测试,选择 Players 服务下的玩家,然后在 Properties 窗口中观察 blasterStateClient 属性。注意在选择爆破器时它显示为 Disabled,大部分时间为 Ready,在按下按钮后不到一秒钟为 Blasting。
这个短暂的暂停阻止您以最快的速度进行爆破。例如,如果您将函数更改为始终返回 true,您可以快速爆破而没有任何延迟,这对于激光标记游戏来说是不现实的。
local function canLocalPlayerBlast(): boolean
return true -- 更新行,请确保更改回去
end生成爆破数据
在验证玩家的爆破器处于 Ready 状态后,attemptBlastClient 调用 ReplicatedStorage ⟩ attemptBlastClient ⟩ blastClient。blastClient 的第一步是将 blasterStateClient 玩家属性设置为 Blasting,以避免之前的快速发射情况。
下一步是生成爆破数据。如果您查看 ReplicatedStorage ⟩ Blaster ⟩ BlastData,您会看到每次爆破由三部分信息组成:
- 发起爆破的玩家。
- 一个表示爆破起点的 DataType.CFrame。
- 一个包含每个激光束最终目的地和击中玩家(如果击中其他玩家)的 RayResult 表。
为了生成这些数据,blastClient 调用 ReplicatedStorage ⟩ attemptBlastClient ⟩ blastClient ⟩ 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 来检索玩家的爆破器类型。示例提供了两种类型的爆破器:一种产生多个具有宽水平扩散的光束,另一种产生单个光束。您可以在 ReplicatedStorage ⟩ Instances ⟩ LaserBlastersFolder 中找到它们的配置。
然后,该函数使用 currentCamera.CFrame 作为爆破的起点,将其传递给 getDirectionsForBlast。此时,代码不再关心爆破器,而是激光束,您将在本教程的 检测击中 部分中了解更多。最后,在创建 rayResults 表后,generateBlastData 拥有返回爆破数据给 blastClient 所需的所有信息。
通知服务器
一旦 blastClient 拥有完整的爆破数据,它会触发两个事件:
local laserBlastedBindableEvent = ReplicatedStorage.Instances.LaserBlastedBindableEvent
local laserBlastedEvent = ReplicatedStorage.Instances.LaserBlastedEvent
laserBlastedBindableEvent:Fire(blastData)
laserBlastedEvent:FireServer(blastData)BindableEvent 通知其他客户端脚本爆破。例如,ReplicatedStorage ⟩ FirstPersonBlasterVisuals 使用此事件来知道何时显示视觉效果,例如爆破动画和冷却条。类似地,RemoteEvent 通知服务器脚本爆破,这开始在 ServerScriptService ⟩ 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为了帮助防止作弊,服务器必须验证每个客户端发送的所有数据。这些检查包括:
- BlastData 是一个表吗?它是否包含一个 Class.CFrame 和另一个名为 rayResults 的表?
- 玩家是否装备了爆破器?
- 玩家是否在世界中有角色和位置?
- 在发送爆破数据后,玩家是否从他们爆破激光束的位置移动了过大的距离?
最后一项检查涉及判断,根据服务器延迟和玩家移动速度,您可能会决定不同的值对于您自己的游戏来说是过大的。为了演示如何做出这个判断,您可以在 getValidatedBlastData 中添加一个打印语句并进行游戏测试。
local distanceFromCharacterToOrigin = blastData.originCFrame.Position - rootPartCFrame.Position
print(distanceFromCharacterToOrigin.Magnitude) -- 更新行,请确保删除
if distanceFromCharacterToOrigin.Magnitude > ToleranceValues.DISTANCE_SANITY_CHECK_TOLERANCE_STUDS then
warn(`Player {player.Name} failed an origin sanity check while blasting`)
return
end当您移动并爆破时,请注意输出。它可能看起来像这样:
1.9019629955291748
3.1549558639526367
2.5742883682250977
4.8044586181640625
2.6434271335601807如果您在 ReplicatedStorage ⟩ PlayerStateHandler ⟩ togglePlayerMovement 中增加玩家的移动速度,然后再次进行游戏测试,您可能会由于爆破之间的过度移动而遇到许多失败的检查。
local ENABLED_WALK_SPEED = 60 -- 更新行,请确保更改回去然后,服务器执行以下操作:
- 验证 rayResults。
- 检查玩家是否可以爆破。
- 重置爆破器状态。
- 减少任何被标记玩家的生命值。
- 将爆破复制给所有其他玩家,以便他们可以看到第三人称视觉效果。
有关这些服务器操作的更多信息,请参见本教程的 检测击中 部分。
重置爆破器
在示例激光标记游戏中,爆破器使用热量机制。它们不是在设定数量的爆破后重新装填,而是在每次爆破之间需要时间“冷却”。这种冷却延迟在客户端(blastClient)和服务器(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 属性是 ReplicatedStorage ⟩ Instances ⟩ LaserBlastersFolder 中爆破器配置的一部分。在 secondsBetweenBlasts 延迟过去后,玩家可以再次爆破,整个过程重复。为了帮助玩家理解何时可以再次爆破,游戏包括一个冷却条。
此时,玩家可以生成和重生,瞄准和爆破,但游戏仍然需要确定每次爆破的结果。在本教程的下一部分中,您将学习如何编程使爆破器检测爆破何时击中其他玩家,然后根据爆破器设置减少相应的玩家生命值。