Project1

标题: RPGMakerMV的插件学习 [打印本页]

作者: bulangnisi    时间: 2016-3-31 17:05
标题: RPGMakerMV的插件学习
本人程序员,写过几年js,但是真正用到的东西根本算是皮毛中的皮毛。

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

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

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

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

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

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

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

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

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

下面开始操作:

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

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

Window_TitleCommand.prototype.makeCommandList 方法  这个方法里面就是画出初始化游戏的窗口,把这个方法整个复制到自己的js文件里

从这里就能看到,官方是把这里的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');, ,这样

4,将插件加入进来,运行游戏

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

作者: bulangnisi    时间: 2016-3-31 19:10
感谢感谢,这下我好像有点明白了为啥更多时候写插件会是call一下原来的函数(ΦωΦ)
作者: bulangnisi    时间: 2016-3-31 19:17
怎么设置成帖子过期啦?要怎么弄回原创呢- -
作者: bulangnisi    时间: 2016-3-31 20:13
感谢版主大神给弄回了原创!好流弊!666666
作者: bulangnisi    时间: 2016-3-31 21:11
本帖最后由 bulangnisi 于 2016-3-31 21:21 编辑

接下来简单说明一下事件这块的东西

在我们给菜单栏添加了一个选项之后,看是能看到了,但是点击之后是没有任何效果的,那是因为没有有效的事件绑定到选项的点击,所以是没有反应的

我认为点击之后弹出个消息啥的太没新意了,所以咱们这次做个在启动游戏的初始界面加入一个图鉴的选项(就是官方自带的那个ItemBook图鉴插件),点击图鉴就打开图鉴,想要做这样的东西

是因为之前测试ItemBook这个插件的时候,让我没处调用,给道具加入事件或者在地图上加入一个事件是可以由触发这个事件调用pluginCommand来打开 ,如

设置图右下这个pluginCommand 来开启,但是我想把这个打开加入到选项里啊亲,这种收集的东西放到道具里或者某个地点来触发总觉得不爽啊亲```

言归正传

1,首先还是之前的一样,把图鉴这个选项加进去 ,跟之前的差别就是把第一个参数(显示的名称)和第二个参数(标示)改了一下

2,找到绑定事件的地方,在rpg_scenes.js文件中的310行左右, ,这里就是绑定初始化窗口的事件的地方,把这个函数全部复制到我们的js文件里,然后再给

绑定一个我们自己的事件,

this._commandWindow.setHandler('ItemBook',  this.commandOpenItemBook.bind(this));

这句话就是把这个事件绑定到图鉴这个选项上,他就是通过我们之前设置标示(ItemBook)来找到事件该绑定到哪儿的,所以这个第一个参数 一定要跟前面添加的时候设置的标示一致

第二个参数设置是一个事件的监听(我也是凭感觉这样叫,有大神请指教。。。),commandOpenItemBook这个函数是没有的,是我们自己定义的一个函数,所以在下面要写一个这个函数

,(ps:你可以简单的把this就理解成Scene_Title.prototype,这就是这个对象的原型,具体js所谓的原型链是啥,我也解释不太清楚,为了不误导大家,请去

问下度娘)

到这里选项也加上了,事件也绑定,然后就差我们点击图鉴做的事了

3,在我们写的commandOpenItemBook函数中做跟设置事件那个pluginCommand一样的事儿

于是在rpg_objects.js文件的最后找到了Game_Interpreter.prototype.pluginCommand(command,args);这个方法,你仔细往看这一块的官方注释你会发现,编辑界面中所有的事件编辑都是这

里写的 ,看函数名上面的注释就知道了

这里稍微解释一下用js调用这个方法和在界面上调用这个方法有啥区别

3.1在界面上 ,第一个‘ ItemBook’这是调用注册的插件名称(我是这么理解),就像ItemBook.js源码里 ,当你把插件放进

来,并且设置on,启动游戏的时候,他就会把这个插件注册进来(当我们自己写的插件需要带命令来操作的时候,就得先把我们的插件注册进来,由于之前写的东西不大需要带命令操作,所以我

们就没有注册,而是直接开写),第二个‘open’代表命令的参数,大致的规则是   命令[空格]参数1[空格]...参数n

3.2在js里面调用命令 Game_Interpreter.prototype.pluginCommand('ItemBook',['open']); 跟界面上操作也是差不多的,第一个参数是注册的插件名称,第二个参数得注意一下,他是一个数组

大致规则是 Game_Interpreter.prototype.pluginCommand('命令',['参数1'.....'参数n']);  , 所以在这里我们的操作其实跟界面上设置pluginCommand的操作是一样的

4,准备就绪,记得把ItemBook插件加入进来 ,然后运行查看效果



最后完成的代码是这个样纸,

但是经过之前大神的点拨,太老实重写原本方法的话,说不定会跟其他的插件冲突,于是把代码优化一下, ,为了方便大家看清是改动了哪里,我把多余的代码

只是注释掉了,然后新增了两个对象_myWindow_TitleCommand 和 _myScene_Title ,把原方法保存下来,执行的时候先让他们调用一下原方法执行原本执行的东西,然后在后面执行我们拓

展的东西。
作者: bulangnisi    时间: 2016-3-31 23:17
睡之前来一发今天的研究成果,做一个改变文字颜色的小插件

首先来说下设计流程,对于官方的源码来看,文字显示出来的样子其实是一个字符一个字符处理出来的,处理过程中他做了一个转译和判断

这里主要说一些要改变文字颜色的关键部分,其余的请同学们自己多研究研究

在处理Text文本的时候,官方会通过这个方法把里面每个字符一个个的遍历出来(rpg_windows.js,307行)


其中 \x1b 这个东西的转译就是 ‘ \ ’ 反斜杠 ,这里官方把出现反斜杠后面的东西拿出来专门做处理,就是这个方法
this.processEscapeCharacter(this.obtainEscapeCode(textState), textState);

然后在rpg_windows.js,364行里,就是对后面带有以下字符的包含的文字特殊处理 , 其中包括C , I , { , }

你在新建一个文本消息,鼠标移到文本中不动的话,他会有提示怎样怎样会改变文字颜色啊,大小啊等,比如  ‘\{’ ‘ \}’ ‘\C’等,


I表示指定一个图标 用法: \I[图标id]


如图 左下角的1表示图标的id 就是那个骷髅的图标


\{ 表示后面的字符大小加大 比如要把 ‘你好吗?’ 中间的好加大  就在显示文本中这样写  ‘你\{好\}吗?’
\} 表示后面的字符大小减小 比如要把 ‘你好吗?’ 中间的好减小  就在显示文本中这样写  ‘你\}好\{吗?’


C表示改变后面文本的颜色  用法: \C[数字] 要变色的文字 \C
最后的\C 表示后面的文字不变色
那么既然有现成的文字变色,为啥还要自己写插件呢?原因有二

一,我看不懂他颜色值的算法- -(求大神教教)
二,我想随心所欲的用任意颜色,比方我随便用取色器取个颜色就放上去了

好,这就开工
知道了他是如何处理,我们就知道如何去改我们想要的东西了
就在他原本处理颜色这上面做手脚
case 'C':
        this.changeTextColor(this.textColor(this.obtainEscapeParam(textState)));
        break;

1,首先大家以往看到添加插件的时候,插件的参数啊,作者啊,描述啊,其实就是给加上注释就行了,咱们新建一个插件,叫myTextColor.js,然后把注释写上,使用的时候就能在界面上看到我们流弊的插件信息了

一一对照到注释来看添加插件界面就不难理解注释的意义了吧

2,根据之前的讲解,我们首先要注册我们的插件,同时还得把参数传进来的值用变量保存起来

这里的var colorNames = String(parameters['colorNames'] || 'white,red,green,blue');  稍微解释一下  String后面带的参数意义是,如果在界面操作的时候,你不填colorNames的value是多少,我这里就默认给出这个值,colorValue雷同.

3,把传进来的值用个对象以健值对的形势保存起来,以便后面使用


这里的_colorObjs最后的样子就是
{
  'white'   : '#ffffff' ,
  'red'      : '#ff545d' ,
  'green'  : '#7aff3c' ,
  'blue'    : '#6da9ff'
}

4,为了显得我们插件流弊一点,得学学人家可以用命令动态添加颜色吧,再贴心一点,咱们把用法也给人说一下,免得别人用我们的插件一打开就萌比了


注释中的 myTextColor addColor orange #ff8153 就代表在pluginCommand里面这样用就能添加一个颜色
或者用js的方法调用的话Game_Interpreter.prototype.pluginCommand('myTextColor',['addColor','orange','#ff8153']);

5,最重要的地方,咱得改写以前的处理方式,在以前 \C 那里别人官方不是 \C[数字] 这样用么?咱给他整个流弊的 \C[颜色名称] 这样来用,其中这个颜色名称会从我们之前保存好的_colorObjs里面找到对应的颜色值,比如 \C['red'] ,这样就把后面的字的颜色改为了 '#ff545d'。

在rpg_windows.js中的354行,官方是这样取出他 \C[数字]中的数字的


咱们就如法炮制 ,给整个这个,差别就是官方的取的是个数字,咱取的是个字符串


6,所有准备工作都做好了,直接上菜吧,还是我的老习惯,给官方的改改改


大功告成,不过整个插件的验证不太可靠,就是说要是乱给写参数进来的话,报错啥的就不好说了,比如你把颜色值写个djsahdjsa什么的````

先贴个全部代码图



再来个使用示例图,这里咱把官方的那几个字体变大小啊,图标啊一并用上来玩

效果图


最后强调一下,为啥要在最后那句话前面写上 \C  ,因为前面的 \C[颜色名] 会修改后面的所有文字 ,所以这里带上一个 \C 意思就是把颜色还原成默认的。




作者: bulangnisi    时间: 2016-4-3 16:48
本帖最后由 bulangnisi 于 2016-4-3 16:51 编辑

这次来了解一下 精灵(Sprite)元素

对于做游戏的大神都知道精灵(Sprite)对于游戏的重要性(ps:我不是大神,我是初心者。。。)

这里我仅用自己的思路大致来描述一下,有偏差的地方请同学们指正一下。

拿这个rpgmaker来说,里面大大小小的东西都可以算作是精灵,首页显示的背景算是,地图的每个单元算是,更不用说我们操作的角色了,你游戏中几乎所有看到的东西动起来,仅仅是精灵单元改变自己的图片而已,由于游戏的fps(帧)刷新得比较快,所以我们看到的就是一个连贯动作的精灵(就像我小时候在课本每页的右下角画小人,然后每一页画一个动作,然后把书翻起来的话,就会看到一个连贯的动作,就是这个意思)。

在rpgmaker中大多数对象都具有update的方法,这就像是我们在翻书的动作,然后里面的图片素材就是我们画的小人。

所以这次我们来做个自己想要的标题页,可以动起来的,主要操作的东西,就是精灵(Sprite)。

在rpg_scene.js的233行找到创建标题页的代码



其中createBackground方法就给了我们很好的示例,他在里面创建了2个精灵(说白了你甚至可以把精灵直接理解为一个图片都行- -),

JAVASCRIPT 代码复制
  1. Scene_Title.prototype.createBackground = function() {
  2.     this._backSprite1 = new Sprite(ImageManager.loadTitle1($dataSystem.title1Name));
  3.     this._backSprite2 = new Sprite(ImageManager.loadTitle2($dataSystem.title2Name));
  4.     this.addChild(this._backSprite1);
  5.     this.addChild(this._backSprite2);
  6. };


这里就是我们在System里面选择的图片



$dataSystem.title1Name 背景图片名称 左一列表所选择的
$dataSystem.title2Name 前景图片名称 左二列表所选择的

然后通过ImageManager中的loadTitle1方法,传入名称去获取到图片

稍微描述一下这个ImageManager,他里面的每个loadxxxx的方法都直接对应到的是项目文件下img文件里的每个文件夹



而且他每个load方法的实现也是很容易看懂的



创建了精灵之后,还需要把这个精灵加入到我们的屏幕(Scene)中去

就像这句的写法就行了, this.addChild(this._backSprite1);

具体精灵有哪些参数和方法,大家可以去官方文档查看,这里不做介绍了

现在开始做我们的标题页,这里为想做一个背景在移动,中间再放个人物,然后人物背后一遍遍的扩散自己本身,效果如下



首先我们把背景换成 SeaofClouds 这个图片,这里不是要你直接在System里面去改,因为那样的话图片是不会动的,而我们需要一个水平移动的背景

老样子,复制源码过来改改改

JAVASCRIPT 代码复制
  1. var _myScene_Title_create = Scene_Title.prototype.create;
  2.  
  3. Scene_Title.prototype.create = function() {
  4.  
  5.   _myScene_Title_create.call(this);//原本官方执行的方法不去动它的,他该怎么创建就怎么创建
  6.  
  7.   //创建我们自己的背景;
  8.   this.create_myTitleSprite();
  9. };


然后实现我们自己创建背景的方法

JAVASCRIPT 代码复制
  1. Scene_Title.prototype.create_myTitleSprite = function(){
  2.  
  3.   this._myBackSprite = new Sprite(ImageManager.loadParallax('SeaofClouds'));
  4.  
  5.   this.addChild(this._myBackSprite);
  6.  
  7. };


这里的效果跟官方设置背景的效果是一模一样的,如果仅是这样做的话,就等于用我们的方式再画了一个精灵贴上去而已,也就是然并卵,我们要让这个背景水平动起来

JAVASCRIPT 代码复制
  1. var _myScene_Title_update = Scene_Title.prototype.update;
  2.  
  3.   Scene_Title.prototype.update = function() {
  4.  
  5.     _myScene_Title_update.call(this);
  6.  
  7.     this._myBackSprite.x += 1 ;
  8.  
  9.   }


这里是让标题页的每一帧刷新的时候,让我们的背景精灵 x 坐标 +1 ,先运行看看效果吧

是不是看到问题出来了,这我们的背景精灵向左移动的同时,把他覆盖的下面的官方设置的背景元素露出来了。也许你有些编程的思路的话,你会考虑放两张图片上去,两张图片水平放置,一张图片移动的时候另一张也跟着移动出来吧啦吧啦吧啦吧啦...... 太麻烦了,rpgmaker提供了另一种精灵对象就能轻松完成这个任务,就是平铺精灵(TilingSprite)。我们先把创建的普通精灵改成平铺精灵

JAVASCRIPT 代码复制
  1. Scene_Title.prototype.create_myTitleSprite = function(){
  2.  
  3.   //this._myBackSprite = new Sprite(ImageManager.loadParallax('SeaofClouds'));//普通精灵
  4.  
  5.   this._myBackSprite = new TilingSprite(ImageManager.loadParallax('SeaofClouds'));//平铺精灵
  6.  
  7.   //平铺精灵需要设置他默认的位置,否则是看不到的
  8.   //通过平铺精灵的move函数设置他的位置
  9.   //参数1  TilingSprite(平铺精灵) 的 X 坐标
  10.   //参数2  TilingSprite(平铺精灵) 的 Y 坐标
  11.   //参数3  TilingSprite(平铺精灵) 的宽度
  12.   //参数4  TilingSprite(平铺精灵) 的高度
  13.   //这里的Graphics的width和height是游戏窗口显示的宽度和高度,我们这样设置就会让这个平铺精灵全部占满屏幕
  14.   this._myBackSprite.move(0,0,Graphics.width,Graphics.height);
  15.  
  16.   //将我们的精灵添加到标题页
  17.   this.addChild(this._myBackSprite);
  18.  
  19. };


接下来让他动起来,但是平铺精灵跟普通精灵不同,要让他水平动的话,是要改变它的origin.x(而非普通精灵的 x) ,所以继续修改我们的update方法

JAVASCRIPT 代码复制
  1. var _myScene_Title_update = Scene_Title.prototype.update;
  2.  
  3.   Scene_Title.prototype.update = function() {
  4.  
  5.     _myScene_Title_update.call(this);
  6.  
  7.     //this._myBackSprite.x += 1 ;
  8.  
  9.     this._myBackSprite.origin.x += 1 ;
  10.  
  11.   }


如此,就能看到效果,背景平铺的动起来了,滚动的快慢与我们设置每一帧执行update移动的大小有关

比如你这样设置  this._myBackSprite.origin.x += 100 ;  

这样屏幕动得飞快(当然你还可以更快 -- ),这里希望大家把脑洞打开,知道超级机器人大战吧?里面的必杀场景是不是特流弊的场景飞快滚动?是的,我们如果去写个插件,当使用某个必杀的时候,这样把战斗背景一弄,就能做出流弊的必杀场景了。

继续来完成这次的目标,放个人物立绘上去,我这里选择的是dlc Cover Art Characters 里面的Package2_3这个人物,把他放在了我们项目文件的picture文件里。同样,我们在创建我们标题页的方法里面新增一个普通精灵放上去。

JAVASCRIPT 代码复制
  1. Scene_Title.prototype.create_myTitleSprite = function(){
  2.  
  3.   //this._myBackSprite = new Sprite(ImageManager.loadParallax('SeaofClouds'));//普通精灵
  4.  
  5.   this._myBackSprite = new TilingSprite(ImageManager.loadParallax('SeaofClouds'));//平铺精灵
  6.  
  7.   //平铺精灵需要设置他默认的位置,否则是看不到的
  8.   //通过平铺精灵的move函数设置他的位置
  9.   //参数1  TilingSprite(平铺精灵) 的 X 坐标
  10.   //参数2  TilingSprite(平铺精灵) 的 Y 坐标
  11.   //参数3  TilingSprite(平铺精灵) 的宽度
  12.   //参数4  TilingSprite(平铺精灵) 的高度
  13.   //这里的Graphics的width和height是游戏窗口显示的宽度和高度,我们这样设置就会让这个平铺精灵全部占满屏幕
  14.   this._myBackSprite.move(0,0,Graphics.width,Graphics.height);
  15.  
  16.   this._myForeSprite = new Sprite(ImageManager.loadPicture('Package2_3'));//因为这里我仅是放一个人物立绘上去,所以就使用平铺精灵就好了
  17.  
  18.   //设置人物立绘的位置
  19.   this._myForeSprite.anchor.x = 0.5;               //设置立绘精灵的横向锚点,初始值是0,最大值为1,从左到右,最左是0,最右是1,这里设置的0.5就表示正中间
  20.   this._myForeSprite.anchor.y = 1;                  //设置立绘精灵的纵向锚点,初始值是0,最大值为1,从上到下,最上是0,最下是1,这里设置的1就表示最底部
  21.   this._myForeSprite.x = Graphics.width / 2;  //设置立绘精灵的水平坐标为整个屏幕宽度的一半,与我们之前设置的横向锚点配合,立绘精灵就会在屏幕的水平正中间
  22.   this._myForeSprite.y = Graphics.height;      //设置立绘精灵的纵向坐标为整个屏幕的高度,与我们之前设置的纵向锚点配合,立绘精灵的最底部就会跟屏幕的最底部重合
  23.  
  24.   //将我们的精灵添加到标题页
  25.   this.addChild(this._myBackSprite);
  26.   this.addChild(this._myForeSprite);
  27.  
  28. };


这样背景屏幕水平滚动的时候,中间还会有一个人物立绘在屏幕正中间,并且底部与屏幕底部贴合。

接下来做个看上去奇怪的效果,让人物立绘背后有个一模一样的人物扩散开来,一直循环,同样继续修改create_myTitleSprite方法

JAVASCRIPT 代码复制
  1. Scene_Title.prototype.create_myTitleSprite = function(){
  2.  
  3.   //this._myBackSprite = new Sprite(ImageManager.loadParallax('SeaofClouds'));//普通精灵
  4.  
  5.   this._myBackSprite = new TilingSprite(ImageManager.loadParallax('SeaofClouds'));//平铺精灵
  6.  
  7.   //平铺精灵需要设置他默认的位置,否则是看不到的
  8.   //通过平铺精灵的move函数设置他的位置
  9.   //参数1  TilingSprite(平铺精灵) 的 X 坐标
  10.   //参数2  TilingSprite(平铺精灵) 的 Y 坐标
  11.   //参数3  TilingSprite(平铺精灵) 的宽度
  12.   //参数4  TilingSprite(平铺精灵) 的高度
  13.   //这里的Graphics的width和height是游戏窗口显示的宽度和高度,我们这样设置就会让这个平铺精灵全部占满屏幕
  14.   this._myBackSprite.move(0,0,Graphics.width,Graphics.height);
  15.  
  16.   this._myForeSprite = new Sprite(ImageManager.loadPicture('Package2_3'));//因为这里我仅是放一个人物立绘上去,所以就使用平铺精灵就好了
  17.  
  18.   //设置人物立绘
  19.   this._myForeSprite.anchor.x = 0.5;               //设置立绘精灵的横向锚点,初始值是0,最大值为1,从左到右,最左是0,最右是1,这里设置的0.5就表示正中间
  20.   this._myForeSprite.anchor.y = 1;                  //设置立绘精灵的纵向锚点,初始值是0,最大值为1,从上到下,最上是0,最下是1,这里设置的1就表示最底部
  21.   this._myForeSprite.x = Graphics.width / 2;  //设置立绘精灵的水平坐标为整个屏幕宽度的一半,与我们之前设置的横向锚点配合,立绘精灵就会在屏幕的水平正中间
  22.   this._myForeSprite.y = Graphics.height;      //设置立绘精灵的纵向坐标为整个屏幕的高度,与我们之前设置的纵向锚点配合,立绘精灵的最底部就会跟屏幕的最底部重合
  23.  
  24.   this._myForeSprite2 = new Sprite(ImageManager.loadPicture('Package2_3'));//背后闪现的幻影
  25.  
  26.   //设置人物立绘的幻影,与人物立绘相同
  27.   this._myForeSprite2.anchor.x = 0.5;               
  28.   this._myForeSprite2.anchor.y = 1;                 
  29.   this._myForeSprite2.x = Graphics.width / 2;
  30.   this._myForeSprite2.y = Graphics.height;
  31.  
  32.   //将我们的精灵添加到标题页
  33.   this.addChild(this._myBackSprite);
  34.   this.addChild(this._myForeSprite2);
  35.   //这里添加的时候,我们首先是添加的幻影的那个精灵,再添加的立绘的精灵,因为我想达到的效果是在人物立绘的背后出现幻影,如果先添加立绘精灵的话,这个幻影就会在立绘精灵的前面闪动,这里各位可以试着调换一下顺序就明白了
  36.   this.addChild(this._myForeSprite);
  37.  
  38.  
  39. };


到此,实际的效果只是立绘精灵和幻影精灵重叠了而已,没有任何动作,我们修改update方法来让幻影精灵动起来,实现思路是,我让幻影精灵的透明度(Opacity)慢慢的减弱,并且让幻影精灵的缩放(Scale)慢慢的变大,就能表现出一种向外拓展的效果了,同时幻影精灵完全变成透明的时候,我们要重置他的缩放(Scale)和透明度(Opacity)。
JAVASCRIPT 代码复制
  1. var _myScene_Title_update = Scene_Title.prototype.update;
  2.  
  3.   Scene_Title.prototype.update = function() {
  4.  
  5.     _myScene_Title_update.call(this);
  6.  
  7.     //this._myBackSprite.x += 1 ;
  8.  
  9.     this._myBackSprite.origin.x += 1 ;
  10.  
  11.     //幻想精灵透明度(Opacity)完全透明的时候,重置他的缩放和透明度
  12.       if(this._myForeSprite2.opacity <= 0){
  13.  
  14.         this._myForeSprite2.opacity = 255;
  15.  
  16.         this._myForeSprite2.scale.x = 1;
  17.  
  18.         this_myForeSprite2.scale.y = 1 ;
  19.  
  20.       }
  21.     //幻想精灵并没有完全透明的时候,我们让他的透明度一直减小,同时通过改变他的缩放来看上去一种扩散的感觉
  22.       else{
  23.  
  24.         this._myForeSprite2.opacity -= 3 ;
  25.  
  26.         this._myForeSprite2scale.x += 0.003;
  27.  
  28.         this._myForeSprite2.scale.y += 0.003;
  29.  
  30.       }


最后把插件加入到游戏里面去

到此,我们这次的目的就能完成了,最后的效果就是这样(脑补一下,动起来的喔)



只要大家脑洞大开,这样继续拓展,我觉得做些啥流弊效果都没问题的吧
作者: yochii    时间: 2016-4-4 01:49
看到前文提到了一点。。可否请问要怎么给道具加事件。。。?比如我希望key item里,对某一项按确定之后跳出图片以及对话框,再按确定或者取消之后返回key item界面里,这样的效果该怎么做呢?谢赐教
作者: bulangnisi    时间: 2016-4-4 22:18
yochii 发表于 2016-4-4 01:49
看到前文提到了一点。。可否请问要怎么给道具加事件。。。?比如我希望key item里,对某一项按确定之后跳出 ...

呼~~~

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

过程中也发现了rpgmaker的一些弊端,连续使用SceneManager.push是会出问题的```,请大家记住这个问题,如果连续执行push的话,他有可能没刷新过来,导致前面压入栈堆的scene全部都是第一次的那个scene。

所以本来想好的思路完全不行,这里首先用我自己的语言介绍一下rpgmaker内部的一些概念,首先rpg_scene.js和rpg_window.js这两个脚本,我之前一直都是傻傻分不清楚,现在搞了自己的提示窗口(优化之后就放上来跟大家分享一下)和这个使用道具弹出消息,已经明白了7,80%了。

scene是用作每个操作的单屏,SceneManager.goto是代表直接跳转到这个屏,不压入栈堆,SceneManager.push是代表将本屏压入栈堆,(栈堆这里大家可以简单理解为保存我们用过的屏的存储地方)

举个栗子,游戏主页面(Scene_Map)中,按esc按钮进入菜单页(Scene_Menu),再选择道具页(Scene_Item),他这一个流程全部都是用的push,压入到栈堆里面的,当我们在道具页(Scene_Item)取消的时候,再调用SceneManager.pop来剔除当前屏,回到栈堆中的上一屏。

然后我还发现,当Scene_Map每次启动的时候,他都会清空栈堆,这也算是防止栈堆过大的一种策略

所以这次要完成这样的效果,同时考虑到连续push会出问题这一因素,我们得用一个标示来禁止Scene_Map启动的时候清空栈堆。

然后在文本消息结束的时候,调用pop回到前面的页面

大致流程如下

游戏主页面(Scene_Map) —— 菜单页(Scene_Menu)—— 道具页(Scene_Item)—— 使用道具弹出消息(Scene_Map)并弹出消息

由于我们设置了标示防止回到Scene_Map的时候清空栈堆,所以以上的流程会在栈堆里,当我们调用SceneManager.pop之后,就会回到道具页(Scene_Item)

这里有个问题就是,如果我们选择道具页的贵重品栏中的道具,回来之后他会清空之前选择的栏位,回到第一栏,所以这里我的办法是再设置一个变量来保存前一次操作所选择的栏位,然后回到这个页面的时候,直接点亮那个栏位,并且把我们之前用来控制回到Scene_Map不清空栈堆的标示改为失效

接下来上代码

JAVASCRIPT 代码复制
  1. /**
  2.  * 使用过道具之后,弹出消息再回到道具页面
  3.  */
  4. Scene_Map.prototype.start = function() {
  5.   Scene_Base.prototype.start.call(this);
  6.   //如果需要重新打开道具页面,则不清空栈
  7.   if(!$gameSystem._needReplayItemMenu){
  8.     SceneManager.clearStack();
  9.   }
  10.   if (this._transfer) {
  11.     this.fadeInForTransfer();
  12.     this._mapNameWindow.open();
  13.     $gameMap.autoplay();
  14.   } else if (this.needsFadeIn()) {
  15.     this.startFadeIn(this.fadeSpeed(), false);
  16.   }
  17.   this.menuCalling = false;
  18. };
  19.  
  20. //为了不影响其他正常的消息展示,这里同样使用之前的标示来控制。当这个标示有效的时候,SceneManager.pop();让屏幕回到上一屏
  21. var _my_Window_Message_terminateMessage = Window_Message.prototype.terminateMessage;
  22. Window_Message.prototype.terminateMessage = function() {
  23.   _my_Window_Message_terminateMessage.call(this);
  24.  
  25.   if($gameSystem._needReplayItemMenu){
  26.     SceneManager.pop();
  27.   }
  28. };
  29.  
  30. //因为消息是在Scene_Map中的一个window,所以要显示消息的话,得回到Scene_Map中,所以这里把Scene_Map push进了栈堆,同时显示出消息
  31. var _my_Scene_Item_useItem = Scene_Item.prototype.useItem;
  32. Scene_Item.prototype.useItem = function() {
  33.   _my_Scene_Item_useItem.call(this);
  34.  
  35.   SceneManager.push(Scene_Map);
  36.   $gameMessage.add('使用了:'+this.item().name);
  37.   $gameSystem._needReplayItemMenu = true;
  38. };
  39.  
  40. //创建道具栏的分类的时候,调用我们的设置最后选择的分类的方法
  41. var _my_Scene_Item_createCategoryWindow = Scene_Item.prototype.createCategoryWindow;
  42. Scene_Item.prototype.createCategoryWindow = function() {
  43.   _my_Scene_Item_createCategoryWindow.call(this);
  44.  
  45.   this.setSelectLast();
  46. };
  47.  
  48. //当分类栏点击确定的时候,把最后选择的分类栏的下标保存下来
  49. var _my_Scene_Item_onCategoryOK = Scene_Item.prototype.onCategoryOk;
  50. Scene_Item.prototype.onCategoryOk = function() {
  51.   _my_Scene_Item_onCategoryOK.call(this);
  52.  
  53.   $gameSystem._categoryWindowSelectInx = this._categoryWindow._index;
  54.  
  55. };
  56.  
  57. //如果最后选择的分类栏的下标存在,则默认选中
  58. Scene_Item.prototype.setSelectLast = function(){
  59.   if($gameSystem._needReplayItemMenu){
  60.     //关闭是否回到Scene_Map清空栈堆的标示
  61.     $gameSystem._needReplayItemMenu = false;
  62.     this._categoryWindow._index = isNaN($gameSystem._categoryWindowSelectInx) ? 0 : $gameSystem._categoryWindowSelectInx;
  63.     this._categoryWindow.activate();
  64.   }
  65. };


接下来得创建道具


这里稍微提一下,这里的scope选none,意思就是随便用,如果scope是1 ally及后面的话,会弹出submenu来选择使用对象,所以我这里就直接选的none

最后插件管理里面加起来,跑起来



使用了道具,弹出提示,然后关闭之后又回到了道具栏,呼`````
作者: yochii    时间: 2016-4-5 03:49
bulangnisi 发表于 2016-4-4 22:18
呼~~~

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

天哪……………………简直太感谢了!!!!!!!原来每次scene_map都要清空栈…………总之太感谢了!!!!
作者: BBB    时间: 2016-4-7 14:27
专业的就是不一样,几天就顶我几个月的研究
作者: Ai唯心    时间: 2016-4-9 22:28
加油!!。。。
作者: bulangnisi    时间: 2016-4-11 18:10
有几天没来更新了,在做其它的事儿

这会有空了,马上来把我的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刷新着呢

上代码
JAVASCRIPT 代码复制
  1. /*:
  2.  * @plugindesc 主场景小提示插件
  3.  * @author BenJ
  4.  *
  5.  *
  6.  *
  7.  * @param tipIconPadding
  8.  * @desc 如果带icon图标的话,文字与图标的间距
  9.  * @default 36
  10.  *
  11.  *
  12.  * @help
  13.  *
  14.  * Plugin Command:
  15.  *
  16.  * myTipWindow showTip msg  #添加一条消息
  17.  * myTipWindow showTip msg iconId  #添加一条带icon的消息
  18.  *
  19.  *
  20.  */
  21. var parameters = PluginManager.parameters('myTipWindow');
  22. var tipIconPadding = Number(parameters['tipIconPadding'] || 36);
  23.  
  24. var _myGame_Interpreter_pluginCommand = Game_Interpreter.prototype.pluginCommand;
  25.  
  26. Game_Interpreter.prototype.pluginCommand = function(command,args){
  27.   _myGame_Interpreter_pluginCommand.call(this);
  28.   if(command == 'myTipWindow'){
  29.     $gameMap._showTip = true;//是否需要打开tip窗口
  30.     $gameMap._isShownTip = true;//tip窗口是否被打开
  31.     $gameMap._tip = {
  32.       msg : args[0] ,//tip的文本
  33.       icon : args[1]  //tip的图标
  34.       //showed : false //是否已经显示过该信息  防止一直刷新
  35.     };
  36.     this.setWaitMode('tipMessage');
  37.   }
  38. };
  39.  
  40. //修改Game_Map初始化,增加tip相关属性
  41. var _jGame_Map_setup = Game_Map.prototype.setup;
  42. Game_Map.prototype.setup = function(mapId) {
  43.   _jGame_Map_setup.call(this,mapId);
  44.   this._showTip = false;
  45.   this._isShownTip = false;
  46.   this._tip = {};
  47. };
  48.  
  49. //重写Scene_Map初始化部分窗口函数
  50. var _jScene_Map_createAllWindows = Scene_Map.prototype.createAllWindows;
  51. Scene_Map.prototype.createAllWindows = function() {
  52.   _jScene_Map_createAllWindows.call(this);
  53.   this.createTipWindow();
  54. };
  55.  
  56. //创建tipWindow加入到Scene_Map
  57. Scene_Map.prototype.createTipWindow = function() {
  58.   this._tipWindow = new Window_JTip();
  59.   this.addWindow(this._tipWindow);
  60. };
  61.  
  62. //todo 自定义提示窗体
  63. function Window_JTip() {
  64.   this.initialize.apply(this, arguments);
  65. }
  66.  
  67. Window_JTip.prototype = Object.create(Window_Base.prototype);
  68. Window_JTip.prototype.constructor = Window_JTip;
  69.  
  70. Window_JTip.prototype.initialize = function() {
  71.   var width = this.windowWidth();
  72.   var height = this.windowHeight();
  73.   Window_Base.prototype.initialize.call(this, (Graphics.width - width)/2, 200, width, height);
  74.   this.openness = 0;//尝试了好多次,原来这就是关键点所在,初始化的时候,把openness设置为0的时候,调用open函数的时候,才会去刷新
  75. };
  76.  
  77. //窗口宽度
  78. Window_JTip.prototype.windowWidth = function() {
  79.   return Number(this.width || 240);
  80. };
  81.  
  82. //窗口高度
  83. Window_JTip.prototype.windowHeight = function() {
  84.   return this.fittingHeight(1);
  85. };
  86.  
  87. //刷新tip窗口
  88. Window_JTip.prototype.refresh = function(_tipArgs) {
  89.   this.contents.clear();
  90.   //this.visible = $gameMap.isTipWindowShow();
  91.   if(_tipArgs){
  92.     //this.resetTipWindow(_tipArgs);//这个函数里面执行过显示窗口函数核心Bitmap的resize,如果在后面resize,会清空内容栏,具体我也没看到哪里在清空,但是事实如此
  93.     //todo 绘制消息板
  94.     var _cnTest =  /[\u4E00-\u9FA5]/;
  95.     var _msg = _tipArgs.msg.toString();
  96.     var _width = 0;
  97.     //通过文字计算窗口大小
  98.     for(var i=0 ; i<_msg.length ; i++){
  99.       //判断是否为中文,不是中文,只用字体大小的一半
  100.       if(_cnTest.test(_msg[i])){
  101.         _width += this.standardFontSize() ;
  102.       }else{
  103.         _width += this.standardFontSize() / 2 ;
  104.       }
  105.     }
  106.     //添加默认内容左右padding
  107.     _width += this.standardPadding() * 2;
  108.  
  109.     //是否包含icon
  110.     if(_tipArgs.icon != undefined && _tipArgs.icon != 0){
  111.       _width += tipIconPadding;
  112.     }
  113.  
  114.     this.width = _width;
  115.     this.x = (Graphics.width - _width)/2;
  116.     this.contents.resize(this.contentsWidth(),this.contentsHeight());//这里设置的内容的宽度,如果照官方的算法,这个宽度可能会显示不完我们的内容
  117.  
  118.     //todo 绘制内容
  119.     //_tipArgs.showed = true; //显示过的信息改变此标示
  120.     if(_tipArgs.icon){
  121.       //显示带icon的信息
  122.       this.drawIcon(_tipArgs.icon,0,0);
  123.       this.drawText(_tipArgs.msg.toString(),tipIconPadding,0);
  124.     }else{
  125.       //显示普通信息
  126.       this.drawText(_tipArgs.msg.toString(),0,0);
  127.     }
  128.   }
  129. };
  130.  
  131. Window_JTip.prototype.update = function(){
  132.   Window_Base.prototype.update.call(this);
  133.   //根据window_message的实现来看,弹出样式是打开和关闭的时候调用的updateOpen和updateClose
  134.   while(!this.isOpening() && !this.isClosing()){
  135.     //在非打开中和非关闭中,调用一遍所有需要执行的东西
  136.     //message处理的方式是检测文本信息是否已经消费完来启动
  137.     if(this.updateInput()){
  138.       return;
  139.     }else if(this.needShow()){
  140.       this.startTip();
  141.     }else{
  142.       return;
  143.     }
  144.   }
  145. };
  146.  
  147. //启动tip窗口
  148. Window_JTip.prototype.startTip = function() {
  149.   this.refresh($gameMap._tip);
  150.   this.open();
  151.   $gameSystem.disableMenu();
  152.   $gameMap._showTip = false;//启动了窗口就把是否启动tip关掉,否则update一直刷新启动造成死循环了
  153.   this.pause = true;//启动了窗口,并且刷新了窗口之后,设置暂停
  154. };
  155.  
  156. Window_JTip.prototype.needShow = function() {
  157.   return $gameMap._showTip;
  158. };
  159.  
  160. Window_JTip.prototype.isTriggered = function() {
  161.   return (Input.isRepeated('ok') || Input.isRepeated('cancel') || TouchInput.isRepeated());
  162. };
  163.  
  164. //当窗口暂停,并且有按键触发,我们就关闭窗口
  165. Window_JTip.prototype.updateInput = function() {
  166.   if(this.pause){
  167.     if(this.isTriggered()){
  168.       $gameSystem.enableMenu();
  169.       this.pause = false;
  170.       $gameMap._isShownTip = false;
  171.       this.close();
  172.     }
  173.     return true;
  174.   }else{
  175.     return false;
  176.   }
  177. };
  178.  
  179. //这里由于官方内定了好几种等待模式,而且写得比较死,我就直接在官方的函数里拓展了,增加了tipMessage的标示
  180. Game_Interpreter.prototype.updateWaitMode = function() {
  181.   var waiting = false;
  182.   switch (this._waitMode) {
  183.     case 'message':
  184.       waiting = $gameMessage.isBusy();
  185.       break;
  186.     case 'transfer':
  187.       waiting = $gamePlayer.isTransferring();
  188.       break;
  189.     case 'scroll':
  190.       waiting = $gameMap.isScrolling();
  191.       break;
  192.     case 'route':
  193.       waiting = this._character.isMoveRouteForcing();
  194.       break;
  195.     case 'animation':
  196.       waiting = this._character.isAnimationPlaying();
  197.       break;
  198.     case 'balloon':
  199.       waiting = this._character.isBalloonPlaying();
  200.       break;
  201.     case 'gather':
  202.       waiting = $gamePlayer.areFollowersGathering();
  203.       break;
  204.     case 'action':
  205.       waiting = BattleManager.isActionForced();
  206.       break;
  207.     case 'video':
  208.       waiting = Graphics.isVideoPlaying();
  209.       break;
  210.     case 'image':
  211.       waiting = !ImageManager.isReady();
  212.       break;
  213.     case 'tipMessage':
  214.       waiting = $gameMap._isShownTip;
  215.       break;
  216.   }
  217.   if (!waiting) {
  218.     this._waitMode = '';
  219.   }
  220.   return waiting;
  221. };


关于插件的调用和设置就不多说了,下面看成果图
作者: Neras    时间: 2016-12-28 22:24
司机我要加加你,想请教怎么在插件里面选中对象 = =原谅我没看你的源码
作者: xjzsq    时间: 2017-1-1 15:40
给您提供一个有助于您研究的东西吧!
https://rpg.blue/thread-385523-1-1.html
这个帖子不仅汉化了脚本注释,还吧原生脚本给分离开来了,十分符合RMVA的脚本显示方式,可以参考一下。
另外,建议您去看看VA的帮助文档,里面解读篇和实践篇部分介绍了脚本系统RGSS3的结构,尽管是Ruby语言,而且有很多方法MV里面都没有了(比如alias),但是对于理解MV的脚本系统还是很有帮助的,因为MV的脚本思想和VA的非常相似。
希望对您有所帮助!!!加油!!!
作者: 星夜無月    时间: 2017-1-2 04:11
先膜拜一下,回头过来仔细看。
作者: woodytt    时间: 2017-9-8 14:43
bulangnisi 发表于 2016-4-11 18:10
有几天没来更新了,在做其它的事儿

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

提示cannot read property "filter"of undefined
作者: zhuzhe552481    时间: 2017-9-27 23:40
不错不错~~~




欢迎光临 Project1 (https://rpg.blue/) Powered by Discuz! X3.1