SceneEntity 与 EntityPose
统一世界二期的模组可见层级 API(查询面)。宿主把实体层级(世界 → 轨道区 → 轨道i → …)以查询面对模组开放:每个实体即纯变换节点(组),没有渲染内容;宿主不做「按树渲染模组回调」,模组经只读查询取世界矩阵 / 继承量后自行变换自绘内容。
SceneEntity是不透明句柄,没有任何方法;创建、挂载、查询、销毁全部经 GameStateAccess:createEntity/mountToTrackArea/mountToTrack/mountToEntity/entityPose/entityWorldMatrix/entityVisible/setEntityVisible/entityAlpha/setEntityAlpha/destroyEntity。EntityPose是实体本地位姿的写入面:标量偏移 + 有序 op,每 tick 开始由宿主清空(未写 = 恒等,帧间不累积),只能在GameplayTickEvent.Pre/Post回调内取得。
方法速览
| 方法 | 用途 |
|---|---|
offsetX() / setOffsetX(double) | 读/写本 tick 水平偏移(实体本地坐标,像素) |
offsetY() / setOffsetY(double) | 读/写本 tick 垂直偏移(实体本地坐标,像素) |
translate(double, double) | 原地叠加标量平移,返回 this |
rotateZ(double, double, double) | 追加绕 Z 轴旋转 op |
rotateX(double, double, double) | 追加绕 X 轴旋转 op |
rotateY(double, double, double) | 追加绕 Y 轴旋转 op |
translate3D(double, double, double) | 追加三轴平移 op |
scale(double, double, double, double) | 追加以本地圆心缩放的 op |
完整签名
public interface SceneEntity {
}
public interface EntityPose {
double offsetX();
void setOffsetX(double x);
double offsetY();
void setOffsetY(double y);
EntityPose translate(double dx, double dy);
EntityPose rotateZ(double degrees, double pivotX, double pivotY);
EntityPose rotateX(double degrees, double pivotY, double pivotZ);
EntityPose rotateY(double degrees, double pivotX, double pivotZ);
EntityPose translate3D(double dx, double dy, double dz);
EntityPose scale(double sx, double sy, double pivotX, double pivotY);
}合成口径:标量 offsetX/offsetY 的平移先施加(标量之间是可交换加法),再按 op 列表顺序逐项右乘;所有圆心/轴点一律写实体本地坐标,不是世界坐标,也不是「执行后」坐标。落盘后的本地变换 = translate(offsetX, offsetY) 后按列表顺序逐项右乘 op;世界矩阵 = 世界(父) · 本地(实体)。
所有权:实体归创建方模组独占。改他人实体的挂载 / 销毁 / 可见性 / alpha 会被宿主拒绝并输出 [ModConflict] 限频告警(不抛异常);挂到宿主实体(轨道区 / 轨道)任何模组均可。entityPose 的接口契约只承诺对无效 / 已销毁句柄返回 null,所有权纪律依赖「句柄不跨模组传递」。
窗口:entityPose 仅在 tick 分发期间合法,窗口外返回 null 并限频告警。mountTo* 与 destroyEntity 属于装配类操作,仅在非 tick 上下文合法(如 GameStartEvent),tick 分发期间调用被拒绝。查询 entityWorldMatrix / entityVisible / entityAlpha 任意时机合法。
生命周期:随游玩场景存续;游玩结束时宿主自动级联销毁全部模组实体,句柄失效(查询返回 null / false / 0 并限频告警)。显式 destroyEntity 同样级联子树。
确定性:禁随机、禁墙钟(时间取 GameplayTickEvent#musicTimeMs())、禁跨帧持有 EntityPose 引用;与 TrackPoseAdjuster 同一口径。纯视觉,判定引擎零消费。
offsetX()
签名:
double offsetX();行为:本 tick 的水平偏移(实体本地坐标,像素)。每 tick 开始时复位为 0;未写即恒等。
示例:
double dx = pose.offsetX();setOffsetX(double)
签名:
void setOffsetX(double x);行为:写入本 tick 的水平偏移(实体本地坐标,像素),绝对覆盖当前值。
示例:
pose.setOffsetX(40.0);offsetY()
签名:
double offsetY();行为:本 tick 的垂直偏移(实体本地坐标,像素)。每 tick 开始时复位为 0。
示例:
double dy = pose.offsetY();setOffsetY(double)
签名:
void setOffsetY(double y);行为:写入本 tick 的垂直偏移(实体本地坐标,像素),绝对覆盖当前值。
示例:
pose.setOffsetY(-18.0);translate(double, double)
签名:
EntityPose translate(double dx, double dy);行为:在当前标量偏移上叠加 (dx, dy)(实体本地坐标,像素),返回 this 便于链式书写。纯视觉。
示例:
pose.translate(40.0, 25.0);rotateZ(double, double, double)
签名:
EntityPose rotateZ(double degrees, double pivotX, double pivotY);行为:追加「绕平行 Z 轴旋转」op(画布平面内,度;正值 = 屏幕顺时针)。pivotX/pivotY 为实体本地坐标。op 保序,返回 this。
示例:
pose.rotateZ(12.0, 0.0, 0.0);rotateX(double, double, double)
签名:
EntityPose rotateX(double degrees, double pivotY, double pivotZ);行为:追加「绕平行 X 轴旋转」op(度,轴过本地 (任意 x, pivotY, pivotZ))。op 保序,返回 this。
示例:
pose.rotateX(8.0, 0.0, 0.0);rotateY(double, double, double)
签名:
EntityPose rotateY(double degrees, double pivotX, double pivotZ);行为:追加「绕平行 Y 轴旋转」op(度,轴过本地 (pivotX, 任意 y, pivotZ))。op 保序,返回 this。
示例:
pose.rotateY(-6.0, 0.0, 0.0);translate3D(double, double, double)
签名:
EntityPose translate3D(double dx, double dy, double dz);行为:追加三轴平移 op(像素,+z 向屏幕内;在标量 translate 之外、进 op 列表并保序)。返回 this。
示例:
pose.translate3D(0.0, 0.0, 60.0);scale(double, double, double, double)
签名:
EntityPose scale(double sx, double sy, double pivotX, double pivotY);行为:追加「以本地圆心缩放」op,等价于 translate(p) · scale(sx, sy) · translate(-p);pivotX/pivotY 为实体本地坐标。op 保序,返回 this。
示例:
pose.scale(1.1, 1.1, 0.0, 0.0);完整消费示例(装配在 GameStartEvent,每帧位姿在 GameplayTickEvent.Post,查询在渲染回调):
// onInit 内保存 ModContext,并订阅 GameStartEvent / GameplayTickEvent.Post
private GameStateAccess gameState() {
return modCtx == null ? null : modCtx.capabilities().get(GameStateAccess.class);
}
// GameStartEvent:非 tick 上下文,创建 + 挂载
GameStateAccess gs = gameState();
if (gs == null) {
return; // 非游玩场景
}
SceneEntity entity = gs.createEntity("my_entity");
gs.mountToTrack(entity, 2);
// GameplayTickEvent.Post:tick 窗口,每帧重写位姿
EntityPose pose = gs.entityPose(entity);
if (pose != null) {
double t = evt.musicTimeMs() / 1000.0;
pose.translate(40.0 * Math.sin(t), 0.0)
.rotateZ(15.0, 0.0, 0.0);
}
// 渲染回调:读取本 tick 落盘后的世界矩阵(16 元素,列主序,防御性拷贝)
double[] world = gs.entityWorldMatrix(entity);线程:渲染线程(tick 分发、实体查询与位姿写入均在其上)。
稳定性:2026-10-16 新增:2.x 内只增不破。