美文网首页RPG游戏制作大师RPG Maker MV
【RPG Maker MV插件编程】【实例教程4】玩转标题画面

【RPG Maker MV插件编程】【实例教程4】玩转标题画面

作者: 鳗驼螺 | 来源:发表于2017-06-21 20:47 被阅读4542次
    • 作者:Mandarava(鳗驼螺)
    • 微博:@鳗驼螺pro

    标题画面代表了游戏的脸面,必须好看。标题画面对应的类是Scene_Title,所以对于标题画面的修改可以通过重写Scene_Title中的相关方法来实现。

    本文涉及的内容包括:

    1. 美化游戏标题
    2. 让背景动起来
    3. 自定义标题菜单
    4. 美化菜单

    创建一个名为 LEARN_TitleMenu.js 的JavaScript文件,保存到 js/plugins 目录下,在RMMV的插件管理中安装该插件。

    美化游戏标题

    Scene_Title类中可以找到Scene_Title.prototype.createForeground方法,它用于创建标题画面的前景。在原始实现中,这个“前景”就是标题文字(简单的给文字加了描边效果)。我们可以在【数据库 - 系统 - 标题画面】选项中设置是否绘制标题名称。
      这里,我们重新实现createForeground方法,使用一个游戏logo图片来代替标题文字,相对于纯文字来说,图片可以包含图文效果,更高大上。实现代码:

    Scene_Title.prototype.createForeground = function() {
        if ($dataSystem.optDrawTitle) {
            var gameLogo = ImageManager.loadBitmap("img/mndtitle/", "GameLogo");
            this._gameTitleSprite = new Sprite(gameLogo);
            this._gameTitleSprite.anchor = new Point(0.5, 0);
            this._gameTitleSprite.x = Graphics.width / 2;
            this._gameTitleSprite.y = 50;
            this.addChild(this._gameTitleSprite);
        }
    };
    

    这段代码将在画面显示一个Logo图片。我们的logo图片放到img/mndtitle目录下,名称为GameLogo.png,这张图片的内容是“武林外史”几个特效文字。if ($dataSystem.optDrawTitle)用于检测是否要绘制标题(这个就是前面说的在数据库中可以配置的选项)。因为mndtitle是个自定义目录,所以从该目录加载图片时要使用ImageManager.loadBitmap方法,参数指定文件夹位置和加载的图片名称。将这个图片放置在画面中间靠上的位置,如果你要放到正中间,可以使用this.centerSprite()方法。再一次提醒,如果你要用到图片的宽高尺寸来给图片设置坐标,那么设置方法应该放到Scene_Title.prototype.start中去做。最后效果如下。背景是MV自带素材,不是这里的重点;顶部的“武林外史”就是要展示的游戏logo兼游戏标题(随便网络找的一个素材,勿商用)。

    Screenshot1.png

    让背景动起来

    背景部分,如果只是想改成其它静态图,那么在“数据库”中更改就足够了,没必要自己重写。当然,如果你想,或者如果想实现动态背景,那么就需要修改Scene_Title.prototype.createBackground方法了。
      createBackground方法用于创建标题画面的背景。在这个方法的重写版本中,我们先创建一个用于显示背景的精灵,加载动态背景所需要的帧序列图片,然后在Scene_Title.prototype.update方法中按顺序用帧序列图片循环更新精灵的显示图片,从而实现背景的动态效果。先来看一下实现效果:

    Title.gif
      这个动态效果由三张图片组成(直接拿MV中的官方的标题图片 Devil.png 改的,旋转了一下怪物的二只爪子,做成了动画序列)。资源图片放到 img/mndtitle 目录下,名称按动画顺序依次为:TitleBack1.pngTitleBack2.pngTitleBack3.png
      下面是Scene_Title.prototype.createBackground的实现代码:
    Scene_Title.prototype.createBackground = function() {
        this._animFrameImgs=[
            ImageManager.loadBitmap("img/mndtitle/", "TitleBack1"),
            ImageManager.loadBitmap("img/mndtitle/", "TitleBack2"),
            ImageManager.loadBitmap("img/mndtitle/", "TitleBack3")
        ];
        this._animFrames=[0,1,2,1];
        this._currFrame=0;
        this._animDelay=0.2;
        this._backSprite = new Sprite(this._animFrameImgs[0]);
        this.centerSprite(this._backSprite)
        this.addChild(this._backSprite);
    };
    

    首先依次加载三张背景图片存放到this._animFrameImgs中,this._animFrames是一个索引数组,表示一个动画序列,每个元素代表了在this._animFrameImgs数组中的图片索引,this._currFrame是动画序列中的当前帧,this._animDelay表示动画的二帧之间要求的时间间隔,this._backSprite是显示背景图片的精灵。
      因为createBackground方法的原始实现中创建了this._backSprite1this.__backSprite2,且在start方法中使用了这二个精灵,但我们现在重写的createBackground没有创建它们也没有去call原始方法,所以还要先重写Scene_Title.prototype.start方法,删除与_backSprite1_backSprite2有关的代码,如下:

    Scene_Title.prototype.start = function() {
        Scene_Base.prototype.start.call(this);
        SceneManager.clearStack();
        //this.centerSprite(this._backSprite1);//删除
        //this.centerSprite(this._backSprite2);//删除
        this.playTitleMusic();
        this.startFadeIn(this.fadeSpeed(), false);
    };
    

    接下来是Scene_Title.prototype.update方法,在其中每隔this._animDelay的时间就按动画序列中的索引更新到下一张背景图片,代码如下。

    var _Scene_Title_update = Scene_Title.prototype.update;
    Scene_Title.prototype.update = function() {
        _Scene_Title_update.call(this);
    
        this._elapsedSinceLastUpdate = this._elapsedSinceLastUpdate || 0;
        if (this._elapsedSinceLastUpdate >= this._animDelay) {
            this._currFrame++;
            this._currFrame = this._currFrame % this._animFrames.length;
            var animFrameIndex = this._animFrames[this._currFrame];
            this._backSprite.bitmap = this._animFrameImgs[animFrameIndex];
            this._elapsedSinceLastUpdate = 0;
        }
        this._elapsedSinceLastUpdate += 1 / Graphics._fpsMeter.fps;
    };
    

    可以通过Graphics._fpsMeter.fps获取游戏当前的FPS,这里用1 / Graphics._fpsMeter.fps的方式粗略的表示二个update之间的时间间隔。this._elapsedSinceLastUpdate表示自上次更新背景图片以来流逝的游戏时间,在每次update都会累计一次,直到当流逝时间超过this._animDelay后,就开始绘制新一帧的图片,从而不断循环,实现动态背景的不断往复循环。
      当然,这里只是演示一个简单的动画效果,更多关于动画的技巧可以参考另一篇教程:【实例教程5】制作小游戏:坦克大战(上)

    自定义标题菜单

    默认标题画面只显示 开始游戏、继续游戏、设置 三个菜单命令,如果要增加其它的菜单,如:官方网站、致谢 等菜单,那么需要重写二个方法:Window_TitleCommand.prototype.makeCommandListScene_Title.prototype.createCommandWindow。前者用于在菜单画面创建菜单命令,后者用于将事件绑定到菜单。如果你读过之前的一篇教程:玩转菜单初级篇,应该就会猜到 Window_TitleCommand 对应的是标题界面中间偏下那个由白边框围成的菜单区域,菜单的显示由它创建,而事件处理由它所在的场景,也就是Scene_Title来绑定和处理。菜单绑定事件用setHandler,相关的实现也可以参考“玩转菜单初级篇”一文。最后实现代码如下:

    var _Window_TitleCommand_makeCommandList = Window_TitleCommand.prototype.makeCommandList;
    Window_TitleCommand.prototype.makeCommandList = function () {
        _Window_TitleCommand_makeCommandList.call(this);
    
        this.addCommand("官方网站", 'homepage');//增加一个新菜单,标识符为 homepage
    };
    
    var _Scene_Title_createCommandWindow = Scene_Title.prototype.createCommandWindow;
    Scene_Title.prototype.createCommandWindow = function() {
        _Scene_Title_createCommandWindow.call(this);
    
        this._commandWindow.setHandler('homepage', this.commandHomepage.bind(this)); //将标识符为homepage的菜单绑定到commandHomepage方法
    };
    
    Scene_Title.prototype.commandHomepage = function() {
        this._commandWindow.activate();
        //打开url
        var cmd;
        if (process.platform === 'darwin') cmd = 'open';
        if (process.platform === 'win32') cmd = 'explorer.exe';
        if (process.platform === 'linux') cmd = 'xdg-open';
        var spawn = require('child_process').spawn;
        spawn(cmd, ["http://www.jianshu.com/nb/13204998"]);
    };
    

    这样,在游戏时点击标题画面的“官方网站”菜单后会打开本人在简书写的关于RMMV的文章专题首页。这里,this._commandWindow.activate();是重新激活当前游戏窗口,因为使用其它进程打开网页时会造成游戏窗口失焦。
      如果要删除菜单,更简单,只需要重写Window_TitleCommand.prototype.makeCommandList方法,删除不想看到的菜单即可,当然不要去call原始方法。

    美化菜单

    默认的菜单就是白边框围成的几个菜单文本,与目前的画面不太搭,所以现在的目标是用美化的图片来代替文本做成菜单。先看看完成效果:


    Final

    四个菜单全部由图片做成,实际上它们是图片按钮Sprite_Button对象,本质上也是Sprite精灵,但能绑定方法能回应点击。像原始菜单一样,这些图片菜单可以用鼠标点击,也可以用方向键上下移动切换菜单,在当前选中的菜单前面会显示一个指示标记。
      要实现这个效果,首先要重写Scene_Title.prototype.create方法,这个方法用于初始化图片菜单的相关资源。实现代码:

    var _Scene_Title_create = Scene_Title.prototype.create;
    Scene_Title.prototype.create = function () {
        _Scene_Title_create.call(this);
        this._commandWindow.visible = false;//不显示原始的文本菜单
        this._commandWindow.x=Graphics.width;//移到画面外去,否则虽然不显示仍能点击
        var btnimgs=["CmdStartGame", "CmdContinueGame", "CmdOptions", "CmdHomepage"];
        var clicks=[
            function(){this.commandNewGame(); SoundManager.playOk();},
            function(){this.commandContinue(); SoundManager.playOk();},
            function(){this.commandOptions(); SoundManager.playOk();},
            function(){this.commandHomepage(); SoundManager.playOk();}
        ];
        this._cmdButtons=[];//所有图片菜单
        for(var i in btnimgs){
            var sprite=new Sprite_Button();
            sprite.width=184;
            sprite.height=53;
            sprite.bitmap=ImageManager.loadBitmap("img/mndtitle/", btnimgs[i]);
            //sprite.anchor=new Point(0.5,0.5);//不要设置,设置这个会出现菜单点不中的问题,不清楚原因。
            sprite.x=Graphics.width/2-92;
            sprite.y=360+60*i;
            sprite.setClickHandler(clicks[i].bind(this));
            this._cmdButtons.push(sprite);
            this.addChild(sprite);
        }
        this._cmdSelect=new Sprite(ImageManager.loadBitmap("img/mndtitle/", "CmdSelect"));//选中菜单的指示器
        this._cmdSelect.anchor=new Point(1,0);//因为按钮的anchor是默认的(0,0),这个指示器要放在按钮左侧,所以让它的anchor为(1,0)更容易定位
        this.addChild(this._cmdSelect);
    };
    

    this._commandWindow.visible用于将原始的文本菜单隐藏掉,并将它放到画面外部,因为这个菜单即使不显示也能点击到,也能用上下方向键切换选择其菜单,不过,我们又不能删除它。当然,你可以重写Scene_Title.prototype.createCommandWindow方法,并只删除原始代码中的this.addWindow(this._commandWindow);,从而真的不让它出现,但那样你就得自己实现一些方法,比如:在游戏启动时原始的文本菜单会根据是否有存档自动选择 继续游戏 还是 开始游戏,可以用上下方向键切换不同的菜单,可以用确定键打开选中的菜单,如果不让它出现,那么这些功能就得自己去为图片菜单实现。所以,为了简单起见,我们让图片菜单与原始的文本菜单保持联动,每个图片菜单与原始的文本菜单一一对应。
      btnimgs是各个图片菜单的图片名称(也就是 img/mndtitle文件夹下与菜单相关的图片的名称),要按照原始文本菜单的顺序排列,以便使图片菜单与原始的文本菜单顺序是一致的。
      clicks是保存了各个菜单按钮要绑定的方法的一个数组,这个数组中,也是按顺序定义好各个图片菜单对应要绑定的方法。所有图片菜单都是一个Sprite_Button对象,这里注意,不要忘记设置它们的宽高,这会影响点击的热区,否则可能点在菜单上却没有反应。使用sprite.setClickHandler(clicks[i].bind(this));将图片菜单与对应的方法进行绑定。
      this._cmdButtons是存放图片按钮的数组,在update方法会用到。this._cmdSelect是个指示器,会显示在当前选中的菜单左侧。
      接下来就是要让选择指示器的显示在选中的菜单左侧,这个需要在Scene_Title.prototype.update中处理,在update方法中添加以下代码到最后(前文中已经重写了该方法,现在再添加代码进去):

        var btnSelect = this._cmdButtons[this._commandWindow.index()];
        this._cmdSelect.x = btnSelect.x;
        this._cmdSelect.y = btnSelect.y;
    

    this._commandWindow.index()是用来获取原始的文本菜单中当前所选中的菜单索引,根据这个索引我们用this._cmdButtons[index]来从图片菜单数组中得到对应的图片菜单对象,然后将指示器放到它左侧。

    补充:优化菜单点选方式

    菜单的功能基本完成,但在运行过程中,我们发现它的工作方式与RMMV原始的方式不太一样。原始方式是:点击一个菜单时,如果该菜单不是当前选中的菜单,则只是选中它(在菜单上显示一个白色透明背景的方框,以呈高亮显示),让它成为当前选中的菜单;如果是当前选中的菜单,则直接进入该菜单功能。我们的菜单点击任何一个按钮,不论指示器在哪里,都会直接进入菜单功能。

    1. 如果要实现RMMV自带的原始点选方式,可以将clicks换成如下定义:
    var clicks=[
      function(){if(this._commandWindow.index()!=0){this._commandWindow.select(0);}else{this._commandWindow.processOk();} },
      function(){if(this._commandWindow.index()!=1){this._commandWindow.select(1);}else{this._commandWindow.processOk();} },
      function(){if(this._commandWindow.index()!=2){this._commandWindow.select(2);}else{this._commandWindow.processOk();} },
      function(){if(this._commandWindow.index()!=3){this._commandWindow.select(3);}else{this._commandWindow.processOk();} }        
    ];
    

    这种方式其实还不是很好,对于一个非当前选中的菜单,需要双击才能进入菜单功能,个人认为更好的方式是下面这种:

    1. 点击任何一个菜单(不论它是否为当前选中的菜单),指示器都会指向它,并直接进入菜单的功能,要实现这种方式,更简单:
    var clicks=[
      function(){ this._commandWindow.select(0); this._commandWindow.processOk(); },
      function(){ this._commandWindow.select(1); this._commandWindow.processOk(); },
      function(){ this._commandWindow.select(2); this._commandWindow.processOk(); },
      function(){ this._commandWindow.select(3); this._commandWindow.processOk(); }
    ]
    

    目前的源码中推荐使用最后一种方式。

    那么到这里,整个效果也已经完成了。本文涉及的资源、代码请到 这里 下载。

    by Mandarava(鳗驼螺) 2017.06.21

    相关文章

      网友评论

      • 844cb4f189d5:请问作者 ,如果我只想消除标题菜单的白框,应该怎么做? @鳗驼螺
      • 吃货毛玉:你好我想在标题上添加一个退出游戏(关闭窗口)的按钮,请问命令是什么?应该在哪里查找相关代码资料?
        鳗驼螺:@吃货毛玉 一方面你可以读源码,另一方面是凭经验用关键字查找一下。官方源码也没提供完整注释,只能靠自己一点点去分析。
        吃货毛玉:在源代码里找到这个“SceneManager.exit();”,难道这些API 都是在源代码里找的?那岂不是大海捞针一般了?
      • v林林v:你好我想问一下,我已经将菜单换成了我自定义的图片,我想鼠标悬停到菜单上,换成我另一张自定义的图片,鼠标移走后,又换回最初的图片,要怎么做呢?就是一个鼠标移入移出图片变化的效果
        鳗驼螺:这个可以重写TouchInput._onMouseMove方法,用它来获取鼠标移动时鼠标的坐标,然后检测这个鼠标坐标是否处于你的按钮区域内,再根据是否情况,更新按钮的图片,类似下面这样:
        var _TouchInput_onMouseMove = TouchInput._onMouseMove;
        TouchInput._onMouseMove = function (e) {
        _TouchInput_onMouseMove.call(this, e);

        if(!(SceneManager._scene instanceof Scene_Title)) return; //检测当前场景是否为Scene_Title,如果不是直接退出
        var cmdButtons = SceneManager._scene._cmdButtons; //获得Scene_Title中的_cmdButtons,也就是我们自定义的菜单按钮
        var mousex = Graphics.pageToCanvasX(e.pageX); //获取鼠标在画板上的x坐标
        var mousey = Graphics.pageToCanvasY(e.pageY); //获取鼠标在画板上的y坐标
        //这里以第一个按钮为例(如果你要处理所有按钮,用比如forEach遍列去处理)
        var button = cmdButtons[0]; //只拿一个按钮做示例
        if(mousex>=button.x && mousex<=button.x+button.width && mousey>=button.y && mousey<=button.y+button.height)//检测当前的鼠标坐标是否处在按钮范围内
        {//鼠标悬浮在按钮上
        button.bitmap = hoverImage; //将按钮的图片设置为鼠标悬浮状态的图片
        }else{//鼠标移出按钮范围
        button.bitmap = normalImage; //按钮钮的图片设置为正常图片
        }
        }
      • 黄粱一梦也该醒:大神在吗,我想问一下为什么选择菜单会只显示两个官方网站,其他都没有
        鳗驼螺:这个你比对一下我的代码吧,特别是一些数组的索引号有没有写错,是否用了同一个索引。
      • 黃晧庭:想問為甚麼我的標題無法放置在中間
        this.centerSprite()這段是要放在哪裡
        我是小菜鳥 請多見諒0.0
        鳗驼螺:要调用centerSprite()这个方法需要给它一个传一个参数,将要放置在中心的sprite传给它,如:`centerSprite(yourSprite)`。yourSprite就是你要放在中心的精灵对象。
      • 想不出名字_:大神你好,为什么我用这个方法出错了
        Error
        Failed to load:
        function%20(array)%20%7B%0A%20%20%20%20if%20(!array%207C%7c%20this.length%20!%3D%20array.length)%20%7B%0A%20%20%20%20%20%20%20%return%20false%3B%0A%20%20%20%20%7D%0A%20%20%20%20for%20(var%20i%20%3D%200%3B%20i%20%3C%20t).......png
        鳗驼螺:看起来是你的代码格式上有点问题,看看是不是哪个引号、大括号、小括号之类的漏了。
      • 533834ee91f6:您好大神,我按照你的本文的方法重新弄了一个标题菜单,但没有弄背景动态图和增加菜单栏的部分,现在的情况是已经菜单已经根据自己的图优化好了,但菜单的指示器却没有跟随鼠标或者键盘自动移动到指定的选项上,请问这个该如何解决?谢谢!
        鳗驼螺:@windyc7 这个功能其实没做,要做也很简单,定义clicks时换成下面的代码(就这个问题,我会更新一下教程):
        var clicks=[
        function(){if(this._commandWindow.index()!=0){this._commandWindow.select(0);}else{this._commandWindow.processOk();}; SoundManager.playOk();},
        function(){if(this._commandWindow.index()!=1){this._commandWindow.select(1);}else{this._commandWindow.processOk();}; SoundManager.playOk();},
        function(){if(this._commandWindow.index()!=2){this._commandWindow.select(2);}else{this._commandWindow.processOk();}; SoundManager.playOk();},
        function(){if(this._commandWindow.index()!=3){this._commandWindow.select(3);}else{this._commandWindow.processOk();} SoundManager.playOk();}
        ];
        533834ee91f6:@鳗驼螺 谢谢您,根据你的提示已经找到原因了,但还有一个问题是,根据上面的方法,指针是可以根据键盘的方向键来上下移动,但却不能跟随鼠标自由移动,请问是哪里出了问题呢?
        鳗驼螺:动态背景、增加菜单和图片菜单无关。这里只有一个注意事项,就是不要删除旧的文本菜单,只隐藏它们,然后在update中更新指示器的位置。如果照着这样做,应该不会有问题,你可以对比下的我的代码,哪里不一样。
      • 晚生寒舍:大神,有几个问题想请教一下,能否推个文章详细介绍一下战斗画面的设置。
        我的想法是这样的:
        1、能实现自动选择技能攻击,不必每次点选攻击或者魔法攻击
        2、敌我双方不是静止站立的,可以朝向对方扑过去施展攻击动作
        3、在战斗过程中,根据敏捷度实现一定几率的格挡和闪避效果,格挡通过格挡动作实现,闪避通过突然后移实现
        4、最后一击致死时,死方倒地吐血之后再返回原画面

        可不可以给个关于脚本的比较具体的修改方法?如果太麻烦,给个思路也行,我是新手,请多指教,如果看到留言,请加我的QQ好吗65990617,非常感谢您
        晚生寒舍:@鳗驼螺 收到,非常感谢您
        鳗驼螺:@四季如歌_b0ca 大神不敢当,都是摸着石头过河。自动攻击这个需要加入AI控制,攻击还是防守用哪种方式攻击是否要加血该给谁加血等等,不是个简单的活。至于朝向对方扑过去,这种属于攻击动画,这个yanfly有Action Sequence Pack插件可以试试:http://yanfly.moe/yep/。至于几率格挡闪避,这个mv本身的战斗系统就有吧。关于战斗方面的东西你可以研究下Scene_Battle类和BattleManager类,前者是战斗场景类,后者是战斗管理类,一般可以从这二个类入手去扩展。现在因为做其它事,MV目前也没啥新的研究,涉及战斗系统的话可以去看看yanfly的插件STB、CTB、STB等等看如何实现的。
      • 9daecef3502a:你好我想请问一下,中间的菜单栏,也就是那几个选项要怎么往右调呢?
        9daecef3502a:@鳗驼螺 非常感谢,我去试一下
        鳗驼螺:sprite.x=Graphics.width/2-92;
        sprite.y=360+60*i;
        x,y随需要自己改。

      本文标题:【RPG Maker MV插件编程】【实例教程4】玩转标题画面

      本文链接:https://www.haomeiwen.com/subject/ihbaqxtx.html