2082 字
10 分钟
内核课程 - 0:模组结构、编程语言和库

本文是ICMod开发教程的第0章,主要内容为模组结构编程语言,适用版本为InnerCore 2.x(在玩家社区中通常以Horizon指代),注意发布时间。

模组结构#

ICMod是以文件夹的形式放在对应InnerCore包下的innercore/mods文件夹,在发布时以ZIP格式的压缩包并通常使用.icmod作为文件后缀。

在ICMod中,许多文件的相对位置和名称都可以在build.config中指定,除了以下文件:

模组文件夹
│ build.config - 模组的结构信息和编译信息
│ config.json - 模组的配置文件
│ config.info.json - 模组配置文件的描述文件,包括本地化(翻译)和配置属性
│ mod.info - 模组信息
│ mod_icon.png - 模组图标
|
└─.dex - 已编译的模组源码

先简单说一下mod.info的内容,包含以下信息:

{
"name": "模组名称",
"author": "模组作者",
"version": "模组版本",
"description": "模组简介"
}

至于配置文件以后会单独讲解,这里先按下不表。

build.config中的内容可由模组作者自行修改,这里举个例子:

//EnergyTech(能源科技) - V4.0.2
//该build.config由官方的toolchain自动生成,在较新的模组中是相当典型的例子
{
"defaultConfig":{ //主要参数
"readme":"this build config is generated automatically by mod development toolchain",
"api":"CoreEngine", //指定API,还可以选择 AdaptedScript 但缺少了很多拓展功能,更贴近底层(Java层)
"buildType":"develop" //编译类型,通常建议在发布时更改为 release
},
"compile":[ //可执行文件,应 launcher 和 mod 源码类型各至少有一个
{
"path":"source/main.js",
"sourceType":"mod"
},
{
"path":"source/launcher.js",
"sourceType":"launcher"
},
{
"path":"library/BlockEngine.js",
"sourceType":"library"
},
{
"path":"library/ChargeItem.js",
"sourceType":"library"
},
{
"path":"library/SoundLib.js",
"sourceType":"library"
}
],
"resources":[ //资源文件,如模组贴图和GUI贴图
{
"path":"resources/res",
"resourceType":"resource"
},
{
"path":"gui/gui",
"resourceType":"gui"
}
],
"nativeDirs":[], //原生模组模块文件夹
"javaDirs":[], //Java模块文件夹
"buildDirs":[] //可行性构建文件夹
}

可以看到在compile中指定了很多js文件,其中mod类型即为模组的主要代码;launcher类型的文件只能有一个,用于决定模组是否启动;library类型为库,除了像这样一个一个添加之外还可以在defaultConfig中指定libraryDir,这样将会将该目录下的所有文件当作库并尝试执行;除了以上三种之外还有另外两种类型,分别是在模组加载之前执行的preloader和仅能在其他类型中使用runCustomSource方法调用的custom类型,值得注意的是这两种类型除了部分常量之外无法使用IC提供的API(虽然可以通过rhino调用,但不建议也没必要)。

resources用于指定资源文件,没什么好说的,只需要注意GUI纹理资源需要单独指定。

nativeDirsjavaDirs的用法见下文,此处单讲buildDirsbuildDirs允许开发者将可执行文件分散成多个文件以方便开发,举个简单的例子:

"buildDirs":[
{
"dir":"source/dev/",
"targetSource":"source/main.js"
}
]

dir指定了可执行文件存放的文件夹,此文件夹内必须有一个includes文件逐行记录了各个文件的相对位置,InnerCore将会按顺序合并为一个文件并覆写到targetSource指定的文件中。此过程并非简单的文本拼接,不可将完整的代码文件随意拆分为两个文件,否则可能会导致错误。

编程语言#

ICMod主要使用的编程语言是JavaScript,使用Rhino作为引擎,支持ES5和极少一部分ES6特性和调用Java,主要劣势为运行速度相当慢,与主流JS引擎相差百倍,但对于大多数情况都是够用的。这里推荐几个JS学习资源:

JavaScript编程精解,相当不错的入门书籍。

JavaScript | MDN,权威且全面,页面内还附有其他资源链接。

Rhino ES2015 Support,关于Rhino引擎对于ES6特性的实际支持情况。

但JS有一个特点,就是它是弱类型语言,对于一些更习惯强类型语言的开发者可以选择TypeScript,在官方的toolchain中就支持使用TS编写模组并在之后转译为JS供InnerCore使用。同样的,推荐一些教程:

官方文档,权威,最新。

TypeScript入门教程菜鸟教程,中文,方便阅读但不是最新版本。

除了使用JS/TS,ICMod还允许使用Java或者Native模块来拓展API,需要有Java和C/C++知识并了解一些Android SDK/NDK开发,此处不再提供相关资源链接,但应注意始终以对应版本的官方文档为准,善用搜索引擎查找问题以及在提问前明确问题。

#

库用于拓展API,可以是单个的JS文件也可以是以模块形式的DEX文件或者SO文件又或者是单独的模组,关于JS格式的库在模组中的存放位置已在前文叙述,这里仅将如何编写内容和使用。

JS库#

在库的开头,需要使用LIBRARY方法来描述库的基本信息:

LIBRARY({
name: "名称", // 库的唯一识别名称,不应再修改
version: 版本, // 库的版本,必须为大于0的整数,更高版本的库会覆盖更低版本的
shared: false, // 如果为 true 则库将作为全局API,即所有使用该库的模组共用相同的API对象,否则仅作用于单个模组
api: "CoreEngine", // 库使用的API
dependencies: ["name1:version1",
//...
] //在库中导入的依赖库列表
});

要导出库中的API,使用EXPORT方法:

EXPORT("API对象名称", API对象);
//EXPORT还支持指定导出API对象的最低库版本
//如加入在一个库中存在以下三条EXPORT
EXPORT("print", function () {
alert("new version print");
});
EXPORT("print:3", function () {
alert("older print");
});
EXPORT("print:1", function () {
alert("first version print");
});
//一般情况下,默认使用第一个print方法
//但当使用者指定的库版本>1且≤3时,将使用第二个print方法
//若指定的库版本为1,则使用第三个print方法

在模组中导入库时,使用IMPORT方法:

IMPORT("库名称"); //导入整个库
IMPORT("库名称", "API对象名称"); //导入特定的API
IMPORT("库名称:库版本"); //指定库版本

原生模块和Java模块#

nativeDirsjavaDirs中与buildDirs相似,只需要指定path即可,如下:

"nativeDirs": [
{
"path": "source/native/"
}
]

在指定的目录下必须有一个manifest文件来指定相关信息,对于原生模块,格式是这样的:

{
"shared": {
"name": "name", //此名称为编译后生成的so文件名称,如 libname.so
"include": [ //编译时包含的源码文件夹,可选
"shared_headers"
]
},
"depends": [ //在编译的时候依赖的内置库,可选
"innercore",
"nativejs"
]
}

同时需要一个main.cpp文件包含基本的代码,你可以在官方的toolchain中使用NDK对其进行编译,也可以在Horzion中使用gcc编译。

对于Java模块,manifest文件的格式是这样的:

//Toolchain示例模组
{
"source-dirs": ["src"], //源码文件夹
"library-dirs": ["lib"], //JAR库文件夹
"verbose": true, //是否开启verbose输出
"options": [], //verbose选项
"boot-classes": ["com.sample_mod.sample_package.Boot"] //要执行的类
}

Java编译可以在Toolchain中使用JDK完成,也可以在Horizon中完成。

要使用原生模块和Java模块,InnerCore提供了以下方法:

IMPORT_NATIVE(name, target); //获取原生模块方法并注入到指定对象中
WRAP_JAVA(name); //获取Java对象并返回
WRAP_NATIVE(name); //获取原生模块并返回

在Java模块中,得益于Rhino,所定义的Java类都可直接使用,而在原生模块中,需要nativejs.h头文件提供的方法才能创建可供JS使用的JS模块:

JS_MODULE_VERSION(模块名称, 1); //指定模块名称和版本
JS_EXPORT(模块名称, 方法名称, "I()", (JNIEnv* env) { //导出一个返回值为整型无参数的方法
//...
NativeJS::wrapIntegerResult(0); //返回 0
});

非常感谢您能读完本篇教程,因本人能力不足,对于教程中的疏漏与错误望各位指出斧正。

本系列教程将按照俄国社区先前编写的一份课程表格(谷歌文档)进行,下一篇将会简要讲解物品

内核课程 - 0:模组结构、编程语言和库
https://fuwari.vercel.app/posts/innercoretutorial-0/
作者
Sugar Breeze
发布于
2022-01-17
许可协议
CC BY-NC-SA 4.0