分享Cocos2d-x优化中纹理优化
1. 纹理像素格式
纹理优化工作的一个重要指标是纹理像素格式。在最大程度满足用户对保真度要求的前提下,选择合适的像素格式,能够大幅提高纹理的处理速度。此外,纹理像素格式与硬件密切相关。
下面我们先来了解一下主要的纹理像素格式:
- RGBA8888:32位色,是默认的像素格式。每个通道为8位(比特),每个像素占用4个字节。
- BGRA8888:同样是32位色,每个通道8位(比特),每个像素4个字节。
- RGBA4444:16位色,每个通道4位(比特),每个像素2个字节。
- RGB888:24位色,没有Alpha通道,因此不具备透明度。每个通道8位(比特),每个像素3个字节。
- RGB565:16位色,没有Alpha通道,没有透明度。R和B通道各为5位,G通道为6位。
- RGB5A1(或RGBA5551):16位色,每个通道各4位,Alpha通道仅用1位表示。
- PVRTC4:4位PVR压缩纹理格式,PVR格式是专门为iOS设备上的PowerVR图形芯片设计的。在iOS设备上使用效果极佳,因为可以直接加载到显卡上,无需经过中间的计算转化。
- PVRTC4A:具有Alpha通道的4位PVR压缩纹理格式。
- PVRTC2:2位PVR压缩纹理格式。
- PVRTC2A:具有Alpha通道的2位PVR压缩纹理格式。
此外,PVR格式在保存时还可采用Gzip和zlib压缩格式进行压缩,对应的保存文件为pvr.gz和pvr.ccz。经过压缩后,文件体积会更小,加载时占用的内存也更少。虽然在转化为纹理时需要解压,但对CPU的影响很小。
2. 纹理缓存异步加载
在启动游戏和进入场景时,由于需要加载的资源过多,游戏会出现卡顿现象,影响用户体验。我们可以采用纹理缓存(TextureCache)异步加载纹理图片。TextureCache类的异步加载函数如下:
virtual void addImageAsync(const std::string &filepath, std::function<void(Texture2D *)> callback)
其中,第一个参数是文件路径,第二个参数是回调函数。下面通过一个实例来介绍纹理缓存异步加载的使用。假设有200张小图片,需要加载到纹理缓存中,并且在加载过程中会在界面上显示加载进度。
纹理缓存异步加载实例
HelloWorldScene.cpp中的主要代码如下:
bool HelloWorld::init()
{
if (!Layer::init())
{
return false;
}
Size visibleSize = Director::getInstance()->getVisibleSize();
Vec2 origin = Director::getInstance()->getVisibleOrigin();
auto closeItem = MenuItemImage::create(
"CloseNormal.png",
"CloseSelected.png",
CC_CALLBACK_1(HelloWorld::menuCloseCallback, this));
closeItem->setPosition(Vec2(origin.x + visibleSize.width - closeItem->getContentSize().width / 2,
origin.y + closeItem->getContentSize().height / 2));
auto menu = Menu::create(closeItem, NULL);
menu->setPosition(Vec2::ZERO);
this->addChild(menu, 1);
_labelLoading = Label::createWithTTF("loading…", "fonts/Marker Felt.ttf", 35);
_labelPercent = Label::createWithTTF("0%", "fonts/Marker Felt.ttf", 35);
_labelLoading->setPosition(Vec2(visibleSize.width / 2, visibleSize.height / 2 - 20));
_labelPercent->setPosition(Vec2(visibleSize.width / 2, visibleSize.height / 2 + 20));
this->addChild(_labelLoading);
this->addChild(_labelPercent);
_numberOfLoadedSprites = 0;
_imageOffset = 0;
auto sharedFileUtils = FileUtils::getInstance();
std::string fullPathForFilename = sharedFileUtils->fullPathForFilename("ImageMetaData.plist"); // ①
ValueVector vec = FileUtils::getInstance()->getValueVectorFromFile(fullPathForFilename); // ②
_numberOfSprites = vec.size(); // ③
// 加载纹理
for (auto& e : vec) // ④
{
auto row = e.asValueMap();
auto filename = "icons/" + row.at("filename").asString();
Director::getInstance()->getTextureCache()->addImageAsync(filename,
CC_CALLBACK_1(HelloWorld::loadingCallBack, this)); // ⑤
}
return true;
}
void HelloWorld::loadingCallBack(Texture2D *texture) // ⑥
{
++_numberOfLoadedSprites;
__String* str = __String::createWithFormat("%d%%",
(int)(((float)_numberOfLoadedSprites / _numberOfSprites) * 100)); // ⑦
_labelPercent->setString(str->getCString()); // ⑧
Size visibleSize = Director::getInstance()->getVisibleSize();
int i = ++_imageOffset * 60;
auto sprite = Sprite::createWithTexture(texture); // ⑨
sprite->setAnchorPoint(Vec2(0, 0));
addChild(sprite, -1);
sprite->setPosition(Vec2(i % (int)visibleSize.width, (i / (int)visibleSize.width) * 60));
if (_numberOfLoadedSprites == _numberOfSprites) // ⑩
{
_numberOfLoadedSprites = 0;
}
}
代码解释
- 第①行代码:获取资源目录下ImageMetaData.plist文件的全路径。ImageMetaData.plist文件用于描述要加载的图标文件名,其内容如下:
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> <plist version="1.0"> <array> <dict> <key>filename</key> <string>01-refresh.png</string> </dict> <dict> <key>filename</key> <string>02-redo.png</string> </dict> <dict> <key>filename</key> <string>03-loopback.png</string> </dict> <dict> <key>filename</key> <string>04-squiggle.png</string> </dict> <!-- ... --> </array> </plist> - 第②行代码:通过FileUtils的getValueVectorFromFile函数将ImageMetaData.plist文件内容读入到ValueVector类型的变量vec中。
- 第③行代码:
_numberOfSprites = vec.size()用于获取数组的长度,并将其赋值给成员变量_numberOfSprites,以便计算加载进度。 - 第④行代码:循环遍历数组,数组中的每个元素是键值对结构。
auto row = e.asValueMap()用于获取键值对结构,然后通过row.at("filename").asString()从键值对对象row中取出键为filename对应的值。 - 第⑤行代码:调用TextureCache的addImageAsync函数实现异步加载图片缓存,
HelloWorld::loadingCallBack是回调函数,this参数表示回调函数的目标对象。 - 第⑥行代码:定义回调函数的实现。
- 第⑦行代码:表达式
(int)(((float)_numberOfLoadedSprites / _numberOfSprites) * 100)用于计算加载进度,"%d%%"用于显示百分号,其中%d是格式化输出数字,%%用于输出%,前面的%起到转义作用。 - 第⑧行代码:
_labelPercent->setString(str->getCString())用于设置进度标签_labelPercent的内容。 - 第⑨行代码:
auto sprite = Sprite::createWithTexture(texture)通过纹理对象texture创建精灵对象。 - 第⑩行代码:
if (_numberOfLoadedSprites == _numberOfSprites)用于判断是否完成加载任务,_numberOfLoadedSprites是已加载的图片数,_numberOfSprites是要加载的全部图片数。