引擎架构:从 krkr.exe 到 KAG 标签的调用链
启动时发生了什么
用户双击 krkr.exe 后,引擎按以下顺序初始化:
- 加载核心 DLL:kirikiri.dll、krdevui.dll、plugins 目录下的 tpm 插件
- 确定项目目录:命令行参数 > 拖拽文件 > 同目录 startup.tjs 探测
- 执行 startup.tjs:这是你写的第一个脚本入口,用来初始化变量、加载资源、启动 KAG
- KAG 引擎接管:startup.tjs 通常只有一行
TJS.startup("main"),然后 KAG 开始解析 scenario
核心模块一览
┌─────────────────────────────────────┐
│ KAG (标签脚本引擎) │ ← 你写剧本的地方
│ scenario 文件 + [标签]语法 │
├─────────────────────────────────────┤
│ TJS2 (脚本解释器) │ ← 引擎的编程语言
│ 变量 / 函数 / 类 / 事件回调 │
├─────────────────────────────────────┤
│ Kirikiri 核心 (C++ 原生) │ ← 渲染、音频、文件 I/O
│ Layer / Image / Wave / Storage │
├─────────────────────────────────────┤
│ 插件系统 (.dll / .tpm) │ ← 第三方扩展(Live2D 等)
└─────────────────────────────────────┘
关键原生类
引擎内置了大量 C++ 实现的类,TJS2 脚本可以直接调用:
| 类名 | 职责 | 常用场景 |
|---|---|---|
Layer | 图层管理,所有立绘、背景、文字框都是 Layer | Layer.getCurrent()、Layer.blit() |
Image | 图片加载与操作 | Image.load("background.tlg") |
Wave | 音频播放(BGM/SE) | Wave.play("bgm/title.ogg") |
Storage | 文件系统、XP3 包读写 | Storage.openArchive("data.xp3") |
Script | TJS2 脚本动态执行 | 运行时载入额外脚本 |
Object | 通用对象容器 | KAG 内部大量使用 |
KAG 与 TJS2 的关系
一个常见误解:KAG 和 TJS2 是两套独立的东西。实际上:
- KAG 本身就是用 TJS2 写的。KAG 标签解析器是一个 TJS2 类。
- 你在 scenario 里写的每个
[标签]都会被 KAG 翻译成对应的 TJS2 方法调用。 - 反过来,scenario 文件里可以直接嵌入 TJS2 代码块(用
<% %>包裹),随时跳到底层。
这意味着:当 KAG 某个标签的行为不符合你的需求时,你可以直接修改 KAG 的 TJS2 源码(它就在 SDK 的 base 目录里)。这是吉里吉里2 最大的灵活性来源。
回调生命周期
引擎在运行过程中会触发一系列回调,你可以在 TJS2 里覆盖它们来注入自定义逻辑:
onFirstStart()— 游戏第一次启动onStart()— 每次启动(包括读档后)onExit()— 退出游戏onSave()/onLoad()— 存档/读档时机
线程模型
吉里吉里2 是单线程引擎。所有脚本执行、渲染、音频都在主线程上顺序完成。这简化了并发问题,但也意味着耗时操作(比如大文件加载)会阻塞渲染——需要用异步加载或多帧分配来避免卡顿。