查看: 15139|回复: 19

[原创发布] RPGMakerMV的插件学习

[复制链接]
梦石
0
星屑
50
在线时间
19 小时
注册时间
2016-3-30
回帖
12
发表于 2016-3-31 17:05:00 | 显示全部楼层 |阅读模式

加入我们,或者,欢迎回来。

您需要 登录 才可以下载或查看,没有账号?注册会员

×
本人程序员,写过几年js,但是真正用到的东西根本算是皮毛中的皮毛。

本着独立游戏开发者的心态,入了正版RM来想自己做游戏,而且这版的语言也正好是js

就着这个机会一边研究插件一边深入学习js

有能力的同学可以试着去看看Youtube上面Soulpour777的几个视频

对于想学js和想做插件的同学都有挺大的收获

这里就把我对做这插件的学习记录放进来,可能有些乱

首先经过视频和其他大神的插件,了解到这么一个问题,就是很多时候要实现自己的插件,基本上都是去重写官方的原型链中的方法(我不太清楚这样称呼是否明确,请大神给指出错误)

就是你随便打开一个官方的js文件,就会看到里面诸如  xxx.prototype.yyy 这样的东西

这个时候在自己新建的插件里面把这整个方法复制下来, 并且在RPGMaker的插件管理加上自己的插件,启动的时候就会执行重写的那个方法

做个简单的事例(同学们请自己动手喔),我们来实现一个在启动游戏的时候(就是显示新游戏NewGame,继续Continue和选项Options那个页面),加入一个新的选择项目 测试项

下面开始操作:

1,首先在游戏项目下的js/plugins文件夹里创建一个自己的插件文件(我这里命名的是testPlugin.js)

test

test
,

2,然后得找到启动游戏这个东西是在哪儿画出来的,rpg_window.js 这个文件就是控制来画出所有想要的窗口的官方文件,在文件的5747这行里,有个

Window_TitleCommand.prototype.makeCommandList 方法  这个方法里面就是画出初始化游戏的窗口,把这个方法整个复制到自己的js文件里 屏幕快照 2016-03-31 16.42.50.png

从这里就能看到,官方是把这里的3个选项依次加进去,addCommand这个方法有4个参数传进去
Window_Command.prototype.addCommand = function(name, symbol, enabled, ext)
第一个参数 类型(string) 代表的是显示的名称
第二个参数 类型(string)代表的是标示(在事件处理的时候会通过标示来确定执行什么事件)
第三个参数 类型(boolean)代表这个选项是否可用,这里的继续游戏continue按钮是灰色,因为他判断了是否游戏里面有存档this.isContinueEnabled(),如果有存档就返回的true,否则false,默认是true
第四个参数 类型(任意)是拓展 用于其他特殊的标示,类似于第二个参数

3,现在我们把一个测试项放进启动游戏的选项里面,只需要在我们的js文件里面,跟着方法后面写上this.addCommand('测试项','testOption');, 屏幕快照 2016-03-31 17.00.36.png ,这样

4,将插件加入进来,运行游戏 屏幕快照 2016-03-31 17.01.53.png 屏幕快照 2016-03-31 17.02.46.png

当当当!就出现了!可是你点击是没有任何效果的,因为这里还没给设置绑定的事件,下次我会继续讲解如何绑定事件.

点评

很棒的教程!不过最好不要直接修改原方法,这样做容易和其他插件不兼容,建议后面的教程都先重定义方法,再修改新定义的方法,以免杯具  发表于 2016-3-31 17:19

评分

参与人数 7星屑 +221 梦石 +2 收起 理由
xjzsq + 30 精品文章,值得精读!加油!
柳岳枫 + 60
CFRoo + 20 精品文章
冷峻逸 + 46 补个111
gonglinyuan + 45 精品文章
dongdongdjh + 20 插眼,养肥了再看。
余烬之中 + 2 加油

查看全部评分

梦石
0
星屑
50
在线时间
0 小时
注册时间
2017-9-27
回帖
2
发表于 2017-9-27 23:40:54 | 显示全部楼层
不错不错~~~
回复

使用道具 举报

梦石
0
星屑
50
在线时间
45 小时
注册时间
2008-3-2
回帖
94
发表于 2017-9-8 14:43:09 | 显示全部楼层
bulangnisi 发表于 2016-4-11 18:10
有几天没来更新了,在做其它的事儿

这会有空了,马上来把我的tip插件来完成了

提示cannot read property "filter"of undefined
魔幻大航海II:伟大航路 制作中....
●剧情:■□□□□□□□□□10%
◎美工:■■■■■■■■■□90%
●音效:■■■■■■■■■□90%
◎脚本:■■■■■■■□□□70%
●枫野别墅:http://xiaomu2100.blog.163.com
 ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄
回复

使用道具 举报

梦石
0
星屑
50
在线时间
13 小时
注册时间
2016-3-31
回帖
8
发表于 2017-1-2 04:11:07 | 显示全部楼层
先膜拜一下,回头过来仔细看。
回复

使用道具 举报

梦石
2
星屑
34497
在线时间
614 小时
注册时间
2014-7-18
回帖
647

MZ评测员开拓者

发表于 2017-1-1 15:40:26 | 显示全部楼层
给您提供一个有助于您研究的东西吧!
https://rpg.blue/thread-385523-1-1.html
这个帖子不仅汉化了脚本注释,还吧原生脚本给分离开来了,十分符合RMVA的脚本显示方式,可以参考一下。
另外,建议您去看看VA的帮助文档,里面解读篇和实践篇部分介绍了脚本系统RGSS3的结构,尽管是Ruby语言,而且有很多方法MV里面都没有了(比如alias),但是对于理解MV的脚本系统还是很有帮助的,因为MV的脚本思想和VA的非常相似。
希望对您有所帮助!!!加油!!!
回复

使用道具 举报

梦石
0
星屑
685
在线时间
0 小时
注册时间
2016-12-27
回帖
1
发表于 2016-12-28 22:24:34 | 显示全部楼层
司机我要加加你,想请教怎么在插件里面选中对象 = =原谅我没看你的源码
回复

使用道具 举报

梦石
0
星屑
50
在线时间
19 小时
注册时间
2016-3-30
回帖
12
 楼主| 发表于 2016-4-11 18:10:17 | 显示全部楼层
有几天没来更新了,在做其它的事儿

这会有空了,马上来把我的tip插件来完成了

上次完成一个测试版的之后,发现了一个很大问题,第一,显示出的tip框是直接就出现了,第二,关闭tip的时候没法回车去关闭,而且还强制改写了Scene_Map的update来实现的关闭,相当难看,第三,打开tip的事件角色只转身一下就又转回去了。

说白了,是相当的失败,于是又花了两天时间仔细研究了一下Window_Message的实现,有一些小的收获,这里先大致讲解一下实现的思路

首先我目标中的tip,是在Scene_Map中弹出来,跟对话的消息是一样的,如果新建一个Scene来显示的话,后面是的画面是不会动的,所以这里照着跟Window_Message一样,仅新建一个Window来在Scene_Map中显示

这里提下关于打开tip(Message也是一样)的关键所在,update,刷新帧,Window_Base自带有的一个方法,所以每个继承至Window_Base的都会有这个方法,当创建Scene之后,然后往Scene里添加Window,这些Window都会一直调用这个方法,通过这个方法来检测某些变量或者函数返回值来做出动作

拿Window_Message来举例(Window_Message里面包涵了除了显示消息以外的好几个东西,这里就简单拿显示消息来做例子),Window_Message的update实现是在非打开中和非关闭中的时候,去判断是否要显示消息框,其中当消息(就是我们调用的时候传入的那些字符信息)存在的时候打开了消息框,同时字符会在里面打印的同时并把它们都消费掉,当字符未被消费完,同时达到窗口容纳字符的最大值的时候,先暂停,然后当触发按键的时候打开下一页。看到那几个简单的字眼估计就明白了,比如newLine啊newPage啊什么的。

我们做的这个tip没这么复杂,至少我不想去每个字符慢悠悠的打出来,所以消费字符这一步我这里直接是全部拿出去了

然后再提出几个关键点

第一,就是当打开消息框的时候,按esc,不会打开菜单,而是关闭掉消息框,同样我们的tip也需要这一个操作,但是这个动作我之前是用了一个比较笨的方法,就是菜单监听esc的事件里先判断是否打开tip,如果是,则先关闭tip,其实官方的方式是,设置菜单的可用性,于是这次我也沿用官方的做法,打开tip的时候,先设置菜单不可用,然后后面的esc就不会触发打开菜单

第二,打开的tip怎么出现的,之前也是用的错误的办法,直接设置tip窗口的visible,效果嘛,就一闪,直接打开了```感觉很突兀,而标题页和消息框的那些菜单都是展开的形式打开的,方法我也想过,无非刷新帧的时候去做个动画而已嘛,实际不用这么麻烦,直接调用本体的open函数就行了,他会每一帧增加一个opacity的值,每次增加32,8次就能达到大于255的极值了,在这个加值的过程,window的isOpening属性会是true,所以就不用担心他在原本的update函数里面捣乱(update函数里面实现的时候是要去判断非打开中和非关闭中的状态),用样close也是类似的动作,所以这一动作就作出了让我们看到的展开的形式(ps:其实这里我还是有点疑问,就是opacity该叫做透明度的,怎么动画效果会是展开呢。。。),这里注意一点,初始化tip窗口的时候,记得把this.openness = 0;,这是open函数操作的关键之一。

第三,事件触发的目标是人物的话,他不会朝向主角,这里是由于有一个等待模式的介入,大家在rpg_object里面找到command101看函数最后就明白了,他设置了一个消息的等待模式,除了消息,其它好几个需要等待的,都是这样处理的,当等待模式启动之后,触发事件的目标就会朝向主角,并且如果带动作效果的,他的动作也不会停,因为等待模式也是一种update刷新着呢

上代码
[pre lang="javascript"]
/*:
* @plugindesc 主场景小提示插件
* @author BenJ
*
*
*
* @param tipIconPadding
* @desc 如果带icon图标的话,文字与图标的间距
* @default 36
*
*
* @help
*
* Plugin Command:
*
* myTipWindow showTip msg  #添加一条消息
* myTipWindow showTip msg iconId  #添加一条带icon的消息
*
*
*/
var parameters = PluginManager.parameters('myTipWindow');
var tipIconPadding = Number(parameters['tipIconPadding'] || 36);

var _myGame_Interpreter_pluginCommand = Game_Interpreter.prototype.pluginCommand;

Game_Interpreter.prototype.pluginCommand = function(command,args){
  _myGame_Interpreter_pluginCommand.call(this);
  if(command == 'myTipWindow'){
    $gameMap._showTip = true;//是否需要打开tip窗口
    $gameMap._isShownTip = true;//tip窗口是否被打开
    $gameMap._tip = {
      msg : args[0] ,//tip的文本
      icon : args[1]  //tip的图标
      //showed : false //是否已经显示过该信息  防止一直刷新
    };
    this.setWaitMode('tipMessage');
  }
};

//修改Game_Map初始化,增加tip相关属性
var _jGame_Map_setup = Game_Map.prototype.setup;
Game_Map.prototype.setup = function(mapId) {
  _jGame_Map_setup.call(this,mapId);
  this._showTip = false;
  this._isShownTip = false;
  this._tip = {};
};

//重写Scene_Map初始化部分窗口函数
var _jScene_Map_createAllWindows = Scene_Map.prototype.createAllWindows;
Scene_Map.prototype.createAllWindows = function() {
  _jScene_Map_createAllWindows.call(this);
  this.createTipWindow();
};

//创建tipWindow加入到Scene_Map
Scene_Map.prototype.createTipWindow = function() {
  this._tipWindow = new Window_JTip();
  this.addWindow(this._tipWindow);
};

//todo 自定义提示窗体
function Window_JTip() {
  this.initialize.apply(this, arguments);
}

Window_JTip.prototype = Object.create(Window_Base.prototype);
Window_JTip.prototype.constructor = Window_JTip;

Window_JTip.prototype.initialize = function() {
  var width = this.windowWidth();
  var height = this.windowHeight();
  Window_Base.prototype.initialize.call(this, (Graphics.width - width)/2, 200, width, height);
  this.openness = 0;//尝试了好多次,原来这就是关键点所在,初始化的时候,把openness设置为0的时候,调用open函数的时候,才会去刷新
};

//窗口宽度
Window_JTip.prototype.windowWidth = function() {
  return Number(this.width || 240);
};

//窗口高度
Window_JTip.prototype.windowHeight = function() {
  return this.fittingHeight(1);
};

//刷新tip窗口
Window_JTip.prototype.refresh = function(_tipArgs) {
  this.contents.clear();
  //this.visible = $gameMap.isTipWindowShow();
  if(_tipArgs){
    //this.resetTipWindow(_tipArgs);//这个函数里面执行过显示窗口函数核心Bitmap的resize,如果在后面resize,会清空内容栏,具体我也没看到哪里在清空,但是事实如此
    //todo 绘制消息板
    var _cnTest =  /[\u4E00-\u9FA5]/;
    var _msg = _tipArgs.msg.toString();
    var _width = 0;
    //通过文字计算窗口大小
    for(var i=0 ; i<_msg.length ; i++){
      //判断是否为中文,不是中文,只用字体大小的一半
      if(_cnTest.test(_msg)){
        _width += this.standardFontSize() ;
      }else{
        _width += this.standardFontSize() / 2 ;
      }
    }
    //添加默认内容左右padding
    _width += this.standardPadding() * 2;

    //是否包含icon
    if(_tipArgs.icon != undefined && _tipArgs.icon != 0){
      _width += tipIconPadding;
    }

    this.width = _width;
    this.x = (Graphics.width - _width)/2;
    this.contents.resize(this.contentsWidth(),this.contentsHeight());//这里设置的内容的宽度,如果照官方的算法,这个宽度可能会显示不完我们的内容

    //todo 绘制内容
    //_tipArgs.showed = true; //显示过的信息改变此标示
    if(_tipArgs.icon){
      //显示带icon的信息
      this.drawIcon(_tipArgs.icon,0,0);
      this.drawText(_tipArgs.msg.toString(),tipIconPadding,0);
    }else{
      //显示普通信息
      this.drawText(_tipArgs.msg.toString(),0,0);
    }
  }
};

Window_JTip.prototype.update = function(){
  Window_Base.prototype.update.call(this);
  //根据window_message的实现来看,弹出样式是打开和关闭的时候调用的updateOpen和updateClose
  while(!this.isOpening() && !this.isClosing()){
    //在非打开中和非关闭中,调用一遍所有需要执行的东西
    //message处理的方式是检测文本信息是否已经消费完来启动
    if(this.updateInput()){
      return;
    }else if(this.needShow()){
      this.startTip();
    }else{
      return;
    }
  }
};

//启动tip窗口
Window_JTip.prototype.startTip = function() {
  this.refresh($gameMap._tip);
  this.open();
  $gameSystem.disableMenu();
  $gameMap._showTip = false;//启动了窗口就把是否启动tip关掉,否则update一直刷新启动造成死循环了
  this.pause = true;//启动了窗口,并且刷新了窗口之后,设置暂停
};

Window_JTip.prototype.needShow = function() {
  return $gameMap._showTip;
};

Window_JTip.prototype.isTriggered = function() {
  return (Input.isRepeated('ok') || Input.isRepeated('cancel') || TouchInput.isRepeated());
};

//当窗口暂停,并且有按键触发,我们就关闭窗口
Window_JTip.prototype.updateInput = function() {
  if(this.pause){
    if(this.isTriggered()){
      $gameSystem.enableMenu();
      this.pause = false;
      $gameMap._isShownTip = false;
      this.close();
    }
    return true;
  }else{
    return false;
  }
};

//这里由于官方内定了好几种等待模式,而且写得比较死,我就直接在官方的函数里拓展了,增加了tipMessage的标示
Game_Interpreter.prototype.updateWaitMode = function() {
  var waiting = false;
  switch (this._waitMode) {
    case 'message':
      waiting = $gameMessage.isBusy();
      break;
    case 'transfer':
      waiting = $gamePlayer.isTransferring();
      break;
    case 'scroll':
      waiting = $gameMap.isScrolling();
      break;
    case 'route':
      waiting = this._character.isMoveRouteForcing();
      break;
    case 'animation':
      waiting = this._character.isAnimationPlaying();
      break;
    case 'balloon':
      waiting = this._character.isBalloonPlaying();
      break;
    case 'gather':
      waiting = $gamePlayer.areFollowersGathering();
      break;
    case 'action':
      waiting = BattleManager.isActionForced();
      break;
    case 'video':
      waiting = Graphics.isVideoPlaying();
      break;
    case 'image':
      waiting = !ImageManager.isReady();
      break;
    case 'tipMessage':
      waiting = $gameMap._isShownTip;
      break;
  }
  if (!waiting) {
    this._waitMode = '';
  }
  return waiting;
};
[/pre]

关于插件的调用和设置就不多说了,下面看成果图 屏幕快照 2016-04-11 18.09.25.png
回复

使用道具 举报

梦石
0
星屑
234
在线时间
124 小时
注册时间
2015-2-18
回帖
11
发表于 2016-4-9 22:28:48 | 显示全部楼层
加油!!。。。
回复

使用道具 举报

梦石
0
星屑
50
在线时间
48 小时
注册时间
2010-10-26
回帖
15
发表于 2016-4-7 14:27:21 | 显示全部楼层
专业的就是不一样,几天就顶我几个月的研究
回复

使用道具 举报

梦石
0
星屑
50
在线时间
19 小时
注册时间
2015-12-15
回帖
5
发表于 2016-4-5 03:49:43 | 显示全部楼层
bulangnisi 发表于 2016-4-4 22:18
呼~~~

看到你的回复之后,搞了我好几个小时,毕竟是第一个向我提问的人啊,我这不就全力以赴的帮你研 ...

天哪……………………简直太感谢了!!!!!!!原来每次scene_map都要清空栈…………总之太感谢了!!!!
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 注册会员

本版积分规则

Powered by Discuz! X5.0 © 2001-2026 Discuz! Team.

在本版发帖返回顶部