上回的制作入门(1)中,我们制作了一个在角色名字后面追加ID显示的简单插件。可以通过插件参数设定初始显示,然后通过插件指令来切换显示。

这次也继续上回,拓展这个插件。

将状态保存到存档文件中
上次制作的插件虽然准备了插件指令,但有一个缺点。就是无法保存在存档中,就算通过指令开关,只要游戏重启又会回到初始状态。

因此这次,要将show_id的值保存到存档中,这样就算重启游戏也能维持状态。

3种存档文件
RPG Maker MV所生成的游戏中,有以下三种存档文件。
    config.rpgsave
    file*.rpgsave
    global.rpgsave

'config.rpgsave'是为了保存游戏设定的文件。标题画面以及游戏菜单的设置状态保存在这里。比如下面的设置画面。
plugin-dev-02-01.jpg
被保存的数据如下。
{
  "alwaysDash":false,
  "commandRemember":false,
  "bgmVolume":100,
  "bgsVolume":100,
  "meVolume":100,
  "seVolume":100,
}


'file*.rpgsave'作为实际的保存文件,保存着游戏各种状态。存档文件可以生成数个,*的部分为连号。这个文件所储存的内容将会在下一章说明。

'global.rpgsave' 是储存在存档选择画面中所使用的数据,在存档生成和更新时更新。比如对应下面的存档选择画面。
plugin-dev-02-02.jpg
所保存的数据如下。
[
  null,
  {
    "globalId":"RPGMV",
    "title":"RTK1 Dev1",
    "characters":[["Actor1",0],["Actor1",7],["Actor3",7],["Actor2",6]],
    "faces":[["Actor1",0],["Actor1",7],["Actor3",7],["Actor2",6]],
    "playtime":"00:00:04",
    "timestamp":1468556954489
  },
  {
    "globalId":"RPGMV",
    "title":"RTK1 Dev1",
    "characters":[["Actor1",0],["Actor1",7],["Actor3",7],["Actor2",6]],
    "faces":[["Actor1",0],["Actor1",7],["Actor3",7],["Actor2",6]],
    "playtime":"00:00:48",
    "timestamp":1468855189632
  }
]


游戏存档的文件内容
我们知道'file*.rpgsave' 是实际的存档文件,那么是否所有内容都存在其中?那么省略一部分,来粗略介绍下主要部分吧。
{
	"system":{
		"_saveEnabled":true,
		"_menuEnabled":true,
		"_encounterEnabled":true,
		"_formationEnabled":true,
		"_battleCount":0,
		"_winCount":0,
		// 中间省略
		"@":"Game_System"
	},
	"screen":{
		"_brightness":255,
		"_fadeOutDuration":0,
		"_fadeInDuration":0,
		"_tone":[0,0,0,0],
		// 中间省略
		"@":"Game_Screen"
	},
	"timer":{"_frames":0,"_working":false,"@":"Game_Timer"},
	"switches":{"_data":[null,null,null,null,null,null,null,null,true],"@":"Game_Switches"},
	"variables":{"_data":[],"@":"Game_Variables"},
	"selfSwitches":{"_data":{"1,3,A":true},"@":"Game_SelfSwitches"},
	"actors":{
		"_data":[
			null,
			{
				"_actorId":1,
				"_name":"Harold",
				"_nickname":"Sword boy",
				"_hp":450,
				"_mp":90,
				"_tp":0,
				"_hidden":false,
				"_paramPlus":[0,0,0,0,0,0,0,0],
				// 中间省略
				"@":"Game_Actor"
			},{
				"_actorId":2,
				// 以下省略
			},{
				"_actorId":3,
				// 以下省略
			{
				"_actorId":4,
				// 以下省略
			}
		],
		"@":"Game_Actors"
	},
	"party":{
		"_inBattle":false,
		"_gold":2000,
		"_steps":0,
		"_lastItem":{"_dataClass":"","_itemId":0,"@":"Game_Item"},
		"_menuActorId":0,
		"_targetActorId":0,
		"_actors":[1,2,3,4],
		"_items":{"1":6,"2":1,"10":50,"11":2,"12":2,"13":2,"14":2},
		"_weapons":{"1":1,"2":2,"4":10},
		"_armors":{"3":1,"4":1},
		"@":"Game_Party"
	},
	"map":{
		"_interpreter":{
			"_depth":0,
			"_mapId":0,
			"_eventId":0,
      // 中间省略
			"@":"Game_Interpreter"
		},
		"_mapId":1,
		"_tilesetId":1,
		"_events":[
			null,
			{
				"_x":4,
				"_y":1,
				// 中间省略
				"@":"Game_Event"
			},
			// 以下省略
		],
		"_commonEvents":[],
		// 中间省略
		"@":"Game_Map"
	},
	"player":{
		"_x":4,
		"_y":4,
		// 中间省略
		"_followers":{
			"_visible":true,
			"_gathering":false,
			"_data":[ // 省略
			],"
			@":"Game_Followers"
		},
		"_encounterCount":603,
		"@":"Game_Player"
	}
}


在上回制作入门(1)中,介绍过$game变量一览。上述游戏存档的各种元素,就是对应$game 变量的。

也就是说可以理解成游戏的存档文件就是将$game变量的值保存在里面。

利用哪个变量呢
这次又说了很长的前言呢...

既然知道这些 $game 变量被保存在存档里的话,就让我们利用吧!只要从中选择一个值来储存的话,就能任凭系统去读取保存了。

个人经常使用的是 $gameSystem ,所以这次也用这个吧。

关于保存时所使用的名字,需要考虑是否会和其他插件重名。这里推荐名称为,插件名或者名字前面追加数据种类。

实际处理
首先将使用 show_id 的部分进行拓展。
var _Game_Actor_name = Game_Actor.prototype.name;
Game_Actor.prototype.name = function() {
    var ret = _Game_Actor_name.call(this);
    var f = $gameSystem[N + "_show_id"];
    if (f === undefined ? show_id : f) {
      return ret + ":" + this.actorId();
    } else {
      return ret;          
    }
};


变更尽量少,一行就够了,仅仅将if的条件改变了而已。

增加的1行是将 $gameSystem 保存的值读取到f变量中处理。名称按照惯例为N定义的插件名加上"_show_id" 作为储存的变量名就行了。

然后if语句的判断,如果保存的值为空则 f 为 undefined ,所以作为替换将使用前面的 show_id 。如果有保存的值则使用保存的值。

使用保存值的部分是OK了,将值保存起来的处理还没有呢。就修正插件指令的部分吧。
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") {
        $gameSystem[N + "_show_id"] = 1;
      } else if (args[1] == "off") {
        $gameSystem[N + "_show_id"] = 0;
      }
    }
  }
};


嗯,这里的修正也不多呢。只是将$gameSystem设定的值替代掉show_id而已。

然后,这样就完成了对应存档了。补上一行,修改三行。仅此而已。

修正前是如下的样子
    show_id 的初始值由插件参数指定
    通过插件指令变更show_id的值
    show_id的值无法保存

修正后效果如下
    show_id的值由插件参数指定,以后不可以变更
    通过插件指令改变$gameSystem.RTK_Test_show_id的值
    实际执行的时候使用$gameSystem.RTK_Test_show_id的值
        但是如果找不到的话就使用show_id的值

show_id的变量从主角变成了作为初始值的配角。作为替代使用了 $gameSystem.RTK_Test_show_id 的变量,因为包含在 $gameSystem 中,所以会被存档保存。

这样的话有一个好处,只要不使用插件指令的话就不会消费存档文件的空间。

稍微下点功夫吧
在前面的章节中我们能保存了。作为目的应该足够了。这个章节作为附赠,稍微下点小功夫。

现在所使用的结构里,show_id的变量反应在插件参数里吧。也就是说是在游戏开发的时候设定的值,实际游戏发布后就无法变更了。

以此为前提,为了更加节约存档文件空间,我们将代码变成这样。
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") {
        if (show_id) {
          delete $gameSystem[N + "_show_id"];
        } else {
          $gameSystem[N + "_show_id"] = 1;          
        }
      } else if (args[1] == "off") {
        if (!show_id) {
          delete $gameSystem[N + "_show_id"];
        } else {
          $gameSystem[N + "_show_id"] = 0;
        }
      }
    }
  }
};


可能稍微有点复杂呢。这串代码的概念如下。
    如果和初始值相同的话就没有必要储存起来。
       不需要的值用delete删除掉

这次的值只有一个,所以可能并不需要特地去删除掉。但是「良好礼节挂心中的代码」是很重要的,要经常有这样的意识,所以稍微下了点功夫介绍了下例子。

想要认真处理储存的情况
这次的例子里,利用$game的变量难免有点偷工。这里说下如何好好处理。

首先保存的时候,扩张以下存档生成处理比较好。
var _DataManager_makeSaveContents = DataManager.makeSaveContents;
DataManager.makeSaveContents = function() {
  var contents = _DataManager_makeSaveContents.call(this);
  // 这里将自己的数据设定成contents
  return contents;
};


然后为了读取设定好的数据,扩张如下存档处理比较好。
var _DataManager_extractSaveContents = DataManager.extractSaveContents;
DataManager.extractSaveContents = function(contents) {
  var ret = _DataManager_extractSaveContents.call(this, contents);
  // 这里将会从contents中读取数据
  return ret;
};


追加插件的机能吧
难得机会,稍微追加下插件的机能吧。不仅仅是角色,追加敌人的ID显示吧!

战斗中,使用控制台来查找类。$game变量中 $gameTroop 有点像,作为起点吧。
plugin-dev-02-03.png


原来如此,在rpg_object.js文件的第4379行的位置呢。
Game_Enemy.prototype.name = function() {
    return this.originalName() + (this._plural ? this._letter : '');
};


plural 是什么呢?于是在源码中查找后,发现了以下的代码。相同组(Troop)内有一样名字的时候好像会设置 true 。就是那个呢,如果有一样的敌人就会在名字后面加上A或者B什么的。
  this.members().forEach(function(enemy) {
      var name = enemy.originalName();
      if (this._namesCount[name] >= 2) {
          enemy.setPlural(true);
      }
  }, this);


那么,敌人的name函数扩张起来吧。首先在不追加的情况下做下扩张的准备工作。


var _Game_Enemy_name = Game_Enemy.prototype.name;
Game_Enemy.prototype.name = function() {
  var ret = _Game_Enemy_name.call(this);
  return ret;
};



在有时间的情况下,在这个状态下进行游戏测试。在自己对代码进行调试前,先看看扩张的代码对游戏有什么坏影响没,提前打个预防针。

不,实际上真的有哦。烦恼了几个小时,结果发现是简单的第一步就拼写错误导致失败不断,何等的犯糊涂...不,应该不只我会这样。

那么追写实际的代码吧。不过和写角色的情况差不多...
var _Game_Enemy_name = Game_Enemy.prototype.name;
Game_Enemy.prototype.name = function() {
  var ret = _Game_Enemy_name.call(this);
  var f = $gameSystem[N + "_show_eid"];
  if (f === undefined ? show_eid : f) {
    return ret + ":" + this.enemyId();
  } else {
    return ret;          
  }
};


姑且,将使用的变量改成show_eid。所以在插件参数以及插件指令的部分也必须要扩张。

不过和角色差不多,这里就不细细说明代码了。最后会将代码全部丢上来,到时候再来确认追加的代码吧。

完成插件吧
那么,到此为止所说明的插件,结果也不错姑且就算完成了吧。

名字叫 'RTK_Test' 也不好呢。就改名'RTK_ShowID' 吧。在变更文件名的同时,别忘了修改最初N变量的定义。

帮助也很不充分(写着不包含插件指令,但是实际上是有的),需要各种追加和变更表述。

于是完成结果在这里 -> RTK_ShowID.js

因为有点长,可以借助GitHub的功能进行查看。源链接也可以储存下载js。

这就是插件开发相关的最低限度的说明。制作各种插件,尝试乐在其中吧!

那么,再会!

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