本文为RPG Maker MV所使用的基于JavaScript的Game Scripting System (JGSS) 插件入门资料。需要有一定的JS(JavaScript)基础知识。

创建插件文件
以下为我经常使用的插件模板文件。

//=============================================================================
// RTK_Test.js	2016/07/30
// The MIT License (MIT)
//=============================================================================

/*:
 * @plugindesc 测试用插件
 * @author Toshio Yamashita (yamachan)
 *
 * @help 本插件不包含插件指令。
 * 由于是测试用的文件,实际使用的时候请适当重命名
 */

(function(_global) {
  	// 这里写上插件处理
})(this);


下载点此->RTK_Test.js

下图为RPG Maker MV的插件管理页面。JS中的注释@plugindesc 和 @author 行会各自显示在相应的位置。

插件面板.jpg

开发工具与游戏变量的基础
游戏中经常使用的变量,会以$data开头。其作为游戏基本的数据,会通过RPG Maker MV自动生成,data文件以*.json的形式保存。这些文件会在游戏开始时读取,基本上是不会发生变更的静态数据,本指导中会将这些称之为「$data变量」,被保存的值则称为「静态数据」。

与其对应的以$game开头的变量,在游戏内被使用,被更新的动态数据,这些都会保存在存档中。在本指导中将其称为「$game变量」,被保存的值则称之为「动态数据」。

在RPG Maker MV中测试游戏的时候按下F8就会启动开发工具。开发工具中的控制台是非常便利的东西。

实际启动控制台,输入$data 后,以$data 开头的变量会提示出一堆。$game也同样。控制台中的提示辅助功能,对帮助理解游戏内的状态有很大的帮助。
plugin-dev-03.pngplugin-dev-02.png

关于$data变量的第一个$dataActors,让我们稍微看一下吧。在控制台上选择这个变量名,然后按下回车键显示内容。

$dataActors的长度为5,第一个元素为null,其他四个为Object。那么我们输入$dataActors[1] 来获取第一个Object。
plugin-dev-04.jpg
name.jpg

在$dataActors[1]中的name显示角色设定名为 "挑战者" 。其他还有各种设定,但因为是$data变量,这些基本都是在数据库中所设定的值。

最下面的_proto_是这个Object的父元素,但因为是Object,并没有设置上级的类,只是单纯用作数据的Object。

那么接下来看下$game变量的第一个$gameActors 。

展开_proto_,这次不是单纯的数据Object,有定义名为Game_Actors的类。

plugin-dev-05.png


Game_Actors类中,定义了一个名为actors的函数。在控制台上输入这个函数名后,就能看到这个函数的定义(源码文件)。右边显示了这个js文件的名称和行数,想要知道更详细的内容不妨点击对照下源码。

控制台是非常便利的工具,请务必活用。

稍微多讲点 - 关于数据的阶层

研究下RPG Maker MV所生成的游戏,$data静态的世界(变量的定义)和$game动态的世界相当好的分离了开来。

系统开发遵循了MVC(Model-View-Control)模型,个人觉得$data的世界是Model,$game的世界接近Contorol层。

比如之前的Game_Actors中有actors函数。一眼看过去好像是一个很便利的函数,但是我觉得有如下设计意图。

$game层与$data层分离

    $game Object只能知道最低限度的$data Object
    从$game Object角度出发,$data层是抽象化的

稍微有点难,再讲得琐碎点就是。

不用知道$game Object 在$game层是怎么被Actor管理的
    $game Object想要知道 Actor 的情报的话,往往会通过Game_Actors 

Game_Actors这个类,是掌管$game世界关于Actor的全部的存在。就是SPE(single point of entry) 。将$game世界和$data世界进行适当的分离。

所以作为插件开发者,会写操作 $game 这边的很多代码。


var anActor = $dataActors[id];
var anActor = $gameActors.actors(id);



想要参阅角色情报的话,下面那行代码可能更合适。

写一个简单的插件吧!

前文有点过长了呢。总之第一步,我们先做一个简单的插件吧。

因为前面提到了Actor,所以我们稍微调戏下名字吧。首先我们在控制台或者源文件的rpg_objects.js中找到定义的函数。


Game_Actor.prototype.name = function() {
    return this._name;
};



非常简单的内容呢。那么我们在名字后面加上ID吧。在先前介绍的插件文件中,写上插件处理。


(function(_global) {
  	// 这里写上插件处理
    Game_Actor.prototype.name = function() {
        return this._name + ":" + this.actorId();
    };
})(this);



指令等目前还没改写,总之先运行下。

游戏界面.jpg


是不是很简单?

防止与其他插件冲突
在先前的例子中,我们调戏了Actor的名字。这个功能也不是完全没用,至少在开发的时候能用来检查ID。

这个插件自己用大概是没什么问题。但是,在网上公开,或者给别人用还是算了。现阶段这个插件并没有考虑冲突问题,是一个会造成麻烦的插件。

设想有一个插件有着相同的构造,同样是一款影响角色名字的插件。于是,两个插件一起读取后,最终会显示哪个?结果大概是先读取的被后读取的覆盖,然后执行后读取的。

就像争夺场地一样。Game_Actor.prototype.name这个函数所定义的场地只有一个,所以定义就会覆盖,前面的定义消失,只有最后的定义生效。这就是最简单易懂的插件冲突例子。

解决这个问题非常简单,不要覆盖定义,追加就好了。就像下面的代码。


(function(_global) {
  	// 这里写上插件处理
    var _Game_Actor_name = Game_Actor.prototype.name;
    Game_Actor.prototype.name = function() {
        var ret = _Game_Actor_name.call(this);
        return ret + ":" + this.actorId();
    };
})(this);




准备_Game_Actor_name这个变量,将前面的定义先保存起来。然后运行自己的插件代码的时候,先将保存的定义(函数)执行。再基于执行后的值,将自己处理的,也就是在末尾追加“:”和ID执行。

这个时候,想实现的处理和原来的值并无冲突。如果是要实现「和原先定义无关,在名字的地方只显示ID」这样一个插件的话,只用按照如下来写。


(function(_global) {
  	// 这里写上插件处理
    var _Game_Actor_name = Game_Actor.prototype.name;
    Game_Actor.prototype.name = function() {
        var ret = _Game_Actor_name.call(this);
        return String(this.actorId());
    };
})(this);



即便是不使用作为返回值的ret,也要将原先的定义提出来是一个「很好的礼仪」。在这个世界中有许许多多的插件,有许多意想不到的理由来使用函数的情况。所以,这个值哪怕是不使用,将函数单独拎出来,就能够防止插件之间的冲突。

制作插件不能只考虑自己逻辑上的效率,无时无刻考虑避免影响其他插件的运作,是一个非常重要的意识。

然后这里所使用的call函数,具体请参阅 JavaScript 参考书什么的。但是希望有一点不要忘记,下面这样的函数所获取到的参数(这次是name)请不要忘记传递给原本的定义。


(function(_global) {
  	// 这里写上插件处理
    var _Game_Actor_setName = Game_Actor.prototype.setName;
    Game_Actor.prototype.setName = function(name) {
        var ret = _Game_Actor_setName.call(this, name);
        // 这里写上自己的处理代码
        return ret;
    };
})(this);



上面的例子中,如果有其他返回的值就原封不动返回。现在setName函数什么都没有返回,所以最后的retuen没有意义。但是将来版本升级可能会有什么值返回过来,或者有什么会返回过来的插件存在。所以附上比较安心。

稍微多讲点 - 局部作用域
如上面的例子定义了一个_Game_Actor_setName的变量,这个变量只会在这个插件中有效,并不会影响其他插件。所以只要不与这个插件中的名字冲突,基本不怕和其他插件冲突。

这就是一般的局部变量的概念,在JavaScript中简单的「在函数中定义的定义(变量等)就会成为只在函数内有效」
(function(_global) {
    // 这里写上插件处理
    // 这里定义的函数和变量只在这个范围内有效
})(this);

因为这是JavaScript相关的话题,只是简单说明下。具体请查阅相关书籍和网站来学习。

题外话,插件中对于保持函数定义的变量名,可以基于原本的函数定义

开头加上下划线(_)
中途的点或者.prototype.的部分改写成下划线(_)

有如上命名习惯的插件作者有很多,我也是。

查阅参数吧!
这次试下利用插件参数吧。在插件的注释部分,@author的下面追加下面的注释。

* @param 角色名后显示ID
* @desc 角色名后显示ID (0:OFF 1:ON)
* @default 1

于是插件管理界面就有如下的参数。

插件面板.jpg


如下图能够变更值。

参数.jpg


那么,看下这个设定值在插件端的样子。以下是我常用的书写方式。
(function(_global) {
    // 这里写上插件处理
    var N = 'RTK_Test';
  	var param = PluginManager.parameters(N);
  	var show_id = Number(param['角色名后面显示ID'])||1;

    // 以下略…
})(this);


关于PluginManager.parameters 函数,通过以下的定义理解起来会比较快。可以明白大小写并没有关系(toLowerCase())。


PluginManager.parameters = function(name) {
    return this._parameters[name.toLowerCase()] || {};
};



最初的插件的名字定义变量‘N’纯属是本人的个人习惯。但是插件的名字可能会在代码中多次使用,将来也可能会改变文件名字,推荐开始的时候以某种形式来定义会方便日后使用这个定义。

定义的面板值会进入show_id ,最后的 ||1 是默认值。如果使用者在插件参数里输入了非数字的值,那么通过 Number() 函数会返回 NaN 。这个情况下 ||1 会发挥作用将值替换成 1 。

||1  是 JavaScript方式的省略写法,实际运行结果和下面的if语句处理是差不多一样的。


  var show_id = Number(param['角色名后面显示ID']);
  if (show_id != 0) {
    show_id = 1;
  }



那么利用这个值在插件内部来处理 ON/OFF 吧。


(function(_global) {
    // 这里写上插件处理
    var N = 'RTK_Test';
  	var param = PluginManager.parameters(N);
  	var show_id = Number(param['角色名后面显示ID'])||1;

    var _Game_Actor_name = Game_Actor.prototype.name;
    Game_Actor.prototype.name = function() {
        var ret = _Game_Actor_name.call(this);
        if (show_id) {
          return ret + ":" + this.actorId();
        } else {
          return ret;          
        }
    };
})(this);



嗯,没问题能跑起来。

但是 show_id 的值从外部无法变更话,为了不给游戏多余的负担,个人喜欢如下的写法。


(function(_global) {
    // 这里写上插件处理
    var N = 'RTK_Test';
  	var param = PluginManager.parameters(N);
  	var show_id = Number(param['角色名后面显示ID'])||1;

    if (show_id) {
      var _Game_Actor_name = Game_Actor.prototype.name;
      Game_Actor.prototype.name = function() {
          var ret = _Game_Actor_name.call(this);
          return ret + ":" + this.actorId();
      };
    }
})(this);


感觉只是爱好问题。只要有「show_id 的值从外部不会变更」这个条件,就自问一开始函数的替换本身是需要的吗?

但是,从接下来的章节开始,「show_id的值会从外部被改变」,所以在后面的例子中并没有使用。只是单纯的让人知道还有这种写法,仅供参考。 

通过指令来改变运作吧!
插件参数是用于游戏开发者设定插件用的,所以在实际游戏中无法自由变更。如果要在游戏中变更的话,就需要使用插件指令。

指令的标准如下。
RTK_Test show_id on
RTK_Test show_id off


那么,就让我们试着处理指令部分吧。追加Game_Interpreter.prototype.pluginCommand函数,处理插件独自的指令。
var _Game_Interpreter_pluginCommand = Game_Interpreter.prototype.pluginCommand;
Game_Interpreter.prototype.pluginCommand = function(command, args) {
  _Game_Interpreter_pluginCommand.call(this, command, args);
  if (command == N) {
    if (args[0] == "show_id") {
      if (args[1] == "on") {
        show_id = 1;
      } else if (args[1] == "off") {
        show_id = 0;
      }
    }
  }
};


在pluginCommand函数中,最初的指令名是通过command参数传进来。因为很明显这个指令名是配合插件名的,所以这次也就配合事前定义的N来做比较确认。确认给自己的指令后,处理参数args,改变show_id变量的值。

这样子就能够通过事件中的插件指令,来控制开关游戏中ID的显示。

插件指令.jpg


总之完成了
姑且是将功能写出来了,这次就先这样吧!

一个给角色名字追加ID显示的简单插件。可以通过插件参数来设定最初是否显示,然后游戏中通过插件指令来开关显示。

但是有一个缺点就是状态无法保存。这个作为下次的课题来攻克吧!

以下就是这次所写的全部源代码。文件名和注释写好了的话,作为插件公开是没问题了吧。不过,是否真的需要这种插件就是另外个问题了...
//=============================================================================
// RTK_Test.js	2016/07/30
// The MIT License (MIT)
//=============================================================================

/*:
 * @plugindesc 测试用插件
 * @author Toshio Yamashita (yamachan)
 *
 * @param 角色名后面显示ID
 * @desc 角色名后面显示ID (0:OFF 1:ON)
 * @default 1
 *
 * @help 本插件包含插件指令。
 * 由于是测试用的文件,实际使用的时候请适当重命名
 */

(function(_global) {
	// 这里写上插件处理
	var N = 'RTK_Test';
	var param = PluginManager.parameters(N);
	var show_id = Number(param['角色名后面显示ID'])||1;

  var _Game_Actor_name = Game_Actor.prototype.name;
	Game_Actor.prototype.name = function() {
		var ret = _Game_Actor_name.call(this);
		if (show_id) {
			return ret + ":" + this.actorId();
		} else {
			return ret;          
		}
	};

	var _Game_Interpreter_pluginCommand = Game_Interpreter.prototype.pluginCommand;
	Game_Interpreter.prototype.pluginCommand = function(command, args) {
		_Game_Interpreter_pluginCommand.call(this, command, args);
		if (command == N) {
			if (args[0] == "show_id") {
				if (args[1] == "on") {
					show_id = 1;
				} else if (args[1] == "off") {
					show_id = 0;
				}
			}
		}
	};
})(this);


制作各种插件,尝试乐在其中。
那么,再会!

翻译自:https://github.com/yamachan/jgss-hack/blob/master/guide/plugin-dev-01.ja.md

上回:RPG Maker MV 插件制作入门(2)