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

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

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

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

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

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


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

'global.rpgsave' 是储存在存档选择画面中所使用的数据,在存档生成和更新时更新。比如对应下面的存档选择画面。
plugin-dev-02-02.jpg
所保存的数据如下。
  1. [
  2. null,
  3. {
  4. "globalId":"RPGMV",
  5. "title":"RTK1 Dev1",
  6. "characters":[["Actor1",0],["Actor1",7],["Actor3",7],["Actor2",6]],
  7. "faces":[["Actor1",0],["Actor1",7],["Actor3",7],["Actor2",6]],
  8. "playtime":"00:00:04",
  9. "timestamp":1468556954489
  10. },
  11. {
  12. "globalId":"RPGMV",
  13. "title":"RTK1 Dev1",
  14. "characters":[["Actor1",0],["Actor1",7],["Actor3",7],["Actor2",6]],
  15. "faces":[["Actor1",0],["Actor1",7],["Actor3",7],["Actor2",6]],
  16. "playtime":"00:00:48",
  17. "timestamp":1468855189632
  18. }
  19. ]


游戏存档的文件内容
我们知道'file*.rpgsave' 是实际的存档文件,那么是否所有内容都存在其中?那么省略一部分,来粗略介绍下主要部分吧。
  1. {
  2. "system":{
  3. "_saveEnabled":true,
  4. "_menuEnabled":true,
  5. "_encounterEnabled":true,
  6. "_formationEnabled":true,
  7. "_battleCount":0,
  8. "_winCount":0,
  9. // 中间省略
  10. "@":"Game_System"
  11. },
  12. "screen":{
  13. "_brightness":255,
  14. "_fadeOutDuration":0,
  15. "_fadeInDuration":0,
  16. "_tone":[0,0,0,0],
  17. // 中间省略
  18. "@":"Game_Screen"
  19. },
  20. "timer":{"_frames":0,"_working":false,"@":"Game_Timer"},
  21. "switches":{"_data":[null,null,null,null,null,null,null,null,true],"@":"Game_Switches"},
  22. "variables":{"_data":[],"@":"Game_Variables"},
  23. "selfSwitches":{"_data":{"1,3,A":true},"@":"Game_SelfSwitches"},
  24. "actors":{
  25. "_data":[
  26. null,
  27. {
  28. "_actorId":1,
  29. "_name":"Harold",
  30. "_nickname":"Sword boy",
  31. "_hp":450,
  32. "_mp":90,
  33. "_tp":0,
  34. "_hidden":false,
  35. "_paramPlus":[0,0,0,0,0,0,0,0],
  36. // 中间省略
  37. "@":"Game_Actor"
  38. },{
  39. "_actorId":2,
  40. // 以下省略
  41. },{
  42. "_actorId":3,
  43. // 以下省略
  44. {
  45. "_actorId":4,
  46. // 以下省略
  47. }
  48. ],
  49. "@":"Game_Actors"
  50. },
  51. "party":{
  52. "_inBattle":false,
  53. "_gold":2000,
  54. "_steps":0,
  55. "_lastItem":{"_dataClass":"","_itemId":0,"@":"Game_Item"},
  56. "_menuActorId":0,
  57. "_targetActorId":0,
  58. "_actors":[1,2,3,4],
  59. "_items":{"1":6,"2":1,"10":50,"11":2,"12":2,"13":2,"14":2},
  60. "_weapons":{"1":1,"2":2,"4":10},
  61. "_armors":{"3":1,"4":1},
  62. "@":"Game_Party"
  63. },
  64. "map":{
  65. "_interpreter":{
  66. "_depth":0,
  67. "_mapId":0,
  68. "_eventId":0,
  69. // 中间省略
  70. "@":"Game_Interpreter"
  71. },
  72. "_mapId":1,
  73. "_tilesetId":1,
  74. "_events":[
  75. null,
  76. {
  77. "_x":4,
  78. "_y":1,
  79. // 中间省略
  80. "@":"Game_Event"
  81. },
  82. // 以下省略
  83. ],
  84. "_commonEvents":[],
  85. // 中间省略
  86. "@":"Game_Map"
  87. },
  88. "player":{
  89. "_x":4,
  90. "_y":4,
  91. // 中间省略
  92. "_followers":{
  93. "_visible":true,
  94. "_gathering":false,
  95. "_data":[ // 省略
  96. ],"
  97. @":"Game_Followers"
  98. },
  99. "_encounterCount":603,
  100. "@":"Game_Player"
  101. }
  102. }


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

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

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

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

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

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

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


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

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

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

使用保存值的部分是OK了,将值保存起来的处理还没有呢。就修正插件指令的部分吧。
  1. var _Game_Interpreter_pluginCommand = Game_Interpreter.prototype.pluginCommand;
  2. Game_Interpreter.prototype.pluginCommand = function(command, args) {
  3. _Game_Interpreter_pluginCommand.call(this, command, args);
  4. if (command == N) {
  5. if (args[0] == "show_id") {
  6. if (args[1] == "on") {
  7. $gameSystem[N + "_show_id"] = 1;
  8. } else if (args[1] == "off") {
  9. $gameSystem[N + "_show_id"] = 0;
  10. }
  11. }
  12. }
  13. };


嗯,这里的修正也不多呢。只是将$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的变量反应在插件参数里吧。也就是说是在游戏开发的时候设定的值,实际游戏发布后就无法变更了。

以此为前提,为了更加节约存档文件空间,我们将代码变成这样。
  1. var _Game_Interpreter_pluginCommand = Game_Interpreter.prototype.pluginCommand;
  2. Game_Interpreter.prototype.pluginCommand = function(command, args) {
  3. _Game_Interpreter_pluginCommand.call(this, command, args);
  4. if (command == N) {
  5. if (args[0] == "show_id") {
  6. if (args[1] == "on") {
  7. if (show_id) {
  8. delete $gameSystem[N + "_show_id"];
  9. } else {
  10. $gameSystem[N + "_show_id"] = 1;
  11. }
  12. } else if (args[1] == "off") {
  13. if (!show_id) {
  14. delete $gameSystem[N + "_show_id"];
  15. } else {
  16. $gameSystem[N + "_show_id"] = 0;
  17. }
  18. }
  19. }
  20. }
  21. };


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

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

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

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


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


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

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


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


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


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


  1. var _Game_Enemy_name = Game_Enemy.prototype.name;
  2. Game_Enemy.prototype.name = function() {
  3. var ret = _Game_Enemy_name.call(this);
  4. return ret;
  5. };



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

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

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


姑且,将使用的变量改成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