Cocos Creator实现Google Play obb 分包

背景

Google Play 对 APK 大小限制是 100 M,但是游戏稍微重度一点,资源就会很多,包体很容易就超过了这个限制;Google Play 提供了 obb 分包方案,来解决包体问题。

OBB 是 Opaque Binary Blob 的缩写,是一种类型 zip 文件格式,作为安卓应用的扩展数据包。
参考:安卓开发指南和百度百科

上面打不开的话,试试这个:https://developer.android.google.cn/google/play/expansion-files

思路

游戏多数资源都不需要在启动游戏时,就加载到内存,通常都是在游戏运行期间,需要时再动态去加载;因此可以:

  1. 将动态的资源分离出来,打包到 obb 文件中;
  2. 将 obb 文件和 APK 包一起上传到 Google Play;
  3. 玩家在下载 APK 时,会同时将 obb 文件下载到手机的 /Android/obb/包名/obb文件 路径;
  4. 首次启动游戏后,解压 obb 文件 到指定目录;
  5. 将 此目录 添加游戏资源的搜索路径中。

根据上面的思路,这里写了一个 demo,供大家参数,cocos creator 版本 2.2.0,过程如下:

一 新建 Demo

  1. 新建一个 HelloWorld 工程
  2. 新建 resources 目录,将图片 icon.png 加入到 resources,作为需要动态加载的资源
  3. 然后再 ui 上新建一个 Sprite,去掉纹理,在代码中动态加载 obb 中的资源

编辑器中设置如图:
Cocos Creator实现Google Play obb 分包_第1张图片
4. 打开 HelloWorld 脚本,新建一个 Sprite 变量,关联到刚刚新建的精灵
5. 在 onLoad 方法动动态加载图片

onLoad: function () {
        this.label.string = this.text;
       // 动态加载资源代码
        cc.loader.loadRes("icon", cc.SpriteFrame, ((err, spf)=>{
            if (err) {
                console.error(err.message || err);
                return;
            }
            this.sp.spriteFrame = spf;
        }));
    },

这时候可以用网页运行一下:
Cocos Creator实现Google Play obb 分包_第2张图片
这一步就是我们平时正常的项目开发流程,前期是不用管obb分包和打包的,只管开发功能就行。等功能开发好之后,才需要进一步的对接渠道,打包,分包等。

二 制作 obb 分包

  1. 构建 android 工程, 将上一步的 demo 构建 android 工程
    Cocos Creator实现Google Play obb 分包_第3张图片

  2. 找到要分包的资源,打开新构建出的 android 工程,刚刚需要动态加载的资源。
    Cocos Creator实现Google Play obb 分包_第4张图片

  3. 分离 obb 分包资源,新建一个目录 ‘raw-assets/0c/’,将资源剪切过来,注意是剪切,原来的资源不要了。
    Cocos Creator实现Google Play obb 分包_第5张图片

  4. 制作 obb 分包,用压缩软件将 raw-assets 压缩成 zip 文件,然后改名为 main.1.org.cocos2d.helloworld.obb,命名规则是 [main/patch].[versionCode].[packageName].obb,到这一步 obb 分包就制作完成了。
    在这里插入图片描述

  5. 打包 APK,将分离 obb 文件之后工程,打包成 apk。此时如果运行 apk,图片资源是加载不出来的。

Cocos Creator实现Google Play obb 分包_第6张图片

找资源,分离资源,打包重命名,过程都很繁琐,建议通过脚本自动化完成

三 上传下载 obb 包

经过上一步构建和分离资源得到了一个 obb 文件,如果是正式上线,需要将分离资源后的工程打包成 apk,然后将 obb 文件 和 apk 一起,提交到 Google Play,玩家下载 apk 时,就会将 obb文件一起下载到手机中的 /Android/obb/包名中。为了测试,直接把生成的 apk 直接拷贝到这个目录中:

Cocos Creator实现Google Play obb 分包_第7张图片
拷贝的方式,不同的模拟器或是手机,方式不一样,请自行搜索。

四 解压 obb 包

obb 文件已经下载到手机之后,游戏首次启动需要先将 obb 文件解压到指定目录。为了能找到资源,还需要将解压的目录,加入到搜索路径中。

  1. 添加搜索路径,修改 HelloWorld.js ,在加载资源之前,将搜索路径加上。
onLoad: function () {
        // 将解压目录添加到资源搜索路径中
        if (window.jsb) {
            let obbDir = ((jsb.fileUtils ? jsb.fileUtils.getWritablePath() : "/") + "obb")
            jsb.fileUtils.addSearchPath(obbDir, true);
            console.log(obbDir);
        }

        this.label.string = this.text;
        
        cc.loader.loadRes("icon", cc.SpriteFrame, ((err, spf)=>{
            if (err) {
                console.error(err.message || err);
                return;
            }
            this.sp.spriteFrame = spf;
        }));
    },
  1. 解压 obb 资源,在 APPActivity 的 onCreate 方法中,添加如下代码解压资源:
        String obbFilepath = Utils.getInstance().getObbFilepath();
        String outPath = "/data/data/org.cocos2d.helloworld/files/obb/res";
        Utils.unzipFile(obbFilepath, outPath);
  1. 实现解压方法,在制作 obb 的过程可以知道 obb 实际上就是 zip 文件,因此只需要 zip 文件解压就可以了,这里提供一个 作为参数:
	// 获取 obb 文件路径
    public String getObbFilepath() {
        try {
            Cocos2dxActivity context = (Cocos2dxActivity) AppActivity.getContext();
            String packageName = context.getPackageName();
            int versionCode = context.getPackageManager().getPackageInfo(packageName, 0).versionCode;
            return context.getObbDir().getPath()
                    + File.separator
                    + "main."
                    + versionCode
                    + "."
                    + packageName
                    + ".obb";
        } catch (PackageManager.NameNotFoundException e) {
            e.printStackTrace();
            Log.e(TAG, "getFilepath fail");
            return "";
        }
    }

	// 解压 zip 文件
    public static boolean unzipFile(String zipFilepath, String unzipPath) {
        try {
            File zipFile = new File(zipFilepath);
            if (!zipFile.exists()) {
                Log.e(TAG, "no zip file " + zipFilepath);
                return false;
            }

            File outDir = new File(unzipPath);
            if (!outDir.exists()) {
                if (!outDir.mkdirs()) {
                    Log.e(TAG, "create unzip dir fail");
                    return false;
                }
            }

            ZipInputStream zipStream = new ZipInputStream(new FileInputStream(zipFile));
            ZipEntry zipEntry;
            String entryName;
            while ((zipEntry = zipStream.getNextEntry()) != null) {
                entryName = zipEntry.getName();
                if (zipEntry.isDirectory()) {
                    entryName = entryName.substring(0, entryName.length() - 1);
                    File subDir = new File(unzipPath + File.separator + entryName);
                    if (!subDir.exists() && !subDir.mkdirs()) {
                        Log.e(TAG, "create unzip sub dir fail " + subDir.getName());
                        return false;
                    }
                } else {
                    File subFile = new File(unzipPath + File.separator + entryName);

                    String parentPath = subFile.getParent();
                    if (parentPath == null) {
                        Log.e(TAG, "get sub file parent dir fail" + subFile.getName());
                        return false;
                    }
                    File parentDir = new File(parentPath);
                    if (!parentDir.exists() || !parentDir.isDirectory()) {
                        if (!parentDir.mkdirs()) {
                            Log.e(TAG, "create sub file parent dir fail" + subFile.getName());
                            return false;
                        }
                    }

                    if (!subFile.createNewFile()) {
                        Log.e(TAG, "create sub file fail" + subFile.getName());
                        return false;
                    }

                    FileOutputStream out = new FileOutputStream(subFile);
                    int len;
                    byte[] buffer = new byte[1024];
                    while ((len = zipStream.read(buffer)) != -1) {
                        out.write(buffer, 0, len);
                        out.flush();
                    }
                    out.close();
                }
            }
            zipStream.close();
            return true;
        } catch (Exception e) {
            Log.e(TAG, "unzipFile fail exception " + zipFilepath + " " + unzipPath);
            e.printStackTrace();
            return false;
        }
    }
  1. 重新打开 apk,然后运行可以看到,现在图片资源显示出来了。
    Cocos Creator实现Google Play obb 分包_第8张图片
    打开我们的解压目录,可以看到解压之后的资源
    Cocos Creator实现Google Play obb 分包_第9张图片

*** 若是压缩包内容很大,可以考虑用C++实现解压 ***

后记

obb 分包制作流程就是这样的,但是有几点内容还需要进一步的完成:

  1. obb 资源分离,需要一个配置文件,来指定哪些资源需要移到 obb 文件中;另外还需要一个自动化脚本,将配置文件中指定的资源,移动到 obb 文件对应的目录,然后压缩重命名;
  2. 解压 obb 文件,按照 demo 的方法,每次都会解压,需要一个标记,只有首次安装,或者是解压目录不存在时才需要解压
  3. 热更新,如何用热更新下来的资,覆盖掉obb 文件的资源?其实解压obb文件的,就是为了方便后续做热更新,不然其实可以不用解压,可以有方法直接读取 obb 文件中的内容。

最后,有问题可以加Q群(830756115)讨论。

Cocos Creator实现Google Play obb 分包_第10张图片

你可能感兴趣的:(游戏开发)