查看: 2175|回复: 1

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

[复制链接]
梦石
0
星屑
15
在线时间
1 小时
注册时间
2023-5-8
回帖
0
发表于 2023-5-9 00:20:25 | 显示全部楼层 |阅读模式

加入我们,或者,欢迎回来。

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

×
本帖最后由 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.


[pre lang="javascript"]const main = new Main();
main.run();[/pre]

Main.run() 中一共有四步

[pre lang="javascript"]    run() {
        this.showLoadingSpinner();
        this.testXhr();
        this.hookNwjsClose();
        this.loadMainScripts();
    }[/pre]

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

[pre lang="javascript"]    showLoadingSpinner() {
        const loadingSpinner = document.createElement("div");  
        const loadingSpinnerImage = document.createElement("div");
        loadingSpinner.id = "loadingSpinner";
        loadingSpinnerImage.id = "loadingSpinnerImage";
        loadingSpinner.appendChild(loadingSpinnerImage);
        document.body.appendChild(loadingSpinner);
    }[/pre]

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

[pre lang="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;
}[/pre]

读取动画的效果:

ezgif-3-f6024e22bc.jpg

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


[pre lang="javascript"]    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.
    }[/pre]

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

[pre lang="javascript"]    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());
        }
    }[/pre]

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

[pre lang="javascript"]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;
      }
  }[/pre]

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

[pre lang="javascript"]    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);
    }[/pre]

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

[pre lang="javascript"]    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;
    }[/pre]

显示出的错误如下:

ezgif-3-c22c219686.jpg

下一章节将会贴出关于场景管理器的研究。
梦石
0
星屑
41
在线时间
4 小时
注册时间
2023-4-27
回帖
1
发表于 2023-5-10 16:56:22 | 显示全部楼层
厉害,跟着一起学习
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 注册会员

本版积分规则

Powered by Discuz! X5.0 © 2001-2026 Discuz! Team.

在本版发帖返回顶部