游戏引擎使用分包
分包原理
loadBundle接口将从本地加载独立的bundle包资源,如果配置了分包,直接加载分包是不会自动下载到本地的,需要在loadBundle之前先加载分包,分包加载成功之后再加载bundle包,最后再从bundle包里面加载需要的游戏资源。
芒果TV小游戏分包加载接口跟微信保持一致,使用mgtv.loadSubpackage,打包完之后跟微信一样需要在根目录的game.json里面配置分包的名字和目录信息。
示例代码
COCOS
下面给了一段cocos creator3.x的实例代码,游戏方可以在调用了assetManager.loadBundle(cocos creator2.x就使用cc.assetManager.loadBundle)的地方调用PlatformUtils.loadBundle接口,内部会根据当前的环境自动调用加载分包,并等其完成后自动加载bundle包并返回,如果bundle包已经缓存到bundles,后续再调用会直接返回缓存的bundle。
import { assetManager } from "cc"
export default class PlatformUtils {
static loadSubpackage(packageName) {
return new Promise((resolve, reject) => {
let mgtv = window["mgtv"]
if (mgtv) {
const loadTask = mgtv.loadSubpackage({
name: packageName, // name 可以填 name 或者 root
success: function (res) {
// 分包加载成功后通过 success 回调
resolve(res)
},
fail: function (res) {
// 分包加载失败通过 fail 回调
reject(res)
}
})
} else {
// 没有芒果TV环境,直接算成功
resolve(true)
}
})
}
static bundles = {}
static loadBundle(packageName) {
return new Promise((resolve, reject) => {
if (this.bundles[packageName]) return resolve(this.bundles[packageName])
this.loadSubpackage(packageName).then((res) => {
assetManager.loadBundle(packageName, (err, bundle) => {
if (err) return reject(err)
this.bundles[packageName] = bundle
resolve(bundle)
})
}).catch((reason) => {
reject(reason)
})
})
}
}
Laya
LayaAir3.x
可以查看官方的web分包的文档,需要注意的是,需要在“始终包含的资源目录”中设置分包的资源,不然相关的资源laya不会打包到发布目录。

需要注意的是,"是否是远程包"和"启动时自动加载"不用勾选。
开启远程包相当于不放在分包里面了,游戏商自行承担cdn费用,这个不勾很好理解。 启动时自动加载,这个勾了相当于会自动加载本地的分包,但此时分包没有下载下来,自然就加载不到,所以必须先调用mgtv的加载分包,再手动加载才能正常的下载下来,所以这个也不能勾。
下面给了一段LayaAir3.x的示例代码,因为loadSubpackage并不会返回bundle的对象,所以只需要记录是否加载过就行了
export default class PlatformUtils {
static loadSubpackage(packageName) {
return new Promise((resolve, reject) => {
let mgtv = window["mgtv"]
if (mgtv) {
const loadTask = mgtv.loadSubpackage({
name: packageName, // name 可以填 name 或者 root
success: function (res) {
// 分包加载成功后通过 success 回调
resolve(res)
},
fail: function (res) {
// 分包加载失败通过 fail 回调
reject(res)
}
})
} else {
// 没有芒果TV环境,直接算成功
resolve(true)
}
})
}
static bundles = {}
static loadBundle(packageName) {
return new Promise((resolve, reject) => {
if (this.bundles[packageName]) return resolve(true)
this.loadSubpackage(packageName).then((res) => {
Laya.loader.loadPackage(packageName).then((bundle) => {
this.bundles[packageName] = true
resolve(true)
})
}).catch((reason) => {
reject(reason)
})
})
}
}
LayaAir2.x
Laya2.x的机制更简单,因为没有分包系统,直接将调用mgtv的loadSubpackage接口将资源解压到对应的目录即可使用Laya.loader.load进行加载。
export default class PlatformUtils {
static loadSubpackage(packageName) {
return new Promise((resolve, reject) => {
let mgtv = window["mgtv"]
if (mgtv) {
const loadTask = mgtv.loadSubpackage({
name: packageName, // name 可以填 name 或者 root
success: function (res) {
// 分包加载成功后通过 success 回调
resolve(res)
},
fail: function (res) {
// 分包加载失败通过 fail 回调
reject(res)
}
})
} else {
// 没有芒果TV环境,直接算成功
resolve(true)
}
})
}
static bundles = {}
static loadBundle(packageName) {
return new Promise((resolve, reject) => {
if (this.bundles[packageName]) return resolve(true)
this.loadSubpackage(packageName).then(() => {
this.bundles[packageName] = true
resolve(true)
}).catch((reason) => {
reject(reason)
})
})
}
}
Unity
先创建一个中间层,在Unity的Addressables系统加载资源前,先调用芒果平台的分包加载接口。等待分包下载完成后,再初始化Unity的Addressables系统。
芒果平台接口封装
using System;
using UnityEngine;
using UnityEngine.Events;
public class MangoPlatformUtility : MonoBehaviour
{
private static MangoPlatformUtility _instance;
public static MangoPlatformUtility Instance => _instance ??= CreateInstance();
private static MangoPlatformUtility CreateInstance()
{
var obj = new GameObject("MangoPlatformUtility");
DontDestroyOnLoad(obj);
return obj.AddComponent<MangoPlatformUtility>();
}
// 加载分包接口
public void LoadSubpackage(string packageName, UnityAction<string> onSuccess, UnityAction<string> onFail)
{
// 检查是否在芒果平台环境中
if (Application.platform == RuntimePlatform.WebGLPlayer)
{
// 调用JavaScript接口
Application.ExternalCall("MangoLoadSubpackage", packageName,
onSuccess, onFail);
}
else
{
// 非芒果平台环境,模拟成功
Debug.LogWarning("Not in Mango platform environment, simulating success");
onSuccess?.Invoke("simulated_success");
}
}
}
Addressables加载管理器
using System.Collections;
using UnityEngine;
using UnityEngine.AddressableAssets;
using UnityEngine.Events;
public class AddressablesLoader : MonoBehaviour
{
private static AddressablesLoader _instance;
public static AddressablesLoader Instance => _instance ??= CreateInstance();
private static AddressablesLoader CreateInstance()
{
var obj = new GameObject("AddressablesLoader");
DontDestroyOnLoad(obj);
return obj.AddComponent<AddressablesLoader>();
}
// 加载分包和Addressables资源
public void LoadBundle(string packageName, UnityAction<object> onSuccess, UnityAction<string> onFail)
{
StartCoroutine(LoadBundleRoutine(packageName, onSuccess, onFail));
}
private IEnumerator LoadBundleRoutine(string packageName, UnityAction<object> onSuccess, UnityAction<string> onFail)
{
// 先调用芒果平台的分包加载接口
bool subpackageLoaded = false;
bool subpackageFailed = false;
string errorMessage = "";
MangoPlatformUtility.Instance.LoadSubpackage(
packageName,
result => {
subpackageLoaded = true;
Debug.Log($"Subpackage {packageName} loaded: {result}");
},
error => {
subpackageFailed = true;
errorMessage = error;
}
);
// 等待分包加载完成
yield return new WaitUntil(() => subpackageLoaded || subpackageFailed);
if (subpackageFailed)
{
onFail?.Invoke($"Failed to load subpackage: {errorMessage}");
yield break;
}
// 分包加载成功后,加载Addressables资源
var loadOperation = Addressables.LoadAssetAsync<object>(packageName);
yield return loadOperation;
if (loadOperation.Status == AsyncOperationStatus.Succeeded)
{
onSuccess?.Invoke(loadOperation.Result);
}
else
{
onFail?.Invoke($"Failed to load asset: {loadOperation.OperationException}");
}
Addressables.Release(loadOperation);
}
}
API接口
using UnityEngine;
using UnityEngine.Events;
public static class PlatformUtils
{
// 加载分包和资源包
public static void LoadBundle(string packageName, UnityAction<object> onSuccess, UnityAction<string> onFail)
{
AddressablesLoader.Instance.LoadBundle(packageName, onSuccess, onFail);
}
// 加载指定类型的资源
public static void LoadAsset<T>(string assetKey, UnityAction<T> onSuccess, UnityAction<string> onFail) where T : class
{
Addressables.LoadAssetAsync<T>(assetKey).Completed += handle =>
{
if (handle.Status == AsyncOperationStatus.Succeeded)
{
onSuccess?.Invoke(handle.Result);
}
else
{
onFail?.Invoke($"Failed to load asset {assetKey}: {handle.OperationException}");
}
};
}
}
js插件
mergeInto(LibraryManager.library, {
MangoLoadSubpackage: function(packageName, onSuccess, onFail) {
var packageNameStr = Pointer_stringify(packageName);
if (typeof mgtv !== 'undefined' && mgtv.loadSubpackage) {
mgtv.loadSubpackage({
name: packageNameStr,
success: function(res) {
// 回调到Unity
var successCallback = 'onSuccess';
unityInstance.SendMessage('MangoPlatformUtility', successCallback, JSON.stringify(res));
},
fail: function(res) {
// 回调到Unity
var failCallback = 'onFail';
unityInstance.SendMessage('MangoPlatformUtility', failCallback, JSON.stringify(res));
}
});
} else {
// 没有芒果环境,模拟成功
console.warn('Mango platform not detected, simulating success');
var successCallback = 'onSuccess';
unityInstance.SendMessage('MangoPlatformUtility', successCallback, 'simulated_success');
}
}
});