查看: 108|回复: 0

[转载发布] RPG Maker MZ 游戏引擎源码研究 - (1) 游戏入口

[复制链接]
  • TA的每日心情
    开心
    2024-5-10 09:55
  • 签到天数: 37 天

    连续签到: 3 天

    [LV.5]常住居民I

    2028

    主题

    32

    回帖

    7260

    积分

    管理员

    Rank: 9Rank: 9Rank: 9

    VIP
    0
    卡币
    5184
    OK点
    16
    积分
    7260
    发表于 同元九百九十六年四月十四日(夏) | 显示全部楼层 |阅读模式
    游戏跑在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.

    JAVASCRIPT 代码
    1. const main = new Main();
    2. main.run();
    复制代码


    Main.run() 中一共有四步

    JAVASCRIPT 代码
    1. run(){
    2.         this.showLoadingSpinner();
    3.         this.testXhr();
    4.         this.hookNwjsClose();
    5.         this.loadMainScripts();
    6.     }
    复制代码


    第一步是显示一个读取界面的spinner动画,这是用css实现的

    JAVASCRIPT 代码
    1. showLoadingSpinner(){
    2.         const loadingSpinner = document.createElement("div");  
    3.         const loadingSpinnerImage = document.createElement("div");
    4.         loadingSpinner.id = "loadingSpinner";
    5.         loadingSpinnerImage.id = "loadingSpinnerImage";
    6.         loadingSpinner.appendChild(loadingSpinnerImage);
    7.         document.body.appendChild(loadingSpinner);
    8.     }
    复制代码


    对应的css可以在css/game.css 中找到

    CSS 代码
    1. #loadingSpinner{
    2.     margin: auto;
    3.     position: absolute;
    4.     top: 0px;
    5.     left: 0px;
    6.     right: 0px;
    7.     bottom: 0px;
    8.     width: 120px;
    9.     height: 120px;
    10.     z-index: 10;
    11. }
    12. #loadingSpinnerImage{
    13.     margin: 0px;
    14.     padding: 0px;
    15.     border-radius: 50%;
    16.     width: 96px;
    17.     height: 96px;
    18.     border: 12pxsolid rgba(255, 255, 255, 0.25);
    19.     border-top: 12pxsolid rgba(255, 255, 255, 1);
    20.     animation: fadein 2s ease, spin 1.5s linear infinite;
    21. }
    复制代码


    读取动画的效果:



    第二步会测试XHR。XHR就是XMLHttpRequest,是微软在IE5发布的时候第一次引入的功能,允许js脚本向服务器发起HTTP的请求,是AJAX技术的核心。这里测试的方法就是发送一个XMLHttpRequest的GET请求,获取当前运行的脚本。要注意这里的测试是异步的,因为测试的结果暂时还没有用到。


    JAVASCRIPT 代码
    1. testXhr(){
    2.         const xhr = new XMLHttpRequest();
    3.         xhr.open("GET", document.currentScript.src);    //initializes a newly-created request, or re-initializes an existing one - open(method, url)
    4.         xhr.onload = () => (this.xhrSucceeded = true);
    5.         xhr.send();  //sends the request to the server.
    6.     }
    复制代码


    第三步是个补丁,注释说关闭窗口的时候nw.js进程可能有时候不会被终止,所以这里强制把窗口关闭事件和退出app绑定在一起。

    JAVASCRIPT 代码
    1. hookNwjsClose(){
    2.         // [Note] When closing the window, the NW.js process sometimes does
    3.         //   not terminate properly. This code is a workaround for that.
    4.         if(typeof nw === "object"){
    5.             nw.Window.get().on("close", () => nw.App.quit());
    6.         }
    7.     }
    复制代码


    第四步才是整个过程的核心,把每一个script都放上DOM来load。这些script都用defer但不是async的方法来读取,也就是在后台异步下载,然后在DOM ready的时候按顺序执行。每个script在load完成之后会更新loadCount,最后一个script读取完之后,PluginManager会开始设置插件。在window load完之后,这时候才来检查之前的XHR是不是成功。然后检查是不是游戏在/private/var里面运行,不然的话可能存不了档,这一段不是太重要就不贴了。然后会通过调取initEffekseerRuntime() 来初始化Effekseer这个用来实现粒子效果的工具。

    JAVASCRIPT 代码
    1. const scriptUrls = [
    2.     "js/libs/pixi.js",
    3.     "js/libs/pako.min.js",
    4.     "js/libs/localforage.min.js",
    5.     "js/libs/effekseer.min.js",
    6.     "js/libs/vorbisdecoder.js",
    7.     "js/rmmz_core.js",
    8.     "js/rmmz_managers.js",
    9.     "js/rmmz_objects.js",
    10.     "js/rmmz_scenes.js",
    11.     "js/rmmz_sprites.js",
    12.     "js/rmmz_windows.js",
    13.     "js/plugins.js"
    14. ];   
    15.   loadMainScripts(){
    16.         for(const url of scriptUrls){
    17.             const script = document.createElement("script");
    18.             script.type = "text/javascript";
    19.             script.src = url;
    20.             script.async = false;
    21.             script.defer = true;
    22.             script.onload = this.onScriptLoad.bind(this);
    23.             script.onerror = this.onScriptError.bind(this);
    24.             script._url = url;
    25.             document.body.appendChild(script);
    26.         }
    27.         this.numScripts = scriptUrls.length;
    28.         window.addEventListener("load", this.onWindowLoad.bind(this));
    29.         window.addEventListener("error", this.onWindowError.bind(this));
    30.     }
    31.   onScriptLoad(){
    32.         if(++this.loadCount === this.numScripts){
    33.             PluginManager.setup($plugins);
    34.         }
    35.     }
    36.   onScriptError(e){
    37.       this.printError("Failed to load", e.target._url);
    38.   }
    39. onWindowLoad(){
    40.         if(!this.xhrSucceeded){
    41.             const message = "Your browser does not allow to read local files.";
    42.             this.printError("Error", message);
    43.         }elseif(this.isPathRandomized()){
    44.             const message = "Please move the Game.app to a different folder.";
    45.             this.printError("Error", message);
    46.         }elseif(this.error){
    47.             this.printError(this.error.name, this.error.message);
    48.         }else{
    49.           this.initEffekseerRuntime();
    50.       }
    51.   }
    52.   onWindowError(event){
    53.       if(!this.error){
    54.           this.error = event.error;
    55.       }
    56.   }
    复制代码


    这里在初始化Effekseer之后就把读取的spinner给擦除了,然后我们会就可以读取Scene_Boot这个场景来进入到第一个画面。

    JAVASCRIPT 代码
    1. const effekseerWasmUrl = "js/libs/effekseer.wasm";
    2.     initEffekseerRuntime(){
    3.         constonLoad = this.onEffekseerLoad.bind(this);
    4.         constonError = this.onEffekseerError.bind(this);
    5.         effekseer.initRuntime(effekseerWasmUrl, onLoad, onError);
    6.     }
    7.     onEffekseerLoad(){
    8.         this.eraseLoadingSpinner();
    9.         SceneManager.run(Scene_Boot);
    10.     }
    11.     onEffekseerError(){
    12.         this.printError("Failed to load", effekseerWasmUrl);
    13.     }
    复制代码


    顺便来研究一下这里显示错误信息的方式。错误有名称和消息。如果遇到了错误的话,首先会把读取动画给停止,然后插入一个errorPrinter的div来分别显示错误的名称和消息。

    JAVASCRIPT 代码
    1. printError(name, message){
    2.         this.eraseLoadingSpinner();
    3.         if(!document.getElementById("errorPrinter")){
    4.             const errorPrinter = document.createElement("div");
    5.             errorPrinter.id = "errorPrinter";
    6.             errorPrinter.innerHTML = this.makeErrorHtml(name, message);
    7.             document.body.appendChild(errorPrinter);
    8.         }
    9.     }
    10.     makeErrorHtml(name, message){
    11.         const nameDiv = document.createElement("div");
    12.         const messageDiv = document.createElement("div");
    13.         nameDiv.id = "errorName";
    14.         messageDiv.id = "errorMessage";
    15.         nameDiv.innerHTML = name;
    16.         messageDiv.innerHTML = message;
    17.         return nameDiv.outerHTML + messageDiv.outerHTML;
    18.     }
    复制代码


    显示出的错误如下:



    下一章节将会贴出关于场景管理器的研究。
                 本帖来自P1论坛作者etherstalker,因Project1站服务器在国外有时候访问缓慢不方便作者交流学习,经联系P1站长fux2同意署名转载一起分享游戏制作经验,共同为国内独立游戏作者共同创造良好交流环境,原文地址:https://rpg.blue/forum.php?mod=viewthread&tid=493141  若有侵权,发帖作者可联系底部站长QQ在线咨询功能删除,谢谢。

    本帖子中包含更多资源

    您需要 登录 才可以下载或查看,没有账号?立即注册

    x
    天天去同能,天天有童年!
    回复 论坛版权

    使用道具 举报

    ahome_bigavatar:guest
    ahome_bigavatar:welcomelogin
    您需要登录后才可以回帖 登录 | 立即注册

    本版积分规则

    QQ|Archiver|手机版|小黑屋|同能RPG制作大师 ( 沪ICP备12027754号-3 )

    GMT+8, 2024-5-20 18:29 , Processed in 0.048796 second(s), 42 queries .

    Powered by Discuz! X3.4

    Copyright © 2001-2020, Tencent Cloud.

    快速回复 返回顶部 返回列表