查询检测
阅读本文大概需要 10 分钟。
游戏开发中,我们常常需要进行检测。比如在射击游戏中判断子弹是否击中了敌人、炸药爆炸时一定范围内是否有物体等,本章节我们就一起来学习一下射线检测与范围检测。
关于本章介绍的 API 我们可以在 API 文档中查询详细示例:QueryUtil | API (ark.online)
1. 射线检测
2.1 什么是射线检测
射线检测(Raycasting)是一种常见的碰撞检测技术,用于判断投射出的射线是否与场景中的物体相交,并获取相交点的信息。射线检测通常用于识别物体之间的碰撞、计算物体之间的交点或确定可视性等任务。例如玩家与环境的交互、射击游戏中的子弹轨迹计算、物体拾取等。
在编辑器中提供了多种射线检测的类型,根据类型的不同我们发射出的射线形状不同,开发者需要自行选择合适的射线类型以更好的适配游戏。
因为射线检测依赖于碰撞信息,所以关闭碰撞的对象使用射线检测是无法检测到的,如果要让某些物体不被射线检测到,可以尝试将它们的碰撞设为关闭。
2.2 检测类型
- 线段检测 (QueryUtil.lineTrace)
- 球体范围检测 (QueryUtil.sphereTrace)
- 盒体范围检测 (QueryUtil.boxTrace)
- 胶囊体范围检测 (QueryUtil.capsuleTrace)
2.3 在代码中使用射线检测
射线检测会从开始位置到结束位置发射一条或多条射线,会将沿途的物体以 HitResult 数组的形式传出。
这里我使用最常见的射线检测,也就是 lineTrace 线段检测,作为示例:
typescript
@Component
export default class Test extends Script {
/** 当脚本被实例后,会在第一帧更新前调用此函数 */
protected onStart(): void {
if (SystemUtil.isClient()) {
InputUtil.onKeyDown(Keys.F1, () => {
this.lineTraceByCharacter();
})
}
}
/** 向角色前方进行射线检测 */
private async lineTraceByCharacter() {
// 获取当前玩家
const player = Player.localPlayer;
// 获取玩家角色位置
const playerPos = player.character.worldTransform.position;
// 获取玩家当前朝向
const front = player.character.localTransform.getForwardVector();
// 进行射线检测
const results = QueryUtil.lineTrace(playerPos, playerPos.clone().add(front.multiply(1000)), true, true);
// 遍历检测到的物体
for (let res of results) {
// 排除检测到玩家
if (res.gameObject instanceof Character) continue;
console.log("碰撞点在这里", res.position);
//这里尝试播放一个火焰特效方便观看
EffectService.playAtPosition("4330", res.position);
}
}
}
@Component
export default class Test extends Script {
/** 当脚本被实例后,会在第一帧更新前调用此函数 */
protected onStart(): void {
if (SystemUtil.isClient()) {
InputUtil.onKeyDown(Keys.F1, () => {
this.lineTraceByCharacter();
})
}
}
/** 向角色前方进行射线检测 */
private async lineTraceByCharacter() {
// 获取当前玩家
const player = Player.localPlayer;
// 获取玩家角色位置
const playerPos = player.character.worldTransform.position;
// 获取玩家当前朝向
const front = player.character.localTransform.getForwardVector();
// 进行射线检测
const results = QueryUtil.lineTrace(playerPos, playerPos.clone().add(front.multiply(1000)), true, true);
// 遍历检测到的物体
for (let res of results) {
// 排除检测到玩家
if (res.gameObject instanceof Character) continue;
console.log("碰撞点在这里", res.position);
//这里尝试播放一个火焰特效方便观看
EffectService.playAtPosition("4330", res.position);
}
}
}
2. 范围检测
2.1 什么是范围检测
与射线检测类似,范围检测(Overlap Testing)也是一种常见的碰撞检测技术,用于判断物体之间的重叠关系。范围检测通常使用物体的碰撞体积来检测物体之间的重叠,它主要关注碰撞体积的位置和尺寸,而不考虑其旋转。可以用于检测玩家与物体的接触、触发区域的进入与离开等场景。
在编辑器中我们同样提供了多种检测类型,因为线段与物体只有相交关系而不会重叠,所以在范围检测中是没有线段类型的。
关于性能
因为范围检测仅仅计算物体间是否重叠,返回值较少。性能相对于射线检测要好一点,对于仅需要考虑物体间是否重叠的场景,我们应该优先考虑使用范围检测。
2.2 检测类型
- 盒体范围检测 (QueryUtil.boxOverlap)
- 球体范围检测 (QueryUtil.sphereOverlap)
- 胶囊体范围检测 (QueryUtil.capsuleOverlap)
2.3 在代码中使用范围检测
范围检测会在指定的范围生成检测区域,将检测到的游戏对象以数值形式返回。
这里我使用常见的球体范围检测作为演示:
typescript
@Component
export default class Test extends Script {
/** 当脚本被实例后,会在第一帧更新前调用此函数 */
protected onStart(): void {
if (SystemUtil.isClient()) {
InputUtil.onKeyDown(Keys.F1, () => {
this.overlapByCharacter();
})
}
}
/** 向角色周围 150 距离进行检测 */
private async overlapByCharacter() {
// 获取当前玩家
const player = Player.localPlayer;
// 获取玩家角色位置
const playerPos = player.character.worldTransform.position;
// 进行射线检测
const goArray = QueryUtil.sphereOverlap(playerPos, 150, true);
// 遍历检测到的对象并输出对象名
for (let go of goArray) {
// 如果是玩家就不输出
if (go instanceof Character) continue;
console.log("范围中物体名字:", go.name);
}
}
}
@Component
export default class Test extends Script {
/** 当脚本被实例后,会在第一帧更新前调用此函数 */
protected onStart(): void {
if (SystemUtil.isClient()) {
InputUtil.onKeyDown(Keys.F1, () => {
this.overlapByCharacter();
})
}
}
/** 向角色周围 150 距离进行检测 */
private async overlapByCharacter() {
// 获取当前玩家
const player = Player.localPlayer;
// 获取玩家角色位置
const playerPos = player.character.worldTransform.position;
// 进行射线检测
const goArray = QueryUtil.sphereOverlap(playerPos, 150, true);
// 遍历检测到的对象并输出对象名
for (let go of goArray) {
// 如果是玩家就不输出
if (go instanceof Character) continue;
console.log("范围中物体名字:", go.name);
}
}
}
最终输出结果为:
可以看到,这里除了角色我们手动跳过了,其它所有范围内的物体都被检测到了包括角色脚下的地板。