解读官方插件
插件的写法当然有多种多样,但这里将以官方插件 TextPicture.js 为范例,讲解一般插件的写法。
将整个插件用函数包裹
查看范例的实现,可以发现除了注释之外,几乎所有的处理都包含在下面的写法中。
(() => {
// 所有的处理都写在这里
})();
虽然括号很多看起来很复杂,但做的事情只是“定义函数并立即执行”。
明明要立即执行却特意创建函数来写处理,有以下两个原因。
- 防止插件中定义的变量影响其他插件
- 能够在函数内使用
use strict
如前一章所述,在函数外部声明变量,该变量会成为“全局变量”。
全局变量可以被包括其他插件在内的所有地方引用,因此如果多个插件定义了相同的全局变量,就会导致冲突。
基本原则是:除了真正必要的变量之外,不要随意定义全局变量。
通过在函数内部定义变量,可以将变量的作用域(有效范围)限定在插件内部。
关于 use strict,范例的官方插件中并没有包含,但建议写上,因为它可以事先提醒那些容易导致 bug 的写法
用法有点特殊,如下所示,在函数开头只定义字符串 'use strict'。
(() => {
'use strict';
// 执行某些处理
})();
例如,如果不声明变量就直接使用,它会被当作全局变量处理,但如果 use strict 生效,则会报错并停止执行。
帮助注释的写法
接下来看范例的注释部分。
在 rmmz 的插件中,以 /*: 开头的部分被视为特殊注释。
注释中应该能看到一些以 @ 开头的描述,这些会被 rmmz 编辑器读取并解析为帮助信息。
这称为“注解”。
/*:
* @target MZ
* ....
*/
注解有很多种类,将在下一章中讲解。
重新定义现有方法
终于要进入实现部分的讲解了。
之前已经讲解过,核心脚本是庞大函数的集合。
插件通过“重新定义”核心脚本所定义的函数,来改变 rmmz 的游戏行为。
以下是重新定义的基本写法。
const _Game_Picture_show = Game_Picture.prototype.show;
Game_Picture.prototype.show = function() {
_Game_Picture_show.apply(this, arguments);
// 执行某些追加处理
};
首先,在第 1 行声明变量 _Game_Picture_show,并将核心脚本的函数 Game_Picture.prototype.show 赋值给它。
然后在第 2 行重新定义函数 Game_Picture.prototype.show。
最后,在第 3 行执行赋值给变量 _Game_Picture_show 的函数。
apply 是用于执行赋值给变量的函数的方法。
这一系列处理所做的是:在调用核心脚本函数 Game_Picture.prototype.show 的原始处理之后,再添加插件作者想要追加的新处理。
插件就是这样通过反复重新定义函数来制作的。
插件就是这样通过反复重新定义函数来制作的。
用于调用函数的函数
刚才讲解 apply 这个函数时,您可能会想:“执行函数不是用小括号吗?”确实,用 _Game_Picture_show() 也可以执行,但由于某些原因,这样会导致错误。
错误发生的原因需要对面向对象和 js 有充分的理解,很遗憾在此省略。
插件命令的定义
rmmz 中插件命令的规格已经被刷新。
在 MV 中,命令名和参数都是直接输入的,而在 rmmz 中,遵循与参数相同的写法,使用注解来定义命令名和参数信息。

而实际的命令处理如下所示。
PluginManager.registerCommand("插件名", "命令名", args => {
// 在这里写命令执行时的处理
});
第一个参数指定插件名,第二个参数指定命令名,第三个参数指定命令执行时被调用的函数。
第三个参数的函数中的参数 args 会以对象形式存储调用命令时指定的参数。
前作 MV 中是以数组形式传递的,这一点也发生了变化。
在范例中,如下使用。
const pluginName = "TextPicture";
let textPictureText = "";
PluginManager.registerCommand(pluginName, "set", args => {
textPictureText = String(args.text);
});
如果要调用 Game_Interpreter 的方法,请不要使用箭头函数,而是定义普通函数,并加上 this 来调用。
PluginManager.registerCommand("插件名", "命令名", function(args) {
this.character(0);
});
关于闭包
在范例的命令处理中,改写了定义在函数外部的变量 textPictureText 的值。这看起来有些奇怪,但它是通过“闭包”机制实现的。
textPictureText 的作用域是其被定义的块内部,这也包括嵌套在其中的函数内部。
这是因为在定义函数时作用域就已经被决定(词法作用域)的规格所致。
先不管其原理,这一规格在制作插件时非常有用。
let 和 const 的使用区分
范例中使用的变量 textPictureText 是用 let 定义的,而 pluginName 是用 const 定义的。
const 也和 let 一样用于声明变量,但与 let 不同的是,它不能重新赋值。
textPictureText 用 let 定义,是因为想要重新赋值。
在核心脚本中,原则上使用 const,只有在需要重新赋值时才使用 let。
通过这样适当区分使用,可以更容易传达代码作者的意图。
对象的生成
在函数 createTextPictureBitmap 中,有如下描述。
const tempWindow = new Window_Base(new Rectangle());
这个 new 的写法用于生成对象。
但之前我们曾说明过,对象的生成可以如下写法。
const tempWindow = {};
用 {} 创建会生成一个空的对象。
而使用 new 生成对象,简单来说就是“按照预先决定好的设计图,在属性和函数已被定义的状态下”生成对象。
在上述例子中,由于指定了 Window_Base 作为设计图,因此 tempWindow 从一开始就具备作为窗口的功能而被生成。
我们曾说明核心脚本是函数的集合,但实际上也许说它是这些设计图的集合更为恰当。
核心脚本中也频繁使用 new,请在此记住它的用法。
这个设计图在面向对象中被称为“类”。
具体的实现讲解
讲解到这里,终于可以讲解 TextPicture.js 的具体实现了。
这个插件的作用是将插件命令中指定的字符串作为图片显示。
那么,下面讲解它是如何实现的。
首先是插件命令的实现。
这里在执行名为 set 的命令时,将想要作为图片绘制的字符串保存到变量 textPictureText 中。
let textPictureText = "";
PluginManager.registerCommand(pluginName, "set", args => {
textPictureText = String(args.text);
});
接着,添加到图片显示处理中。
显示图片的方法是 Game_Picture.prototype.show。
即使不知道具体实现,大概也能从名字上理解。
const _Game_Picture_show = Game_Picture.prototype.show;
Game_Picture.prototype.show = function() {
_Game_Picture_show.apply(this, arguments);
if (this._name === "" && textPictureText) {
this.mzkp_text = textPictureText;
this.mzkp_textChanged = true;
textPictureText = "";
}
};
这里追加的处理是:当图片名为空(图像指定为“无”)且插件命令已执行时,保存绘制的字符串并设置标志。
要点是“在此时还没有进行绘制”。
因为在核心脚本中,保持图片状态的类(Game_Picture)和图片图像本身的类(Sprite_Picture)是分开存在的。
在图片图像本身的类中,每一帧都会监视 Game_Picture 的状态。
不仅限于这个类,名为 update 的方法很可能是每帧执行以确认和更新状态的方法。
const _Sprite_Picture_updateBitmap = Sprite_Picture.prototype.updateBitmap;
Sprite_Picture.prototype.updateBitmap = function() {
_Sprite_Picture_updateBitmap.apply(this, arguments);
if (this.visible && this._pictureName === "") {
// 判断是否需要绘制的处理
} else {
this.mzkp_text = "";
}
};
实际的判断处理稍长,请确认代码。
简单说明实现的话,就是监视 Game_Picture 的内容,当判断为需要时执行绘制和销毁。
绘制和销毁分别由以下函数实现。
如果只是让程序正确运行,销毁并非必须,但图像对象占用内存较大,在不需要时将其销毁可以期待更稳定的运行。
createTextPictureBitmapdestroyTextPictureBitmap
Sprite 与 Bitmap
之前我将 Sprite_Picture 说明为图片图像本身的类,但准确来说它更接近于存放图像的容器。
这个容器称为 Sprite。
而图像本身的类称为 Bitmap。
Sprite 拥有一个名为 bitmap 的属性,其中存储 Bitmap 的对象。
Sprite 保存图像的显示位置、放大率、色调等信息,而 Bitmap 只单纯保存每个 XY 坐标的颜色信息。