creator同步加载资源的方法,推荐!

creator同步加载资源的方法
原理:
注意:只在native中有效
native中,即使异步,也始终逃不过要在c++代码中读取文件,然后把文件内容传递给js层,跟踪代码发现,加载图片资源的代码在jsb_global.cpp的jsb_global_load_image函数中
下图中的代码1部分是在线程池中添加一个读取图片资源的任务,代码2是在cocos线程中将内容传递给js层。


然后就可以改代码了,拷贝一个initImageFunc函数,改名为initImageSyncFunc函数,
将代码调用改为同步的调用

auto initImageSyncFunc = [path, callbackVal](const std::string& fullPath, unsigned char* imageData, int imageBytes) {
	Image* img = new (std::nothrow) Image();
	bool loadSucceed = false;
	if (fullPath.empty())
	{
		loadSucceed = img->initWithImageData(imageData, imageBytes);
		free(imageData);
	}
	else
	{
		loadSucceed = img->initWithImageFile(fullPath);
	}

	struct ImageInfo* imgInfo = nullptr;
	if (loadSucceed)
	{
		imgInfo = createImageInfo(img);
	}

	se::AutoHandleScope hs;
	se::ValueArray seArgs;

	if (loadSucceed)
	{
		se::HandleObject retObj(se::Object::createPlainObject());
		Data data;
		data.copy(imgInfo->data, imgInfo->length);
		se::Value dataVal;
		Data_to_seval(data, &dataVal);
		retObj->setProperty("data", dataVal);
		retObj->setProperty("width", se::Value(imgInfo->width));
		retObj->setProperty("height", se::Value(imgInfo->height));
		retObj->setProperty("premultiplyAlpha", se::Value(imgInfo->hasPremultipliedAlpha));
		retObj->setProperty("bpp", se::Value(imgInfo->bpp));
		retObj->setProperty("hasAlpha", se::Value(imgInfo->hasAlpha));
		retObj->setProperty("compressed", se::Value(imgInfo->compressed));
		retObj->setProperty("numberOfMipmaps", se::Value(imgInfo->numberOfMipmaps));
		if (imgInfo->numberOfMipmaps > 0)
		{
			se::HandleObject mipmapArray(se::Object::createArrayObject(imgInfo->numberOfMipmaps));
			retObj->setProperty("mipmaps", se::Value(mipmapArray));
			auto mipmapInfo = img->getMipmaps();
			for (int i = 0; i < imgInfo->numberOfMipmaps; ++i)
			{
				se::HandleObject info(se::Object::createPlainObject());
				info->setProperty("offset", se::Value(mipmapInfo[i].offset));
				info->setProperty("length", se::Value(mipmapInfo[i].len));
				mipmapArray->setArrayElement(i, se::Value(info));
			}
		}

		retObj->setProperty("glFormat", se::Value(imgInfo->glFormat));
		retObj->setProperty("glInternalFormat", se::Value(imgInfo->glInternalFormat));
		retObj->setProperty("glType", se::Value(imgInfo->type));

		seArgs.push_back(se::Value(retObj));

		delete imgInfo;
	}
	else
	{
		SE_REPORT_ERROR("initWithImageFile: %s failed!", path.c_str());
	}

	callbackVal.toObject()->call(seArgs, nullptr);
	img->release();
};

然后再下面调用initImageFunc的地方添加一个同步调用的方法,如图所示


当然,还需要一个变量是控制同步调用还是异步调用,修改变量的方法也要有,还要到处给js层用

//变量控制是否用异步
bool useAsync = true;
void setUseAsync(bool async)
{
	useAsync = async;
}
//js调用接口
static bool js_setUseAsync(se::State& s)
{
	const auto& args = s.args();
	size_t argc = args.size();
	CC_UNUSED bool ok = true;
	if (argc == 1) {
		bool async;
		ok &= seval_to_boolean(args[0], &async);

		SE_PRECONDITION2(ok, false, "js_setUseAsync : Error processing arguments");

		setUseAsync(async);
		return true;
	}
	SE_REPORT_ERROR("wrong number of arguments: %d, was expecting %d", (int)argc, 1);
	return false;
}
SE_BIND_FUNC(js_setUseAsync)
//添加js绑定
__jsbObj->defineFunction("setUseAsync", _SE(js_setUseAsync));

在整个工程里面搜performFunctionInCocosThread,还可以找到其他异步调用的方法,还有个载入音频资源也是用到了异步,有需要的同学可以看看
改了之后,经过我的测试,加载一个prefab,并没有在同一帧里面读取到所有资源,在下一帧才读取到,好像是解析依赖的时候有新的依赖需要解析的话就放到下一帧?这个要再研究下,但是native的加载效果确实好了些,跟cocos2dx很像了,
特别是打开背包的时候,那么多物品,总是会先显示原始的物品icon,过那么几帧才能刷新到正确的icon,现在一打开就是正确的icon了,舒服多了

还有一个忘了,要在jsb-engine.js代码中,改一下downloadImage函数
原先的样子:

function downloadImage(item, callback) {
    var img = new Image();
    img.src = item.url;
    img.onload = function (info) {
        callback(null, img);
    };
    // Don't return anything to use async loading.
}

改后的样子

function downloadImage(item, callback) {
    var img = new Image();
    img.onload = function (info) {
        callback(null, img);
    };
    img.src = item.url;
    // Don't return anything to use async loading.
}

因为同步的加载的话,调用 img.src = item.url完毕的时候,资源已经加载了,这个时候还没设置onlond函数,会导致callback不能调用,所以需要交换下位置

楼主牛b,赞一个

不建议直接修改 downloadImage改变cocoscreator的加载机制 cocoscreator异步加载资源在体验上比同步的要好,有同步需求的地方可以使用同步的方法,我的做法是添加一个同步加载资源的方法

1赞

不建议修改或者添加C++源码的同步加载,可以使用 ts 的 asnyc await

你还是不懂为什么有的人需要同步加载,比如在打开背包,要显示某个icon,我需要的立即显示出来,不是先显示个问号,再等待几帧,再显示出来我需要的icon,ts的asnyc和await只是语法糖,本质上还是异步加载

你这个方法也不错,但是我如果load一个prefab,里面有很多依赖项,只有有限的几个项才是图片资源,但是根据cocos的加载流程,这里面的图片资源还是会走异步加载流程,除非load一个prefab之前,解析到里面需要的图片资源,然后再同步加载这些资源,在load这个prefab,但这样操作的话明显麻烦了,可能你们用起来很不错,但不是我想要的效果,其他同学可以参考下

你可能还是不懂为什么引擎组要使用多线程加载资源。你的比如需求,跟同步异步加载方式可以没有半毛钱关系(说的好像使用同步加载是光速一样),如果你只是想在代码层次上使用同步模式,我依然建议你仅在js层使用同步模式

我知道引擎组为什么使用多线程,为了和h5保持统一,对于自己游戏业务的需求,我是比引擎组更了解的,所以做了些更改,我也同样理解有些人为什么想要同步加载资源,所以做了这些分享,你也用不着给我建议,我只为我的项目的效果负责,达到我期望的效果就可以了,我也不会使用你的建议,我也不指望引擎组更改代码适应我的需求,只是分享出来给有需求的小伙伴,并不是要每个人都这样做的,谢谢

1赞

請問 你有試過自定義多線程方法嗎?

楼主的需求不是加快加载速度,而是加载完成后再打开界面,避免界面打开后还在发生变化,这个在js层能办到?
不建议改动c++的理由呢?
同步异步各有合适的使用场景哪里有银弹

加载完成在做某事,这个需求跟加载方式有什么关系,为何在js层办不到。没有理由,可能是我嘴碎,是我错了

同步加载可能会破坏引擎的主循环,帧数暴跌。而且加载不一定要在弹框的时候加载的,你可以选择加载完再弹框。
或者还有一个方案就是

  1. 预先加载第一页
  2. 按钮点击后加载第二页
  3. 弹框打开后静默加载剩下的数据

咱也不敢说,咱也不敢问。静静的看看大佬们的对话,从中学习一二

1赞