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,将插件加入进来,运行游戏
屏幕快照 2016-03-31 17.02.46.png (861.82 KB, 下载次数: 13)
下载附件 保存到相册
2016-3-31 17:03 上传
当当当!就出现了!可是你点击是没有任何效果的,因为这里还没给设置绑定的事件,下次我会继续讲解如何绑定事件.
有几天没来更新了,在做其它的事儿
这会有空了,马上来把我的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刷新着呢
上代码
- /*:
- * @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[i])){
- _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;
- };
关于插件的调用和设置就不多说了,下面看成果图
看到你的回复之后,搞了我好几个小时,毕竟是第一个向我提问的人啊,我这不就全力以赴的帮你研究这个了么,总算弄出来了
过程中也发现了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不清空栈堆的标示改为失效
接下来上代码
- /**
- * 使用过道具之后,弹出消息再回到道具页面
- */
- Scene_Map.prototype.start = function() {
- Scene_Base.prototype.start.call(this);
- //如果需要重新打开道具页面,则不清空栈
- if(!$gameSystem._needReplayItemMenu){
- SceneManager.clearStack();
- }
- if (this._transfer) {
- this.fadeInForTransfer();
- this._mapNameWindow.open();
- $gameMap.autoplay();
- } else if (this.needsFadeIn()) {
- this.startFadeIn(this.fadeSpeed(), false);
- }
- this.menuCalling = false;
- };
- //为了不影响其他正常的消息展示,这里同样使用之前的标示来控制。当这个标示有效的时候,SceneManager.pop();让屏幕回到上一屏
- var _my_Window_Message_terminateMessage = Window_Message.prototype.terminateMessage;
- Window_Message.prototype.terminateMessage = function() {
- _my_Window_Message_terminateMessage.call(this);
- if($gameSystem._needReplayItemMenu){
- SceneManager.pop();
- }
- };
- //因为消息是在Scene_Map中的一个window,所以要显示消息的话,得回到Scene_Map中,所以这里把Scene_Map push进了栈堆,同时显示出消息
- var _my_Scene_Item_useItem = Scene_Item.prototype.useItem;
- Scene_Item.prototype.useItem = function() {
- _my_Scene_Item_useItem.call(this);
- SceneManager.push(Scene_Map);
- $gameMessage.add('使用了:'+this.item().name);
- $gameSystem._needReplayItemMenu = true;
- };
- //创建道具栏的分类的时候,调用我们的设置最后选择的分类的方法
- var _my_Scene_Item_createCategoryWindow = Scene_Item.prototype.createCategoryWindow;
- Scene_Item.prototype.createCategoryWindow = function() {
- _my_Scene_Item_createCategoryWindow.call(this);
- this.setSelectLast();
- };
- //当分类栏点击确定的时候,把最后选择的分类栏的下标保存下来
- var _my_Scene_Item_onCategoryOK = Scene_Item.prototype.onCategoryOk;
- Scene_Item.prototype.onCategoryOk = function() {
- _my_Scene_Item_onCategoryOK.call(this);
- $gameSystem._categoryWindowSelectInx = this._categoryWindow._index;
- };
- //如果最后选择的分类栏的下标存在,则默认选中
- Scene_Item.prototype.setSelectLast = function(){
- if($gameSystem._needReplayItemMenu){
- //关闭是否回到Scene_Map清空栈堆的标示
- $gameSystem._needReplayItemMenu = false;
- this._categoryWindow._index = isNaN($gameSystem._categoryWindowSelectInx) ? 0 : $gameSystem._categoryWindowSelectInx;
- this._categoryWindow.activate();
- }
- };
接下来得创建道具
这里稍微提一下,这里的scope选none,意思就是随便用,如果scope是1 ally及后面的话,会弹出submenu来选择使用对象,所以我这里就直接选的none
最后插件管理里面加起来,跑起来
这次来了解一下 精灵(Sprite)元素
对于做游戏的大神都知道精灵(Sprite)对于游戏的重要性(ps:我不是大神,我是初心者。。。)
这里我仅用自己的思路大致来描述一下,有偏差的地方请同学们指正一下。
拿这个rpgmaker来说,里面大大小小的东西都可以算作是精灵,首页显示的背景算是,地图的每个单元算是,更不用说我们操作的角色了,你游戏中几乎所有看到的东西动起来,仅仅是精灵单元改变自己的图片而已,由于游戏的fps(帧)刷新得比较快,所以我们看到的就是一个连贯动作的精灵(就像我小时候在课本每页的右下角画小人,然后每一页画一个动作,然后把书翻起来的话,就会看到一个连贯的动作,就是这个意思)。
在rpgmaker中大多数对象都具有update的方法,这就像是我们在翻书的动作,然后里面的图片素材就是我们画的小人。
所以这次我们来做个自己想要的标题页,可以动起来的,主要操作的东西,就是精灵(Sprite)。
在rpg_scene.js的233行找到创建标题页的代码
其中createBackground方法就给了我们很好的示例,他在里面创建了2个精灵(说白了你甚至可以把精灵直接理解为一个图片都行- -),
- Scene_Title.prototype.createBackground = function() {
- this._backSprite1 = new Sprite(ImageManager.loadTitle1($dataSystem.title1Name));
- this._backSprite2 = new Sprite(ImageManager.loadTitle2($dataSystem.title2Name));
- this.addChild(this._backSprite1);
- this.addChild(this._backSprite2);
- };
这里就是我们在System里面选择的图片
$dataSystem.title1Name 背景图片名称 左一列表所选择的
$dataSystem.title2Name 前景图片名称 左二列表所选择的
然后通过ImageManager中的loadTitle1方法,传入名称去获取到图片
稍微描述一下这个ImageManager,他里面的每个loadxxxx的方法都直接对应到的是项目文件下img文件里的每个文件夹
而且他每个load方法的实现也是很容易看懂的
创建了精灵之后,还需要把这个精灵加入到我们的屏幕(Scene)中去
就像这句的写法就行了, this.addChild(this._backSprite1);
具体精灵有哪些参数和方法,大家可以去官方文档查看,这里不做介绍了
现在开始做我们的标题页,这里为想做一个背景在移动,中间再放个人物,然后人物背后一遍遍的扩散自己本身,效果如下
首先我们把背景换成 SeaofClouds 这个图片,这里不是要你直接在System里面去改,因为那样的话图片是不会动的,而我们需要一个水平移动的背景
老样子,复制源码过来改改改
- var _myScene_Title_create = Scene_Title.prototype.create;
- Scene_Title.prototype.create = function() {
- _myScene_Title_create.call(this);//原本官方执行的方法不去动它的,他该怎么创建就怎么创建
- //创建我们自己的背景;
- this.create_myTitleSprite();
- };
然后实现我们自己创建背景的方法
- Scene_Title.prototype.create_myTitleSprite = function(){
- this._myBackSprite = new Sprite(ImageManager.loadParallax('SeaofClouds'));
- this.addChild(this._myBackSprite);
- };
这里的效果跟官方设置背景的效果是一模一样的,如果仅是这样做的话,就等于用我们的方式再画了一个精灵贴上去而已,也就是然并卵,我们要让这个背景水平动起来
- var _myScene_Title_update = Scene_Title.prototype.update;
- Scene_Title.prototype.update = function() {
- _myScene_Title_update.call(this);
- this._myBackSprite.x += 1 ;
- }
这里是让标题页的每一帧刷新的时候,让我们的背景精灵 x 坐标 +1 ,先运行看看效果吧
是不是看到问题出来了,这我们的背景精灵向左移动的同时,把他覆盖的下面的官方设置的背景元素露出来了。也许你有些编程的思路的话,你会考虑放两张图片上去,两张图片水平放置,一张图片移动的时候另一张也跟着移动出来吧啦吧啦吧啦吧啦...... 太麻烦了,rpgmaker提供了另一种精灵对象就能轻松完成这个任务,就是平铺精灵(TilingSprite)。我们先把创建的普通精灵改成平铺精灵
- Scene_Title.prototype.create_myTitleSprite = function(){
- //this._myBackSprite = new Sprite(ImageManager.loadParallax('SeaofClouds'));//普通精灵
- this._myBackSprite = new TilingSprite(ImageManager.loadParallax('SeaofClouds'));//平铺精灵
- //平铺精灵需要设置他默认的位置,否则是看不到的
- //通过平铺精灵的move函数设置他的位置
- //参数1 TilingSprite(平铺精灵) 的 X 坐标
- //参数2 TilingSprite(平铺精灵) 的 Y 坐标
- //参数3 TilingSprite(平铺精灵) 的宽度
- //参数4 TilingSprite(平铺精灵) 的高度
- //这里的Graphics的width和height是游戏窗口显示的宽度和高度,我们这样设置就会让这个平铺精灵全部占满屏幕
- this._myBackSprite.move(0,0,Graphics.width,Graphics.height);
- //将我们的精灵添加到标题页
- this.addChild(this._myBackSprite);
- };
接下来让他动起来,但是平铺精灵跟普通精灵不同,要让他水平动的话,是要改变它的origin.x(而非普通精灵的 x) ,所以继续修改我们的update方法
- var _myScene_Title_update = Scene_Title.prototype.update;
- Scene_Title.prototype.update = function() {
- _myScene_Title_update.call(this);
- //this._myBackSprite.x += 1 ;
- this._myBackSprite.origin.x += 1 ;
- }
如此,就能看到效果,背景平铺的动起来了,滚动的快慢与我们设置每一帧执行update移动的大小有关
比如你这样设置 this._myBackSprite.origin.x += 100 ;
这样屏幕动得飞快(当然你还可以更快 -- ),这里希望大家把脑洞打开,知道超级机器人大战吧?里面的必杀场景是不是特流弊的场景飞快滚动?是的,我们如果去写个插件,当使用某个必杀的时候,这样把战斗背景一弄,就能做出流弊的必杀场景了。
继续来完成这次的目标,放个人物立绘上去,我这里选择的是dlc Cover Art Characters 里面的Package2_3这个人物,把他放在了我们项目文件的picture文件里。同样,我们在创建我们标题页的方法里面新增一个普通精灵放上去。
- Scene_Title.prototype.create_myTitleSprite = function(){
- //this._myBackSprite = new Sprite(ImageManager.loadParallax('SeaofClouds'));//普通精灵
- this._myBackSprite = new TilingSprite(ImageManager.loadParallax('SeaofClouds'));//平铺精灵
- //平铺精灵需要设置他默认的位置,否则是看不到的
- //通过平铺精灵的move函数设置他的位置
- //参数1 TilingSprite(平铺精灵) 的 X 坐标
- //参数2 TilingSprite(平铺精灵) 的 Y 坐标
- //参数3 TilingSprite(平铺精灵) 的宽度
- //参数4 TilingSprite(平铺精灵) 的高度
- //这里的Graphics的width和height是游戏窗口显示的宽度和高度,我们这样设置就会让这个平铺精灵全部占满屏幕
- this._myBackSprite.move(0,0,Graphics.width,Graphics.height);
- this._myForeSprite = new Sprite(ImageManager.loadPicture('Package2_3'));//因为这里我仅是放一个人物立绘上去,所以就使用平铺精灵就好了
- //设置人物立绘的位置
- this._myForeSprite.anchor.x = 0.5; //设置立绘精灵的横向锚点,初始值是0,最大值为1,从左到右,最左是0,最右是1,这里设置的0.5就表示正中间
- this._myForeSprite.anchor.y = 1; //设置立绘精灵的纵向锚点,初始值是0,最大值为1,从上到下,最上是0,最下是1,这里设置的1就表示最底部
- this._myForeSprite.x = Graphics.width / 2; //设置立绘精灵的水平坐标为整个屏幕宽度的一半,与我们之前设置的横向锚点配合,立绘精灵就会在屏幕的水平正中间
- this._myForeSprite.y = Graphics.height; //设置立绘精灵的纵向坐标为整个屏幕的高度,与我们之前设置的纵向锚点配合,立绘精灵的最底部就会跟屏幕的最底部重合
- //将我们的精灵添加到标题页
- this.addChild(this._myBackSprite);
- this.addChild(this._myForeSprite);
- };
这样背景屏幕水平滚动的时候,中间还会有一个人物立绘在屏幕正中间,并且底部与屏幕底部贴合。
接下来做个看上去奇怪的效果,让人物立绘背后有个一模一样的人物扩散开来,一直循环,同样继续修改create_myTitleSprite方法
- Scene_Title.prototype.create_myTitleSprite = function(){
- //this._myBackSprite = new Sprite(ImageManager.loadParallax('SeaofClouds'));//普通精灵
- this._myBackSprite = new TilingSprite(ImageManager.loadParallax('SeaofClouds'));//平铺精灵
- //平铺精灵需要设置他默认的位置,否则是看不到的
- //通过平铺精灵的move函数设置他的位置
- //参数1 TilingSprite(平铺精灵) 的 X 坐标
- //参数2 TilingSprite(平铺精灵) 的 Y 坐标
- //参数3 TilingSprite(平铺精灵) 的宽度
- //参数4 TilingSprite(平铺精灵) 的高度
- //这里的Graphics的width和height是游戏窗口显示的宽度和高度,我们这样设置就会让这个平铺精灵全部占满屏幕
- this._myBackSprite.move(0,0,Graphics.width,Graphics.height);
- this._myForeSprite = new Sprite(ImageManager.loadPicture('Package2_3'));//因为这里我仅是放一个人物立绘上去,所以就使用平铺精灵就好了
- //设置人物立绘
- this._myForeSprite.anchor.x = 0.5; //设置立绘精灵的横向锚点,初始值是0,最大值为1,从左到右,最左是0,最右是1,这里设置的0.5就表示正中间
- this._myForeSprite.anchor.y = 1; //设置立绘精灵的纵向锚点,初始值是0,最大值为1,从上到下,最上是0,最下是1,这里设置的1就表示最底部
- this._myForeSprite.x = Graphics.width / 2; //设置立绘精灵的水平坐标为整个屏幕宽度的一半,与我们之前设置的横向锚点配合,立绘精灵就会在屏幕的水平正中间
- this._myForeSprite.y = Graphics.height; //设置立绘精灵的纵向坐标为整个屏幕的高度,与我们之前设置的纵向锚点配合,立绘精灵的最底部就会跟屏幕的最底部重合
- this._myForeSprite2 = new Sprite(ImageManager.loadPicture('Package2_3'));//背后闪现的幻影
- //设置人物立绘的幻影,与人物立绘相同
- this._myForeSprite2.anchor.x = 0.5;
- this._myForeSprite2.anchor.y = 1;
- this._myForeSprite2.x = Graphics.width / 2;
- this._myForeSprite2.y = Graphics.height;
- //将我们的精灵添加到标题页
- this.addChild(this._myBackSprite);
- this.addChild(this._myForeSprite2);
- //这里添加的时候,我们首先是添加的幻影的那个精灵,再添加的立绘的精灵,因为我想达到的效果是在人物立绘的背后出现幻影,如果先添加立绘精灵的话,这个幻影就会在立绘精灵的前面闪动,这里各位可以试着调换一下顺序就明白了
- this.addChild(this._myForeSprite);
- };
到此,实际的效果只是立绘精灵和幻影精灵重叠了而已,没有任何动作,我们修改update方法来让幻影精灵动起来,实现思路是,我让幻影精灵的透明度(Opacity)慢慢的减弱,并且让幻影精灵的缩放(Scale)慢慢的变大,就能表现出一种向外拓展的效果了,同时幻影精灵完全变成透明的时候,我们要重置他的缩放(Scale)和透明度(Opacity)。
- var _myScene_Title_update = Scene_Title.prototype.update;
- Scene_Title.prototype.update = function() {
- _myScene_Title_update.call(this);
- //this._myBackSprite.x += 1 ;
- this._myBackSprite.origin.x += 1 ;
- //幻想精灵透明度(Opacity)完全透明的时候,重置他的缩放和透明度
- if(this._myForeSprite2.opacity <= 0){
- this._myForeSprite2.opacity = 255;
- this._myForeSprite2.scale.x = 1;
- this_myForeSprite2.scale.y = 1 ;
- }
- //幻想精灵并没有完全透明的时候,我们让他的透明度一直减小,同时通过改变他的缩放来看上去一种扩散的感觉
- else{
- this._myForeSprite2.opacity -= 3 ;
- this._myForeSprite2scale.x += 0.003;
- this._myForeSprite2.scale.y += 0.003;
- }
最后把插件加入到游戏里面去
到此,我们这次的目的就能完成了,最后的效果就是这样(脑补一下,动起来的喔)
只要大家脑洞大开,这样继续拓展,我觉得做些啥流弊效果都没问题的吧
睡之前来一发今天的研究成果,做一个改变文字颜色的小插件
首先来说下设计流程,对于官方的源码来看,文字显示出来的样子其实是一个字符一个字符处理出来的,处理过程中他做了一个转译和判断
这里主要说一些要改变文字颜色的关键部分,其余的请同学们自己多研究研究
在处理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 意思就是把颜色还原成默认的。
接下来简单说明一下事件这块的东西
在我们给菜单栏添加了一个选项之后,看是能看到了,但是点击之后是没有任何效果的,那是因为没有有效的事件绑定到选项的点击,所以是没有反应的
我认为点击之后弹出个消息啥的太没新意了,所以咱们这次做个在启动游戏的初始界面加入一个图鉴的选项(就是官方自带的那个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 ,把原方法保存下来,执行的时候先让他们调用一下原方法执行原本执行的东西,然后在后面执行我们拓
展的东西。
RPGMakerMV的插件学习相关推荐
- jquery插件学习(六)
继续上面的示例说明 把其中的参数默认值作为$.fn.color对象的属性单独进行设计,然后借助jquery.extend方法,覆盖原来的参数选项即可 ;(function($){$.extend($. ...
- Gradle插件学习笔记(二)
之前介绍了Gradle插件的开发,这次会对功能进行一部分拓展,建议没有读过第一篇文章的朋友,先看一下Gradle插件学习笔记(一) Extension 之前的文章提到过,如何编写一个插件,但是并不能通 ...
- Qt 插件学习(一)
插件是什么 注意:这儿暂时不考虑静态插件(潜意识中总觉得它根本就不算插件). 插件是一个动态库(共享库).动态库是一个独立的文件中的独立模块,可被多个程序访问. 先看动态库的两种用法 1. 程序链接时 ...
- Unity游戏编程自学书籍、插件学习与视频教程
本文主要介绍用Unity进行游戏编程开发的自学书籍.需要学习的插件以及相关视频教程. 由于Unity采用C#进行开发,所以在进行下面的学习之前,确保自己有一定的C#基础并对基础的数据结构与算法有一定的 ...
- CAD插件学习系列教程(六) 文字自动避让工具
我是王石头,这是CAD插件学习系列教程的第六篇,这次推荐的cad插件是重叠文字自动避让的工具. 在日常的cad的制图中,文字压盖情况是无法避免的,尤其在一些大型图件中,如果有大量的文字压盖情况需要修改 ...
- WorldWind学习系列十一:Virtual Earth插件学习
学习WorldWind有很长时间了,理论学习算是基本完成了.我体会是WW的学习主要分为两大步:WW框架体系学习和WW插件学习.学习WW插件逐步深入后,必然要首先学习Direct3D编程,这也算是我的经 ...
- CAD插件学习系列教程(九) 简单好用的CAD图层管理工具
我是王石头,不知不觉,这已是CAD插件学习系列教程的第九篇了,这次推荐一款非常好用的CAD图层管理工具. 众所周知,在CAD制图时,掌握CAD图层的快速开关是提升制图速度的关键,一个CAD制图高手和一 ...
- 谷歌插件学习之某设计网站非会员去水印下载图片
谷歌插件学习之某设计网站非会员去水印下载图片 一.免责声明:此博客只用来进行技术交流,如有不当之处,请联系我删除! 二.谷歌插件学习网址 三.目标网址去水印及非会员下载原理分析 四.效果如下 五.插件 ...
- CAD插件学习系列教程(三) 高程点内插工具
我是王石头,这是CAD插件学习系列教程的第三篇,这次推荐一款高程点内插的cad工具. 此工具由CAD lisp语言编制而成. 使用方法 1. cad界面下输入appload命令加载: 2. 点击加载后 ...
- CAD插件学习系列教程(十) 高程点注记压盖地物批量处理
大家好,我是王石头,这是CAD插件学习系列教程的第十篇干货文章,这次推荐一款高程点注记压盖地物批量处理插件. 目前使用的数字化成图软件在展绘高程点时,高程注记点存在较多压盖图上已有实体,或者高程注记相 ...
最新文章
- 微信小程序Java登录流程(ssm实现具体功能和问题解决方案)
- 【通知】CCIE LAB北京考场9月1日起开放考试
- java rabbitmq 工具类_RabbitMq通用管理工具类
- 有效的括号—leetcode20
- html5 worker的使用场景
- jq和thinkphp经常使用的几种ajax
- 分析一个文本文件中各个单词出现的频率,把频率最高的10个词打印出来
- 684.冗余连接(力扣leetcode) 博主可答疑该问题
- 音视频 即时通讯Linux源码
- Mac 怎样安装虚拟机(VMware fusion 12)
- 金九银十北漂记第3篇:再见,中国航信!
- ExtendSim 10.0.8发布于 2021年8月7日
- 用74ls90组成二十四进制计数器_89c52定时计数器T2
- 体验了下人民搜索,写个体验报告
- html图片旁边加文字
- openstack项目进展
- 鸿蒙系统学习笔记(一) 鸿蒙系统介绍
- 爱签电子合同:湖北率先实现电子印章跨省互认
- Java安全-注入漏洞(SQL注入、命令注入、表达式注入、模板注入)
- FME在变更地类流向统计中的应用