-
Notifications
You must be signed in to change notification settings - Fork 5
Addon Pitfalls
各接口页里所有警告的集中版。写完代码对一遍。
| 坑 | 症状 |
|---|---|
| 客户端类型泄漏到服务端 | 专用服务器启动即崩,崩溃信息不指向你的 App |
| 联动声明不参与可用性判断 | 对方模组没装时,主屏多一个点了没反应的图标 |
capturesKeyboard 两个方向都会错 |
打字打到一半手机关闭、内容全丢;或背包键失灵 |
空点击返回 false 会关机 |
点页面空白处,手机关了 |
裁剪必须走 clipped |
只在界面倍数非 100% 时内容被切掉一块 |
PhoneCanvas 只活一帧 |
画在错误的位置,或对着已关闭的渲染状态操作 |
收尾写在 onClose 而非 onBack |
玩家按 ESC 退出,草稿没保存 |
| 自绘图标要自己开混合 | 抗锯齿边缘发硬发脏,半透明图变成实心 |
VERSION 判断挡不住类加载 |
旧版 MCphone 上抛 NoClassDefFoundError
|
| 商店来源的回调必须切回主线程 | 偶发的注册表 / GUI 状态错乱 |
| SPI 构造失败是静默的 | App 没出现,只有日志里一行 |
症状:专用服务器启动即崩,且崩溃信息不会指向出问题的 App。
原因:api.client 及其子包的接口签名中含 GuiGraphics 一类的客户端类型,实现类只能在客户端加载。被物品、方块、菜单、网络包或服务端事件顺带引用到,服务端就会去加载它。
正确做法:实现类放 yourmod.client 包下,只由客户端代码接触;必要时主类声明 @Mod(value = "yourmod", dist = Dist.CLIENT)。服务端要触发客户端行为时,走一个签名中不含客户端类型的静态方法。
api.cost 的实现类则是两端都会加载的,其中同样不得出现客户端类型。
详见两端安全。
症状:所依赖的模组没装时,主屏上多出一个点了没反应的图标。
原因:isAvailable() 的默认实现只看 requiredMods()。只声明了 companionMods() 而不覆盖 isAvailable(),等同于"永远可用"。
正确做法:属于"没装对方就没有内容可给"这一类的 App,必须自己覆盖 isAvailable()。内建的「任务书」与「阅读」都是自己判的,可以照抄。
详见 companionMods()。
症状(漏返回 true):玩家在输入框里打字,一按到 e 就命中原版背包键,手机当场关闭、内容全丢。打拼音必然按到 e,中文用户躲不开。
症状(多返回 true):没有输入框却吃掉按键,玩家无法用背包键关闭手机。
正确做法:有输入框返回 true,没有就不返回。
症状:玩家点了页面里的空白处,手机关闭。
原因:mouseClicked 返回 false 会落到 MCphone 的默认处理,而默认处理中"点手机外面 = 关机"。
正确做法:页面内的空点击返回 true。
症状:文字或列表被切掉一块,且只在界面倍数不是 100% 时出现——自己在 100% 下怎么测都是好的。
原因:原版 graphics().enableScissor(...) 收的是窗口坐标且不看 PoseStack。玩家可在「设置 → 界面大小」把整个手机缩放到 75%–300%,而页面手上的是未缩放的手机坐标,直接交给原版,裁剪框就停在 100% 时的位置和大小上。
正确做法:用 canvas.clipped(x, y, w, h, () -> { ... }),它会把矩形过一遍当前的变换矩阵。
同理,不要自行从 Minecraft.getInstance().mouseHandler 取鼠标位置——那是屏幕原始坐标,未经换算。要鼠标位置就用回调里给的那两个数。
详见 clipped(...)。
症状:画在错误的位置,或对着已经关闭的渲染状态操作。
原因:PhoneCanvas 每帧新建,其中的鼠标位置与 GuiGraphics 下一帧即过期。
正确做法:不要把它存进字段,只在本次 render 调用内使用。
症状:玩家按 ESC 退出,草稿没保存。
原因:ESC 由 MCphone 统一处理为直接关机,既不走 keyPressed 也不走 onBack。
正确做法:释放资源与保存草稿写在 onClose()——它在从 ◁ 退出、按 ESC、关闭手机、断线时都会被调到。onBack() 只用于"页面内还有一层要退"。
症状:抗锯齿的边缘变硬发脏,整块半透明的图变成实心。
原因:原版 g.blit(ResourceLocation, ...) 从头到尾没碰过混合状态,而 GUI 中每画完一次 fill 或一行字,RenderType 收尾都会 disableBlend。轮到自绘图标时混合基本是关的,半透明像素被当作不透明绘制。
正确做法:
@Override
public void renderIcon(GuiGraphics g, int x, int y, int size, float partialTick) {
RenderSystem.enableBlend();
RenderSystem.defaultBlendFunc();
g.blit(myTexture, x, y, size, size, 0, 0, size, size, size, size);
RenderSystem.disableBlend(); // 收尾关掉:GUI 代码普遍假定画完是关着的
}renderIcon 的默认实现已经替你开了;只有覆盖它时才需要自己写这几行。(MCphone 内部的 GuiUtil.drawTexture 做的就是这件事,但它在 core 包中,不属于 API。)
症状:在旧版 MCphone 上抛 NoClassDefFoundError,那句 if 根本没执行到。
原因:JVM 在校验方法时就要解析其中引用的类型,早于 if 的求值。
正确做法:把新能力的调用单独关进一个类,判断通过后再碰它。示例见 MCphoneApi.VERSION。
症状:偶发的注册表或 GUI 状态错乱。
原因:IAppSource.listAvailable / install 允许在后台线程工作,但其回调中会接触注册表与 GUI 状态。
正确做法:
Minecraft.getInstance().execute(() -> callback.accept(list));症状:App 没有出现在手机里,只有日志中一行记录。
原因:一个附属构造失败不会中断整个扫描(见 util/SpiLoader),但那个 App 就是没了。
正确做法:不要把它当作兜底。实现类的构造函数中不要做会失败的事——尤其不要在其中接触注册表或读文件。