项目脚本
项目脚本是在 pyproject.toml 中定义的有限时宿主机任务,可以在构建、游戏 ZIP 打包或本地世界启动前执行,例如把策划表生成 JSON。任务运行在工具所在的电脑上,不在游戏 Python 环境中,也不会传到远程 Windows 服务端执行。
先确认 CLI 支持该功能:
mcpy script --helpmcpy --local doctor --capabilities --jsonlocal_capabilities 中包含 project-scripts 时可用。不能只凭版本号判断,旧 CLI 可能直接忽略新配置。
然后在 pyproject.toml 中定义任务和触发时机:
[tool.mcpywrap.scripts.assets]python = "tools/assets.py"args = ["--quality", "development"]env = { ASSET_MODE = "development" }timeout = 120
[tool.mcpywrap.scripts.check]cmd = ["node", "tools/check.mjs"]timeout = 30
[tool.mcpywrap.hooks]before_build = ["assets"]before_package = ["assets", "check"]before_run = ["assets"]这是项目工具配置,与 [project.scripts] 的 Python 安装入口无关。
每个任务在 python 与 cmd 中二选一:
| 字段 | 说明 |
|---|---|
python |
项目根目录内的相对脚本路径,用 mcpy 自己的 Python 解释器执行 |
args |
配合 python 使用的固定参数数组 |
cmd |
完整 argv 数组,含程序名和参数;不支持另加 args |
env |
覆盖继承的环境变量,键值必须是字符串 |
timeout |
秒数,默认 120;须大于 0 且不超过 86400 |
补充说明:
- uv tool 安装的 mcpy 使用隔离的工具环境,不会自动读取项目
.venv。需要 Pillow 等库时,先在选定的工具环境中准备好,或用cmd明确调用其他已经准备好的环境。 cmd的程序名从继承的 PATH 查找;带路径的程序相对项目根解析。- 工作目录始终是
--project指向的项目根,不受终端当前目录影响。 env不能覆盖工具自己的MCPY_*/_MCPY_*字段。- 每个任务分别计时,列表中的任务串行运行。失败、超时或取消会立即中断后续任务和本次构建或启动。
mcpy --project <项目> script list --jsonmcpy --project <项目> script run --json assets -- --quality releaselist只读,不执行任务,也不创建日志。run只执行指定任务,不触发任何生命周期。--之后的参数原样追加到固定 argv。子脚本的--json/--help不影响父命令的输出模式。
自动执行时机
Section titled “自动执行时机”| 操作 | 执行 |
|---|---|
build |
before_build 一次,然后组装 |
package |
before_package 一次,然后组装并压缩;不额外运行 before_build |
dev |
首次构建前 before_build 一次;后续文件事件不重复执行 |
本地 run、Qt 启动、单会话 restart |
真正准备新游戏进程时 before_run 一次,在部署指纹和资源组装前 |
| 本地主客机组 | 主机准备时一次;补齐或重试客机不执行 |
| 接管已运行世界、查询、GUI 依赖刷新 | 不执行 |
runtime reload / watch |
不执行 |
| 网络 run/connect、远程 run | 不执行本地启动钩子 |
| add/remove/sync、edit、publish | 不执行 |
同一事件按数组顺序运行。无变化的新启动仍会执行 before_run,若生成的包内容相同则复用现有部署。接管已有游戏不会执行生成。
hooks 只执行当前根项目的任务,不递归执行依赖的 hooks。依赖可以另外声明资源准备入口,见 依赖的资源准备。
原始素材和宿主代码放在包外,只有业务输出放在包内。脚本可以读取这些环境变量:
| 变量 | 含义 |
|---|---|
MCPY_PROJECT_ROOT |
项目根目录 |
MCPY_EVENT |
manual 或具体的 hook 名 |
MCPY_OPERATION |
script / build / package / run |
脚本必须能独立完成:stdin 已关闭,不能等待输入、启动脱离管理的后台服务,也不能当作常驻 watch。需要持续生成时,直接在终端运行生成器自己的 watch,例如 python tools/assets.py --watch。脚本内不应递归调用 mcpy 的构建、启动或依赖写操作,只读查询可以使用。
示例:data/items.csv 生成到行为包中的 JSON,先完成生成与校验,再原子替换最终文件:
import csvimport jsonimport osfrom pathlib import Pathimport tempfile
root = Path(os.environ["MCPY_PROJECT_ROOT"])with (root / "data/items.csv").open(encoding="utf-8", newline="") as stream: rows = list(csv.DictReader(stream))items = {row["id"]: int(row["value"]) for row in rows}if len(items) != len(rows): raise ValueError("重复的物品 ID")
# 临时文件不放进游戏包target = root / "behavior_pack/MyMod/generated/items.json"target.parent.mkdir(parents=True, exist_ok=True)scratch = root / ".mcpy/asset-temp"scratch.mkdir(parents=True, exist_ok=True)fd, name = tempfile.mkstemp(dir=scratch, suffix=".json")try: with os.fdopen(fd, "w", encoding="utf-8", newline="\n") as stream: json.dump(items, stream, ensure_ascii=False, sort_keys=True) stream.write("\n") os.replace(name, target)finally: Path(name).unlink(missing_ok=True)对应任务只需 python = "tools/assets.py";除非脚本实现了参数解析,否则不要照抄上文的 --quality 参数。生成多个文件时,由脚本负责删除不再需要的旧输出。生成的游戏业务代码仍须兼容目标引擎,不能依赖临时调试用的 mcpy.*。
每次任务的日志和结果位于 .mcpy/scripts/<操作 ID>/output.log 和 result.json。JSON 输出中包含脚本记录或 script / script_log / script_exit_code 等失败详情;任务输出送往 stderr。Qt 在准备阶段显示任务,并提供“取消准备”。
Ctrl+C、任务超时或 stop 请求会取消任务并回收子进程。如果无法确认进程已经结束,会报告 script_recovery_required,此时查看 script_state 中的进程身份和日志,不要按进程名批量杀进程。
- 项目和包的骨架必须已存在。脚本用于生成包内内容,不用于修改 pyproject、依赖锁、manifest、包身份或世界配置;执行期间这些配置变化会使本次操作失败。
- 不要让脚本写入
.mcpy/runtime、.runtime、依赖缓存、全局游戏包或世界数据库。 - 首版不提供输入图、产物挂载、任务缓存或目标引擎推断;需要固定引擎格式时由项目明确传入参数。
- 合作锁只避免工具发起的同项目并发写入,不是文件系统沙箱。