cocos2d-x 源码剖析(14)

今天大老板看我加班辛苦,不由上来教育一番,我等屌丝立马重树三观。后又老爸介绍一相亲对象,虽说无甚期待也足够让想象里驰骋会儿了。心情略好,嗯来一篇吧。为了接上思路,我把前面几章看了一遍。自己水平不够,国语稀烂,新手要看懂确实不容易,老手也没必要看了。哈哈,略显尴尬。看个大概就可以了。

好吧言归正传,自上一篇讲完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格式,那么游戏的体积和加载速度会让你惊讶的。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值