Skip to content

Addon IPhoneApp

HoshinoYumeka edited this page Sep 7, 2026 · 1 revision

IPhoneApp

首页附属接口文档 | 下一页:IPhonePage

com.november.mcphone.api.client.app.IPhoneApp

一个 App。实现本接口并用 SPI 注册后即出现在手机中。

仅客户端renderIcon 的签名含 GuiGraphics
注册 SPI:META-INF/services/com.november.mcphone.api.client.app.IPhoneApp
起于 1.0.46
必须实现 getId / getDisplayName / getIconTexture / onPress

⚠ 实现类只能在客户端加载,被物品、方块、菜单、网络包或服务端事件顺带引用会使专用服务器启动即崩。 约定放在 yourmod.client 包下,详见两端安全

⚠ 不要继承内建的 PhoneApp 基类:它把命名空间写死为 mcphone,且位于 core 包,不属于 API。


方法一览

方法 默认实现 调用时机
ResourceLocation getId() 必须实现 目录构建时
Component getDisplayName() 必须实现 绘制主屏、商店、管理器时
ResourceLocation getIconTexture() 必须实现 renderIcon 默认实现读取
void onPress() 必须实现 图标被点击且 openPage() 返回 null
void renderIcon(GuiGraphics, int x, int y, int size, float partialTick) 绘制 getIconTexture() 每帧
IPhonePage openPage() null 图标被点击时
int getBadgeCount() 0 每帧,每个图标各一次
void onUninstall() 玩家卸载该 App 时
boolean isSystemApp() false 商店与管理器判定可否卸载时
List<RequiredMod> requiredMods() List.of() isAvailable() 默认实现读取
List<RequiredMod> companionMods() List.of() 构建「联动 App」页与「关于」页时
boolean isAvailable() requiredMods() 判定 目录构建时一次
boolean isPreinstalled() true 该 App 首次进入目录时
String getVersion() "1.0.0" 详情页
String getAuthor() "" 详情页
String getDescription() "" 详情页

必须实现的方法

getId()

ResourceLocation getId()

App 的唯一标识,形如 mymod:calculator。命名空间使用实现方自己的 modid。

id 冲突时,后登记的实现被直接丢弃。

getDisplayName()

Component getDisplayName()

主屏图标下方、商店列表与 App 管理器中显示的名称。使用 Component.translatable 以支持多语言。

getIconTexture()

ResourceLocation getIconTexture()

图标纹理,20×20 PNG-32。由 renderIcon 的默认实现读取;覆盖 renderIcon 后本方法可不再被使用,但仍须实现。

纹理缺失时原版绘制紫黑格,不会崩溃。

onPress()

void onPress()

图标被点击时调用,用于打开独立 Screen 或执行业务逻辑。

openPage() 返回非 null 时本方法不会被调用,但接口仍要求实现——留空即可,或写一条在旧版 MCphone 上的退路。


绘制

renderIcon(...)

default void renderIcon(GuiGraphics g, int x, int y, int size, float partialTick)
说明
默认实现 getIconTexture()null 时,将整张纹理拉伸到 size×size
调用频率 每帧
partialTick 本帧的插值系数,用于不按整 tick 跳变的平滑动画

因每帧调用,图标可以是动态的:覆盖本方法并按时间挑选一帧绘制。

private static final int FRAMES = 8;
private static final int FRAME_MS = 100;

@Override
public void renderIcon(GuiGraphics g, int x, int y, int size, float partialTick) {
    int frame = (int) ((System.currentTimeMillis() / FRAME_MS) % FRAMES);   // 横向雪碧图
    RenderSystem.enableBlend();
    RenderSystem.defaultBlendFunc();
    g.blit(SHEET, x, y, size, size, frame * size, 0, size, size, size * FRAMES, size);
    RenderSystem.disableBlend();
}

⚠ 覆盖本方法后须自行开启混合,否则半透明像素被当作不透明绘制。 见坑 · 自绘图标要自己开混合

资源包无法让图标动起来:皮肤贴图是独立纹理、不进图集,原版 .mcmeta 的动画机制对其无效。为 app/xxx.png.mcmeta 不产生任何效果。动画只能由代码实现。

openPage()

default IPhonePage openPage()   // 默认 return null

返回一页画在手机屏幕内部的界面,与内建 App 同等待遇:共用状态栏、导航栏、壁纸与返回键。

返回 null 表示改走 onPress(),由实现方自行 setScreen 跳出手机——仅在界面于 120×200 像素内放不下时才应如此。

接口定义见 IPhonePage

getBadgeCount()

default int getBadgeCount()   // 默认 0

主屏图标右上角的角标数,0 表示不绘制,大于 99 显示为 99+

调用频率 每帧,每个图标各一次
约束 只能读取现成的值

⚠ 不得在此查询数据库、发送网络包或执行任何需要等待的操作。需要实时数据时自行按时间间隔限流(内建的美西螈 App 即如此)。


可用性与前置声明

RequiredMod

public record RequiredMod(String modId, String displayName) {}
参数 说明
modId 模组 id,与 ModList.isLoaded 用的是同一个值
displayName 给玩家看的名字,例如 "Waystones(传送石碑)"

displayName 必须写死。不要在运行时从 ModList 查询——需要显示它的时刻,该模组正是没装的那一个。

该记录刻意不含下载链接。

requiredMods()

default List<RequiredMod> requiredMods()   // 默认 List.of()

硬前置:缺失则整个 App 不可用。声明后 isAvailable()、商店的「联动 App」页、「设置 → 关于」的联动模组列表自动生效。

内建 App 一律不用本方法,改用 companionMods():MCphone 自身没有任何前置。本方法保留给附属模组中"缺了对方连加载都不该加载"的情形。

companionMods()

default List<RequiredMod> companionMods()   // 默认 List.of()

联动声明:本 App 沾了哪个模组的光。两种情形都用它声明:

情形 例子 是否须自己覆盖 isAvailable()
装了对方多一块内容,缺了照常可用 「阅读」装了 Patchouli 多几十本手册
没装对方就没有内容可给 「任务书」缺了 FTB Quests

声明后玩家在两处看得到:

  • 「设置 → 关于」的联动模组列表:一律列出,并标明装没装。
  • 商店的「联动 App」页:仅在该 App 当前不可用时收录,并写明缺哪个模组。App 当前可用时不收录——它没有被任何模组卡住,列出只是噪声。

第二条在 1.8.12 之前不成立:那一页当时只认 requiredMods(),于是"靠某模组撑着、却声明为联动"的 App 在对方缺失时会从主屏、商店与该页同时消失,而那一页存在的全部理由正是回答"我怎么没有这个"。

isAvailable()

default boolean isAvailable()

该 App 在当前环境下是否存在。返回 false 则主屏与商店中均不出现。

默认实现:遍历 requiredMods(),任一 ModList.get().isLoaded(modId)false 即返回 false

调用时机 仅在目录构建时问一次,此后不复查

⚠ 不要放入随时间变化的条件——那属于 onPress()。 ⚠ 仅声明 companionMods() 而不覆盖本方法,等同于"永远可用"。见坑 · 联动声明不参与可用性判断


安装与展示

isPreinstalled()

default boolean isPreinstalled()   // 默认 true

true 首次被发现即上主屏;false 进应用商店等待玩家下载。

仅在该 App 首次进入目录时生效,此后以玩家的安装 / 卸载选择为准。

isSystemApp()

default boolean isSystemApp()   // 默认 false

true 表示不可被玩家卸载(如「设置」)。管理器中卸载键置灰并写明原因。

onUninstall()

default void onUninstall()   // 默认空

玩家卸载该 App 时调用,用于清理持久化数据。

getVersion() / getAuthor() / getDescription()

default String getVersion()       // 默认 "1.0.0"
default String getAuthor()        // 默认 ""
default String getDescription()   // 默认 ""

App 详情页与管理器中显示的三行元数据。


快捷键:无需注册

玩家可在「设置 → App 管理器 → 该 App」中为其绑定一个键,在世界中按下即开机并直接进入该 App。

实现方不需要注册任何东西,绑定按 App id 走,附属与内建一视同仁。

快捷键与点击图标走完全相同的代码路径:先开机,再调用 openPage() / onPress()。因此 onPress() 中形如 if (Minecraft.getInstance().screen instanceof PhoneScreen ps) 的判断照样成立,无须为快捷键补分支。

绑定存于玩家客户端配置 config/mcphone-client.tomlappHotkeys,支持 Ctrl / Shift / Alt 组合键与鼠标键(含侧键),一个组合只能属于一个 App。冲突时界面提示占用者,玩家再按一次同一组合即强制绑定。

⚠ 冲突不被拦死,不要假设某个键一定归你的 App 独占

(玩家侧说明见设置 → 每个 App 一个快捷键,1.9.2 起。)


首页附属接口文档 | 下一页:IPhonePage

Clone this wiki locally