加入我们,或者,欢迎回来。
您需要 登录 才可以下载或查看,没有帐号?注册会员
x
本帖最后由 etherstalker 于 2023-5-9 00:21 编辑
游戏跑在nw.js上。nw.js可以让DOM能够直接调用Node.js的模块。这样就可以用HTML,CSS和JS来写应用程序。RPG Maker 这么做是一是为了简化各平台上的分发,二是可以用web技术来开发整个引擎。
NW.js (previously known as node-webkit) lets you call all Node.js modules directly from DOM and enables a new way of writing applications with all Web technologies.
const main = new Main( ) ;
main.run ( ) ;
const main = new Main( ) ;
main.run ( ) ;
Main.run() 中一共有四步
run( ) {
this .showLoadingSpinner ( ) ;
this .testXhr ( ) ;
this .hookNwjsClose ( ) ;
this .loadMainScripts ( ) ;
}
run( ) {
this .showLoadingSpinner ( ) ;
this .testXhr ( ) ;
this .hookNwjsClose ( ) ;
this .loadMainScripts ( ) ;
}
第一步是显示一个读取界面的spinner动画,这是用css实现的
showLoadingSpinner( ) {
const loadingSpinner = document.createElement ( "div" ) ;
const loadingSpinnerImage = document.createElement ( "div" ) ;
loadingSpinner.id = "loadingSpinner" ;
loadingSpinnerImage.id = "loadingSpinnerImage" ;
loadingSpinner.appendChild ( loadingSpinnerImage) ;
document.body .appendChild ( loadingSpinner) ;
}
showLoadingSpinner( ) {
const loadingSpinner = document.createElement ( "div" ) ;
const loadingSpinnerImage = document.createElement ( "div" ) ;
loadingSpinner.id = "loadingSpinner" ;
loadingSpinnerImage.id = "loadingSpinnerImage" ;
loadingSpinner.appendChild ( loadingSpinnerImage) ;
document.body .appendChild ( loadingSpinner) ;
}
对应的css可以在css/game.css 中找到
#loadingSpinner {
margin : auto ;
position : absolute ;
top : 0px ;
left : 0px ;
right : 0px ;
bottom : 0px ;
width : 120px ;
height : 120px ;
z-index : 10 ;
}
#loadingSpinnerImage {
margin : 0px ;
padding : 0px ;
border-radius: 50 % ;
width : 96px ;
height : 96px ;
border : 12px solid rgba( 255 , 255 , 255 , 0 .25 ) ;
border-top : 12px solid rgba( 255 , 255 , 255 , 1 ) ;
animation: fadein 2s ease, spin 1 .5s linear infinite;
}
#loadingSpinner {
margin : auto ;
position : absolute ;
top : 0px ;
left : 0px ;
right : 0px ;
bottom : 0px ;
width : 120px ;
height : 120px ;
z-index : 10 ;
}
#loadingSpinnerImage {
margin : 0px ;
padding : 0px ;
border-radius: 50 % ;
width : 96px ;
height : 96px ;
border : 12px solid rgba( 255 , 255 , 255 , 0 .25 ) ;
border-top : 12px solid rgba( 255 , 255 , 255 , 1 ) ;
animation: fadein 2s ease, spin 1 .5s linear infinite;
}
读取动画的效果:
第二步会测试XHR。XHR就是XMLHttpRequest,是微软在IE5发布的时候第一次引入的功能,允许js脚本向服务器发起HTTP的请求,是AJAX技术的核心。这里测试的方法就是发送一个XMLHttpRequest的GET请求,获取当前运行的脚本。要注意这里的测试是异步的,因为测试的结果暂时还没有用到。
testXhr( ) {
const xhr = new XMLHttpRequest( ) ;
xhr.open ( "GET" , document.currentScript .src ) ; //initializes a newly-created request, or re-initializes an existing one - open(method, url)
xhr.onload = ( ) => ( this .xhrSucceeded = true ) ;
xhr.send ( ) ; //sends the request to the server.
}
testXhr( ) {
const xhr = new XMLHttpRequest( ) ;
xhr.open ( "GET" , document.currentScript .src ) ; //initializes a newly-created request, or re-initializes an existing one - open(method, url)
xhr.onload = ( ) => ( this .xhrSucceeded = true ) ;
xhr.send ( ) ; //sends the request to the server.
}
第三步是个补丁,注释说关闭窗口的时候nw.js进程可能有时候不会被终止,所以这里强制把窗口关闭事件和退出app绑定在一起。
hookNwjsClose( ) {
// [Note] When closing the window, the NW.js process sometimes does
// not terminate properly. This code is a workaround for that.
if ( typeof nw === "object" ) {
nw.Window .get ( ) .on ( "close" , ( ) => nw.App .quit ( ) ) ;
}
}
hookNwjsClose( ) {
// [Note] When closing the window, the NW.js process sometimes does
// not terminate properly. This code is a workaround for that.
if ( typeof nw === "object" ) {
nw.Window .get ( ) .on ( "close" , ( ) => nw.App .quit ( ) ) ;
}
}
第四步才是整个过程的核心,把每一个script都放上DOM来load。这些script都用defer但不是async的方法来读取,也就是在后台异步下载,然后在DOM ready的时候按顺序执行。每个script在load完成之后会更新loadCount,最后一个script读取完之后,PluginManager会开始设置插件。在window load完之后,这时候才来检查之前的XHR是不是成功。然后检查是不是游戏在/private/var里面运行,不然的话可能存不了档,这一段不是太重要就不贴了。然后会通过调取initEffekseerRuntime() 来初始化Effekseer这个用来实现粒子效果的工具。
const scriptUrls = [
"js/libs/pixi.js" ,
"js/libs/pako.min.js" ,
"js/libs/localforage.min.js" ,
"js/libs/effekseer.min.js" ,
"js/libs/vorbisdecoder.js" ,
"js/rmmz_core.js" ,
"js/rmmz_managers.js" ,
"js/rmmz_objects.js" ,
"js/rmmz_scenes.js" ,
"js/rmmz_sprites.js" ,
"js/rmmz_windows.js" ,
"js/plugins.js"
] ;
loadMainScripts( ) {
for ( const url of scriptUrls) {
const script = document.createElement ( "script" ) ;
script.type = "text/javascript" ;
script.src = url;
script.async = false ;
script.defer = true ;
script.onload = this .onScriptLoad .bind ( this ) ;
script.onerror = this .onScriptError .bind ( this ) ;
script._url = url;
document.body .appendChild ( script) ;
}
this .numScripts = scriptUrls.length ;
window.addEventListener ( "load" , this .onWindowLoad .bind ( this ) ) ;
window.addEventListener ( "error" , this .onWindowError .bind ( this ) ) ;
}
onScriptLoad( ) {
if ( ++this .loadCount === this .numScripts ) {
PluginManager.setup ( $plugins) ;
}
}
onScriptError( e) {
this .printError ( "Failed to load" , e.target ._url) ;
}
onWindowLoad( ) {
if ( !this .xhrSucceeded ) {
const message = "Your browser does not allow to read local files." ;
this .printError ( "Error" , message) ;
} else if ( this .isPathRandomized ( ) ) {
const message = "Please move the Game.app to a different folder." ;
this .printError ( "Error" , message) ;
} else if ( this .error ) {
this .printError ( this .error .name , this .error .message ) ;
} else {
this .initEffekseerRuntime ( ) ;
}
}
onWindowError( event) {
if ( !this .error ) {
this .error = event.error ;
}
}
const scriptUrls = [
"js/libs/pixi.js" ,
"js/libs/pako.min.js" ,
"js/libs/localforage.min.js" ,
"js/libs/effekseer.min.js" ,
"js/libs/vorbisdecoder.js" ,
"js/rmmz_core.js" ,
"js/rmmz_managers.js" ,
"js/rmmz_objects.js" ,
"js/rmmz_scenes.js" ,
"js/rmmz_sprites.js" ,
"js/rmmz_windows.js" ,
"js/plugins.js"
] ;
loadMainScripts( ) {
for ( const url of scriptUrls) {
const script = document.createElement ( "script" ) ;
script.type = "text/javascript" ;
script.src = url;
script.async = false ;
script.defer = true ;
script.onload = this .onScriptLoad .bind ( this ) ;
script.onerror = this .onScriptError .bind ( this ) ;
script._url = url;
document.body .appendChild ( script) ;
}
this .numScripts = scriptUrls.length ;
window.addEventListener ( "load" , this .onWindowLoad .bind ( this ) ) ;
window.addEventListener ( "error" , this .onWindowError .bind ( this ) ) ;
}
onScriptLoad( ) {
if ( ++this .loadCount === this .numScripts ) {
PluginManager.setup ( $plugins) ;
}
}
onScriptError( e) {
this .printError ( "Failed to load" , e.target ._url) ;
}
onWindowLoad( ) {
if ( !this .xhrSucceeded ) {
const message = "Your browser does not allow to read local files." ;
this .printError ( "Error" , message) ;
} else if ( this .isPathRandomized ( ) ) {
const message = "Please move the Game.app to a different folder." ;
this .printError ( "Error" , message) ;
} else if ( this .error ) {
this .printError ( this .error .name , this .error .message ) ;
} else {
this .initEffekseerRuntime ( ) ;
}
}
onWindowError( event) {
if ( !this .error ) {
this .error = event.error ;
}
}
这里在初始化Effekseer之后就把读取的spinner给擦除了,然后我们会就可以读取Scene_Boot这个场景来进入到第一个画面。
const effekseerWasmUrl = "js/libs/effekseer.wasm" ;
initEffekseerRuntime( ) {
const onLoad = this .onEffekseerLoad .bind ( this ) ;
const onError = this .onEffekseerError .bind ( this ) ;
effekseer.initRuntime ( effekseerWasmUrl, onLoad , onError ) ;
}
onEffekseerLoad( ) {
this .eraseLoadingSpinner ( ) ;
SceneManager.run ( Scene_Boot) ;
}
onEffekseerError( ) {
this .printError ( "Failed to load" , effekseerWasmUrl) ;
}
const effekseerWasmUrl = "js/libs/effekseer.wasm" ;
initEffekseerRuntime( ) {
const onLoad = this .onEffekseerLoad .bind ( this ) ;
const onError = this .onEffekseerError .bind ( this ) ;
effekseer.initRuntime ( effekseerWasmUrl, onLoad , onError ) ;
}
onEffekseerLoad( ) {
this .eraseLoadingSpinner ( ) ;
SceneManager.run ( Scene_Boot) ;
}
onEffekseerError( ) {
this .printError ( "Failed to load" , effekseerWasmUrl) ;
}
顺便来研究一下这里显示错误信息的方式。错误有名称和消息。如果遇到了错误的话,首先会把读取动画给停止,然后插入一个errorPrinter的div来分别显示错误的名称和消息。
printError( name , message) {
this .eraseLoadingSpinner ( ) ;
if ( !document.getElementById ( "errorPrinter" ) ) {
const errorPrinter = document.createElement ( "div" ) ;
errorPrinter.id = "errorPrinter" ;
errorPrinter.innerHTML = this .makeErrorHtml ( name , message) ;
document.body .appendChild ( errorPrinter) ;
}
}
makeErrorHtml( name , message) {
const nameDiv = document.createElement ( "div" ) ;
const messageDiv = document.createElement ( "div" ) ;
nameDiv.id = "errorName" ;
messageDiv.id = "errorMessage" ;
nameDiv.innerHTML = name ;
messageDiv.innerHTML = message;
return nameDiv.outerHTML + messageDiv.outerHTML ;
}
printError( name , message) {
this .eraseLoadingSpinner ( ) ;
if ( !document.getElementById ( "errorPrinter" ) ) {
const errorPrinter = document.createElement ( "div" ) ;
errorPrinter.id = "errorPrinter" ;
errorPrinter.innerHTML = this .makeErrorHtml ( name , message) ;
document.body .appendChild ( errorPrinter) ;
}
}
makeErrorHtml( name , message) {
const nameDiv = document.createElement ( "div" ) ;
const messageDiv = document.createElement ( "div" ) ;
nameDiv.id = "errorName" ;
messageDiv.id = "errorMessage" ;
nameDiv.innerHTML = name ;
messageDiv.innerHTML = message;
return nameDiv.outerHTML + messageDiv.outerHTML ;
}
显示出的错误如下:
下一章节将会贴出关于场景管理器的研究。