Skip to content
KubeJS Vector

向量与坐标

提示

本文将详细解释 KubeJS 与 Minecraft 中的向量(Vector)与坐标(Coordinate)概念,帮助你理解空间计算的原理、常见用法与开发注意事项。

什么是向量与坐标?

在 Minecraft 脚本开发中,理解"向量"和"坐标"是空间操作的基础。

  • 坐标(Coordinate):用于描述世界中某个"点"的具体位置。例如玩家的当前位置、方块的中心点等。
  • 向量(Vector):不仅可以表示一个点,也可以描述"方向"和"距离"。向量常用于描述实体的移动方向、速度、两点之间的相对关系等。

区别与联系

  • 坐标是"点",向量是"箭头"。
  • 坐标常用于定位,向量常用于计算方向、距离、速度等。
  • 在代码中,很多 API 会混用这两个概念,开发时要注意区分。

实际意义举例

  • 获取玩家位置:player.position() 返回的是一个三维向量(Vec3d),但它也代表玩家的坐标。
  • 计算玩家到目标的方向:target.position().subtract(player.position()) 得到一个"从玩家指向目标"的向量。
  • 让实体朝某方向移动:需要用"单位向量"描述方向,再乘以速度。

坐标系统详解

Minecraft 坐标轴

Minecraft 世界采用"右手坐标系":

Y轴 ↑ (高度)
   |
   |
   └───→ X轴 (东西)
  /
 /
Z轴 (南北)
方向 正值方向 负值方向
X 轴 东西 西
Y 轴 高度
Z 轴 南北

坐标类型对比

类型 精度 用途 示例
number 双精度 精确位置 x: 100.5
BlockPos 整数 方块坐标 [100, 64, 200]
ChunkPos 整数 区块坐标 [6, 12]
Vec3d 双精度 三维向量 (100, 64, 200)
  • number:最常见的坐标类型,表示精确到小数点的世界位置。
  • BlockPos:整数坐标,表示方块格子的中心点,常用于放置/获取方块。
  • ChunkPos:区块坐标,单位为 16 格,常用于区块相关操作。
  • Vec3d:三维向量,既可表示点,也可做向量运算。

坐标转换常见写法

js
// 精确坐标转方块坐标
const blockX = Math.floor(entity.getX());
const blockY = Math.floor(entity.getY());
const blockZ = Math.floor(entity.getZ());

// 方块坐标转区块坐标
const chunkX = Math.floor(blockX / 16);
const chunkZ = Math.floor(blockZ / 16);

// 区块内相对坐标
const relativeX = blockX % 16;
const relativeZ = blockZ % 16;

Vec3d 向量详解

向量(Vector)是描述空间中"方向"和"距离"的数学工具。在 Minecraft/KubeJS 中,Vec3d 是最常用的三维向量类型。

  • 向量的本质:可以看作是从原点出发的一支"箭头",既有长度(magnitude),也有方向(direction)。
  • 用途:描述实体移动、两点间距离、朝向、速度等。

Vec3d 的属性与创建

属性 类型 说明
x number X 分量
y number Y 分量
z number Z 分量
创建方式 说明
new Vec3d(x, y, z) 创建新向量
Vec3d.ZERO 零向量 (0,0,0)
entity.position() 实体位置向量
entity.getEyePosition() 眼部位置向量
entity.getLookAngle() 朝向向量
js
const pos = new Vec3d(10, 64, 20);
console.log(pos.x, pos.y, pos.z); // 10 64 20

const playerPos = player.position();
const look = player.getLookAngle();
js
// 获取玩家眼睛位置
const eye = player.getEyePosition();
// 获取实体朝向向量
const dir = entity.getLookAngle();

数学说明: 向量 (x, y, z) 可以表示空间中的一个点,也可以表示从原点 (0,0,0) 指向 (x, y, z) 的箭头。


向量运算与空间计算

向量运算是空间计算的基础。每种运算不仅有数学意义,也有实际开发用途。下列内容将详细讲解常用运算、典型用法、边界情况和常见误区。

向量加法与减法

  • 加法:两个向量首尾相连,结果是"合力"或"总位移"。
  • 减法:A-B 得到"从 B 指向 A"的方向和距离。
js
const a = new Vec3d(1, 2, 3);
const b = new Vec3d(4, 5, 6);
const c = new Vec3d(-1, 0, 2);
const result = a.add(b).subtract(c); // (6, 7, 7)
js
const zero = Vec3d.ZERO;
const a = new Vec3d(2, 3, 4);
const sum = a.add(zero); // (2, 3, 4)
const diff = a.subtract(zero); // (2, 3, 4)
js
// 错误:不能直接对数组做加法
const arr = [1, 2, 3];
// const result = a.add(arr); // 会报错,需先转为Vec3d
js
// 计算玩家到目标的方向向量
const direction = target.position().subtract(player.position());
// 计算两点中点
const midpoint = a.add(b).multiply(0.5);

数学说明: 加法:(x1, y1, z1) + (x2, y2, z2) = (x1+x2, y1+y2, z1+z2) 减法:(x1, y1, z1) - (x2, y2, z2) = (x1-x2, y1-y2, z1-z2)


标量乘法与分量乘法

  • 标量乘法:向量整体缩放,常用于"调整速度/距离"。
  • 分量乘法:每个轴分别缩放,常用于"非等比缩放"或"遮罩"。
js
const v = new Vec3d(1, 2, 3);
const scaled = v.multiply(2); // (2, 4, 6)
const reversed = v.multiply(-1); // (-1, -2, -3)
js
const v = new Vec3d(5, 8, 2);
const mask = new Vec3d(1, 0, 1);
const xzOnly = v.multiply(mask); // (5, 0, 2)
js
// 沿方向移动指定距离
const from = player.position();
const to = target.position();
const dir = to.subtract(from).normalize();
const newPos = from.add(dir.multiply(5)); // 沿朝向前进5格

数学说明: 标量乘法:(x, y, z) * s = (xs, ys, zs) 分量乘法:(x1, y1, z1) * (x2, y2, z2) = (x1x2, y1y2, z1z2)


单位化与长度

  • 单位化:将向量缩放为长度 1,仅保留方向。
  • 长度:向量的"模",即箭头的实际长度。
js
const v = new Vec3d(3, 4, 0);
const unit = v.normalize(); // (0.6, 0.8, 0)
js
const dir = v.normalize();
const length10 = dir.multiply(10); // (6, 8, 0)
js
const len = v.length(); // 5
js
const zero = Vec3d.ZERO;
const unitZero = zero.normalize(); // (0, 0, 0)

数学说明: 长度:|v| = sqrt(x² + y² + z²) 单位化:v/|v|,零向量单位化结果仍为零


距离计算

  • distanceTo:两点间直线距离。
  • distanceToSqr:距离的平方,适合比较远近时避免开方运算。
js
const a = new Vec3d(1, 2, 3);
const b = new Vec3d(4, 6, 3);
const dist = a.distanceTo(b); // 5
js
function distance2D(a, b) {
    return Math.sqrt(Math.pow(a.x - b.x, 2) + Math.pow(a.z - b.z, 2));
}
js
const sqr = a.distanceToSqr(b); // 25
if (sqr < 100) {
    /* 更快判断是否在10格内 */
}
js
const distZero = a.distanceTo(a); // 0

点积与夹角

  • 点积:判断两个向量是否同向、反向、垂直,计算夹角。
  • 夹角:通过点积公式求得。
js
const a = new Vec3d(1, 0, 0);
const b = new Vec3d(2, 0, 0);
const c = new Vec3d(-1, 0, 0);
const d = new Vec3d(0, 1, 0);
a.dot(b); // 2,同向
a.dot(c); // -1,反向
a.dot(d); // 0,垂直
js
const v1 = new Vec3d(1, 0, 0);
const v2 = new Vec3d(1, 1, 0);
const angle = Math.acos(v1.dot(v2) / (v1.length() * v2.length())); // ≈0.785(弧度)
const deg = (angle * 180) / Math.PI; // ≈45°
js
const look = player.getLookAngle();
const toTarget = target.position().subtract(player.position()).normalize();
const cosThreshold = Math.cos(Math.PI / 6); // 30°
if (look.dot(toTarget) > cosThreshold) {
    // 夹角小于30°
}
js
// 判断玩家是否正对目标(如触发交互、攻击判定等)
const look = player.getLookAngle();
const toTarget = target.position().subtract(player.position()).normalize();
const dot = look.dot(toTarget);
if (dot > 0.98) {
    // 玩家正对目标,允许触发特殊行为
}
js
const look = player.getLookAngle();
const toTarget = target.position().subtract(player.position()).normalize();
if (look.dot(toTarget) < -0.98) {
    // 玩家背对目标
}
js
// 判断实体是否在玩家前方60°锥形区域内
const look = player.getLookAngle();
const toEntity = entity.position().subtract(player.position()).normalize();
const cosCone = Math.cos(Math.PI / 6); // 30°
if (look.dot(toEntity) > cosCone) {
    // entity 在玩家前方锥形区域内
}

实际场景说明

  • 判断玩家是否正对/背对某目标(如交互、攻击、NPC 对话等)。
  • 判断实体是否在玩家视野锥体内(如 AOE 技能、视线检测)。
  • 判断两个方向是否基本一致(如自动对准、导航)。

叉积与法向量

  • 叉积:得到垂直于两个向量的向量,常用于求平面法线、旋转等。
js
const x = new Vec3d(1, 0, 0);
const y = new Vec3d(0, 1, 0);
const z = x.cross(y); // (0, 0, 1)
js
const a = new Vec3d(1, 2, 3);
const b = new Vec3d(2, 4, 6);
const n = a.cross(b); // (0, 0, 0),共线向量叉积为零向量
js
// 求平面法线
function getNormal(a, b, c) {
    const ab = b.subtract(a);
    const ac = c.subtract(a);
    return ab.cross(ac).normalize();
}
js
// 求三角形面积(向量叉积模长的一半)
function triangleArea(a, b, c) {
    const ab = b.subtract(a);
    const ac = c.subtract(a);
    return ab.cross(ac).length() / 2;
}
js
// 判断法线朝向
const a = new Vec3d(0, 0, 0);
const b = new Vec3d(1, 0, 0);
const c = new Vec3d(0, 1, 0);
const normal = getNormal(a, b, c); // (0, 0, 1),朝上
  • 实例:
js
// 通过地形三个点计算法线,判断是否为斜坡
const p1 = player.position();
const p2 = p1.add(1, world.getBlock(p1.add(1, 0, 0)).y - p1.y, 0);
const p3 = p1.add(0, world.getBlock(p1.add(0, 0, 1)).y - p1.y, 1);
const normal = getNormal(p1, p2, p3);
if (Math.abs(normal.y) < 0.95) {
    // 法线y分量小于0.95,说明不是平地,可能是斜坡
}
js
// 判断实体是否"贴地"或"攀爬"
const entityPos = entity.position();
const below = entityPos.add(0, -1, 0);
const blockNormal = getNormal(
    below,
    below.add(1, world.getBlock(below.add(1, 0, 0)).y - below.y, 0),
    below.add(0, world.getBlock(below.add(0, 0, 1)).y - below.y, 1)
);
const up = new Vec3d(0, 1, 0);
const angle = Math.acos(blockNormal.dot(up));
if (angle > Math.PI / 6) {
    // 地面与竖直夹角大于30°,判定为斜坡或可攀爬表面
}
js
// 判断玩家是否正对斜面(如自定义交互)
const look = player.getLookAngle();
const blockNormal = getNormal(blockPos1, blockPos2, blockPos3); // 三点确定表面
const dot = look.dot(blockNormal);
if (dot > 0.95) {
    // 玩家正对表面
}
js
// 沿法线方向生成粒子
const center = new Vec3d(10, 64, 10);
const normal = new Vec3d(0, 1, 0); // 假设为上表面
for (let i = 0; i < 10; i++) {
    const pos = center.add(normal.multiply(i * 0.2));
    level.spawnParticles("minecraft:cloud", pos.x, pos.y, pos.z, 1, 0, 0, 0, 0);
}

实际场景说明

  • 判断玩家是否正对/背对某目标(如交互、攻击、NPC 对话等)。
  • 判断实体是否在玩家视野锥体内(如 AOE 技能、视线检测)。
  • 判断两个方向是否基本一致(如自动对准、导航)。

投影与反射

  • 投影:将一个向量投影到另一个向量上,常用于"沿某方向分量"。
  • 反射:模拟"弹射"或"镜像"效果。
js
function project(a, b) {
    // 投影a到b
    const unitB = b.normalize();
    const projLen = a.dot(unitB);
    return unitB.multiply(projLen);
}

// 投影为零
const a = new Vec3d(1, 0, 0);
const b = new Vec3d(0, 1, 0);
const proj = project(a, b); // (0, 0, 0)
js
// 计算玩家速度在前进方向上的分量
const velocity = player.getDeltaMovement();
const forward = player.getLookAngle();
const forwardSpeed = velocity.dot(forward); // 前进分量
js
function reflect(v, normal) {
    // v沿normal反射
    const n = normal.normalize();
    return v.subtract(n.multiply(2 * v.dot(n)));
}

// 反射夹角
const v = new Vec3d(1, -1, 0);
const normal = new Vec3d(0, 1, 0);
const reflected = reflect(v, normal); // (1, 1, 0)
js
// 计算实体碰撞后的反弹方向
const velocity = entity.getDeltaMovement();
const surfaceNormal = new Vec3d(0, 1, 0); // 假设地面
const bounce = reflect(velocity, surfaceNormal);

实际场景说明

  • 计算速度/力在某方向上的分量(如滑动、摩擦、投射)。
  • 计算实体/粒子碰撞后的反弹方向。
  • 镜像变换、弹射模拟。

位置操作详解

位置设置方法

方法 参数 特点
setPosition(x, y, z) number, number, number 立即设置位置
setPos(x, y, z) number, number, number 立即设置位置
setPos(vec) Vec3d 向量方式设置
teleportTo(x, y, z) number, number, number 传送,有特效
teleportTo(dimension, x, y, z, yaw, pitch) 跨维度参数 跨维度传送
moveTo(x, y, z) number, number, number 平滑移动
absMoveTo(x, y, z) number, number, number 绝对移动
js
// 立即设置位置
entity.setPosition(100, 64, 200);
entity.setPos(new Vec3d(100, 64, 200));

// 传送 (有粒子特效)
entity.teleportTo(100, 64, 200);

// 跨维度传送
entity.teleportTo("minecraft:the_nether", 50, 64, 100, 0, 0);

// 相对移动
const currentPos = entity.position();
const newPos = currentPos.add(10, 0, 0); // 向东移动10格
entity.setPos(newPos);

位置获取方法

方法 返回类型 描述
getX(), getY(), getZ() number 当前精确坐标
getX(tick) number 插值坐标 (渲染用)
position() Vec3d 位置向量
blockPosition() BlockPos 方块位置
getEyePosition() Vec3d 眼部位置
getEyePosition(tick) Vec3d 插值眼部位置

相对位置计算

方法 参数 返回类型 描述
getUpVector(tick) number Vec3d 上方向向量
getForward() - Vec3d 前方向向量
getLookAngle() - Vec3d 朝向向量
getViewVector(tick) number Vec3d 视线向量
js
// 计算实体前方位置
function getFrontPosition(entity, distance) {
    const pos = entity.position();
    const lookAngle = entity.getLookAngle();
    return pos.add(lookAngle.multiply(distance));
}

// 计算实体上方位置
function getAbovePosition(entity, height) {
    const pos = entity.position();
    return pos.add(0, height, 0);
}

// 计算两实体中点
function getMidpoint(entity1, entity2) {
    const pos1 = entity1.position();
    const pos2 = entity2.position();
    return pos1.add(pos2).multiply(0.5);
}
js
// 在实体周围圆形排列其他实体
function arrangeInCircle(centerEntity, entities, radius) {
    const centerPos = centerEntity.position();
    const angleStep = (Math.PI * 2) / entities.length;

    entities.forEach((entity, index) => {
        const angle = angleStep * index;
        const x = centerPos.x + Math.cos(angle) * radius;
        const z = centerPos.z + Math.sin(angle) * radius;
        entity.setPosition(x, centerPos.y, z);
    });
}

实用工具函数

js
// 计算实体到方块表面最近点
function closestPointOnBlock(entity, blockPos) {
    const e = entity.position();
    const min = blockPos;
    const max = blockPos.add(1, 1, 1);
    return new Vec3d(
        Math.max(min.x, Math.min(e.x, max.x)),
        Math.max(min.y, Math.min(e.y, max.y)),
        Math.max(min.z, Math.min(e.z, max.z))
    );
}

// 计算实体到多实体的最近距离
function minDistanceToEntities(entity, entities) {
    const pos = entity.position();
    return Math.min(...entities.map(e => pos.distanceTo(e.position())));
}
js
// 预测实体下一tick位置
function predictNextPosition(entity) {
    return entity.position().add(entity.getDeltaMovement());
}

// 判断射线是否命中AABB盒
function rayIntersectsAABB(origin, direction, min, max) {
    let tmin = (min.x - origin.x) / direction.x;
    let tmax = (max.x - origin.x) / direction.x;
    if (tmin > tmax) [tmin, tmax] = [tmax, tmin];
    let tymin = (min.y - origin.y) / direction.y;
    let tymax = (max.y - origin.y) / direction.y;
    if (tymin > tymax) [tymin, tymax] = [tymax, tymin];
    if ((tmin > tymax) || (tymin > tmax)) return false;
    tmin = Math.max(tmin, tymin);
    tmax = Math.min(tmax, tymax);
    let tzmin = (min.z - origin.z) / direction.z;
    let tzmax = (max.z - origin.z) / direction.z;
    if (tzmin > tzmax) [tzmin, tzmax] = [tzmax, tzmin];
    if ((tmin > tzmax) || (tzmin > tmax)) return false;
    return true;
}
js
// 均匀分布圆周上的点
function pointsOnCircle(center, radius, count, y = 0) {
    return Array.from({ length: count }, (_, i) => {
        const angle = (2 * Math.PI * i) / count;
        return center.add(Math.cos(angle) * radius, y, Math.sin(angle) * radius);
    });
}

// 球面均匀分布点
function pointsOnSphere(center, radius, count) {
    const points = [];
    for (let i = 0; i < count; i++) {
        const phi = Math.acos(1 - 2 * (i + 0.5) / count);
        const theta = Math.PI * (1 + Math.sqrt(5)) * i;
        const x = center.x + radius * Math.cos(theta) * Math.sin(phi);
        const y = center.y + radius * Math.cos(phi);
        const z = center.z + radius * Math.sin(theta) * Math.sin(phi);
        points.push(new Vec3d(x, y, z));
    }
    return points;
}

// 生成圆环上的点
function pointsOnRing(center, innerR, outerR, count, y = 0) {
    return Array.from({ length: count }, (_, i) => {
        const angle = (2 * Math.PI * i) / count;
        const r = (innerR + outerR) / 2;
        return center.add(Math.cos(angle) * r, y, Math.sin(angle) * r);
    });
}
js
// 判断点是否在二维多边形内(射线法)
function inPolygon(point, polygon) {
    let inside = false;
    for (let i = 0, j = polygon.length - 1; i < polygon.length; j = i++) {
        const xi = polygon[i].x, zi = polygon[i].z;
        const xj = polygon[j].x, zj = polygon[j].z;
        const intersect = ((zi > point.z) !== (zj > point.z)) &&
            (point.x < (xj - xi) * (point.z - zi) / (zj - zi + 1e-10) + xi);
        if (intersect) inside = !inside;
    }
    return inside;
}

// 判断点是否在圆环区域
function inAnnulus(point, center, innerR, outerR) {
    const d = Math.sqrt((point.x - center.x) ** 2 + (point.z - center.z) ** 2);
    return d >= innerR && d <= outerR;
}

// 判断点是否在扇形区域
function inSector2D(point, center, forward, angle, radius) {
    const dir = point.subtract(center).normalize();
    const dist = point.distanceTo(center);
    return dist <= radius && forward.dot(dir) > Math.cos(angle / 2);
}

常见应用场景

js
// 让实体A追逐最近的玩家
function chaseNearestPlayer(entity, players, speed) {
    let minDist = Infinity;
    let nearest = null;
    for (const p of players) {
        const d = entity.position().distanceTo(p.position());
        if (d < minDist) {
            minDist = d;
            nearest = p;
        }
    }
    if (nearest) {
        const dir = nearest.position().subtract(entity.position()).normalize();
        entity.setDeltaMovement(dir.multiply(speed));
    }
}

// 让实体在圆环区域内巡逻
function patrolInRing(entity, center, innerR, outerR, speed, tick) {
    const angle = ((tick % 360) / 360) * 2 * Math.PI;
    const r = (innerR + outerR) / 2;
    const target = center.add(Math.cos(angle) * r, 0, Math.sin(angle) * r);
    const dir = target.subtract(entity.position()).normalize();
    entity.setDeltaMovement(dir.multiply(speed));
}
js
// 生成球面粒子
function spawnSphereParticles(level, center, radius, count, particleType) {
    const points = pointsOnSphere(center, radius, count);
    for (const p of points) {
        level.spawnParticles(particleType, p.x, p.y, p.z, 1, 0, 0, 0, 0);
    }
}

// 生成多边形边界粒子
function spawnPolygonParticles(level, polygon, y, particleType) {
    for (let i = 0; i < polygon.length; i++) {
        const a = polygon[i];
        const b = polygon[(i + 1) % polygon.length];
        for (let t = 0; t <= 1; t += 0.1) {
            const x = a.x + (b.x - a.x) * t;
            const z = a.z + (b.z - a.z) * t;
            level.spawnParticles(particleType, x, y, z, 1, 0, 0, 0, 0);
        }
    }
}

// 生成螺旋粒子动画
function spawnSpiralParticles(level, center, radius, height, turns, particleType, count) {
    for (let i = 0; i < count; i++) {
        const t = (i / count) * turns * Math.PI * 2;
        const x = center.x + Math.cos(t) * radius;
        const y = center.y + (height * i) / count;
        const z = center.z + Math.sin(t) * radius;
        level.spawnParticles(particleType, x, y, z, 1, 0, 0, 0, 0);
    }
}
js
// 判断玩家视线是否命中实体AABB
function isLookingAtEntity(player, entity, maxDistance = 10) {
    const origin = player.getEyePosition();
    const direction = player.getLookAngle();
    const min = entity.position().subtract(0.5, 0, 0.5);
    const max = entity.position().add(0.5, entity.getHeight(), 0.5);
    return rayIntersectsAABB(origin, direction, min, max);
}

// 判断玩家是否能"看到"某点(无障碍物)
function canSeePoint(player, point, level) {
    // 伪代码:实际应用需用 rayTrace API
    // 这里只演示向量用法
    const origin = player.getEyePosition();
    const dir = point.subtract(origin).normalize();
    // ...遍历路径判断障碍物
    return true;
}

// 预测实体是否会撞到墙
function willCollide(entity, level) {
    const next = predictNextPosition(entity);
    const block = level.getBlock(next);
    return !block.isAir();
}