今天大老板看我加班辛苦,不由上来教育一番,我等屌丝立马重树三观。后又老爸介绍一相亲对象,虽说无甚期待也足够让想象里驰骋会儿了。心情略好,嗯来一篇吧。为了接上思路,我把前面几章看了一遍。自己水平不够,国语稀烂,新手要看懂确实不容易,老手也没必要看了。哈哈,略显尴尬。看个大概就可以了。
好吧言归正传,自上一篇讲完Touch之后cocos2d-x的主逻辑就没什么类容了。我来整理下:
- cocos2d-x的程序入口和生命周期
- cocos2d-x的跨平台技法
- cocos2d-x的主体逻辑(mainloop)
- cocos2d-x的层次管理(CCNode)
- cocos2d-x的内存管理(引用计数)
- cocos2d-x的Action
- cocos2d-x的Scheduler
- cocos2d-x的Touch
接下来我要讲一些显示上的操作,作为游戏引擎来讲,这部分虽然算作是核心,但不能算作精华。精华还是上面那部分内容。2D引擎浩如烟海,但是真正好用的很少。一个好的软件框架是要不停的积累和反复尝试才能得到的。所谓人文武功,上面是人文,下部分就是武功。如果了解OpenGL的话,这部分也就是些小细节了。
OpenGL的入门就不再讲解了,我们先来看一看cocos2d-x中是如何导入纹理的,这部分内容比较实用。我们知道cocos2-x中的CCSprite可以接受一个文件名作为参数创建出来,而且你肯定也试过使用.png或者.jpg文件来创建Sprite。那么在这过程中发生了什么了,我们来看其代码
bool CCSprite::initWithFile(const char *pszFilename)
{
CCAssert(pszFilename != NULL, "Invalid filename for sprite");
CCTexture2D *pTexture = CCTextureCache::sharedTextureCache()->addImage(pszFilename);
if (pTexture)
{
CCRect rect = CCRectZero;
rect.size = pTexture->getContentSize();
return initWithTexture(pTexture, rect);
}
// don't release here.
// when load texture failed, it's better to get a "transparent" sprite then a crashed program
// this->release();
return false;
}
首先他调用纹理管理组建来导入一个纹理,使用的是这个文件的名字:
CCTexture2D* CCTextureCache::addImage(const char* path)
{
CCAssert(path!=NULL,"TextureCache: fileimage MUST not be NULL");
CCTexture2D* texture=NULL;
CCImage* pImage=NULL;
// Split up directory and filename
// MUTEX:
// Needed since addImageAsync calls this method from a different thread
//pthread_mutex_lock(m_pDictLock);
std::string pathKey = path;
pathKey=CCFileUtils::sharedFileUtils()->fullPathForFilename(pathKey.c_str());
if(pathKey.size()==0)
{
returnNULL;
}
texture = (CCTexture2D*)m_pTextures->objectForKey(pathKey.c_str());
std::string fullpath = pathKey;// (CCFileUtils::sharedFileUtils()->fullPathFromRelativePath(path));
if(!texture)
{
std::string lowerCase(pathKey);
for(unsigned int i=0;i<lowerCase.length();++i)
{
lowerCase[i]=tolower(lowerCase[i]);
}
// all images are handled by UIImage except PVR extension that is handled by our own handler
do
{
if(std::string::npos!=lowerCase.find(".pvr"))
{
texture=this->addPVRImage(fullpath.c_str());
}
else if(std::string::npos!=lowerCase.find(".pkm"))
{
// ETC1 file format, only supportted on Android
texture=this->addETCImage(fullpath.c_str());
}
else
{
CCImage::EImageFormateImageFormat=CCImage::kFmtUnKnown;
if(std::string::npos!=lowerCase.find(".png"))
{
eImageFormat=CCImage::kFmtPng;
}
elseif(std::string::npos!=lowerCase.find(".jpg")||std::string::npos!=lowerCase.find(".jpeg"))
{
eImageFormat=CCImage::kFmtJpg;
}
elseif(std::string::npos!=lowerCase.find(".tif")||std::string::npos!=lowerCase.find(".tiff"))
{
eImageFormat=CCImage::kFmtTiff;
}
elseif(std::string::npos!=lowerCase.find(".webp"))
{
eImageFormat=CCImage::kFmtWebp;
}
pImage=newCCImage();
CC_BREAK_IF(NULL==pImage);
bool bRet=pImage->initWithImageFile(fullpath.c_str(),eImageFormat);
CC_BREAK_IF(!bRet);
texture=newCCTexture2D();
if(texture&&
texture->initWithImage(pImage))
{
#if CC_ENABLE_CACHE_TEXTURE_DATA
// cache the texture file name
VolatileTexture::addImageTexture(texture,fullpath.c_str(),eImageFormat);
#endif
m_pTextures->setObject(texture,pathKey.c_str());
texture->release();
}
else
{
CCLOG("cocos2d: Couldn't create texture for file:%s in CCTextureCache",path);
}
}
}while(0);
}
CC_SAFE_RELEASE(pImage);
//pthread_mutex_unlock(m_pDictLock);
return texture;
}
我大略讲一下:
- 先用文件管理组建来得到资源的路径
- 用这个路径来看看纹理集合中是不是已经导入了
- 如果没有导入将文件名统一转换为小写,根据不同的后缀来设置不同的格式。
- 如果是.pvr和.pkm格式的文件直接导入进来。
- 如果是其他格式就通过格式和文件名来创建一个CCImage对象。
- 再通过这个CCImage对象来初始化一个纹理。
- 如果开启了纹理缓存就将这个纹理添加到缓存中去。
那么纹理的真正导入只有两个入口,如果资源是原生的纹理格式就直接初始化,如果是图片格式就通过CCImage做一层转化。这也就是为什么在iOS上使用.pvr的资源加载速度会比使用图片快的原因。之后我们可以看到通过CCImage做中间转化所做的操作可不是一般的多。
我们把注意力集中到CCImage中去,看看它是怎么将不同的图片格式统一的。在这之前我要强调一点,在不同的平台上CCImage的头文件虽然只有一份,但是不同平台的实现不同,而且头文件中还有一个编译宏。也就是说CCImage的内容在不同平台上是不同的,但是暴露给外部的接口是一致的。这里没有用到什么interface之类的东西,完全是手动控制的。我们先看那个initWithImageFile函数,下面的内容是CCImage.mm中的内容,特此注意!!!
bool CCImage::initWithImageFile(const char * strPath, EImageFormat eImgFmt/* = eFmtPng*/)
{
bool bRet = false;
unsigned long nSize = 0;
unsigned char* pBuffer = CCFileUtils::sharedFileUtils()->getFileData(
CCFileUtils::sharedFileUtils()->fullPathForFilename(strPath).c_str(),
"rb",
&nSize);
if (pBuffer != NULL && nSize > 0)
{
bRet = initWithImageData(pBuffer, nSize, eImgFmt);
}
CC_SAFE_DELETE_ARRAY(pBuffer);
return bRet;
}
其中的核心是initWithImageData这个函数,在这个函数中,根据格式不同又有三个不同的函数被调用:
_initWithRawData(pData,nDataLen,nWidth,nHeight,nBitsPerComponent,false);
_initWithWebpData(pData,nDataLen);
_initWithData(pData,nDataLen,&info);
第一个相当于用像素列表来初始化CCImage,这和CCImage的内置格式是一致的。第二个是使用Webp格式,为什么要单独提出来呢?首先这个格式是Google从V8的视屏格式演化而来,想用来取代jpg的,格式比较新。还有就是他不能通过第三个函数偷懒得到。我们来看第三个函数:
static bool _initWithData(void * pBuffer, int length, tImageInfo *pImageinfo)
{
bool ret = false;
if (pBuffer)
{
CGImageRef CGImage;
NSData *data;
data = [NSData dataWithBytes:pBuffer length:length];
CGImage = [[UIImage imageWithData:data] CGImage];
ret = _initWithImage(CGImage, pImageinfo);
}
return ret;
}
看到那个CGImage了没有,那么你知道CCImage是怎么出来的了吧。这里调用了iOS系统的函数来对图片进行解码工作,转化为CGImage之后再处理:
static bool _initWithImage(CGImageRef cgImage,tImageInfo* pImageinfo)
在这个函数中,调用iOS的系统库来进行转化工作,原理是使用绘制版将其转化为RGBA8888格式。对iOS系统熟悉的同学不难理解,不理解我也无能为力了。有个题外话就是,在源代码中,这个函数接下来有一个函数十分有趣:
static bool _initWithFile(const char* path,tImageInfo* pImageinfo)
{
CGImageRef CGImage;
UIImage *jpg;
UIImage *png;
bool ret;
// convert jpg to png before loading the texture
NSString* fullPath=[NSString stringWithUTF8String:path];
jpg=[[UIImage alloc]initWithContentsOfFile:fullPath];
png=[[UIImage alloc]initWithData:UIImagePNGRepresentation(jpg)];
CGImage=png.CGImage;
ret=_initWithImage(CGImage,pImageinfo);
[png release];
[jpg release];
return ret;
}
还记得我们是怎么一步一步到这里来的吗?这里居然有个函数能一步到位。呵呵,真是好巧啊。其实这个函数在代码中从来没有谁用到过,除非你想不开自己手动调用它。它的问题在于使用了两个临时变量,这造成了两倍的内存使用,这对于图片资源是致命的,你可以算算一张1024的图转化为RGBA8888要占用多少内存。也是有些老的博客中说到cocos2d-x使用jpg会造成2倍内存的缘由。在新版中,不会有这个问题,除非你自己确实想这样做。
关于webp的载入,我想放到下一节Android系统中讲,那时你会看到很多类似的东西。
了解了iOS中cocos2d-x图片载入的细节,你应该了解,既然是使用系统函数解码,那么只要是iOS系统支持的图片格式都可以在cocos2d-x中使用,但是移植性就不能保证了。而且在研究过诸多格式之后你会发现,iOS和Mac都支持Jpeg-2000.为什么会有Webp,就是因为Jpg不支持透明,但Webp解码在IOS上比Jpg慢很多。而Jpeg-2000有着和Webp差不多的质量和体积加上透明,且有着Jpg的解码速度,简直是梦幻般的格式。如果你将你游戏中的png格式全部转化为Jpeg-2000格式,那么游戏的体积和加载速度会让你惊讶的。