检测击中

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

检测击中目标 是检测爆炸与玩家碰撞时是否发生的过程,然后根据情况减少其生命值。在高级别的工作中,您可以将其思考为:

  1. 物理模拟的检查,以确定目标是否被击中。
  2. 一种即时的检查,以确定是否将激光器瞄准目标。

您使用的命中检测类型取决于您的体验的游戏玩法要求。例如,物理模拟的检查对于需要将球从手中离开特定速度、扔出空中或更改方向的�odge球体验来说是合适的。 但是,立即的检查对于激光标签体验来说是最佳的匹配,因为光束必须以近乎理想的速度从手中离开、扔出空中或更改方向。 但

使用示例激光标记体验作为参考,本教程的这个部分教你 about 3D 空间中的命令,包括有关:

  • 从当前相机值和玩家的 Blaster 类输入中获得爆炸方向。
  • 将射线投射到从冲击波发射器直接经过的直线道路。
  • 验证爆炸以防止使用者使用 Blaster 数据。
  • 根据各种冲击伤害减少玩家生命值,以及击中玩家的射线数。

完成此部分后,您可以探索更多的开发主题来增强您的游戏体验,例如音频、照明和特殊效果。

获取爆炸方向

玩家使用冲击波发射他们的冲击波后, ReplicatedStorage > Blaster > attemptBlastClient > 1> blastClient1> > 4> generateBlastData4> 调用两个函数来启动命中检测过程:7> rayDirections()7> 和9> rayResults()9>。

生成爆炸数据

local rayDirections = getDirectionsForBlast(currentCamera.CFrame, blasterConfig)
local rayResults = castLaserRay(localPlayer, currentCamera.CFrame.Position, rayDirections)

对于 rayDirections 的输入是简单的:当前相机位置和旋转值,以及玩家的激光发射器类型。如果样本激光标签体验仅向玩家发射一个激光束, ReplicatedStorage > LaserRay > 1> getDirectionsForBlast</

因此,因为样本提供了一个额外的激光型,它可以生成多个激光束,getDirectionsForBlast 必须根据激光束的角度在 blaster 配置中计算每个激光束的方向:

获取爆炸方向

if numLasers == 1 then
-- 对于单个激光,它们将直接瞄准
table.insert(directions, originCFrame.LookVector)
elseif numLasers > 1 then
-- 对于多个激光,请均匀分布在水平上
-- 在中心周围的间隔激光
local leftAngleBound = laserSpreadDegrees / 2
local rightAngleBound = -leftAngleBound
local degreeInterval = laserSpreadDegrees / (numLasers - 1)
for angle = rightAngleBound, leftAngleBound, degreeInterval do
local direction = (originCFrame * CFrame.Angles(0, math.rad(angle), 0)).LookVector
table.insert(directions, direction)
end
end

要进一步展示此概念,如果您包含一个第三个 Blaster 类型,使用宽垂直布局, 垂直向下 布局,您可以创建一个新的 Blaster 属性,例如 spreadDirection ,然后调整 CFrame 计算使用不同的轴。例如,请注意下面的脚本中


if numLasers == 1 then
table.insert(directions, originCFrame.LookVector)
elseif numLasers > 1 then
local leftAngleBound = laserSpreadDegrees / 2
local rightAngleBound = -leftAngleBound
local degreeInterval = laserSpreadDegrees / (numLasers - 1)
for angle = rightAngleBound, leftAngleBound, degreeInterval do
local direction
if spreadDirection == "vertical" then
direction = (originCFrame * CFrame.Angles(math.rad(angle), 0, 0)).LookVector
else
direction = (originCFrame * CFrame.Angles(0, math.rad(angle), 0)).LookVector
end
table.insert(directions, direction)
end
end
return directions

最终,rayDirections() 函数返回一个表 Vectors 表示每个激光光束的方向。如果有帮助,您可以添加一些日志来获得一个概念,看到这些数据的外观。

生成爆炸数据

local rayDirections = getDirectionsForBlast(currentCamera.CFrame, blasterConfig)
for _, direction in rayDirections do -- 新行
print(direction) -- 新行
end -- 新行
local rayResults = castLaserRay(localPlayer, currentCamera.CFrame.Position, rayDirections)

投射射线

castLaserRay() , 在 ReplicatedStorage > Blaster > 0> attemptBlastClient0>

这些信息对第一人称射击体验非常有用,因为它们允许您看到爆炸与玩家或环境交叉时发生的时间和位置。例如,以下图像显示两个射线正在并行投射时发生并向前移动。根据其起始点和方向,射线 A 错过了墙壁,继续前进,而射线 B 则与墙壁碰撞。有关此过程的更

A diagram where Ray A continues through the wall, and Ray B collides with the wall.

castLaserRay() 参数规定 Raycast() 调用必须考虑工作区中的每个部分 除了 那个角色之外。脚本 then 对每个方向在 1> directions1> 表中生成一个射线。 如果射线击中了什么,它生成一个 4> Datatype.RaycastResult

Datatype.RaycastResult.Instance|Instance 值是最重要的这些属性对于样本激光标记体验的游戏玩法,因为它会在射线与其他玩家碰撞时通信。要恢复此信息,体

castLaserRay() 然后使用 PositionNormal 来创建一个新的 0> Datatype.

激光射线

if result then
-- 有人被炸到了,检查是否是玩家。
destination = CFrame.lookAt(result.Position, result.Position + result.Normal)
taggedPlayer = getPlayerFromDescendant(result.Instance)
else
-- 爆炸没有击中任何东西,所以它的目的地是
-- 最大距离上的点。
local distantPosition = origin + rayDirection * MAX_DISTANCE
destination = CFrame.lookAt(distantPosition, distantPosition - rayDirection)
taggedPlayer = nil
end

验证爆炸

为了防止作弊,上一章 使用 Blaster 发送的所有数据都可以验证 解释 blastClient 如何使用 RemoteEvent

  1. 首先, getValidatedRayResults 调用 validateRayResult 来检查客户端在 rayResults 表中的每个条目是否为 1> Datatype.CFrame1> 和一个 4> Player4> (或为空)。

  2. 接下来,它调用 isRayAngleFromOriginValid 来比较激光溢射的预期角度与客户端的角度。此代码显示服务器可以调用 ReplicatedStorage 本身,存储返回为期望数据,然后与客户端的数据进行比较。

    与上一章的“isRayAngleFromOriginValid”相同,它使用一个容量值来确定角度“过度”的差异:

    是否从原始角度

    local claimedDirection = (rayResult.destination.Position - originCFrame.Position).Unit
    local directionErrorDegrees = getAngleBetweenDirections(claimedDirection, expectedDirection)
    return directionErrorDegrees <= ToleranceValues.BLAST_ANGLE_SANITY_CHECK_TOLERANCE_DEGREES

    Roblox 抽象化数学中最复杂的部分,因此结果是一个简短、高度重用的助手函数,可应用于一系列体验:

    获取角度之间的距离

    local function getAngleBetweenDirections(directionA: Vector3, directionB: Vector3)
    local dotProduct = directionA:Dot(directionB)
    local cosAngle = math.clamp(dotProduct, -1, 1)
    local angle = math.acos(cosAngle)
    return math.deg(angle)
    end
  3. 下个检查是最直观的。 而且 getValidatedBlastData 使用 DISTANCE_SANITY_CHECK_TOLERANCE_STUDS 来验证玩家是否靠近光束的起始位置, isPlayerNearPosition 使用相同的逻辑来检查标记的玩家是否靠近光束的目的地:

    isPlayerNearPosition

    local distanceFromCharacterToPosition = position - character:GetPivot().Position
    if distanceFromCharacterToPosition.Magnitude > ToleranceValues.DISTANCE_SANITY_CHECK_TOLERANCE_STUDS then
    return false
    end
  4. 最后的检查 isRayPathObstructed 使用射线投射操作的变化来检查射线是否位于客户端的墙或其他障碍。例如,如果恶意玩家系统性移除体验中的所有墙壁,服务器将检查并确认射线无效,因为它知道每个对象位置在环境中的所有对象位置。

    是否阻塞

    local scaledDirection = (rayResult.destination.Position - blastData.originCFrame.Position)
    scaledDirection *= (scaledDirection.Magnitude - 1) / scaledDirection.Magnitude

没有防止恶用的策略是完整的,但重要的是要考虑到恶意玩家如何靠近您的体验,以便您可以放置检查,让服务器可以运行来标记可疑行为。

减少玩家生命值

在验证玩家标记了另一个玩家后,在示例激光标签体验中完成主游戏循环的最后几步是减少标记玩家的生命值、增加排行榜和重生玩家回到回合。

始于减少标记玩家的生命值, 生成和重生 涵盖了标记玩家和 Player 之间的区别,即

体验存储在每个冲击波的 damagePerHit 属性上的伤害值。例如,单

Health 不接受负值,因此 onPlayerTagged 有一些理论来保持玩家的生命值在或以上零。 后验证玩家的生命值在零以上后,它将生命值与 damagePerHit 比较,并使用两个值中的

这种方法的问题可能听起来有点复杂。例如,为什么不将玩家的生命值设置为零,如果它是负的? 理由是因为设置生命值会触发强力场。使用 Class.Humanoid:TakeDamage() 方法确保玩家在力场激活时不受伤害。

标记玩家

local function onPlayerTagged(playerBlasted: Player, playerTagged: Player, damageAmount: number)
local character = playerTagged.Character
local isFriendly = playerBlasted.Team == playerTagged.Team
-- 禁止友好的火触发
if isFriendly then
return
end
local humanoid = character and character:FindFirstChild("Humanoid")
if humanoid and humanoid.Health > 0 then
-- 避免负面健康
local damage = math.min(damageAmount, humanoid.Health)
-- TakeDamage 确保 ForceField 处于活动状态时不会降低生命值
humanoid:TakeDamage(damage)
if humanoid.Health <= 0 then
-- 为标记玩家爆炸了一个点
Scoring.incrementScore(playerBlasted, 1)
end
end
end

下一步是增加排行榜。它可能看起来不是必要的 LaserBlastHandler 包括以爆炸数据并爆炸玩家,但没有该信息,体验无法为玩家标记标记。最后,标记出的玩家重生回到回合,你可以在生成和重生中查看。

本教程的五个章节涵盖体验的核心游戏循环,但还有很多区域可以探索,例如:

  • 激光视觉 : 请参阅 复制存储服务 > 第一人称视觉 和 0> 服务器脚本服务0> > 3> 第三人称视觉 3> .
  • 音频 : 请参阅 复制存储服务器 > 音频处理器
  • 自定义模式 :您如何可以修改此体验,以介绍新的目标类型,例如在时间结束前获得最多积分?

对于激光标签体验的扩展游戏逻辑,以及可重用的高质量环境资产,请查看 激光标签 模板。