30 · 小地图导航与目标视野检测
三个不同问题
游戏或数字园区需要知道:目标离角色多远、在角色左边还是右边、是否落在视野角内。这三个问题分别使用长度平方、叉积的有向分量和点积。它们都不需要计算完整夹角。
示例在世界 XZ 平面工作,角色局部 +Z 为前方、+Y 为上方。方向 f 与目标位移 d 的二维表达取 (x, z),于是标量叉积为 f.x * d.z − f.z * d.x。在此约定下,正值表示左侧:当角色朝 +Z,世界 −X 在左边。不要把 XY 平面的符号说明不经检查地搬到 XZ 平面。
视野总角度为 φ,半角阈值是 cos(φ / 2):
距离先用平方比较可省去大部分候选目标的平方根。
完整核心代码
import { createDemo } from './runtime.js';
const { THREE, scene, onFrame } = createDemo();
const actor = new THREE.Group();
scene.add(actor, new THREE.GridHelper(20, 20));
actor.add(new THREE.ArrowHelper(new THREE.Vector3(0, 0, 1), new THREE.Vector3(), 2));
const target = new THREE.Mesh(
new THREE.SphereGeometry(0.25),
new THREE.MeshBasicMaterial({ color: 0xff6644 })
);
scene.add(target);
const actorWorld = new THREE.Vector3();
const targetWorld = new THREE.Vector3();
const forward = new THREE.Vector3();
const delta = new THREE.Vector3();
const mapPoint = new THREE.Vector2();
const range = 6;
const cosineLimit = Math.cos(THREE.MathUtils.degToRad(90 / 2));
function inspectTarget() {
actor.getWorldPosition(actorWorld);
target.getWorldPosition(targetWorld);
actor.getWorldDirection(forward); // Object3D 的世界 +Z;相机的实现不同。
forward.y = 0;
if (forward.lengthSq() < 1e-10) return { visible: false, side: '未知' };
forward.normalize();
delta.subVectors(targetWorld, actorWorld);
delta.y = 0;
const distanceSq = delta.lengthSq();
if (distanceSq < 1e-10) return { visible: true, side: '重合' };
const cross2 = forward.x * delta.z - forward.z * delta.x;
const side = Math.abs(cross2) < 1e-5 ? '共线' : cross2 > 0 ? '左' : '右';
const visible = distanceSq <= range * range &&
forward.dot(delta) / Math.sqrt(distanceSq) >= cosineLimit;
return { visible, side };
}
onFrame((dt, elapsed) => {
target.position.set(Math.sin(elapsed) * 4, 0, Math.cos(elapsed) * 4);
const result = inspectTarget();
target.material.color.set(result.visible ? 0x44cc88 : 0xff6644);
// 200×200 小地图,世界 XZ 的 [-10,10] 映射到像素。
// 此处约定 +Z 朝地图下方,实际 UI 可用 mapPoint 定位图标。
mapPoint.set((targetWorld.x + 10) * 10, (targetWorld.z + 10) * 10);
});
验证、边界与替代方案
目标绕一圈时只应在前方 90° 内变绿。侧向、正后方以及重合位置分别检查;“共线”包括同向和反向,必须结合点积判断前后。
本例忽略高度,适合地面导航。飞行器需要三维锥体检测;建筑楼层则应额外检查高度或楼层编号。本例的 visible 意为“距离和视角符合条件”,不表示没有墙遮挡。进一步从眼睛位置发射射线才可检测可见性。大量对象可先用空间网格、八叉树等缩小候选集合,再做精确查询。
练习(暂不提供答案)
- 将小地图改为北朝上、世界 +Z 朝上,调整像素转换。
- 让角色旋转,验证左右判断随角色朝向变化。
- 增加高度差限制,避免把楼上的目标当作同层目标。
- 添加遮挡墙,让视角通过但被墙挡住的目标显示第三种颜色。