- 作者:Mandarava(鰻駝螺)
- 微博:@鰻駝螺pro
RMMV的菜單很豐富,包括主菜單(Scene_Menu),物品菜單(Scene_Item)、技能菜單(Scene_Skill)、裝備菜單(Scene_Equip)、狀態(tài)菜單(Scene_Status)、設(shè)置菜單(Scene_Options)、保存菜單(Scene_Save)、加載菜單(Scene_Load)、游戲結(jié)束菜單(Scene_GameEnd),還有調(diào)試菜單(Scene_Debug)、商店菜單(Scene_Shop)、(輸入)姓名菜單(Scene_Name)等。標題菜單(Scene_Title)和前面這些有點不同,不屬于Scene_MenuBase
的子類,本文不涉及它,以后再寫文章單獨研究一下。
本文涉及的內(nèi)容包括:
- 給各個菜單界面添加背景
- 讓背景滾動起來
- 在主菜單界面增加自定義菜單:改名
- 在主菜單界面移除菜單命令
- 在主菜單界面增加一個自定義窗口
創(chuàng)建一個名為 MND_SceneMenuEx1.js 的JavaScript文件,保存到 js/plugins 目錄下,在RMMV的插件管理中安裝該插件。
給菜單界面添加背景
主菜單界面對應Scene_Menu
類,其父類是Scene_MenuBase
,分析Scene_MenuBase
類源碼,可以在其中找到一個名為Scene_MenuBase.prototype.createBackground
的方法,顯然,這個方法是用來創(chuàng)建菜單界面背景的。
除非想要將所有菜單界面(如物品菜單、裝備菜單等界面)的背景都設(shè)置為相同,否則不要直接重寫Scene_MenuBase
類的該方法,因為Scene_Item
,Scene_Equip
等類都是Scene_MenuBase
的子類,且所有這些子類默認都沒有重寫該方法(所以實質(zhì)上它們使用同一樣式的背景),一旦重寫基類的該方法,將影響所有子類(當然,這個問題其實很好解決,稍后說明)。
所以,如果要對不同的菜單界面使用不同的背景,就需要分別重寫各個菜單界面類的該方法。下面的代碼重寫主菜單Scene_Menu
的該方法,以使主菜單界面有獨立的背景:
Scene_Menu.prototype.createBackground = function() {
this._backgroundSprite=new Sprite();
this._backgroundSprite.bitmap=ImageManager.loadParallax("Mountains1");
this.addChild(this._backgroundSprite);
};
this._backgroundSprite
在基類Scene_MenuBase
中定義和使用的精靈變量,用于顯示背景圖片,為了兼容,這里直接使用該變量。ImageManager
類處理各種圖片資源的加載,ImageManager.loadParallax("Mountains1")
方法會從 img/parallaxes 目錄加載指定名稱的圖片,這里指定加載 Mountains1.png 圖片作為背景圖片。this.addChild(this._backgroundSprite)
將創(chuàng)建的精靈加入當前場景。運行測試,效果如下圖:
前面說了,菜單界面有十幾個,如果每個菜單界面都要改為單獨的背景,或者某幾個菜單界面想要共用一個背景,那是否一定就得為這十幾個菜單類重寫十幾個createBackground
方法呢?
其實有簡單點的方法,就是前文否定的只重寫Scene_MenuBase
類的該方法的方式。因為這十幾個菜單類都是Scene_MenuBase
類的子類,且它們默認都沒有重寫createBackground
方法,所以實際是可以直接改造該方法的,而避免各個菜單界面背景變得相同的解決方法是:根據(jù)當前對象的類型來區(qū)分它們是哪個菜單界面,從而區(qū)別使用背景圖。在繼續(xù)之前先將MND_SceneMenuEx1.js文件中的內(nèi)容清空以免影響后面的代碼。
要判斷當前運行的是哪個菜單界面,可以使用 instanceof
或者 constructor
屬性來判斷當前實例的類型,根據(jù)判斷結(jié)果選擇要使用的背景,詳細的Scene_MenuBase.prototype.createBackground
重寫代碼如下:
Scene_MenuBase.prototype.createBackground = function() {
this._backgroundSprite=new Sprite();
var imageName;
if(this instanceof Scene_Menu){
imageName="Mountains1";
}else if(this instanceof Scene_Item){
imageName="BlueSky"
}else if(this instanceof Scene_Skill){
imageName="Mountains2"
}else if(this instanceof Scene_Equip){
imageName="Ocean2"
}else if(this instanceof Scene_Save || this instanceof Scene_Load) {
imageName = "Sunset"
}else{
imageName="Mountains4"
}
this._backgroundSprite.bitmap=ImageManager.loadParallax(imageName);
this.addChild(this._backgroundSprite);
};
if(this instanceof Scene_Menu)
代碼部分表示如果當前是主菜單(Scene_Menu
)界面,則使用 Mountains1 圖片作為背景;同理,對于Scene_Item
、Scene_Skill
、Scene_Equip
的菜單界面也分別使用各自不同的背景圖片;而Scene_Save
和Scene_Load
使用同一個背景 Sunset.png;剩下的所有其它菜單界面都使用同一個背景 Mountains4.png。當然,你可以按照類似的方式隨意修改各個菜單界面的背景圖片。
讓背景滾動起來
前文是為各個菜單界面添加了背景圖片,但都是靜態(tài)的。如果要讓這些背景進行循環(huán)滾動,可以使用 TilingSprite
平鋪精靈類代替 Sprite
精靈類。TilingSprite
這個類可以通過簡單的代碼就讓一個背景循環(huán)滾動,只需要在 update
方法中不斷更新平鋪精靈滾動的原點 origin
即可。所以前面的代碼可以改為如下:
Scene_MenuBase.prototype.createBackground = function() {
this._backgroundSprite=new TilingSprite();
var imageName;
if(this instanceof Scene_Menu){
imageName="Mountains1";
}else if(this instanceof Scene_Item){
imageName="BlueSky"
}else if(this instanceof Scene_Skill){
imageName="Mountains2"
}else if(this instanceof Scene_Equip){
imageName="Ocean2"
}else if(this instanceof Scene_Save || this instanceof Scene_Load) {
imageName = "Sunset"
}else{
imageName="Mountains4"
}
this._backgroundSprite.bitmap=ImageManager.loadParallax(imageName);
this._backgroundSprite.move(0,0, Graphics.width, Graphics.height);
this.addChild(this._backgroundSprite);
};
var _Scene_MenuBase_update = Scene_MenuBase.prototype.update;
Scene_MenuBase.prototype.update = function () {
_Scene_MenuBase_update.call(this);
this._backgroundSprite.origin.x+=1;
}
this._backgroundSprite.move(0,0, Graphics.width, Graphics.height);
用于同時設(shè)置背景精靈的坐標和寬高。下面重寫了Scene_MenuBase.prototype.update
方法,在其中更不斷更新this._backgroundSprite.origin
以使平鋪精靈出現(xiàn)循環(huán)滾動的效果。
這種方式也可以用于地圖界面中的遠景滾動,雖然地圖已經(jīng)有一個“遠景”功能可以實現(xiàn)遠景滾動,不過,這種方式可以設(shè)置滾動區(qū)域、大小、位置,甚至多個滾動效果出現(xiàn)在同一張地圖上,所以可以有更多自由、更多訂制。比如主角在一條東西大道上一路狂奔時,遠處的山脈(遠景)會緩慢的滾動,近處或路旁的樹木(近景)會較快的滾動。
最后,在 這里 我提供了一個最終制作完成的用于訂制所有菜單背景的插件:MND_MenuBackground,可用于設(shè)置包括主菜單、物品菜單、技能菜單、裝備菜單、狀態(tài)菜單、選項菜單、保存/加載菜單、商店菜單、結(jié)束游戲菜、改名菜單等菜單背景,支持背景循環(huán)滾動。
在主菜單界面增加自定義菜單:改名
默認的主菜單界面上會顯示 物品、技能、裝備、狀態(tài)、整隊、設(shè)置、保存、游戲結(jié)束 等數(shù)個菜單命令。這幾個菜單命令的顯示歸
Window_MenuCommand
類管理,要添加新的菜單只需要重寫 Window_MenuCommand.prototype.addOriginalCommands
方法,并使用addCommand(name, symbol, enabled, ext)
方法新增一個菜單命令即可,代碼如下:
Window_MenuCommand.prototype.addOriginalCommands = function () {
this.addCommand("改名", "rename", true);
};
這個操作會在 整隊 菜單命令下添加一個 改名 的新菜單命令。addCommand
這個方法有四個參數(shù),name
是菜單顯示的名稱,symbol
需要指定一個唯一的標識符來代表菜單,這個標識符不能與其它菜單的標識符相同,所以不能使用以下標識符:item, skill, equip, status, formation, options, save, gameEnd, cancel,因為這些標識符已經(jīng)被那幾個默認的菜單命令使用了。enabled
表示是否要啟用該菜單,如果設(shè)置為false
,則菜單會呈現(xiàn)灰色的禁用狀態(tài);至于ext
參數(shù)可以不管它,因為我也不知道干嘛的(看起來像保存擴展數(shù)據(jù)用的)。
目前為止,這個 改名 菜單并沒有綁定事件,如果現(xiàn)在測試,點擊該菜單命令是不會有任何效果的。要將菜單命令綁定到事件,這時就要用到Scene_Menu
類(主菜單界面對應于Scene_Menu
類),所有事件處理會在該類中處理,在這里就是重寫Scene_Menu.prototype.createCommandWindow
方法,查看該方法的原始實現(xiàn),就會明白,所有默認菜單都是在這個方法中綁定事件的,所以新增的菜單也在這里綁定事件。在這里,我們要實現(xiàn)點擊 改名 菜單后,進入用戶選擇狀態(tài)(像物品、技能、裝備、狀態(tài)等主要菜單命令一樣),玩家選擇要改名的角色,自動進入改名界面修改所選角色的名稱。先看看實現(xiàn)效果:
具體實現(xiàn)代碼如下:
Window_MenuCommand.prototype.addOriginalCommands = function () {
this.addCommand("改名", "rename", true);
};
var _Scene_Menu_createCommandWindow = Scene_Menu.prototype.createCommandWindow;
Scene_Menu.prototype.createCommandWindow = function () {
_Scene_Menu_createCommandWindow.call(this);
this._commandWindow.setHandler('rename', this.commandRename.bind(this));
};
Scene_Menu.prototype.commandRename = function () {
this._statusWindow.setFormationMode(false);
this._statusWindow.selectLast();
this._statusWindow.activate();
this._statusWindow.setHandler('ok', this.rename_ok.bind(this));
this._statusWindow.setHandler('cancel', this.rename_cancel.bind(this));
};
Scene_Menu.prototype.rename_ok = function() {
SceneManager.push(Scene_Name);
SceneManager.prepareNextScene($gameParty.menuActor()._actorId, 10);
};
Scene_Menu.prototype.rename_cancel = function() {
this._statusWindow.deselect();
this._commandWindow.activate();
};
Window_MenuStatus.prototype.processOk = function() {
$gameParty.setMenuActor($gameParty.members()[this.index()]);
Window_Selectable.prototype.processOk.call(this);
};
首先是重寫Scene_Menu.prototype.createCommandWindow
方法,這個方法用于創(chuàng)建左上部的菜單命令窗口。在這個方法中使用this._commandWindow.setHandler('rename', this.commandRename.bind(this));
的方式將 改名 菜單與Scene_Menu.prototype.commandRename
方法綁定。commandRename
方法的實現(xiàn)可以參考Scene_Menu.prototype.commandPersonal
方法的原始實現(xiàn)。在rename_ok
方法中會使用prepareNextScene
方法去向Scene_Name
傳遞要改名的角色ID。
(PS:這里,所謂的窗口是指在菜單界面看到的一個個由白邊框圍起來的那一塊塊區(qū)域。在主菜單界面,默認有三個窗口,左上部一個窗口用于顯示菜單命令,對應于Window_MenuCommand
類;左下角一個小窗口用于顯示金錢數(shù)量,對應于Window_Gold
類;右側(cè)占用一大半?yún)^(qū)域的窗口,顯示角色狀態(tài),對應于Window_MenuStatus
類,所以,如果想改動這些窗口,重寫它們對應的類的方法是一種方式。)
在commandRename
方法中,再使用setHandler
將“確定選擇角色”和“取消選擇角色”二個操作與rename_ok
和rename_cancel
方法綁定。這里的綁定標識符 ok 和 cancel 是固定的,不能改為其它的,要不然,不能代表“確定選擇”和“取消選擇”二種操作結(jié)果。
最后,這里重點是重寫Window_MenuStatus.prototype.processOk
方法。先來看該方法的原始實現(xiàn):
Window_MenuStatus.prototype.processOk = function() {
Window_Selectable.prototype.processOk.call(this);
$gameParty.setMenuActor($gameParty.members()[this.index()]);
};
可以看到,原始實現(xiàn)和我們這里的重寫方法只是在二行代碼在執(zhí)行順序上進行了換位。為什么要這樣重寫這個方法呢?首先,這個方法是在我們確定選擇改名的角色時觸發(fā),原始實現(xiàn)中會先去執(zhí)行我們自定義的方法rename_ok
(因為Window_Selectable.prototype.processOk
方法中會調(diào)用this.callOkHandler()
,也就是這里的rename_ok
方法的代理進行執(zhí)行),然后才去更新$gameParty
中的 menuActor。所以,如果不重寫,我們在rename_ok
方法中使用$gameParty.menuActor()
獲取到的仍然是上一次選中的角色,而不是這一次選中的角色。重寫之后會先去設(shè)置$gameParty
的 menuActor,然后再執(zhí)行我們的自定義方法rename_ok
,因為,像這里一樣,自定義方法中可能需要知道當前選中的是哪個角色,所以這樣修改顯然比原始實現(xiàn)更合理。
當然,有人會問類似的像 技能(對應場景類Scene_Skill), 裝備(對應場景類Scene_Equip), 狀態(tài)(對應場景類Scene_Status) 這三個命令又為什么沒出現(xiàn)這個問題呢?這三個菜單命令的實現(xiàn)方式其實與我們的 改名 菜單的實現(xiàn)方式并不一樣,它們并不需要在自定義命令中向?qū)膱鼍邦悅鬟f參數(shù),而是直接在相關(guān)的場景類中使用$gameParty.menuActor()
來獲得的,在從菜單界面切換到這些相應的場景時,不論是processOk
還是我們的自定義的rename_ok
方法都已經(jīng)執(zhí)行過了,況且它們并不需要接收參數(shù),所以它們的執(zhí)行順序此時并不重要。根據(jù)這個原理,我們就有另外一種改法,就是像 Scene_Skill
、Scene_Equip
、Scene_Status
類的處理方式一樣,重寫Scene_Name
,具體就是重寫Scene_Name.prototype.create
,代碼如下:
Scene_Name.prototype.create = function() {
Scene_MenuBase.prototype.create.call(this);
//this._actor = $gameActors.actor(this._actorId);//修改為下面的:
this._actor = $gameParty.menuActor();
this.createEditWindow();
this.createInputWindow();
};
這個修改很簡單,改成直接從$gameParty.menuActor()
獲得 menuActor,而忽略傳遞進來的參數(shù) actorId,運行測試,也是沒問題的。不過,因為 Scene_Name
還要考慮在其它地方的使用(如事件編輯器中使用 名字輸入處理... 命令),這么一改會影響到這些地方的使用,Scene_Name
最重要的還是要通過傳遞參數(shù)來改變指定的角色名稱,所以這樣改就存在兼容性問題,是不妥的。不過,假如你要將 改名 菜單(或其它自定義菜單)綁定到一個自定義的場景類而非 Scene_Name
場景,那么就可以考慮使用這種方式。
最后,在 這里 我提供了一個最終完成的改名插件:MND_Rename,除了可以在主菜單界面增加 改名 菜單外,還可以用于綁定一個物品,讓物品擁有改名功能,這樣,可用來制作 改名卡 之類的改名道具。
在主菜單界面移除菜單命令
如果要去除主菜單界面中的菜單命令,只需要重寫Window_MenuCommand.prototype.makeCommandList
和Window_MenuCommand.prototype.addMainCommands
,查看makeCommandList
的原始實現(xiàn),可以看到,這個方法用于向菜單命令窗口添加各個菜單命令,不想添加的,重寫時刪除不要的菜單即可。
由于在makeCommandList
方法中,四個主要菜單命令(物品、技能、裝備、狀態(tài))只能要么一起刪除要么一起添加,所以如果只想去掉其中的一個或幾個,就需要重寫 addMainCommands
方法,在該方法中刪除不要出現(xiàn)的菜單命令即可。
Window_MenuCommand.prototype.makeCommandList = function() {
this.addMainCommands(); //增加主菜單:物品、技能、裝備、狀態(tài)
this.addFormationCommand();//增加 整隊 菜單
this.addOriginalCommands();//增加自定義的菜單
this.addOptionsCommand();//增加 設(shè)置 菜單
this.addSaveCommand();//增加 保存 菜單
this.addGameEndCommand();//增加 游戲結(jié)束 菜單
};
Window_MenuCommand.prototype.addMainCommands = function() {
var enabled = this.areMainCommandsEnabled();
if (this.needsCommand('item')) {
this.addCommand(TextManager.item, 'item', enabled);//增加 物品 菜單
}
if (this.needsCommand('skill')) {
this.addCommand(TextManager.skill, 'skill', enabled);//增加 技能 菜單
}
if (this.needsCommand('equip')) {
this.addCommand(TextManager.equip, 'equip', enabled);//增加 裝備 菜單
}
if (this.needsCommand('status')) {
this.addCommand(TextManager.status, 'status', enabled);//增加 狀態(tài) 菜單
}
};
在主菜單界面增加一個自定義窗口
前面說了,在主菜單界面默認有三個窗口,左側(cè)有一大一小2個,右側(cè)1個大的。左側(cè)有一塊區(qū)域是空的,可以在這個區(qū)域增加一個新的自定義窗口,至于要在窗口里顯示什么,這個看自己的需要,比如顯示一個任務提示。
首先主菜單界面對應于Scene_Menu
類,在該類的原始實現(xiàn)中可以找到一個Scene_Menu.prototype.create
方法,可以看到,在主菜單界面的三個窗口都是在這里創(chuàng)建的。所以,我們只需要重寫該方法,也在這里創(chuàng)建自定義窗口即可。
其次,三個默認窗口都有對應的類實現(xiàn),如左上部的菜單命令窗口對應于Window_MenuCommand
類,左下角金錢窗口,對應于Window_Gold
類;右側(cè)角色狀態(tài)窗口,對應于Window_MenuStatus
類,所以我們要新增的自定義窗口也要創(chuàng)建一個類來實現(xiàn),實現(xiàn)方法可以參考前面這三個類,根據(jù)它們的實現(xiàn)稍微改改就可以了。假如,我們的實現(xiàn)類是Window_Tips
,具體代碼如下:
var _Scene_Menu_create = Scene_Menu.prototype.create;
Scene_Menu.prototype.create = function() {
_Scene_Menu_create.call(this);
//創(chuàng)建自定義窗口,并將它加入主菜單界面
this._tipsWindow = new Window_Tips(0, 0);
this._tipsWindow.y = this._commandWindow.y + this._commandWindow.height + 5; //設(shè)置自定義窗口的Y坐標,由左上部的菜單命令窗口的Y軸坐標及其高度來決定
this.addWindow(this._tipsWindow);
};
function Window_Tips() {
this.initialize.apply(this, arguments);
}
Window_Tips.prototype = Object.create(Window_Base.prototype);
Window_Tips.prototype.constructor = Window_Tips;
Window_Tips.prototype.initialize = function(x, y) {
var width = this.windowWidth();
var height = this.windowHeight();
Window_Base.prototype.initialize.call(this, x, y, width, height);
this.refresh();
};
Window_Tips.prototype.windowWidth = function() {
return 240; //自定義窗口的寬度
};
Window_Tips.prototype.windowHeight = function() {
return this.fittingHeight(4); //自定義窗口的高度:通過設(shè)定窗口要容納的行數(shù)來自動計算高度
};
Window_Tips.prototype.refresh = function() {
var x = this.textPadding();
var width = this.contents.width - this.textPadding() * 2;
this.contents.clear();
//在這里繪制需要顯示的內(nèi)容
this.drawIcon(191, 0, 0);
this.drawTextEx("新的任務", 40, 0);
this.drawTextEx("找到老王頭,拿\n到紅色寶箱。", 0, 40);
};
Window_Tips.prototype.open = function() {
this.refresh();
Window_Base.prototype.open.call(this);
};
在Scene_Menu.prototype.create
方法中創(chuàng)建Window_Tips
窗口,并設(shè)置其坐標,使之呈現(xiàn)在左側(cè)空白區(qū)內(nèi)。對于Window_Tips
類,用Window_Tips.prototype.windowWidth
設(shè)置自定義窗口的寬度,用Window_Tips.prototype.windowHeight
方法設(shè)置其高度;而Window_Tips.prototype.refresh
方法用于繪制內(nèi)容的地方,想要在這個自定義窗口顯示什么內(nèi)容,可以在這個方法中繪制。這里用drawIcon
方法繪制了一個圖標,用drawTextEx
方法繪制文本,文本中可以用 \n
來回車換行。效果如下:
by Mandarava(鰻駝螺) 2017.06.06