本文是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纹理资源需要单独指定。
nativeDirs和javaDirs的用法见下文,此处单讲buildDirs。buildDirs允许开发者将可执行文件分散成多个文件以方便开发,举个简单的例子:
"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对象的最低库版本//如加入在一个库中存在以下三条EXPORTEXPORT("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对象名称"); //导入特定的APIIMPORT("库名称:库版本"); //指定库版本原生模块和Java模块
在nativeDirs和javaDirs中与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});非常感谢您能读完本篇教程,因本人能力不足,对于教程中的疏漏与错误望各位指出斧正。
本系列教程将按照俄国社区先前编写的一份课程表格(谷歌文档)进行,下一篇将会简要讲解物品。