跳到主要内容

解读官方插件

插件的写法当然有多种多样,但这里将以官方插件 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 中,遵循与参数相同的写法,使用注解来定义命令名和参数信息。

img

而实际的命令处理如下所示。

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 的作用域是其被定义的块内部,这也包括嵌套在其中的函数内部。

这是因为在定义函数时作用域就已经被决定(词法作用域)的规格所致。

先不管其原理,这一规格在制作插件时非常有用。

备注

letconst 的使用区分

范例中使用的变量 textPictureText 是用 let 定义的,而 pluginName 是用 const 定义的。

const 也和 let 一样用于声明变量,但与 let 不同的是,它不能重新赋值。

textPictureTextlet 定义,是因为想要重新赋值。

在核心脚本中,原则上使用 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 的内容,当判断为需要时执行绘制和销毁。

绘制和销毁分别由以下函数实现。

如果只是让程序正确运行,销毁并非必须,但图像对象占用内存较大,在不需要时将其销毁可以期待更稳定的运行。

  • createTextPictureBitmap
  • destroyTextPictureBitmap
备注

Sprite 与 Bitmap

之前我将 Sprite_Picture 说明为图片图像本身的类,但准确来说它更接近于存放图像的容器。

这个容器称为 Sprite

而图像本身的类称为 Bitmap

Sprite 拥有一个名为 bitmap 的属性,其中存储 Bitmap 的对象。

Sprite 保存图像的显示位置、放大率、色调等信息,而 Bitmap 只单纯保存每个 XY 坐标的颜色信息。