图片文件格式 |
---|
png |
jpg |
纹理压缩格式 |
---|
ETC1/2 |
PVRTC |
ASTC |
图片属性 | 解释 |
---|---|
分辨率 | 宽高像素值(pt),如:1024*1024 |
位深度 | 用来存储像素颜色的值,如RGBA8888,红黄蓝透明度4个维度每个8bit,一共就是32位,一般使用的就是32位,也被称为真彩色 |
文件大小 | 文件所占用的存储大小 |
图片的优化分为两种:
一般的优化方式是使用压缩工具如pngquant、tinypng等,直接压缩文件大小。
图片文件大小压缩,不意味着读到内存中的大小会减少。
一般情况下,以ARPG8888来说,计算一个1024*1024分辨率的图片读到内存中的大小,计算公式为:
1024 * 1024 * 4 * 8 = 33,554,432 bit = 4,194,304 byte ≈ 4 mb
理解起来就是 1024 * 1024
个像素,每个像素有 argb 4
个通道,每个通道含有 8 bit
数据用来存储颜色值。
png格式不能直接被GPU识别,需要在cpu把图片读进内存中解码后,再传递给gpu使用,这样做会造成一定的cpu消耗和很大的瞬时运行内存(RAM)占用。
因为大部分gpu对于压缩后的纹理有比较好的支持,无需cpu解码,占用内存小。于是我们要寻找一种合适的纹理压缩方式。
这时候astc格式就展露在我们眼前,IOS和安卓端都对astc有较好的支持率。
在运行内存中来说,astc的内存使用能节省75%左右,提升巨大。
creator引擎在编译时会把选中的纹理压缩类型记录到import文件夹下的json文件中,多种图片格式以下划线‘_’区分开,下图演示里选择了astc6x6格式和webp格式,所以生成的类型就是
代码中在读取文件格式时,会根据列举的文件类型判断当前机器是否支持。由下面代码图里能够看到,7@34这里的7代表的就是第7个.astc,4代表的就是第4个.webp。若当前设备不支持astc就会去寻找webp格式。
一切的一切都要从bundle.js这个类说起,类中的load函数会调用cc.assetManager.loadAny函数来下载相关资源并获取资源数据返回。
,传递的参数是资源名paths,类型type,进度回调函数onProgress,完成回调函数onComplete。
cc.assetManager是CCAssetManager类,这个类里有一个loadAny函数,此函数简单封装了一个Task并把task传递到pipeline中。这里的pipeline是定义在shared.js中的new Pipeline(‘normal load’, [])一个名字叫做normal load的管线,异步执行任务,管线填充在CCAssetManager类中pipeline.append(preprocess).append(load);包含一个preprocess和load方法。
task的状态为:
{
input: 资源名,
onProgress: onProgress,
onComplete: onComplete,
options: {
preset: 'default',
__requestType__: 'path',
type: type,
bundle: bundle名,
__outputAsArray__: false,
}
}
4. normal load–pipeline先调用preprocess,这个函数里对Task的options属性做遍历,这里也就是preset、requestType、type、bundle和__outputAsArray__五个key。
requestType、type、bundle和preset键值对保存到subOptions对象中,__outputAsArray__和preset保存到leftOptions对象中,并覆盖掉task的options属性。
新建一个subTask,input为task.input,options为subOptions对象,走transformPipeline管线流程同步执行任务,获取的值设置为task.source和task.output。最后调用done()。
task的状态为:
{
input: 资源名,
onProgress: onProgress,
onComplete: onComplete,
options: {
preset: 'default',
__outputAsArray__: false,
},
output: ,
source: ,
}
subTask的状态为:
{
input: 资源名,
options: {
preset: 'default',
__requestType__: 'path',
type: type,
bundle: bundle名,
}
}
5. transformPipeline里包含两个函数parse和combine。
subTask的状态为:
{
options: {
preset: 'default',
__requestType__: 'path',
type: type,
bundle: bundle名,
},
input: [{
config: bundle所有配置,
uuid: 资源的uuid,
info: {path: path, uuid: uuid},
ext: '.json',
}]
}
源码展示:
function parse (task) {
var input = task.input, options = task.options;
input = Array.isArray(input) ? input : [ input ];
task.output = [];
for (var i = 0; i < input.length; i ++ ) {
var item = input[i];
var out = RequestItem.create();
if (typeof item === 'string') {
item = Object.create(null);
item[options.__requestType__ || RequestType.UUID] = input[i];
}
if (typeof item === 'object') {
// local options will overlap glabal options
cc.js.addon(item, options);
if (item.preset) {
cc.js.addon(item, cc.assetManager.presets[item.preset]);
}
for (var key in item) {
switch (key) {
case RequestType.UUID:
var uuid = out.uuid = decodeUuid(item.uuid);
if (bundles.has(item.bundle)) {
var config = bundles.get(item.bundle)._config;
var info = config.getAssetInfo(uuid);
if (info && info.redirect) {
if (!bundles.has(info.redirect)) throw new Error(`Please load bundle ${info.redirect} first`);
config = bundles.get(info.redirect)._config;
info = config.getAssetInfo(uuid);
}
out.config = config;
out.info = info;
}
out.ext = item.ext || '.json';
break;
case '__requestType__':
case 'ext':
case 'bundle':
case 'preset':
case 'type': break;
case RequestType.DIR:
if (bundles.has(item.bundle)) {
var infos = [];
bundles.get(item.bundle)._config.getDirWithPath(item.dir, item.type, infos);
for (let i = 0, l = infos.length; i < l; i++) {
var info = infos[i];
input.push({uuid: info.uuid, __isNative__: false, ext: '.json', bundle: item.bundle});
}
}
out.recycle();
out = null;
break;
case RequestType.PATH:
if (bundles.has(item.bundle)) {
var config = bundles.get(item.bundle)._config;
var info = config.getInfoWithPath(item.path, item.type);
if (info && info.redirect) {
if (!bundles.has(info.redirect)) throw new Error(`you need to load bundle ${info.redirect} first`);
config = bundles.get(info.redirect)._config;
info = config.getAssetInfo(info.uuid);
}
if (!info) {
out.recycle();
throw new Error(`Bundle ${item.bundle} doesn't contain ${item.path}`);
}
out.config = config;
out.uuid = info.uuid;
out.info = info;
}
out.ext = item.ext || '.json';
break;
case RequestType.SCENE:
if (bundles.has(item.bundle)) {
var config = bundles.get(item.bundle)._config;
var info = config.getSceneInfo(item.scene);
if (info && info.redirect) {
if (!bundles.has(info.redirect)) throw new Error(`you need to load bundle ${info.redirect} first`);
config = bundles.get(info.redirect)._config;
info = config.getAssetInfo(info.uuid);
}
if (!info) {
out.recycle();
throw new Error(`Bundle ${config.name} doesn't contain scene ${item.scene}`);
}
out.config = config;
out.uuid = info.uuid;
out.info = info;
}
break;
case '__isNative__':
out.isNative = item.__isNative__;
break;
case RequestType.URL:
out.url = item.url;
out.uuid = item.uuid || item.url;
out.ext = item.ext || cc.path.extname(item.url);
out.isNative = item.__isNative__ !== undefined ? item.__isNative__ : true;
break;
default: out.options[key] = item[key];
}
if (!out) break;
}
}
if (!out) continue;
task.output.push(out);
if (!out.uuid && !out.url) throw new Error('Can not parse this input:' + JSON.stringify(item));
}
return null;
}
subTask的状态为:
{
options: {
preset: 'default',
__requestType__: 'path',
type: type,
bundle: bundle名,
},
output: [{
config: bundle所有配置,
uuid: 资源的uuid,
info: {path: path, uuid: uuid},
ext: '.json',
url: 具体地址,
}]
}
task的状态为:
{
input: 资源名,
onProgress: onProgress,
onComplete: onComplete,
options: {
preset: 'default',
__outputAsArray__: false,
},
output: [{
config: bundle所有配置,
uuid: 资源的uuid,
info: 具体信息,
ext: '.json',
url: 具体地址,
}],
source: [{
config: bundle所有配置,
uuid: 资源的uuid,
info: 具体信息,
ext: '.json',
url: 具体地址,
}],
}
源码展示:
function combine (task) {
var input = task.output = task.input;
for (var i = 0; i < input.length; i++) {
var item = input[i];
if (item.url) continue;
var url = '', base = '';
var config = item.config;
if (item.isNative) {
base = (config && config.nativeBase) ? (config.base + config.nativeBase) : cc.assetManager.generalNativeBase;
}
else {
base = (config && config.importBase) ? (config.base + config.importBase) : cc.assetManager.generalImportBase;
}
let uuid = item.uuid;
var ver = '';
if (item.info) {
if (item.isNative) {
ver = item.info.nativeVer ? ('.' + item.info.nativeVer) : '';
}
else {
ver = item.info.ver ? ('.' + item.info.ver) : '';
}
}
// ugly hack, WeChat does not support loading font likes 'myfont.dw213.ttf'. So append hash to directory
if (item.ext === '.ttf') {
url = `${base}/${uuid.slice(0, 2)}/${uuid}${ver}/${item.options.__nativeName__}`;
}
else {
url = `${base}/${uuid.slice(0, 2)}/${uuid}${ver}${item.ext}`;
}
item.url = url;
}
return null;
}
task的状态为:
{
onProgress: onProgress,
onComplete: onComplete,
options: {
preset: 'default',
__outputAsArray__: false,
},
input: [{
config: bundle所有配置,
uuid: 资源的uuid,
info: {path: path, uuid: uuid},
ext: '.json',
url: 具体地址,
}],
source: [{
config: bundle所有配置,
uuid: 资源的uuid,
info: 具体信息,
ext: '.json',
url: 具体地址,
}],
}
subTask的状态为:
{
input: {
config: bundle所有配置,
uuid: 资源的uuid,
info: {path: path, uuid: uuid},
ext: '.json',
url: 具体地址,
},
onProgress: onProgress,
onComplete: newComplete,
options: {
preset: 'default',
__outputAsArray__: false,
__exclude__: {},
},
}
task的状态为:
{
onProgress: onProgress,
onComplete: onComplete,
progress : { finish: 0, total: task.input.length, canInvoke: true },
options: {
preset: 'default',
__outputAsArray__: false,
__exclude__: {},
},
input: [{
config: bundle所有配置,
uuid: 资源的uuid,
info: 具体信息,
ext: '.json',
url: 具体地址,
}],
source: [{
config: bundle所有配置,
uuid: 资源的uuid,
info: 具体信息,
ext: '.json',
url: 具体地址,
}],
}
源码展示:
function load (task, done) {
let firstTask = false;
if (!task.progress) {
task.progress = { finish: 0, total: task.input.length, canInvoke: true };
firstTask = true;
}
var options = task.options, progress = task.progress;
options.__exclude__ = options.__exclude__ || Object.create(null);
task.output = [];
forEach(task.input, function (item, cb) {
let subTask = Task.create({
input: item,
onProgress: task.onProgress,
options,
progress,
onComplete: function (err, item) {
if (err && !task.isFinish) {
if (!cc.assetManager.force || firstTask) {
if (!CC_EDITOR) {
cc.error(err.message, err.stack);
}
progress.canInvoke = false;
done(err);
}
else {
progress.canInvoke && task.dispatch('progress', ++progress.finish, progress.total, item);
}
}
task.output.push(item);
subTask.recycle();
cb();
}
});
loadOneAssetPipeline.async(subTask);
}, function () {
options.__exclude__ = null;
if (task.isFinish) {
clear(task, true);
return task.dispatch('error');
}
gatherAsset(task);
clear(task, true);
done();
});
}