我的世界基岩版JS Add-on开发规范详解
全面系统地梳理我的世界基岩版JavaScript/TypeScript Add-on开发规范,从Manifest清单配置、ESM模块架构、beforeEvents与afterEvents生命周期、Dynamic Properties数据持久化到调试与防御性容错,提供工业级标准指南。
一、工程目录与 Manifest.json 清单配置规范
在基岩版 Add-on 体系中,游戏逻辑的承载主体为行为包(Behavior Pack,简称 BP)。与旧版基于实验性服务端事件或客户端 UI 脚本不同,现代 Script API 脚本通过完全内嵌于行为包内的模块系统运行。遵守严格的目录层级和清单规范,是保证模组被游戏引擎正确识别与加载的前提。
1. 规范化行为包目录结构
一个标准的 JS/TS 行为包工程应严格保持职责单一,将开发源码与运行时最终输出物理隔离。项目根目录下必须包含 manifest.json 与 pack_icon.png,脚本逻辑统一由 scripts/ 目录承载:
- manifest.json:行为包唯一元数据清单,声明包名、UUID、最低引擎版本与模块依赖。
- scripts/:基岩版引擎直接执行的 JavaScript 运行时代生产物。核心单一入口文件必须固定命名为
main.js。 - src/:使用 TypeScript 时的源码工程目录。源码在发布或软链调试前,统一通过编译工具压制输出到
scripts/。 - entities/ 与 items/:存放原生行为包 JSON 定义文件。与 JS 脚本解耦,保持数据驱动与逻辑分离。
2. manifest.json 脚本模块与依赖项配置规则
在 manifest.json 中,脚本模块的声明与依赖关系必须满足基岩版引擎的硬性语法校验。以下是工业级标准的行为包清单配置示例:
"format_version": 2, "header": { "name": "CustomRPG Core Behavior Pack", "description": "Core gameplay script module", "uuid": "b1a2c3d4-e5f6-47a8-9b0c-1d2e3f4a5b6c", "version": [1, 0, 0], "min_engine_version": [1, 20, 80] }, "modules": [ { "type": "script", "language": "javascript", "uuid": "f7e8d9c0-b1a2-43c4-85d6-e7f8a9b0c1d2", "entry": "scripts/main.js", "version": [1, 0, 0] } ], "dependencies": [ { "module_name": "@minecraft/server", "version": "1.13.0" }, { "module_name": "@minecraft/server-ui", "version": "1.3.0" } ]
1. 模块 UUID 隔离:modules 数组内部的 UUID 必须与 header 中的 UUID 完全不同,且必须是合法的 UUID-v4 字符串。若两者相同,游戏引擎将直接拒绝载入该包。
2. 禁止依赖泛用通配符:严禁在 dependencies 中使用 "version": "1.0.0-beta" 等不稳定测试版本标签,必须锁定正式稳定版(如 1.13.0),确保在用户游戏版本升级时不会因 API 弃用引发大面积语法中断。
二、工程化构建与标准化开发流程
由于现代 Script API 拥有数以千计的实体、方块与事件类型定义,纯手写原生 JavaScript 极易因拼写或空指针在运行时抛出异常。因此,官方推荐基于 TypeScript 强类型系统组织代码,并通过自动化流水线输出。
1. 脚手架配置与 TypeScript 编译规范
在项目根目录下通过 npm 初始化项目并安装官方类型声明。由于基岩版运行环境内建了 QuickJS/V8 引擎,代码应编译为标准的 ES2020 模块规范:
{
"compilerOptions": {
"target": "ES2020",
"module": "ES2020",
"moduleResolution": "node",
"outDir": "./scripts",
"rootDir": "./src",
"strict": true,
"noImplicitAny": true,
"skipLibCheck": true
},
"include": ["src/**/*"]
}
2. 本地开发与热重载调试规约
开发期间严禁每次修改代码后重新压缩导入。应使用操作系统符号链接(Symbolic Link),将行为包工程目录直接链接到基岩版游戏私有数据目录下的开发专用文件夹:
| 操作系统平台 | 标准开发挂载路径 (Development Path) | 热重载与排障机制 |
|---|---|---|
| Windows 10 / 11 | %LocalAppData%\Packages\Microsoft.MinecraftUWP_8wekyb3d8bbwe\LocalState\games\com.mojang\development_behavior_packs\ |
在游戏内启用“开启内容日志文件记录”,脚本修改后在聊天框输入 /reload 即可重载执行环境,无需重启客户端。 |
| Dedicated Server (BDS) | ./development_behavior_packs/ |
控制台直接键入 reload,或配置自动文件监听守护进程。 |
三、事件生命周期契约与任务调度机制
基岩版 Script API 的核心架构建立在“只读拦截”与“读写分离”的时序契约之上。理解每个单 Tick 内事件的流转时序,是编写无崩溃、无内存泄漏模组的技术关键。
1. beforeEvents 与 afterEvents 边界契约
官方事件系统明确划分为两个命名空间:world.beforeEvents 和 world.afterEvents。这两者在执行时机与操作权限上有着不可违背的硬性界限。
| 事件层级 | 执行时机 | 允许的操作 | 绝对禁止的操作 (违反抛运行时异常) |
|---|---|---|---|
| world.beforeEvents (前置拦截层) |
物理引擎计算或原生动作落实之前 |
• 读取事件上下文(坐标、发起玩家、手持物) • 拦截取消事件: event.cancel = true• 修改事件目标属性(如改变破坏方块产物) |
❌ 严禁生成实体 (spawnEntity)❌ 严禁修改方块、增减物品或施加药水效果 ❌ 严禁调用 show() 弹出 UI 表单
|
| world.afterEvents (后置业务层) |
动作已经在游戏世界中完全落实 |
• 读写世界状态:增删实体、放置方块 • 粒子与音效播放、玩家传送 • 读写 Dynamic Properties 属性 • 弹出 UI 窗口 ( ActionFormData)
|
❌ 无法取消事件(因为动作已经生效) ❌ 避免同步执行阻塞性死循环或复杂耗时运算 |
2. 跨时序延迟处理标准范式
很多业务场景需要在拦截事件的同时触发世界状态变更(例如:玩家在领地内破坏方块被拦截,同时需要对其施加虚弱状态并发送浮字提醒)。由于 beforeEvents 处于只读上下文,直接修改玩家状态会立刻崩溃。规范的解法是使用 system.run 挂载延后微任务:
import { world, system } from "@minecraft/server"; // 规范实践:beforeEvents 仅执行检查与拦截,写操作延迟到下个调度微任务 world.beforeEvents.playerBreakBlock.subscribe((event) => { const { player, block } = event; if (isProtectedArea(block.dimension.id, block.location)) { // 1. 立即阻断原生破坏动作 event.cancel = true; // 2. 安全推入 system.run 读写安全队列 system.run(() => { if (player.isValid()) { player.sendMessage("§c[领地保护] 该区域已被锁定,无权破坏方块!"); player.addEffect("weakness", 60, { amplifier: 1 }); } }); } });
3. 任务调度必须使用 system 模块
在基岩版脚本环境中,严格禁止使用浏览器或 Node.js 原生的 setTimeout 与 setInterval。游戏运行在一个由 Tick(每秒 20 次,1 Tick = 50ms)驱动的确定性主循环中。必须严格使用官方提供的 system 调度器:
system.run(callback):在当前 Tick 末尾或下个 Tick 安全执行一次。system.runTimeout(callback, delayTicks):延迟指定 Tick 数后执行。system.runInterval(callback, intervalTicks):每隔固定 Tick 重复执行。
调用 system.runInterval 会返回一个数字型任务句柄(runId)。若针对特定实体或玩家创建了周期性监测任务,当实体死亡、玩家下线或玩法结束时,必须显式调用 system.clearRun(runId)。否则该定时任务将永久在后台常驻空转,持续消耗服务器 CPU 算力并造成内存恶性泄漏!
四、状态管理与 Dynamic Properties 存储规范
现代基岩版 Add-on 已彻底淘汰旧版通过无形盔甲架实体标签(Tag)或虚拟计分板存储复杂数据的反模式做法。官方原生引入了动态属性(Dynamic Properties),直接与实体、物品或整个世界关联,并随存档自动序列化保存。
1. 作用域划分与选择准则
- World Dynamic Properties:绑定在世界全局,用于存储服务器全局配置、公会列表、副本通关记录等。调用
world.setDynamicProperty(key, value)。 - Entity Dynamic Properties:绑定在独立实体/玩家对象上,随实体存亡而持久化。用于记录玩家法力值、职业等级、技能冷却状态。调用
entity.setDynamicProperty(key, value)。 - ItemStack Dynamic Properties:绑定在具体物品实例上(相当于自定义 NBT),物品丢弃或转移给他人后属性仍然附着。调用
itemStack.setDynamicProperty(key, value)。
2. 命名空间强制前缀与体积控制
所有 Dynamic Property 键名必须遵循统一的命名空间前缀标准:namespace:property_name。
import { Player } from "@minecraft/server"; // 规范:统一声明常量键名,包含模组前缀 const PROP_MANA = "rpg_core:current_mana"; const PROP_CLASS = "rpg_core:player_class"; export function setPlayerMana(player: Player, mana: number): void { if (!player.isValid()) return; // 写入浮点或整型数值 player.setDynamicProperty(PROP_MANA, Math.max(0, mana)); } export function getPlayerMana(player: Player): number { if (!player.isValid()) return 0; return (player.getDynamicProperty(PROP_MANA) as number) ?? 100; }
基岩版引擎对单个字符串类型的 Dynamic Property 施加了 32,767 字符(约 32KB) 的硬上限。如果需要存储复合 JSON 结构,建议剔除所有不必要的冗余键名或采用紧凑数组编码,并在存储前进行体积长度校验,防止抛出超出容量限制的异常。
五、防御性容错、日志系统与生产打包
1. 实体生命周期有效性检查(isValid 规范)
基岩版是高度动态的多人沙盒环境。实体可能由于承受致死伤害、指令清除(/kill)、跨维度传送或远离视距(区块被卸载)而瞬间在引擎底层被释放。在 JavaScript 引用层面,该实体对象指针虽然未被垃圾回收,但其实体在 C++ 底层已被销毁。
因此,在任何延迟任务、Promise 异步回调或跨帧处理中,访问实体的任何属性或方法之前,必须显式调用 entity.isValid()。若实体无效,直接中断后续逻辑,避免引发致命的空指针解引用崩溃。
2. 统一日志规范与防刷屏限频
不要在业务代码中随意散落 console.log。应封装单例 Logger,根据环境开关日志级别。尤其禁止在每 Tick 运行的循环中高频输出日志,避免快速膨胀 content_log.txt 造成磁盘 I/O 阻塞并导致游戏整体掉帧。
export class Logger { private static readonly PREFIX = "[CustomRPG]"; public static isDebugEnabled = false; public static debug(msg: string): void { if (this.isDebugEnabled) { console.warn(`${this.PREFIX}[DEBUG] ${msg}`); } } public static info(msg: string): void { console.warn(`${this.PREFIX}[INFO] ${msg}`); } public static error(msg: string, err?: unknown): void { console.error(`${this.PREFIX}[ERROR] ${msg} | ${err instanceof Error ? err.stack : err}`); } }
3. 生产包打包与发布交付规约
交付最终玩家或上架服务器前,必须经过标准构建流水线处理,严禁将整个开发工作区直接打成压缩包。生产发布必须满足以下四项交付指标:
- 剔除非运行环境文件:移除
src/、node_modules/、tsconfig.json、.git/等仅在构建期有效的文件,产物包仅保留运行所需文件。 - 关闭调试开关:确保
isDebugEnabled = false,清理一切开发指令与测试钩子。 - 语义化递增版本:升级
manifest.json中的version: [x, y, z],基岩版客户端依赖此版本号判断是否覆盖更新已存在的模组缓存。 - 标准化文件封装:将行为包所有文件直接使用 ZIP 格式压缩,并将文件后缀名由
.zip改为.mcpack(单一行为包)或配合资源包合并为.mcaddon(全套扩展包),以便多端双击一键导入。
JS处理含Emoji或中文的字符串转数组乱码问题
2026-11-05 16:12:28
JS怎么使用展开运算符合并对象
2026-11-05 14:42:18
ES6+环境下判断JS对象为空的简洁写法
2026-11-05 12:12:23
怎么通过replacer函数自定义JS中JSON的格式化输出
2026-11-05 11:19:44
JS正则表达式基础:字面量与RegExp构造函数的区别及常用元字符
2026-11-05 11:16:58
相关标签:








