- 作者:Mandarava(鰻駝螺)
- 微博:@鰻駝螺pro
標(biāo)題畫面代表了游戲的臉面,必須好看。標(biāo)題畫面對應(yīng)的類是Scene_Title
,所以對于標(biāo)題畫面的修改可以通過重寫Scene_Title
中的相關(guān)方法來實(shí)現(xiàn)。
本文涉及的內(nèi)容包括:
- 美化游戲標(biāo)題
- 讓背景動(dòng)起來
- 自定義標(biāo)題菜單
- 美化菜單
創(chuàng)建一個(gè)名為 LEARN_TitleMenu.js 的JavaScript文件,保存到 js/plugins 目錄下,在RMMV的插件管理中安裝該插件。
美化游戲標(biāo)題
在Scene_Title
類中可以找到Scene_Title.prototype.createForeground
方法,它用于創(chuàng)建標(biāo)題畫面的前景。在原始實(shí)現(xiàn)中,這個(gè)“前景”就是標(biāo)題文字(簡單的給文字加了描邊效果)。我們可以在【數(shù)據(jù)庫 - 系統(tǒng) - 標(biāo)題畫面】選項(xiàng)中設(shè)置是否繪制標(biāo)題名稱。
這里,我們重新實(shí)現(xiàn)createForeground
方法,使用一個(gè)游戲logo圖片來代替標(biāo)題文字,相對于純文字來說,圖片可以包含圖文效果,更高大上。實(shí)現(xiàn)代碼:
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);
}
};
這段代碼將在畫面顯示一個(gè)Logo圖片。我們的logo圖片放到img/mndtitle
目錄下,名稱為GameLogo.png
,這張圖片的內(nèi)容是“武林外史”幾個(gè)特效文字。if ($dataSystem.optDrawTitle)
用于檢測是否要繪制標(biāo)題(這個(gè)就是前面說的在數(shù)據(jù)庫中可以配置的選項(xiàng))。因?yàn)?code>mndtitle是個(gè)自定義目錄,所以從該目錄加載圖片時(shí)要使用ImageManager.loadBitmap
方法,參數(shù)指定文件夾位置和加載的圖片名稱。將這個(gè)圖片放置在畫面中間靠上的位置,如果你要放到正中間,可以使用this.centerSprite()
方法。再一次提醒,如果你要用到圖片的寬高尺寸來給圖片設(shè)置坐標(biāo),那么設(shè)置方法應(yīng)該放到Scene_Title.prototype.start
中去做。最后效果如下。背景是MV自帶素材,不是這里的重點(diǎn);頂部的“武林外史”就是要展示的游戲logo兼游戲標(biāo)題(隨便網(wǎng)絡(luò)找的一個(gè)素材,勿商用)。
讓背景動(dòng)起來
背景部分,如果只是想改成其它靜態(tài)圖,那么在“數(shù)據(jù)庫”中更改就足夠了,沒必要自己重寫。當(dāng)然,如果你想,或者如果想實(shí)現(xiàn)動(dòng)態(tài)背景,那么就需要修改Scene_Title.prototype.createBackground
方法了。
createBackground
方法用于創(chuàng)建標(biāo)題畫面的背景。在這個(gè)方法的重寫版本中,我們先創(chuàng)建一個(gè)用于顯示背景的精靈,加載動(dòng)態(tài)背景所需要的幀序列圖片,然后在Scene_Title.prototype.update
方法中按順序用幀序列圖片循環(huán)更新精靈的顯示圖片,從而實(shí)現(xiàn)背景的動(dòng)態(tài)效果。先來看一下實(shí)現(xiàn)效果:
這個(gè)動(dòng)態(tài)效果由三張圖片組成(直接拿MV中的官方的標(biāo)題圖片 Devil.png 改的,旋轉(zhuǎn)了一下怪物的二只爪子,做成了動(dòng)畫序列)。資源圖片放到
img/mndtitle
目錄下,名稱按動(dòng)畫順序依次為:TitleBack1.png
,TitleBack2.png
,TitleBack3.png
。下面是
Scene_Title.prototype.createBackground
的實(shí)現(xiàn)代碼:
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
是一個(gè)索引數(shù)組,表示一個(gè)動(dòng)畫序列,每個(gè)元素代表了在this._animFrameImgs
數(shù)組中的圖片索引,this._currFrame
是動(dòng)畫序列中的當(dāng)前幀,this._animDelay
表示動(dòng)畫的二幀之間要求的時(shí)間間隔,this._backSprite
是顯示背景圖片的精靈。
因?yàn)?code>createBackground方法的原始實(shí)現(xiàn)中創(chuàng)建了this._backSprite1
和this.__backSprite2
,且在start
方法中使用了這二個(gè)精靈,但我們現(xiàn)在重寫的createBackground
沒有創(chuàng)建它們也沒有去call原始方法,所以還要先重寫Scene_Title.prototype.start
方法,刪除與_backSprite1
和_backSprite2
有關(guān)的代碼,如下:
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
的時(shí)間就按動(dòng)畫序列中的索引更新到下一張背景圖片,代碼如下。
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
獲取游戲當(dāng)前的FPS,這里用1 / Graphics._fpsMeter.fps
的方式粗略的表示二個(gè)update
之間的時(shí)間間隔。this._elapsedSinceLastUpdate
表示自上次更新背景圖片以來流逝的游戲時(shí)間,在每次update
都會累計(jì)一次,直到當(dāng)流逝時(shí)間超過this._animDelay
后,就開始繪制新一幀的圖片,從而不斷循環(huán),實(shí)現(xiàn)動(dòng)態(tài)背景的不斷往復(fù)循環(huán)。
當(dāng)然,這里只是演示一個(gè)簡單的動(dòng)畫效果,更多關(guān)于動(dòng)畫的技巧可以參考另一篇教程:【實(shí)例教程5】制作小游戲:坦克大戰(zhàn)(上)。
自定義標(biāo)題菜單
默認(rèn)標(biāo)題畫面只顯示 開始游戲、繼續(xù)游戲、設(shè)置 三個(gè)菜單命令,如果要增加其它的菜單,如:官方網(wǎng)站、致謝 等菜單,那么需要重寫二個(gè)方法:Window_TitleCommand.prototype.makeCommandList
和Scene_Title.prototype.createCommandWindow
。前者用于在菜單畫面創(chuàng)建菜單命令,后者用于將事件綁定到菜單。如果你讀過之前的一篇教程:玩轉(zhuǎn)菜單初級篇,應(yīng)該就會猜到 Window_TitleCommand
對應(yīng)的是標(biāo)題界面中間偏下那個(gè)由白邊框圍成的菜單區(qū)域,菜單的顯示由它創(chuàng)建,而事件處理由它所在的場景,也就是Scene_Title
來綁定和處理。菜單綁定事件用setHandler
,相關(guān)的實(shí)現(xiàn)也可以參考“玩轉(zhuǎn)菜單初級篇”一文。最后實(shí)現(xiàn)代碼如下:
var _Window_TitleCommand_makeCommandList = Window_TitleCommand.prototype.makeCommandList;
Window_TitleCommand.prototype.makeCommandList = function () {
_Window_TitleCommand_makeCommandList.call(this);
this.addCommand("官方網(wǎng)站", 'homepage');//增加一個(gè)新菜單,標(biāo)識符為 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)); //將標(biāo)識符為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.lxweimin.com/nb/13204998"]);
};
這樣,在游戲時(shí)點(diǎn)擊標(biāo)題畫面的“官方網(wǎng)站”菜單后會打開本人在簡書寫的關(guān)于RMMV的文章專題首頁。這里,this._commandWindow.activate();
是重新激活當(dāng)前游戲窗口,因?yàn)槭褂闷渌M(jìn)程打開網(wǎng)頁時(shí)會造成游戲窗口失焦。
如果要?jiǎng)h除菜單,更簡單,只需要重寫Window_TitleCommand.prototype.makeCommandList
方法,刪除不想看到的菜單即可,當(dāng)然不要去call原始方法。
美化菜單
默認(rèn)的菜單就是白邊框圍成的幾個(gè)菜單文本,與目前的畫面不太搭,所以現(xiàn)在的目標(biāo)是用美化的圖片來代替文本做成菜單。先看看完成效果:
四個(gè)菜單全部由圖片做成,實(shí)際上它們是圖片按鈕Sprite_Button
對象,本質(zhì)上也是Sprite
精靈,但能綁定方法能回應(yīng)點(diǎn)擊。像原始菜單一樣,這些圖片菜單可以用鼠標(biāo)點(diǎn)擊,也可以用方向鍵上下移動(dòng)切換菜單,在當(dāng)前選中的菜單前面會顯示一個(gè)指示標(biāo)記。
要實(shí)現(xiàn)這個(gè)效果,首先要重寫Scene_Title.prototype.create
方法,這個(gè)方法用于初始化圖片菜單的相關(guān)資源。實(shí)現(xiàn)代碼:
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;//移到畫面外去,否則雖然不顯示仍能點(diǎn)擊
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);//不要設(shè)置,設(shè)置這個(gè)會出現(xiàn)菜單點(diǎn)不中的問題,不清楚原因。
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);//因?yàn)榘粹o的anchor是默認(rèn)的(0,0),這個(gè)指示器要放在按鈕左側(cè),所以讓它的anchor為(1,0)更容易定位
this.addChild(this._cmdSelect);
};
this._commandWindow.visible
用于將原始的文本菜單隱藏掉,并將它放到畫面外部,因?yàn)檫@個(gè)菜單即使不顯示也能點(diǎn)擊到,也能用上下方向鍵切換選擇其菜單,不過,我們又不能刪除它。當(dāng)然,你可以重寫Scene_Title.prototype.createCommandWindow
方法,并只刪除原始代碼中的this.addWindow(this._commandWindow);
,從而真的不讓它出現(xiàn),但那樣你就得自己實(shí)現(xiàn)一些方法,比如:在游戲啟動(dòng)時(shí)原始的文本菜單會根據(jù)是否有存檔自動(dòng)選擇 繼續(xù)游戲 還是 開始游戲,可以用上下方向鍵切換不同的菜單,可以用確定鍵打開選中的菜單,如果不讓它出現(xiàn),那么這些功能就得自己去為圖片菜單實(shí)現(xiàn)。所以,為了簡單起見,我們讓圖片菜單與原始的文本菜單保持聯(lián)動(dòng),每個(gè)圖片菜單與原始的文本菜單一一對應(yīng)。
btnimgs
是各個(gè)圖片菜單的圖片名稱(也就是 img/mndtitle
文件夾下與菜單相關(guān)的圖片的名稱),要按照原始文本菜單的順序排列,以便使圖片菜單與原始的文本菜單順序是一致的。
clicks
是保存了各個(gè)菜單按鈕要綁定的方法的一個(gè)數(shù)組,這個(gè)數(shù)組中,也是按順序定義好各個(gè)圖片菜單對應(yīng)要綁定的方法。所有圖片菜單都是一個(gè)Sprite_Button
對象,這里注意,不要忘記設(shè)置它們的寬高,這會影響點(diǎn)擊的熱區(qū),否則可能點(diǎn)在菜單上卻沒有反應(yīng)。使用sprite.setClickHandler(clicks[i].bind(this));
將圖片菜單與對應(yīng)的方法進(jìn)行綁定。
this._cmdButtons
是存放圖片按鈕的數(shù)組,在update
方法會用到。this._cmdSelect
是個(gè)指示器,會顯示在當(dāng)前選中的菜單左側(cè)。
接下來就是要讓選擇指示器的顯示在選中的菜單左側(cè),這個(gè)需要在Scene_Title.prototype.update
中處理,在update
方法中添加以下代碼到最后(前文中已經(jīng)重寫了該方法,現(xiàn)在再添加代碼進(jìn)去):
var btnSelect = this._cmdButtons[this._commandWindow.index()];
this._cmdSelect.x = btnSelect.x;
this._cmdSelect.y = btnSelect.y;
this._commandWindow.index()
是用來獲取原始的文本菜單中當(dāng)前所選中的菜單索引,根據(jù)這個(gè)索引我們用this._cmdButtons[index]
來從圖片菜單數(shù)組中得到對應(yīng)的圖片菜單對象,然后將指示器放到它左側(cè)。
補(bǔ)充:優(yōu)化菜單點(diǎn)選方式
菜單的功能基本完成,但在運(yùn)行過程中,我們發(fā)現(xiàn)它的工作方式與RMMV原始的方式不太一樣。原始方式是:點(diǎn)擊一個(gè)菜單時(shí),如果該菜單不是當(dāng)前選中的菜單,則只是選中它(在菜單上顯示一個(gè)白色透明背景的方框,以呈高亮顯示),讓它成為當(dāng)前選中的菜單;如果是當(dāng)前選中的菜單,則直接進(jìn)入該菜單功能。我們的菜單點(diǎn)擊任何一個(gè)按鈕,不論指示器在哪里,都會直接進(jìn)入菜單功能。
- 如果要實(shí)現(xiàn)RMMV自帶的原始點(diǎn)選方式,可以將
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();} }
];
這種方式其實(shí)還不是很好,對于一個(gè)非當(dāng)前選中的菜單,需要雙擊才能進(jìn)入菜單功能,個(gè)人認(rèn)為更好的方式是下面這種:
- 點(diǎn)擊任何一個(gè)菜單(不論它是否為當(dāng)前選中的菜單),指示器都會指向它,并直接進(jìn)入菜單的功能,要實(shí)現(xiàn)這種方式,更簡單:
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(); }
]
目前的源碼中推薦使用最后一種方式。
那么到這里,整個(gè)效果也已經(jīng)完成了。本文涉及的資源、代碼請到 這里 下載。
by Mandarava(鰻駝螺) 2017.06.21