IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 游戏开发 -> Egret小游戏如何实现代码分包(含代码绝对干货) -> 正文阅读

[游戏开发]Egret小游戏如何实现代码分包(含代码绝对干货)

随着游戏往重度方向发展,游戏代码包越来越大,为了更好的加载体验,分包加载迫在眉睫。
早在2018年微信小游戏就推出分包API了,但是白鹭引擎官方却迟迟没发布高效的分包解决方案,就目前能找到的分包方案,却有很多问题。

我们先来分析一下目前公开的方案:
方案1:白鹭官方推出的资源分包,只能针对素材分包,没无法针对js代码分包,这里查看文档
方案2:在方案1的基础上,自己把原项目拆分成几个独立项目,一个分包对应一个项目,用类库的方式进行编译输出js。这种方式开发时不灵活,需要管理太多的项目,很不方便。
方案3:白鹭在5.3.6引擎推出了webpack 编译模式,提供的WebpackBundlePlugin插件支持js拆分输出,但是该方式有问题:拆分输出的文件必须一次性加载才能运行,无法按需加载(分包加载),详情参考这里(截止到2021年8月官方还未修正)
目前公开渠道能找到的这几种方法都解决不了实际需求,那就只能自己开发了。

需求目标:开发的时候只有1个项目,根据文件夹将代码编译出多个独立的js,输出到对应的包里面按需分包加载。
解决思路:定义一套输出规则,修改引擎的编译代码,使得项目代码输出成独立的多个js,最终实现分包加载。
1、配置js输出规则:在config.wxgame.ts里面修改ConpilePlugin调用,配合编译代码实现js拆分输出

 new CompilePlugin({ libraryType: "release", defines: { DEBUG: false, RELEASE: true 
      , subpackages:[{name: "game_main", matcher:(filepath)=>{
              //console.log("matcher:", filepath);
              if(filepath.indexOf("miniGame/") >-1 ) return true; //根据文件夹路径来判断
              return false;
          }
      }] 
  } }),

2.1、在config.wxgame.ts里面导入SubPackagePlugin(代码见文末)

import { SubPackagePlugin } from './wxgame/subpackage';

2.2、在config.wxgame.ts里面ManifestPlugin代码处下一行加上分包文件路径配置

, new SubPackagePlugin({ output: 'manifest.js', subPackages: [
          { root: "stage_game",  "includes": ["js/game_main.min.js","js/xxxxx.js"]}
      ]
  })

3、修改引擎编译代码:找到插件CompilePlugin的位置:C:\Users\用户名\AppData\Roaming\Egret\engine\5.2.33\tools\tasks\compile.js,修改tinyCompiler方法

function tinyCompiler(defines, pluginContext) {
    var os = require('os');
    var outfile = FileUtil.joinPath(os.tmpdir(), 'main.min.js');
    var options = egret.args;
    options.minify = true;
    options.publish = true;
    var configParsedResult = compiler.parseTsconfig(options.projectDir, options.publish);
    var compilerOptions = configParsedResult.options;
    var fileNames = configParsedResult.fileNames;
    var tsconfigError = configParsedResult.errors.map(function (d) { return d.messageText.toString(); });
    compilerOptions.outFile = outfile;
    compilerOptions.allowUnreachableCode = true;
    compilerOptions.emitReflection = true;
    compilerOptions.defines = defines;

	var pp = defines.subpackages; //根据编译规则进行js拆分输出
	if(pp){
		for(var j=0; j<pp.length; j++){
			var newList = [];
			for(var i=0; i<fileNames.length; i++){
				if( pp[j].matcher(fileNames[i]) ){
					newList.push(fileNames[i]);
					fileNames.splice(i, 1);
					i--;
				}
			}
			if(newList.length > 0){
				//配置上引擎的d.ts,避免编译时会报出很多错(不配置也可以,这些错不影响实际编译结果)
				newList.push(options.projectDir + "libs/modules/egret/egret.d.ts");
				newList.push(options.projectDir + "libs/modules/game/game.d.ts");
				newList.push(options.projectDir + "libs/modules/assetsmanager/assetsmanager.d.ts");
				var compilerHost = compiler.compile(compilerOptions, newList);
				if (compilerHost.messages && compilerHost.messages.length > 0) {
					global.exitCode = 1;
				}
				var jscode = fs.readFileSync(outfile);
				pluginContext.createFile(pp[j].name + ".js", new Buffer(jscode));
			}
		}
	}
    var compilerHost = compiler.compile(compilerOptions, fileNames);
    if (compilerHost.messages && compilerHost.messages.length > 0) {
        global.exitCode = 1;
    }
    var jscode = fs.readFileSync(outfile);
    return jscode;
}

//注意:在onFinish方法里面调用的时候需要吧 pluginContext传进来:
jscode = tinyCompiler(this.options.defines, pluginContext);

4、执行发布命令:egret publish --target wxgame

以上,有效的解决了js文件拆分和分包的需求。
需要强调的是,拆分了js,跨js调用的Class,需要在js里面设置 window.xxxx = xxxx对外暴露,这样才能正常调用。

附:项目里面调用分包代码:

public static openSubpackage(name, fun){
    if(DEBUG){
        return fun && fun();
    }
    if (window["wx"] && wx.loadSubpackage) {
        let task = wx.loadSubpackage({
            // 开发者根据自身需求更改
            name: name, //"stage1",
            success: function () {
                fun && fun();
            }
        });
        if(!task){
            console.warn("openSubpackage-fail:", name);
            return fun && fun();
        }
        task.onProgressUpdate(res => {
            //进度条UI效果自行补充
            console.warn("onProgressUpdate:", res);
        })
    }
    else {
        window["require"]("./" + name + "/game.js");
        fun && fun();
    }
}
Main.openSubpackage("stage_game", ()=>{
    //加载完成执行回调
})

附:SubPackagePlugin插件代码

import * as path from 'path';

const manifest = {
    initial: [],
    game: []
}

type SubPackagePluginOptions = {

    output: string,

    subPackages: {
        root: string,
        includes: string[]
    }[],

    verbose?: boolean
}

export class SubPackagePlugin {

    private verboseInfo: { filename: string, new_file_path: string }[] = [];

    private includes: {} = {};

    constructor(private options: SubPackagePluginOptions) {
        console.warn("options",options);
        if (this.options.subPackages) {
            this.options.subPackages.forEach((sub_package) => {
                sub_package.includes.forEach((file_name) => {
                    this.includes[file_name] = sub_package.root;
                })

            })
        }

    }

    async onFile(file: plugins.File) {
        const filename = file.relative;
        if(filename == "manifest.js") return;

        const extname = path.extname(filename);
        // console.warn("filename:", filename);
        if (extname == ".js") {
            const basename = path.basename(filename);
            let trans_filename = filename.split("\\").join('/');
            // let new_file_path = "js/" + basename.substr(0, basename.length - file.extname.length) + file.extname;
            let new_file_path = trans_filename;
            let isSubPackage = false;
            if (this.options.subPackages) {
                // 校验文件是否在subpackage包内,采用Object-key标识降低原来二层遍历的复杂度
                if (this.includes[trans_filename]) {
                    // new_file_path = this.includes[trans_filename] + "/js/" + basename.substr(0, basename.length - file.extname.length) + file.extname;
                    new_file_path = this.includes[trans_filename] + "/" + trans_filename;
                    isSubPackage = true;
                }
            }
            // verbose为true则开启文件路径输出调试
            if (this.options.verbose) {
                console.log(`SubPackagePlugin: ${filename} isSubPackage: ${isSubPackage}`);
            }


            file.path = path.join(file.base, new_file_path);

            if (!isSubPackage) {
                const relative = file.relative.split("\\").join('/');
                if (file.origin.indexOf('libs/') >= 0) {
                    manifest.initial.push(relative);
                }
                else {
                    manifest.game.push(relative);
                }
            }

            if (this.options.verbose) {
                this.verboseInfo.push({ filename, new_file_path })
            }
        }

        return file;
    }
    async onFinish(pluginContext: plugins.CommandContext) {
        const output = this.options.output;
        const extname = path.extname(output);
        let contents = '';
        switch (extname) {
            case ".json":
                contents = JSON.stringify(manifest, null, '\t');
                break;
            case ".js":
                contents = manifest.initial.concat(manifest.game).map((fileName) => {
                    let isEgret = fileName.indexOf("egret-library") >-1;
                    return (isEgret ? 'requirePlugin':'require') + `("${fileName}")`;
                }).join("\n")
                break;
        }
        pluginContext.createFile(this.options.output, new Buffer(contents));
        if (this.options.subPackages) {
            const self = this;
            this.options.subPackages.forEach(function (item) {
                let gameJS = "";
                // 配置的文件必须存在
                // gameJS = item.includes.map((file) => `require("${path.basename(file)}")`).join("\n");
                gameJS = item.includes.map((file) => {
                    const extname = path.extname(file);
                    console.log('extname', extname);
                    let return_location = ''
                    switch (extname) {
                        case '.js':
                            return_location = `require("js/${path.basename(file)}")`
                            break;
                    }
                    return return_location
                }).join("\n");
                console.log('output', gameJS);
                pluginContext.createFile(path.join(item.root, "game.js"), new Buffer(gameJS));
            });
        }
        if (this.options.verbose) {
            this.verboseInfo.forEach((item) => {
                console.log(`SubPackagePlugin: ${item.filename} => ${item.new_file_path}`);
            });
        }
    }
}

本文原创,未经作者同意禁止转载

  游戏开发 最新文章
6、英飞凌-AURIX-TC3XX: PWM实验之使用 GT
泛型自动装箱
CubeMax添加Rtthread操作系统 组件STM32F10
python多线程编程:如何优雅地关闭线程
数据类型隐式转换导致的阻塞
WebAPi实现多文件上传,并附带参数
from origin ‘null‘ has been blocked by
UE4 蓝图调用C++函数(附带项目工程)
Unity学习笔记(一)结构体的简单理解与应用
【Memory As a Programming Concept in C a
上一篇文章      下一篇文章      查看所有文章
加:2021-07-28 08:09:12  更:2021-07-28 08:10:14 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年4日历 -2024/4/30 23:09:14-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码