Cocos2d-x优化之纹理优化

2015年03月18日 13:44 0 点赞 0 评论 更新于 2025-11-21 14:24

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压缩纹理格式,该格式专为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参数表示回调函数的目标对象。
  • 第⑥行代码:定义回调函数HelloWorld::loadingCallBack的实现。
  • 第⑦行代码(int)(((float)_numberOfLoadedSprites / _numberOfSprites) * 100)用于计算加载进度,"%d%%"用于显示百分号,其中%d是格式化输出数字,%%用于输出%,前面的%起到转义作用。
  • 第⑧行代码_labelPercent->setString(str->getCString())用于设置进度标签_labelPercent的内容。
  • 第⑨行代码auto sprite = Sprite::createWithTexture(texture)通过纹理对象texture创建精灵对象。
  • 第⑩行代码if (_numberOfLoadedSprites == _numberOfSprites)用于判断是否完成加载任务,_numberOfLoadedSprites表示已加载的图片数,_numberOfSprites表示要加载的全部图片数。

通过以上纹理像素格式的选择和纹理缓存异步加载的实现,可以有效优化Cocos2d-x游戏的纹理处理,提升游戏性能和用户体验。