交互与 use 回调
给车厢和装饰物件划出可点击区域,玩家右键命中时收到局部坐标。按钮、驾驶台、开关门都能脚本化。
两步
先在 create 里声明区域,再在 use 里处理命中。
function create(ctx, state, train) {
ctx.addUseBox("door_left", 0, -1.4, 1.0, -1.2, -1.2, 1.4, -0.8);
ctx.addUseBox("horn", 0, 0.30, 1.05, -2.10, 0.45, 1.15, -2.00);
}
function use(ctx, state, train, event) {
if (event.box === "door_left") {
state.doorRequested = true;
} else if (event.box === "horn") {
state.hornUntil = Date.now() + 800;
}
}
坐标和你画模型用的是同一套。
不需要做任何换算 —— 按钮在模型里的位置,直接就是
addUseBox 的参数。声明区域
| 方法 | 说明 |
|---|---|
ctx.addUseBox(id, car, x1, y1, z1, x2, y2, z2) |
登记一个长方体区域。id 是你自己起的名字,car 是车厢序号(从 0 起)。同名会覆盖。 |
ctx.addUseBox(id, x1, y1, z1, x2, y2, z2) |
不带 car 的重载,等价于 car = 0。装饰物件用这个。 |
ctx.removeUseBox(id) | 移除一个区域。 |
ctx.clearUseBoxes() | 清空全部区域。 |
ctx.useBoxCount() | 当前区域数量,调试用。 |
两个角点的顺序无所谓,内部会自动取最小/最大。
event 有什么
| 字段 | 说明 |
|---|---|
box | 命中的区域 id。 |
car | 车厢序号。装饰物件恒为 0。 |
x y z | 命中点的局部坐标 —— 射线打在区域表面的那一点,不是区域中心。 |
distance | 玩家眼睛到命中点的距离。 |
dirX dirY dirZ | 视线方向的单位向量,已变换到局部坐标系。 |
player | 玩家包装对象。 |
item | 主手物品 id,空手为 minecraft:air。 |
offhandItem | 副手物品 id。 |
sneaking | 是否正在潜行。 |
有了 x/y/z,一个大区域也能当多个按钮用 —— 比如整块驾驶台声明成一个 box,
再按命中点的 X 判断按的是哪个键。
触发条件
| 条件 | |
|---|---|
| 车厢 | 玩家正在乘车,且右键命中已登记的区域。最大距离 5 格。 |
| 装饰物件 | 右键该方块。未命中任何区域时 event 为 null。 |
没登记区域就完全不介入。
不写
addUseBox 的脚本,右键行为和以前一模一样 —— 不会莫名其妙吃掉玩家的操作。为什么是「先声明、后回调」
脚本跑在独立线程,主线程不可能等它返回再决定要不要拦下这次右键。 所以反过来:区域由脚本预先登记,主线程自己做射线命中,命中了才拦截原版右键, 然后把事件异步派发给脚本。
装饰物件的写法
装饰物件的 use 多一个 player 参数,event 在最后:
function create(ctx, state, block) {
ctx.addUseBox("switch", -0.2, 0.9, -0.5, 0.2, 1.1, -0.4);
}
function use(ctx, state, block, player, event) {
if (event && event.box === "switch") {
state.on = !state.on;
}
}
老脚本的 use(ctx, state, block, player) 照常工作 —— event 是追加在末尾的,
不写它不影响任何已有代码。
带后缀的变体
和其他入口一样,useTrain / useBlock 同样会被调用,两组都定义则都执行。