你最喜歡.最常使用.最擅長的RPG製作大師版本是哪個呢?快來哈啦板板面投票吧!
LV. 37
GP 955

【教學】MV教學系列:【一】手把手教你如何在MV裡自製視窗

樓主 食夢 jamessl2
GP47 BP-

作者標示-非商業性

本授權條款允許使用者重製、散布、傳輸以及修改著作,但不得為商業目的之使用。使用時必須按照著作人指定的方式表彰其姓名。

嗯,
身為巴哈萬年潛水艇的我,趁MV發燒的時候先來發個基礎教學文吧。
這次將教給各位如何用js程式碼
從零開始建構屬於自己的視窗。

需要程度:無,完全不會js也可

●教學開始●

【假設你什麼都不知道:一切的開始】

嗯,這就是我寫這篇教學文的前提,我們先假設你是個MV新手,新到不能再新的那種。
你可能還搞不清楚 js / javascript 是什麼東西,或者不知道怎麼建立一個新的 js 檔......
沒關係,我們一個一個來,請你一步一步看清楚。


【你要先搞懂的最最基礎的東西】

沒錯,即使本教學文並不著重於讓你從根本上學會 js,
但最基本的資訊一樣要搞清楚。

第一、javascript 是一種在網頁上使用的程式語言,
MV捨棄了使用多年的Ruby而採取它,就是為了方便在HTML5標準上於瀏覽器發布。
細節不需知道太多,反正我們使用的程式語言叫做 javascript,js 是它的縮寫。

第二、javascript 身為程式語言,它就是一種「特別的」文字檔,
你大可以用Dreamweaver這種高階軟體去處理它,
也可以隨便開個記事本把附檔名從 *.txt 改成 *.js 就可以了。
差別只在於高階軟體在撰寫程式碼時比較方便,
可能提供各種提示、自動完成和高亮功能。
作為教學,本文使用強化版的記事本 Notepad++,完全免費,有需要請自行前往下載。

第三、MV所有可選擇的插件都建立於該專案的 js\plugins 之下,
我們等等也要在這個地方新增 js 檔,先去找到這個位置吧。


(圖1:這就是我們要建立 js 檔的地方,找到了嗎?)


【進入正題:開始建立檔案】

我們這次不打算沿用任何的插件,
所以打開 Notepad++,建立一個新的檔案"new 1",並選擇另存新檔為"FirstWindow.js"。


(圖2:js 檔儲存位置圖)

(圖3:如果順利,這個時候你在遊戲裡就能看到可以安插進這個插件了)

不過當然有插沒插一點差都沒有,因為裡面空空如也。
等我們把東西寫完之後再插不遲。


【這結構看起來複雜實際上簡單無比只是長了點因為廢話很多】

一個視窗之所以能夠顯現出來,在RPG MAKER裡最主要依靠兩大架構:
Window:負責視窗本身的參數和內容
Scene:負責視窗各種運作和互動
講得簡單一點,如果視窗就是扇窗戶,
那麼Window記錄了這扇窗戶的長寬、窗框的材質、玻璃的材料等等,
而Scene記錄了窗戶怎麼左右拉、怎麼鎖住又怎麼打開,
夠明白了吧。

因此,我們準備在FirstWindow.js裡開始寫一些東西了。
首先,我們先建立Window_TestingScene_Testing兩個函數,如下所示:

function Window_Testing() {
    
}
function Scene_Testing() {
    
}

你一定覺得很奇怪,為什麼要這樣寫?
其實你愛怎麼寫對 js 來說都沒差,
這樣寫只是告訴你這兩個函數一個跟Window有關,一個跟Scene有關。
先這樣寫放著它空沒關係,等等再把東西寫進去。


【初始化】

RPG MAKER內建了initialize方法,作為既定初始化用,
每一個 Window 類和 Scene 類函數都需要它。
正常情況下,你不用知道initialize怎麼建起來的,
你只要知道你的兩個函數都需要這個方法,
而且寫法蠻固定的:沒必要亂寫給自己找麻煩吧?

這個範例中,我們要借用 Window_Selectable 的方法給 Window_Testing,
借用 Scene_MenuBase 的方法給 Scene_Testing,
由於 Window_Selectable 視窗有x座標、y座標、寬、高四個屬性,
函數當然要記得丟進去,一個都不能少,一個都不能多(正常來說):
因此,原程式碼就變成了

function Window_Testing() {
    
}
Window_Testing.prototype.initialize = function(x, y, width, height) {
    Window_Selectable.prototype.initialize.call(this, x, y, width, height);
}

function Scene_Testing() {
    
}
Scene_Testing.prototype.initialize = function() {
    Scene_MenuBase.prototype.initialize.call(this);
};

然後,直接在函數中引用它們,各自加上同樣的一句話:

function Window_Testing() {
    this.initialize.apply(this, arguments);
}
Window_Testing.prototype.initialize = function(x, y, width, height) {
    Window_Selectable.prototype.initialize.call(this, x, y, width, height);
}

function Scene_Testing() {
    this.initialize.apply(this, arguments);
}
Scene_Testing.prototype.initialize = function() {
    Scene_MenuBase.prototype.initialize.call(this);
};

為什麼要加這兩句話?
我想...這個問題很簡單,不加這兩句我是要呼叫空函數嗎?
這兩句話基本上功能一致,講得簡單一點,
當我們呼叫這函數時,它就會去找它的initialize方法,
然後開始借用預設的初始化程式碼,嗯,超棒的。


【要素的補齊】

寫到這裡,快結束了。你臉綠了嗎?還好吧。
Scene_Testing 除了initialize之外,還需要兩個方法:createupdate
create用於建立新的 Window_Testing 視窗
update用於對玩家作出動作時的更新回應,例如我們接下來要寫的關閉視窗這個動作。
這次東西有點多,我們分成藍色橘色兩個部分解釋:

function Window_Testing() {
    this.initialize.apply(this, arguments);
}
Window_Testing.prototype.initialize = function(x, y, width, height) {
    Window_Selectable.prototype.initialize.call(this, x, y, width, height);
}

function Scene_Testing() {
    this.initialize.apply(this, arguments);
}
Scene_Testing.prototype.initialize = function() {
    Scene_MenuBase.prototype.initialize.call(this);
};
Scene_Testing.prototype.create = function() {
Scene_MenuBase.prototype.create.call(this);
    this._commandWindow = new Window_Testing(0, 0, 400, 200);
    this.addWindow(this._commandWindow);
}
Scene_Testing.prototype.update = function() {
    if (Input.isTriggered('escape') || Input.isTriggered('cancel')) {
        this._commandWindow.hide();
        SceneManager.goto(Scene_Map);
    }
};

藍色的部分:
一開始跟上一段一樣,從借用的地方去呼叫方法,只不過多了兩句話。
第一句話是建立一個新的Window_Testing物件,
第二句話則是將這個物件用addWindow方法顯示出來。

橘色的部分:
這裡則是一個 if 判斷式,
系統會偵測我們的行為,如果在 Scene_Testing 這個場景按下了
鍵盤上的 ESC 或者平板上的取消鍵時,
就把我們剛剛顯示的 Window_Testing 物件,也就是我們叫出來的視窗給隱藏起來,
並且回到地圖場景上。
如果沒有這句話,那麼你只能叫出視窗,
叫出來之後就一點反應也不會有了,也不能退出XD

至於為什麼是地圖場景呢?其實你要回到哪個場景都可以,
只是因為這個範例等等要做的事情與地圖場景有關,所以先如此設定。


【你有的我也要有】

接著,剩最後一個重點。請在以下位置加入這一句話:

function Window_Testing() {
    this.initialize.apply(this, arguments);
}
Window_Testing.prototype = Object.create(Window_Selectable.prototype);
Window_Testing.prototype.initialize = function(x, y, width, height) {
    Window_Selectable.prototype.initialize.call(this, x, y, width, height);
}

function Scene_Testing() {
    this.initialize.apply(this, arguments);
}
Scene_Testing.prototype = Object.create(Scene_MenuBase.prototype);
Scene_Testing.prototype.initialize = function() {
    Scene_MenuBase.prototype.initialize.call(this);
};
Scene_Testing.prototype.create = function() {
Scene_MenuBase.prototype.create.call(this);
    this._commandWindow = new Window_Testing(0, 0, 400, 200);
this.addWindow(this._commandWindow);
}
Scene_Testing.prototype.update = function() {
    if (Input.isTriggered('escape') || Input.isTriggered('cancel')) {
        this._commandWindow.hide();
        SceneManager.goto(Scene_Map);
    }
};

這兩句話的意思嗎?舉Window_Testing為例,
就是Window_Testing表示:只要是Window_Selectable有的方法,我都要有啦www
這個可不是上面我們說過的「借用」而已,
你要把它想成繼承嘛也行,但看在我眼裡比較像是複製www
如果你沒加這兩句,基本上會出現找不到方法(undefined)的錯誤喔。


【辛辛苦苦熬到現在終於可以測試了】

好了,到這裡整個基本程式碼大功告成了。
它基本上是最簡單的自製視窗結構,所有你之後想自製的視窗全都可以以它為藍圖去增修。
當然,細節上之後我們再說。總之趕快來做個測試吧。


(圖4:你家程式碼應該長成類似這個模樣)


(圖5:請記得打開你引用的插件)

怎麼作測試呢?
很簡單。
在你的地圖上任意一格新增一個事件,然後在內容處選擇腳本,寫入

SceneManager.goto(Scene_Testing)

就可以了。

(圖6:人物隨便選都行,反正它是個事件)

接下來不用我說了吧?
進入遊戲,用你的角色去碰這個事件!


【一片空白的視窗......】

我知道,你想跟我說,這視窗什麼都沒有啊!
是啊,因為它就真的什麼都沒有。
所有的東西都要你自己加,不管是全新的還是調用data內的,都需要額外的程式碼。
例如:

Window_Testing.prototype.initialize = function(x, y, width, height) {
    Window_Selectable.prototype.initialize.call(this, x, y, width, height);
    var textW = 360;
    var textH = 0;
    this.drawText("這是你的第一個自製視窗", 0, 0, textW, 'left');
    textH += this.lineHeight();
    this.drawText("靠左", 0, textH, textW, 'left');
    textH += this.lineHeight();
    this.drawText("置中", 0, textH, textW, 'center');
    textH += this.lineHeight();
    this.drawText("靠右", 0, textH, textW, 'right');
}

你把藍色這段程式碼加進去,存檔之後再開一次試試。

如何,你看到字了嗎?


【教練,可是這樣不好看......】

哦?
在這一篇教學的尾聲,你還想要什麼?
通常來說,我們不會把程式碼完全從頭到尾寫在initialize裡,
這樣既難看又雜亂,程式碼一多很容易被自己弄混。
所以,我們就這樣辦吧!

Window_Testing.prototype.initialize = function(x, y, width, height) {
    Window_Selectable.prototype.initialize.call(this, x, y, width, height);
    this.drawSomeText();
}

Window_Testing.prototype.drawSomeText = function() {
    var textW = 360;
    var textH = 0;
    this.drawText("這是你的第一個自製視窗", 0, 0, textW, 'left');
    textH += this.lineHeight();
    this.drawText("靠左", 0, textH, textW, 'left');
    textH += this.lineHeight();
    this.drawText("置中", 0, textH, textW, 'center');
    textH += this.lineHeight();
    this.drawText("靠右", 0, textH, textW, 'right');
}

如何?
有沒有開始覺得自己像是個專業的程式設計師了?


【教學ENDING】

理所當然,要成為專業你我可能都還有很長的路要走,
但是到這裡為止,第一篇教學真的要結束了,很長很長對吧?
其實占版面的都是程式碼和聊天般的廢話,只是想讓你別一開始就接觸太專業的東西。
剩下的你可以自己參考插件作者的寫法琢磨,
我也是不停在學習著,只是把自己先領略的部分丟出來給想要的人。

也許你覺得做了這麼多,也不過就是寫出一個陽春的視窗罷了,值得嗎?
其實在過程中,你得到的東西比想像中更多。

那麼,下次見了。

P.S. 如果老骨頭還記得精華區RGSS2有類似的文章,沒錯,本篇的確啟發自埃特
47
-
LV. 42
GP 1k
2 樓 WildDagger aquaris
GP13 BP-
稍微補充一下

Java Script每個函數在使用new呼叫的時候會視作「以函數為名的物件建構式」
也就是說,函數Window_Testing在以new Window_Testing()呼叫的時候,
系統會做的事是建立一個Window_Testing物件,並且呼叫函數Window_Testing並把參數丟給該函數執行。
(而在該函數中this指的不是Window_Testing函數而是Window_Testing物件)

另外,Java Script每個函數都有一個內建的陣列變數arguments,
這個陣列會依照你呼叫的次序紀錄你塞進來的所有參數

例如這一篇範例中Window_Testing函數是沒有參數(函數名稱後面的括弧裡甚麼參數都沒寫)的,
但我們呼叫時為何在Scene_Testing中寫下「this._commandWindow = new Window_Testing(0, 0, 400, 200);」這行時,Window_Testing函數卻能收到參數呢?

那就是因為系統會依照次序自動將參數0、0、400與200放入arguments陣列變數中,
不信你可以在Window_Testing函數裡面加入以下幾行

console.log(arguments[0]);
console.log(arguments[1]);
console.log(arguments[2]);
console.log(arguments[3]);

然後開主控台看看結果會不會依序出現0、0、400與200(笑)

至於Window_Testing裡面為什麼會使用「this.initialize.apply(this, arguments);」
下面的Window_Testing.prototype.initialize為何要呼叫「Window_Selectable.prototype.initialize.call(this, x, y, width, height);」

簡單說是使用了Java Script內建的兩個函數專用方法call與apply
call方法用來直接呼叫特定函式,並且傳遞參數給該函式後,表明函式的結果由第一個參數指定的物件接收,
也就是說「Window_Selectable.prototype.initialize.call(this, x, y, width, height);」這段的意思是:
系統會以x、y、width、height函數塞給Window_Selectable.prototype.initialize.call這個函式,並且告訴該函式說他執行完後結果會由this,也就是呼叫這個函式的Window_Testing.prototype.initialize函式接收。

apply則是將陣列轉化為個別參數呼叫方法,
也就是說「this.initialize.apply(this, arguments);」這段的意思是:
Window_Testing函數會呼叫剛創造好的Window_Testing物件中的initialize函數,
並且將自己收到的所有參數交給apply方法,由這個方法拆開參數,變成initialize(arguments[0], arguments[1], arguments[2], arguments[3])的語句呼叫initialize函數
而執行完的結果則由this指定的Window_Testing物件接收

也許有人會問萬一呼叫函式時不小心多指定了一個參數,
例如呼叫「new Window_Testing(0, 0, 400, 200);」時多打了一個0變成「new Window_Testing(0, 0, 400, 200, 0);」的話系統會怎麼辦?
沒怎麼辦,系統一樣照做,變成呼叫Window_Testing.prototype.initialize(0, 0, 400, 200, 0),
但因為指定呼叫的Window_Selectable.prototype.initialize只要前四個參數,
所以多出來的那個0系統就會當成沒看到而略過。
13
-
LV. 14
GP 203
3 樓 Penut花生 penut85420
GP0 BP-
我想弄個視窗在地圖上飄著,但是一直跑不出來...
function Window_MSB() {
this.initialize.apply(this, arguments);
};

Window_MSB.prototype = Object.create(Window_Base.prototype);
// Window_MSB.prototype.constructor = Window_MSB;
Window_MSB.prototype.initialize = function(x, y, width, height) {
    Window_Base.prototype.initialize.call(this, x, y, width, height);
};

//===================================
// Scene_Map
//===================================
Penut.MSB.Scene_Map_createDisplayObjects = Scene_Map.prototype.createDisplayObjects;

Scene_Map.prototype.createDisplayObjects = function() {
Penut.MSB.Scene_Map_createDisplayObjects.call(this);
Scene_Map.prototype.createMapStatueBar();
};

Scene_Map.prototype.createMapStatueBar = function() {
this._mapStatueBar = new Window_MSB(0, 0, 400, 200);
this.addWindow(this._mapStatueBar);
};

結果RMMV跟我說Type Error: Cannot read property 'addChild' of undefined
為什麼QAQ...
0
-
未登入的勇者,要加入 4 樓的討論嗎?
板務人員:

2712 筆精華,08/02 更新
一個月內新增 6
歡迎加入共同維護。


face基於日前微軟官方表示 Internet Explorer 不再支援新的網路標準,可能無法使用新的應用程式來呈現網站內容,在瀏覽器支援度及網站安全性的雙重考量下,為了讓巴友們有更好的使用體驗,巴哈姆特即將於 2019年9月2日 停止支援 Internet Explorer 瀏覽器的頁面呈現和功能。
屆時建議您使用下述瀏覽器來瀏覽巴哈姆特:
。Google Chrome(推薦)
。Mozilla Firefox
。Microsoft Edge(Windows10以上的作業系統版本才可使用)

face我們了解您不想看到廣告的心情⋯ 若您願意支持巴哈姆特永續經營,請將 gamer.com.tw 加入廣告阻擋工具的白名單中,謝謝 !【教學】