把一部能用的智能手机塞进 Minecraft —— 拍照、翻相册、换壁纸、听歌、装 App,还能给自己那一部手机起个名。
分支说明:这个仓库按 Minecraft 版本 / 加载器分分支。 你现在看的
main是当前主力版本:Minecraft 1.21.1 + NeoForge。 Minecraft 1.20.1 + Forge 在1.20.1-forge分支上, 功能已完整移植、可以下载,但还比这一支年轻得多。
Minecraft 1.21.1 · NeoForge 21.1.200+ · 客户端与服务端都需安装
可选依赖(装了多点东西,不装一切照常):
| 模组 | 装了会怎样 |
|---|---|
| Curios | 手机多一个饰品栏槽位,可以挂在腰上 |
| Waystones | 多一个「传送石」App |
| MCEF | 多一个「浏览器」App |
| NetMusic | 它刻出来的 CD 能塞进手机的唱片仓,走到哪儿放到哪儿 |
| Patchouli | 多一个「阅读」App,整合包里所有教程手册收进一个书城 |
| GuideME | 「阅读」的书城里多出用它做手册的模组那几本(AE2、现代化工艺都硬依赖它),自动发现,不用一个个适配 |
| Immersive Engineering | 「阅读」的书城里多一本它的《工程师手册》——那本书自成一套,靠白名单单独适配 |
| FTB Quests | 多一个「任务书」App,主屏一格直接开整合包的任务书 |
没装对应模组时,依赖它的 App 不会出现在主屏和应用商店里——商店里躺着一个点了会报错的东西,比它不存在更糟。
工作台合成,一次一部:
玻璃 玻璃 玻璃
红染料 黄染料 蓝染料
铁锭 红石 铁锭
拿在手上右键开机,按 Esc、或点机身外的区域关机。手机放在背包或饰品槽里时按 H 也能开机(可在原版「选项 → 按键设置 → MCphone」改键)。
Esc 不分层级,开到哪一页按下去都是直接关机;退回上一层走屏幕底部导航栏的 ◁。
| App | 说明 |
|---|---|
| 📷 相机 | 取景框 + 分帧截图。默认 V 拍照、X 退出,可在原版「选项 → 按键设置 → MCphone」改键。照片存进游戏目录的 screenshots/。快门那一下默认是满屏白闪,可以换成模糊一下 |
| 🖼 相册 | 缩略图网格、分页、大图查看、左右切换、删除(二次确认)。缩略图按需生成并缓存。右上角「打开文件夹」直接用系统的文件管理器打开截图目录 |
| 🎵 音乐 | 播放 config/mcphone/music/ 下你自己的音乐:播放 / 暂停 / 继续、上一首下一首、三种循环模式、进度条、音量。支持 OGG / MP3 / WAV。另有唱片仓——把唱片放进手机,声音跟着你走,周围的人也听得见 |
| 🏪 应用商店 | 安装 / 卸载 App,安装状态持久化;系统 App 标灰不可卸载。末格是「联动App」,见下方 |
| ⚙️ 设置 | 更换壁纸、字体颜色、设备命名、App 管理器(每个 App 一页:来历、前置/联动模组装没装、卸载) |
| 💬 美西螈 | 手机上的聊天 App。双向好友(申请 / 同意 / 拒绝)、会话列表(未读数、在线状态、最后一条预览)、气泡式会话界面。消息存进世界存档,对方离线也能发。能发相册里的照片与表情包(GIF 会动),点开看大图。在线好友那一行右下角有个小图标,点一下就传送到他身边 |
| 📝 记事本 | 随手记点东西。笔记跟着玩家走,死了也不会丢,写完能印成一本书递给别人 |
| 📦 末影箱 | 随身打开自己的末影箱。就是原版那一个,与方块末影箱、跨维度完全互通。付费 App,售价 1 × 末影箱 |
| 🗿 传送石 | 打开传送石碑的选点界面,去任何一个已激活的传送点。需要 Waystones,付费 App,售价 1 × 传送石 |
| 🌐 浏览器 | 在手机里上网。点开是一块占屏幕九成的面板,地址栏兼搜索框。需要 MCEF,详见下方 |
| 📖 阅读 | 整合包里的教程书全在这儿,点一本就翻开。常翻的几本收进「书架」,不用每次从几十本里找。联动 Patchouli 与沉浸工程,详见下方 |
| ✅ 任务书 | 整合包的任务书,开机点一下就是,不用再让它占着快捷栏。联动 FTB Quests,详见下方 |
主屏排列:按住图标直接拖就能换位置,其余图标实时让位,松手落定——是插入不是对调,把第一个拖到第三格,中间那些依次前移。摆好的顺序按存档记在 config/mcphone/installed/<存档>.json 里,重进游戏还在原位;新装的 App 落在最后一格,不会挤乱你摆好的东西。轻点仍然是打开 App,挪够 3 像素才算拖,手抖不会误排序。
多页主屏:一页 4×5=20 个,装多了自动分页,底部有页码点。翻页有三条路:滚轮、在空白处横着划一下、点页码点直接跳。只有一页时不画页码点——一个孤零零的点会让人以为还能往旁边划。
要把 App 挪到别的页,就拖着它停在屏幕左右边上,边条会由浅到深亮起来,停满 0.4 秒自动翻页,手不用松。不是"碰到就翻":拖去最右那一列的路上必然会扫过右边条,一碰就翻的话最后一格永远放不进去。
美西螈:手机上的聊天 App,名字就叫这个。加好友要对方点头。在它右上角的「+」里从当前在线的玩家中点选,对方在同一个界面里同意或拒绝;两个人同时点「添加」会直接成为好友。聊天限定在好友之间,陌生人发不进来。消息存进世界存档,对方不在线也能发,他下次上线就能看到;解除好友后聊天记录不删,重新加回来历史就回来了。
发照片与表情:输入栏左边是一个「+」,点开有两项——图片与表情。
「图片」是相册:相机 App 拍的、按 F2 截的都在里面,选一张点一下就发出去。也可以直接把图片文件拖进游戏窗口,正开着会话时拖进来就是发给他,省得先把文件挪进 screenshots/ 再进相册翻。
「表情」是你自己的表情包,放在 config/mcphone/stickers/(跟着客户端走,换服务器换存档都在)。导入两条路:把图片拖进游戏窗口(表情页开着时是"收进来"而不是"发出去",一次能拖一整包),或者点那一页右上角的「打开文件夹」,用系统的文件管理器丢进去。导入之后点一下就发,同一张表情反复发只在服务器上占一份存储(按内容存,见下方「服主须知」)。
GIF 是动的。 拖一张动图表情进来,发出去在对方手机上就是动的——拆成帧、拼成一张图传过去,播放在客户端。太大的会自动降一档帧尺寸、再不行就隔帧抽稀(至多 36 帧);连这样都塞不进上限的,退回去发第一帧,总比发不出去强。表情页的格子里显示的是第一帧(那一页不做动画,一屏几十张同时动只会让人晕)。
收到的图直接就看得见(图片不套气泡,就是图本身),点一下放大铺满屏幕,右上角能存进相册(存的是原始文件,存完就能再发给别人),点别处收回去。动图存进相册的是第一帧——相册是给截图用的,放一张会动的进去,别处都读不了。
发之前会先压:长边压到 512 像素,体积由服主定上限(chatImageMaxKb,默认 512 KB)。存原图只是白占服主的硬盘与所有人的带宽——手机放大看图时也只有 448×680 个真实像素。压完还超上限的(雨天、树叶这类噪点多的截图,PNG 压不动)会自动降一档尺寸重压(384 → 320 → 256 → 192),实在压不下来会告诉你换一张。
每对会话只留最近 20 张图。再往前的图片消息还在记录里,但显示成「已过期」——不删那一行,是因为删掉之后聊天记录会凭空少几句,而少的是什么谁也想不起来。文字消息不受这条限制,仍是每对会话 100 条。
图跟消息一样存在世界存档里,对方不在线也能发,他下次上线就看得见。服主可以整个关掉,见下方「服主须知」。
传送到好友身边:在线好友那一行的右下角有一个小图标,点它人就过去了,手机同时自动关掉。放右下角是为了不跟未读数、时间挤在一起,名字也不用让出宽度。落点与原版 /tp 玩家 玩家 一模一样——落在他站的那个坐标上,朝向也和他一致。跨维度也传,他在下界你在主世界照样过去。离线的人没有这个图标;鼠标停上去图标会亮起来,第二行那句消息预览临时换成「传送到他身边」,省得对着一个 8 像素的图标猜。
不需要对方点头——好友本来就要双方同意才加得上,能加上就当作接受了这件事。但对方会知道:两头各响一次末影人传送声,他的动作栏上还会写明是谁来了。
传送不要钱,也没有冷却。对方在你点下去那一刻正好下线的话,会提示"对方已经不在线了"——列表上的在线状态 3 秒才刷一次,这几秒的空档撞得上。
服主可以关掉它:serverconfig/mcphone-server.toml 里 allowFriendTeleport = false,那个图标就不再显示,服务端也会拒绝传送请求。默认是开着的。好友关系与聊天不受影响。
音乐的两条路:曲库里点一首是「耳机」——自己解码,只有你听得见,能暂停、能看进度。曲库里只有你自己放进目录的文件,游戏里的唱片不会混进来。唱片仓是「外放」——手持唱片点一下放进去,服务端播原版音效,周围人都听得见,声音跟着你走,就像扛着一台唱片机。外放只有播和停,没有暂停:原版音效系统里没有"从中间接着放"这回事。
本地的 MP3、OGG 之所以不能外放,是因为服务端没法把你硬盘上那个文件发给别人,别人电脑上也没有它。两条路可以同时响,就像一边戴耳机一边开外放。
装了 NetMusic 的话,唱片仓还收它的 CD。用它的电脑方块搜歌、刻成 CD,把 CD 放进手机,按播放——走到哪儿放到哪儿,周围人都听得见。
网络音乐能外放而本地文件不能,原因就在上一段:服务端没法把你硬盘上那个 mp3 发给别人,但网上那首歌人人都拿得到——它只需要广播一个地址,每个客户端自己去拉。搜歌、登录、VIP、刻盘全是 NetMusic 的事,我们只接手"放"这一件。
支持的格式:OGG / OGA(游戏自带解码器,最省事)、MP3 / MP2 / MP1、WAV / AIFF / AU。放进 config/mcphone/music/ 就行,进音乐 App 会自动重扫,不用重启游戏。MP3 靠打包进来的 JavaMP3(MIT 许可),许可证全文在 jar 里的 THIRD-PARTY.txt。
MP3 只认 MPEG-1(采样率 44100 / 48000 / 32000 Hz,绝大多数音乐都是这一档)。MPEG-2 与 MPEG-2.5 放不了 —— 那是低采样率的 MP3(22050 / 24000 / 16000 / 11025 / 12000 / 8000 Hz),常见于低码率的语音与老文件。这是打包的解码库的限制,它自己的 issue #8 就是这一条。
碰上了不会没声没响:曲库里那一行会变灰,鼠标停上去写着「MPEG-2 放不了,只认 MPEG-1」,日志里则有完整的规格,形如
[MCphone] 打开 MP3 我的歌.mp3 —— MPEG-1 Layer III, 44100Hz, 立体声, 320kbps
[MCphone] 放不了 local:老歌.mp3:MPEG-2 Layer III, 22050Hz, 单声道, 64kbps。
这个播放器只认 MPEG-1(44100 / 48000 / 32000 Hz),请转成 44.1kHz 再放进来
用任何转码工具转成 44.1kHz 的 MP3 即可,或者干脆转成 OGG —— 那一档走游戏自带的解码器,最稳。
上限:好友 100 人,每人待处理申请 50 条,每对会话保留最近 100 条消息,单条 256 字。
联动 App:有些 App 靠别的模组撑着(传送石靠 Waystones,浏览器靠 MCEF,任务书靠 FTB Quests)。MCphone 自己一个前置都没有,少了谁都照常开机,只是少那一个 App。对方没装时这些 App 不会出现在主屏和商店的普通列表里——商店里躺着一个点了必然报错的东西,比它不存在更糟。
但那样玩家也就无从知道自己缺了什么。所以应用商店的最后一格是「联动App」,排在所有可下载 App 之后,点进去一行一个列着:图标、名字、缺哪个模组、已装还是未装。没齐的整行压暗,看得见、点不动。App 是用 requiredMods() 还是 companionMods() 声明的都算数——只声明了后者的,这一页在它当前不可用时会回退去读,不然它会从玩家眼前彻底消失。手机里「设置 → 关于」也会列出这些模组各自装没装。
浏览器:唯一一个跳出机身的 App —— 手机屏幕是 120×200 像素,网页塞进去一行正文占不下十个字,所以它点开是一块占屏幕九成的居中面板,退出回到手机。上方一条工具条:后退、前进、刷新、地址栏,右侧那个点是加载指示。
地址栏兼搜索框:打网址就开网址,打关键词就搜(带 :// 的原样打开;带空格的当搜索词;剩下看有没有点,有点补 https://,没点去搜)。MCEF 自带的快捷键可用:Ctrl+滚轮 或 Ctrl+=/-/0 缩放、Alt+←/→ 前进后退、Ctrl+R 刷新,macOS 上用 ⌘。
装了 MCEF 之后第一次用还要等它下载约 200 MB 的原生库,这期间会显示「MCEF 还没准备好」,不是坏了。
已知限制:target="_blank" 的链接点了没反应,搜索结果里这种链接很常见。这是 MCEF 的缺口——它没有注册 CefLifeSpanHandler,而离屏渲染下也没有窗口可开,于是点了什么都不发生。眼下的办法是把地址复制进地址栏手动打开。
阅读:整合包里的教程书通常有几十本,每一本都是一个物品——想查点什么就得先在仓库里翻出那本书,翻完还得放回去;出门在外想起要看,书多半不在身上。这个 App 把它们全列在一页里,一行是书名,第二行是它出自哪个模组(玩家找书的思路几乎总是「某某模组那本书」,而不是书名)。行首那张小图就是那本书自己的样子。
书架与书城:底部两个页签。书城是整合包里有的全部书,几十本;书架是你自己收下的那几本。常翻的书通常不超过五六本,书架就是把它们从几十本里摘出来——点开「阅读」默认就停在书架上。
收藏靠每行右端那颗 ☆:点一下进书架,再点一下拿出来。不长按也不另开菜单——长按在这种小屏上没有任何提示,等于把功能藏起来。
收藏记在 config/mcphone/reader/shelf.json,按存档之外的全局一份:书来自你客户端装的模组,同一个整合包连到哪个服务器都是那几本,按存档分的结果是"同一个包、换个服务器书架就空了"。换了整合包则当前认不出的书自动不显示,但不会从文件里删掉——回到原来那个包,它们还在架上。
停在哪一页是一局之内记着的:从书城点开一本书、看完回来还在书城,不用每次重点一次。
搜索:两页顶上那条都是搜索框,进这一页它已经拿着焦点了,想找哪本直接打字,不用先点一下。书名、模组名、模组 id 三样都能搜——中文客户端里一本书的名字是中文、出处是英文很常见,只搜一个字段就会「我明明记得这个模组叫什么,却搜不到它的书」。多个词用空格分开是「与」的关系,各自可以命中不同字段(ars 笔记)。
结果按分数排:书名命中排在模组名命中前面,前缀命中排在中间命中前面——打「新生」的人要的是那本书,不是「出自某个名字里带新生的模组」的一堆书。右上角的数字在搜的时候变成「命中/总数」。
这一页会吃掉所有按键(包括背包键),和记事本、改设备名那几页一样:打拼音必然按到 e,不吃掉就成了开背包。ESC 照旧是直接关机。眼下没做拼音搜索(xsmy → 新生魔艺),那要一张拼音表。
翻书仍然是 Patchouli 在翻:点开一本书,接管屏幕的是它自己的界面,进度、已读标记、条目锁定全都对得上,和你在背包里右键那本书一模一样。我们只提供目录——它的书界面是 272×180,手机屏幕只有 120×200,放不下;而自己重画就要把它几十种页面类型重新实现一遍,它一改版就断。
合上书之后:按 ESC 回到游戏(ESC 本来就该干这个),下次开机手机会直接停在书架上,不用再从主屏点一遍。
同一套手册系统的,一次全收:AE2 的作者把自家那套手册系统单独拆成了模组 GuideME,AE2 与现代化工艺都硬依赖它。GuideME 自己就有"列出全部手册"的能力,所以这一支是自动发现——不写死任何一本书的 id,将来第三第四个模组接上它,书城里自动就有。
点开走的是它手册物品右键那条路,所以在手机里点开与在背包里右键,进的是同一个界面、同一段历史。图标是它给每本手册造的那个物品,AE2 的和现代化工艺的各不相同。
不是 Patchouli 做的手册,靠白名单收:有的模组自己写了一整套手册系统,与 Patchouli 毫无关系,扫描扫不出来——**沉浸工程的《工程师手册》**就是第一个。这类书走一份写死的白名单:每条记着它出自哪个模组、拿哪个物品当图标和书名、以及怎么打开它。
点开走的是它自己物品右键那条路(ManualHelper.getManual().getGui()),所以和你在背包里右键那本手册完全一样,连"停在上次看的那一页"都一致。书名取的是那个物品的名字,它自带中文,与你在背包里、JEI 里看到的是同一行字。
要再加一个模组,就写一个 ExternalBook 实现,往 ExternalBookSource 的白名单里加一行——对方没装时自动跳过。这类代码同样一律走反射、不加编译依赖。
和「书籍特例」的分别:特例管的是"这本书扫出来了,但要特殊对待"(新生魔艺那种),白名单管的是"这本书根本扫不出来,得我们报上去"(沉浸工程这种)。特例那一层刻意没有"凭空多出一本书"的能力,就是为了把这两件事分开。
个别模组要特殊照顾:有些模组把自己的手册整个换掉了,Patchouli 里只剩一本没人维护的旧书。这类靠一层「书籍特例」处理,它能做三件事:把某本书从架子上拿掉、改写它的显示、接管「点开之后打开什么」。
目前只有一条——新生魔艺(Ars Nouveau):它 5.x 起换成了自研的文档系统,右键笔记本打开的是那个界面,但 jar 里仍留着一本 Patchouli 书(条目只有英文,内容停在换系统那天)。所以在手机里点它的笔记本,打开的是它自己的文档界面,和你在背包里右键完全一样。
要再加一条,就写一个 BookQuirk 实现,加进 BookQuirks 的名单——对方没装时自动跳过。这类代码一律走反射、不加编译依赖:为一个方法搭进去几十 MB 的前置不值,而且断了要能退回默认行为(打开那本遗留的 Patchouli 书),不能变成一个点了没反应的死按钮。
没有物品的书也在:有些模组的书是 no_book 的,游戏里根本没有对应物品,只能用命令打开——它们在这里一样列着,点了就能看。这类书没有图标,画的是自带的那本书。
任务书:任务书是一局里翻得最勤的一样东西——每做完一件事就要回去看下一步。而它是个物品:要么长期占着快捷栏一格,要么每次都得回箱子里翻出来。这一格把它变成「开机 → 点一下」,书还在原处,只是不用再随身带着。联动 FTB Quests —— 没装则这个 App 不出现在主屏与商店的普通列表里,但商店的「联动App」页照样列着它、写明缺的是哪个模组。MCphone 本身不需要它也能跑,少的只是这一个 App。
任务界面仍然是 FTB 在画。 点开之后接管屏幕的是它自己的界面,章节、进度、领奖、编辑权限全对得上,和你在背包里右键那本书一模一样——我们照抄的就是那本书右键时执行的那一句。
开不了的时候,说话的也是它。 任务档案还没从服务端同步过来、服主在配置里关掉了任务书界面、你的队伍被锁——这三种情形 FTB 自己都有聊天提示或 toast,我们不在前面加一道自己的检查。加了的话玩家点下去只会得到静默无反应,而交给它,玩家看到的和右键实体书时看到的是同一句话。
预装且免费。 任务书在绝大多数整合包里是开局白送、丢了还能再做的,卖它没有对应物;而这个 App 存在的意义就是少走几步,埋进商店等玩家自己发现,等于第一步就多走了。
为什么不收进「阅读」的书城。 教程书是"想查点什么才去翻",几十本,所以要一个带搜索的列表;任务书全局只有一本,而且随时要看。塞进书城的话每次都要走「开机 → 阅读 → 从几十本里找到它 → 点开」,比拿实体书还慢——多那一层列表,这个 App 就没有存在的必要了。
它是纯客户端的:任务档案本来就同步在客户端,开界面也在客户端,服务端一个包都不会多收。接触面只有一个零参方法,走反射、不加编译依赖,理由与「阅读」那几支书源一样,写在 feature/quests/client/FtbQuestsBook.java 的类注释里。
App 管理器:「设置 → App 管理器」列出已装的每一个 App,点一行进它自己的一页——图标、名字、作者、版本、介绍,以及它是哪个模组给的、声明的前置与联动模组装没装。卸载在这一页,而且要点两次(第一次上膛、第二次才真卸),跟相册删照片同一条规矩;系统 App 的卸载键是灰的并写明原因。装回来仍然走应用商店。
这一页往后是每个 App 的开关都该待的地方——操作区是一行一个的形状,加一个开关就是加一行。第一个这样的开关是快捷键。
相机的快门闪光可以换成模糊:在「设置 → App 管理器 → 相机」里,「快门闪光」那一行点一下就在白闪与模糊之间换。白闪是照相机的老习惯,但夜里或在暗处拍照时它会把整个屏幕顶到全白,眼睛得缓几秒;模糊那一版只是把画面糊一下再收回来,同样交代了"拍下来了",亮度不变。两种都不会进照片——闪光是抓完图之后才开始的。
模糊借的是原版菜单背景那条后处理链,不额外带着色器文件;半径跟着时间收,所以是化开又收回去,不是硬切一块糊画面。这也是客户端设置,存在 config/mcphone-client.toml 的 cameraSoftFlash 里。
每个 App 一个快捷键:在「设置 → App 管理器 → 点开某个 App」里点那行「快捷键」,然后按一个键——以后在世界里按它就直接开机并进这个 App,不必再「开机 → 找图标 → 点开」。默认未指定,每个 App 各绑各的,绑过的键会显示在管理器的列表行上,一眼看得完。ESC 是清除(与原版「按键设置」里的意思一致),绑定期间再点一次那一行就是算了。
键盘和鼠标键都能绑,包括鼠标侧键(4/5)、中键、右键——和原版「按键设置」里能绑的范围一样。左键除外,它是"算了":左键是你在手机里点东西的那只手,等键时点一下就是取消这次绑定;真绑上去的话,在世界里每挖一次方块都会开一次手机。绑在鼠标键上的 App 打开时不会顺带挥手或用一次物品——那一下会被整个吃掉。
支持组合键:按住 Ctrl / Shift / Alt 再按主键就是组合,可以叠(Ctrl+Shift+K),鼠标键同样可以(Ctrl+侧键 4)。修饰键自己按下去不算数,等你按主键那一下才成立。触发时要求完全一致:绑了 Ctrl+K,光按 K 不会开,按 Ctrl+Shift+K 也不会——否则两个 App 分别绑这两个组合时,后者会连着前者一起响。Mac 上的 Command 就是这里的 Ctrl。
撞了车不拦你,但会先说一声:一个组合已经被别的 App、或者被原版/别的模组的键位占着时,那一行会变成「再按一次强制」并写出是被谁占的;再按同一个组合就照绑。要说这一声是因为这套快捷键不出现在原版的「按键设置」界面里——真让「按 E 同时开背包和手机」这种事默默发生,日后你查不出是谁干的。强制之后那一行会一直带个 ⚠,提醒你这个组合是重着的。
顺带一提,加了修饰键并不等于就避开了冲突:原版自己的键位判"要不要响应"时不管你有没有按住修饰键,所以 Ctrl+E 照样会连带开背包。真按下去时手机会把那条键位这一下攒的点击倒掉,不至于等你关掉手机再补开一次背包。
手机已经开着时按这些键不会切换 App:那时候你多半正在手机里打字。
绑定是客户端设置,存在 config/mcphone-client.toml 的 appHotkeys 里,跟着这台电脑走,换服务器、换存档都在。
自定义壁纸:把任意尺寸的 PNG 放进 config/mcphone/wallpapers/,在「设置 → 更换壁纸」里选——那一页右上角有个「打开文件夹」,点了直接用系统的文件管理器开那个目录,省得自己一层层找;点过之后这一页会盯着目录,拖进去的图切回游戏就看得见,不必退出去再进来。壁纸选择由服务端保存并同步,多人游戏中每位玩家的壁纸各自独立。
界面大小:在「设置 → 界面大小」里把整个手机放大或缩小 —— 拖那条,或者点 − + 一档 25%,范围 75%–300%,默认 100%。改的时候手机(连同这一页自己)就在跟着变,不用另外预览。
为什么不用原版的 GUI 缩放:那一项是全局的,为了看清手机把它调大,聊天框、物品栏、所有界面都会跟着变大。这一项只管手机。
放大是整体缩放,不是重排布局:手机里所有东西的相对位置一点不变,鼠标坐标由模组自己换算回去。
「贴合清晰倍数」默认开着。 字是位图、按最近邻画,所以每个像素方不方正只看一个乘积:GUI 缩放 × 界面大小。是整数就横平竖直,不是整数就有的列占两像素、有的占三像素,细线时粗时细。开着的时候档位自动对齐到 1/GUI缩放——缩放 2 是 100/150/200…,缩放 3 是 100/133/166…,缩放 4 是 100/125/150…,− + 一次正好走一档。关掉就能拖到任意值,代价就是上面那点不匀。窗口放不下时会自动夹回去,配置里的数不动,窗口拉大之后又回到你要的大小;被夹着的时候那一页会写一行「窗口放不下,实际 xxx%」。
也是客户端设置,存在 config/mcphone-client.toml 的 uiScale 里。
字体颜色:在「设置 → 字体颜色」里从六个预设里挑一个 —— 白色(默认)、黑色、琥珀、天青、薄荷、樱粉。用了浅色壁纸就换黑色,否则浅底浅字看不清。选中即生效,不用重启。
这是客户端设置,存在你自己的 config/mcphone-client.toml 里,不同步给服务端,换服务器也不会变 —— 和资源包一个性质。也可以从模组列表的「配置」按钮进去改。
只有画在壁纸上的字跟着变。导航栏、通知、聊天气泡、商店按钮上的字不跟随:那些底是手机自带的深色构件,字跟着一起变就陷进底里看不见了。
设备名称:在「设置 → 设备名称」里起名,起过名的手机在物品栏显示该名,上限 24 字符,服务端做校验与截断。铁砧改名优先级更高,会盖过设备名。
关于中文输入:手机里的输入框用的就是原版按 T 那个聊天框的同一个控件,能不能打中文与原版完全一致。要注意的是 Minecraft 不会把输入法候选窗定位到光标处(GLFW 缺少相应接口,原版聊天框也一样),打拼音时基本看不见候选列表,等于盲打——想稳妥就用 Ctrl+V 粘贴。
界面上的每个视觉元素都能用贴图替换。放了贴图就用贴图,没放就用内置配色,功能完全不受影响——不会出现缺图变紫黑格子的情况。
把 PNG 放进资源包的 assets/mcphone/textures/,路径按下表(相对该目录):
| 文件名 | 画的是什么 | 建议尺寸 | 没放时 |
|---|---|---|---|
phone/frame.png |
手机外壳边框 | 136×216(含边框的整机)。中间 120×200 必须透明——外壳画在最上层,不透明会糊掉整个屏幕;反过来你在中间画的内圆角、刘海都会显示 | 画纯色边框 |
phone/status_bar.png |
顶部状态栏底 | 120×10 | 纯色 |
phone/nav_bar.png |
底部导航栏底 | 120×14 | 纯色 |
phone/nav_back.png |
导航栏返回键 | 40×14 | 画 ◁ 字符 |
phone/nav_home.png |
导航栏主页键 | 40×14 | 画 ○ 字符 |
phone/nav_tasks.png |
导航栏多任务键 | 40×14 | 画 □ 字符 |
phone/drop_slot.png |
主屏拖动排序时「松手落这儿」的空槽 | 20×20 | 半透明纯色 |
phone/page_dot.png |
主屏底部的页码点(不是当前页) | 3×3 | 半透明纯色 |
phone/page_dot_active.png |
页码点(当前页) | 3×3 | 纯白 |
phone/page_edge.png |
拖着图标停在屏幕边上时的翻页提示条 | 10×176(竖条) | 半透明纯色 |
chat/bubble_self.png |
自己发出的聊天气泡底(只有文字消息用,图片不套气泡) | 随内容拉伸 | 纯色 |
chat/bubble_peer.png |
对方发来的聊天气泡底(同上) | 随内容拉伸 | 纯色 |
chat/input_bar.png |
会话界面输入栏底 | 90×14 | 纯色 |
chat/teleport.png |
会话列表里在线好友那一行右下角的「传送到他身边」小图标 | 7×7(按实际绘制尺寸画,这里不做平滑缩放) | 画 → 字符 |
chat/attach.png |
会话界面输入栏左边那个「+」(点开是图片 / 表情) | 9×9 | 画 + 字符 |
music/prev.png |
音乐播放条上的「上一首」 | 9×9 | 画 ⏮ 字符 |
music/play.png |
「播放」键 | 9×9 | 画 ▶ 字符 |
music/pause.png |
「暂停」键 | 9×9 | 画 ⏸ 字符 |
music/next.png |
「下一首」 | 9×9 | 画 ⏭ 字符 |
music/eject.png |
唱片仓的「取出」 | 9×9 | 画 ⏏ 字符 |
music/backpack.png |
唱片仓的「从背包放」(只在仓是空的时候出现) | 9×9 | 画 ▤ 字符 |
music/mode_list_loop.png |
循环模式:列表循环 | 9×9 | 画 ↻ 字符 |
music/mode_single_loop.png |
循环模式:单曲循环 | 9×9 | 画 ① 字符 |
music/mode_shuffle.png |
循环模式:随机播放 | 9×9 | 画 ⇄ 字符 |
gallery/delete.png |
相册单张查看里的「删除」键(点过一次之后变成文字「再点一次确认」,那一态不走贴图) | 9×9 | 画「删除」两个字 |
phone/toast.png |
收到消息时右上角的通知底 | 160×32 | 纯色加一圈描边 |
phone/unread_badge.png |
未读条数的角标底 | 12×9 | 纯色 |
store/button.png |
应用详情页上可点的按钮底(购买 / 下载) | 100×16 | 纯色 |
store/button_disabled.png |
点不动时的按钮底(已安装 / 买不起) | 100×16 | 纯色 |
store/companion.png |
商店里「联动App」入口格的图标 | 20×20 | 纯色底加三个小方块 |
browser/panel.png |
浏览器那块大面板的底 | 320×200 | 纯色 |
browser/bar.png |
浏览器工具条那一条的底 | 320×22 | 纯色 |
browser/back.png |
工具条上的「后退」键 | 22×18(整个键框,不是居中的小图标) | 画 ◀ 字符 |
browser/forward.png |
「前进」键 | 22×18 | 画 ▶ 字符 |
browser/reload.png |
「刷新」键 | 22×18 | 画 ↻ 字符 |
reader/book.png |
书架列表里那本兜底的书(只在书源画不出那本书自己的图标时用得上) | 16×16(按实际绘制尺寸画,这里不做平滑缩放) | 纯色 |
reader/search_bar.png |
阅读页顶上那条搜索栏的底 | 112×12 | 纯色 |
reader/shelved.png |
书已收进书架时行右端那颗星 | 9×9(按实际绘制尺寸画) | 画 ★ 字符 |
reader/unshelved.png |
书还没收进书架时那颗星 | 9×9 | 画 ☆ 字符 |
reader/tab.png |
底部「书架 / 书城」当前那一页的底 | 54×12 | 纯色(另一页不画底) |
几点说明:
-
尺寸不必精确匹配,贴图会被拉伸到目标区域。想要不变形,按建议尺寸或其等比放大画即可。
-
默认整张拉伸;要固定圆角就加一份
.png.mcmeta。 不带元数据的贴图行为和以前一样——整张被拉到实际大小,带圆角的图在宽气泡上抻长、在窄气泡上压扁,所以纯色或纵向渐变最稳妥。想要圆角不变形,在 PNG 旁边放一份同名的.png.mcmeta,内容{"mcphone_skin": {"border": 3}},这张图就改走九宫格:四角按源图上那 3 个像素原样画,只有四条边和中间被拉伸,气泡多宽多高,四个角都一样大。border大于源图短边的一半时忽略,退回整张拉伸;老资源包不写这份元数据,什么都不用改。 -
上面这张表里的贴图,除了浏览器那三个键,模组都自带——40 个位里的 37 个。最后补齐的十三张是状态栏、拖动空槽、两颗页码点、翻页提示条、商店的两个按钮底、浏览器的面板与工具条、阅读的搜索栏 / 标签 / 收藏与未收藏两颗星。资源包放同路径的图仍然照常覆盖,只是「没放时」那一列的兜底色平时已经轮不到出场了——它还留在表里,是因为资源包塞进来一张坏 PNG 时仍然会退到那一档,而不是变成紫黑格子。浏览器那三个键还没有自带图,画的是 ◀ ▶ ↻ 三个字符;那三个位是 1.9.2 才开出来的,谁都可以填。气泡与输入栏那三张自带的图用的就是上面那份
border: 3元数据。 -
贴图改不了颜色,状态只能靠亮度或透明度。 悬停、点不动这类状态,在没有贴图的年代是换一个颜色画出来的;这个位上一旦有了贴图,那个颜色就再也看不见。所以模组在有贴图时改用别的表达:导航键与商店按钮悬停是【整张按倍数提亮】(资源包画的是什么颜色,亮起来还是那个颜色),浏览器那三个键点不动时是【整张压暗】,音乐键与聊天的「+」是把高亮块画在贴图【底下】。画贴图时不必自己去表达这些状态。
-
半透明是有效的:贴图里 alpha 介于 0 与 255 之间的像素会正常混合,抗锯齿边缘、整块半透明的底(做"壁纸上压一层暗色"这种效果)都可以画。1.7.40 之前这类像素会被当成不透明画出来,边缘发硬、半透明的底变成实心——那是模组的 bug,不是你的图有问题。
-
建议尺寸是「画在屏幕上有多大」,不是「文件必须多大」。 贴图会被拉伸到目标区域,整数倍放大的图一样能用,而且线条细、有弧度的图案这么做更清楚:模组自带的
phone/frame.png是 2 倍(272×432),导航栏那三个键是 10 倍(400×140),书架那两颗星是 4 倍(36×36)。放大倍数取整数——非整数倍在低 GUI 缩放下会掉线条。 -
phone/unread_badge.png两处共用:会话列表里的未读数和消息通知右上角的计数是同一张图,换一次两处都变。 -
store/button_disabled.png是单独一张,而不是把可点的那张调暗——调暗是我们替美术做的决定,「不可点」该长什么样应该由画贴图的人说了算。 -
browser/panel.png绝大部分会被网页盖住,真正看得见的只有加载中的空白期,纯色就够。browser/bar.png那一条在面板外面,浮在面板上方的留白里,不占网页高度。 -
phone/toast.png的 160×32 是原版通知的槽位尺寸,照这个画才不会和其他模组的通知挤在一起错位。 -
玩家头像不在换肤范围内 —— 那是玩家自己的皮肤,取自 Tab 玩家列表,资源包不该覆盖。
-
改完按 F3+T 重载资源包即可看到效果,不用重启游戏。
-
App 图标单独放在
app/下,文件名就是 App 的短名:内建的十四个是app/settings.png、app/app_store.png、app/chat.png、app/camera.png、app/gallery.png、app/music.png、app/ender_chest.png、app/notes.png、app/waystone.png、app/browser.png、app/clock.png、app/weather.png、app/reader.png、app/quests.png,都是 20×20。附属模组的 App 图标路径由它自己决定,见「给附属模组作者」。 -
天气页那张大图标在
weather/下,六张 32×32:clear.png、rain.png、snow.png、thunder.png、dry.png、none.png,按当前是什么天各显其一。这六张和上面那张表不是一回事——它们没有兜底色,因为模组自带全套,资源包只是覆盖。要换就六张一起换,只换一两张会出现画风打架。 -
完整的贴图位清单(含每个位现有的图长什么样、缺图时退回哪个颜色)可以自己生成:
python3 docs/make_texture_manifest.py,出一张 HTML。不在文档里存一份静态清单,是因为那种清单必然过期——PhoneTheme头部那张就是这么烂掉的。 -
放进去的文件不是合法 PNG 时会退回纯色,并在日志里记一条警告,不会崩。
-
老路径仍然认。 1.2.7 之前所有贴图平铺在
textures/gui/下(phone_frame.png、app_icon_chat.png这种)。按那套做的资源包不用改也能继续用——新路径找不到时会自动回退,并在日志里提一句。新做的资源包请按上表。
装了 Integrated Dynamics 时,MCphone 会在方块注册的末尾把 ID 全部方块的掉落表提前解析一次。这不改变任何掉落行为,也不给玩家多出任何功能——它是为了让别人的崩溃能被正确指认。
NeoForge 的注册阶段只要收到任何一个模组抛的异常,就会把整个注册表回滚成原版状态;而回滚过程本身会在 ID 的墙上火把那里踩到一个空指针,于是真正的错误被顶掉,崩溃报告上只剩一句 Trying to access unbound value: integrateddynamics:menril_torch_stone——看起来像是 ID 的锅,其实 ID 只是最后一个倒下的。提前解析一次就绕开了那个空指针,回滚能正常跑完,把真凶抛出来。
这不会救回服务器,起不来还是起不来,它只让崩溃报告指认对人。细节写在 compat/IntegratedDynamicsCompat.java 的类注释里。没装 ID 时这段代码一行都不会执行。
服务端配置在存档的 serverconfig/mcphone-server.toml 里,目前有两项:
| 项 | 默认 | 关掉之后 |
|---|---|---|
allowFriendTeleport |
true |
好友那一行的传送图标不再显示,服务端也拒绝传送请求。好友关系与聊天不受影响 |
allowChatImages |
true |
输入栏左边那个图片键不再显示,服务端也拒收上传的图。已经发过的图不删,仍然看得见 |
chatImageMaxKb |
512 |
一张图最多多少 KB(64–768)。客户端按它压,服务端按它收。这个数直接换成硬盘,见下 |
图片占多少硬盘:chatImageMaxKb × 20。每对会话最多留 20 张不同的图,所以每对好友封顶就是这个数的 20 倍——默认 512 KB 就是每对 10 MB,200 对常聊的好友是 2 GB 上限。这是最坏情况,实际远小于它:Minecraft 的截图大片是天空与地形,512 长边压出来常常只有 1 KB,上限卡的是雨天、粒子那种满屏噪点的画面,以及动图(所有帧拼成一张,所以它才是最吃这个上限的)。图片按内容存(文件名是"会话键 + 图片字节"的哈希),所以同一张表情在同一对好友之间发一百次,硬盘上仍然只有一份,也只占那 20 个名额里的一个。
嫌大就把 chatImageMaxKb 调小:调小之后客户端会自动降一档尺寸重压(图变糊),动图会先掉帧、再不行退成一张静态图——功能不会坏,只是画质让路。128 KB 大致是每对 2.5 MB。彻底不想要就把 allowChatImages 关掉,已经发过的仍然看得见。上限 768 KB 是硬的:再往上就顶到原版"服务端发给客户端"那 1 MB 的包上限了。
服务器启动时会扫一遍图片仓,删掉没有任何消息认领的文件(写完文件、消息还没落盘就崩了会留下这种孤儿)。
数据存在哪儿(做备份时要知道):
| 数据 | 位置 | 随什么走 |
|---|---|---|
| 好友关系、聊天记录 | 世界存档的 data/ |
存档 |
| 聊天里发的图片 | 世界存档的 mcphone/chat-images/ |
存档 |
| 笔记、已购 App、唱片仓、壁纸选择、未读标记 | 玩家数据(playerdata/) |
存档 + 玩家 |
| App 安装状态与主屏排列 | 客户端 config/mcphone/installed/<存档>.json |
玩家自己的客户端 |
| 书架收藏 | 客户端 config/mcphone/reader/shelf.json |
玩家自己的客户端,全局一份不分存档 |
| 表情包 | 客户端 config/mcphone/stickers/ |
玩家自己的客户端,全局一份不分存档 |
前两行在服务器上,删存档就没了;后两行在玩家自己电脑上,服务器备份不包含它们。
📖 完整的接口文档在 docs/addon-api.md —— 每个接口的每个方法、
坐标与线程的约定、几个非踩不可的坑、版本兼容承诺。下面只是一段概览。
App 系统通过 SPI 开放,你的模组不需要被 MCphone 感知也能往手机里装 App。内建 App 走的是同一套机制,没有走后门。
注册一个 App:实现 com.november.mcphone.api.client.app.IPhoneApp,在 META-INF/services/com.november.mcphone.api.client.app.IPhoneApp 中登记实现类,App 即自动出现。
自定义商店来源:实现 com.november.mcphone.api.client.store.IAppSource,用 AppInfo 描述可下载的 App,在 META-INF/services/com.november.mcphone.api.client.store.IAppSource 中登记。
这两个接口在 api.client 下,签名里有 GuiGraphics 之类的客户端类型,只能被客户端加载。给 App 定价用的 com.november.mcphone.api.cost.IAppPriceProvider 则不在 client 下:价格两端都要用,客户端画详情页,服务端在扣东西前核对,所以实现类里不许出现客户端类型。
声明前置:你的 App 依赖另一个可选模组时,覆盖 IPhoneApp.requiredMods():
@Override
public List<RequiredMod> requiredMods() {
return List.of(new RequiredMod("someothermod", "Some Other Mod(显示名)"));
}声明一次,三件事自动发生:对方没装时你的 App 不进目录(主屏与商店的普通列表里都不出现);它会出现在商店的「联动App」页,缺哪个模组写得明明白白;手机里「设置 → 关于」的联动模组列表也会自动带上它。内建的「传送石」和「浏览器」走的就是这条。
显示名要自己写死,别在运行时从 ModList 查——要显示它的那一刻,那个模组正好没装。
判断条件说不清时(比如还要看对方的版本)才去覆盖 isAvailable();它的默认实现就是按 requiredMods() 回答的。覆盖了它而不声明前置,「联动App」页就会与真实可用性对不上。
画在手机屏幕里:覆盖 IPhoneApp.openPage() 返回一个 IPhonePage,你的界面就长在手机里——状态栏、导航栏、壁纸、返回键都由 MCphone 管。详见接口文档第 2 节。
换浏览器后端:com.november.mcphone.feature.browser.client 下的 IBrowser / IBrowserBackend 是一层不含 MCEF、JCEF 类型的抽象,画面以 GL 纹理 id 交出来。想换成别的实现,在 MCphone 装上默认后端之前调 BrowserBackends.set() 即可;已经有人接管时再 set 会被拒绝并告警,不静默顶掉。注意它还在 feature 下,不受 api 包那五条兼容承诺保护,理由见接口文档第 7 节。
./gradlew build产物在 build/libs/。开发环境用 ./gradlew runClient 启动。
映射使用 Mojang 官方名 + Parchment,其许可见 https://github.com/NeoForged/NeoForm/blob/main/Mojang.md。
MIT。
仓库根目录的 TEMPLATE_LICENSE.txt 是另一回事:那是 NeoForged 给 MDK
模板文件的 MIT 声明,按其署名要求保留,与本模组自身的许可无关。