Cocos2d-x学习笔记(四)—— 内存管理

前端之家收集整理的这篇文章主要介绍了Cocos2d-x学习笔记(四)—— 内存管理前端之家小编觉得挺不错的,现在分享给大家,也给大家做个参考。

内存管理:

  • C/C++内存管理机制
  • 引用计数机制
  • 自动释放池
  • 纹理缓存

在对Cocos2d的内存管理机制进行认识之前,我们先回忆一下之前C/C++里面的内存管理机制new / delete,malloc / free,帮助我们更好的认识Cocos2d的内存管理。

C/C++内存管理机制

在C中,我们如果要为对象,变量分配内存,我们会使用malloc,对应释放用free;在C++中,我们会new一个对象来分配内存,用delete来做释放。

我们在普通项目中新建两个文件GameObject.h以及GameObject.cpp,如图:

GameObject.h:

  1. #ifndef __GAMEOBJECT_H__
  2. #define __GAMEOBJECT_H__
  3.  
  4. class GameObject
  5. {
  6. public:
  7. int count;
  8. GameObject();
  9. ~GameObject();
  10. private:
  11. };
  12.  
  13. #endif // !__GAMEOBJECT_H__
GameObject.cpp:
  1. #include "GameObject.h"
  2.  
  3. GameObject::GameObject()
  4. {
  5. count = 1;
  6. }
  7.  
  8. GameObject::~GameObject()
  9. {
  10. }
main.cpp:
  1. int main()
  2. {
  3. // c++中内存管理的方法,这里会调用构造函数和析构函数
  4. GameObject *gameobject = new GameObject();
  5. gameobject->count = 0;
  6. delete gameobject;
  7.  
  8. GameObject *gameobject2 = new GameObject[4];
  9. gameobject2->count = 0;
  10. delete[] gameobject2;
  11.  
  12.  
  13. // c中内存管理的方法,需要强制类型转换,c 中不会调用构造函数,因为c没有类
  14. GameObject *gameobject3 = (GameObject*)malloc(sizeof(GameObject));
  15. gameobject3->count = 0;
  16. free(gameobject3);
  17. }

引用计数机制

引用计数的关键作用在于,当我们创建一个对象后,该对象被其他对象引用,为防止在释放的时候出现错误,我们使用引用计数表明被引用的次数,因而需要所有引用对象及对象本身被释放,此对象才能算是被完全释放。修改上面的HelloWorld项目。

GameObject.h:

  1. <pre name="code" class="cpp">#ifndef __GAMEOBJECT_H__
  2. #define __GAMEOBJECT_H__
  3.  
  4. class GameObject
  5. {
  6. public:
  7. int age;
  8. int count;
  9. GameObject();
  10. ~GameObject();
  11. void freeobject();
  12. private:
  13. };
  14.  
  15. #endif // !__GAMEOBJECT_H__
  1.  
GameObject.cpp:
  1. #include "GameObject.h"
  2.  
  3. GameObject::GameObject()
  4. {
  5. count = 1;
  6. }
  7.  
  8. GameObject::~GameObject()
  9. {
  10. freeobject();
  11. }
  12.  
  13. void GameObject::freeobject()
  14. {
  15. --count;
  16. if (count <= 0)
  17. {
  18. delete this;
  19. }
  20. }
main.cpp:
  1. #include <iostream>
  2. using namespace std;
  3.  
  4. int main()
  5. {
  6. // 引用计数机制
  7. GameObject *gameobject = new GameObject();
  8. // 创建一次对象,引用计数count加 1
  9. GameObject *gameobject2 = gameobject;
  10. // 这里引用计数的作用在于防止gameobject被释放后,gameobject2的内存为空,导致使用时出错
  11. gameobject->count++;
  12. cout << count << gameobject->count << endl;
  13. // 调用函数释放对象,释放时引用计数减 1,直到引用计数为 0时释放对象
  14. gameobject->freeobject();
  15. cout << count << gameobject->count << endl;
  16. // 这里能调用age说明对象还没有被释放,只是引用计数减 1
  17. gameobject2->age = 2;
  18. gameobject2->freeobject();
  19. cout << count << gameobject->count << endl;
  20. // gameobject->count = 0; // 这里引用计数已经为0,对象被释放,因此调用时会出错
  21. }

输出结果:

count:2
count:1
count:-17891602


Cocos2d的引用计数函数

  1. sprite->retain() // 引用计数加1
  2. sprite->release() // 释放对象,引用计数减1
  3. sprite->getReferenceCount() // 获取引用计数
节点数引用计数函数
  1. virtual void addChild(CCNode *child);
  2. virtual void addChild(CCNode *child,int zOrder);
  3. virtual void addChild(CCNode *child,int zOrder,int tag);
  4. virtual void removeChild(CCNode *child,bool cleanup);
  5. void removeChildByTag(int tag,bool cleanup);
  6. virtual void removeAllChildrenWithCleanup(bool cleanup);

新建一个Cocos2d项目“HelloWorld”,在HelloWorldScene.cpp中的bool HelloWorld::init()函数添加以下代码

  1. CCSprite *sprite = new CCSprite();
  2. log("spritecount:%d",sprite->getReferenceCount());<span style="white-space:pre"> </span>// 获取引用计数
  3. CCSprite *sprite2 = sprite;
  4. sprite2->retain();<span style="white-space:pre"> </span>// 引用计数加1操作
  5. log("spritecount:%d",sprite->getReferenceCount());
  6. // 这里被添加到图层,引用计数也会加1
  7. this->addChild(sprite);
  8. log("spritecount:%d",sprite->getReferenceCount());
  9. this->removeChild(sprite);
  10. log("spritecount:%d",sprite->getReferenceCount());
  11. sprite2->release();<span style="white-space:pre"> </span>// 释放对象,引用计数减 1
  12. log("spritecount:%d",sprite->getReferenceCount());
  13. sprite->release();
  14. log("spritecount:%d",sprite->getReferenceCount());

运行结果:

spritecount:1
spritecount:2
spritecount:3
spritecount:2
spritecount:1
spritecount:-17891602


自动释放池(CCAutoreleasePool)

CCAutoreleasePool不能被开发者自己创建。Cocos2d-x会为我们每一个游戏创建一个自动释放池实例对象,游戏开发者不能新建自动释放池,仅仅需要专注于release/retain cocos2d::CCObject的对象。

静态构造函数包含autorelease()函数调用这样的函数不需要手动释放,自动释放池会帮我们做释放,这样的静态函数有:

  1. CCAsdf::createWithXxxx(…)
  2. object->init(…)

代码实例:
  1. <pre name="code" class="cpp">CCSprite *sprite = CCSprite::create("HelloWorld.png");
  2. this->addChild(sprite);
等同于
  1. CCSprite *sprite = new CCSprite();
  2. sprite->initWithFile("HelloWorld.png");
  3. sprite->autorelease();<span style="white-space:pre"> </span>// 对象会在结束时被自动释放
  4. this->addChild(sprite);


纹理缓存(TextureCache)

CCTextureCache继承自TextureCache,纹理(纹理可看做像素)缓存是将纹理缓存起来方便之后的绘制工作。每一个缓存的图像的大小,颜色和区域范围都是可以被修改的。这些信息都是存储在内存中的,不用在每一次绘制的时候都发送给GPU。下次可直接利用缓存,避免重新绘制,减少cpu和GPU内存的消耗。

下面为Cocos2d中的源代码,会把添加图片加入到纹理缓存,这些在Cocos2d底层实现,我们可以直接调用

  1. <pre name="code" class="cpp">bool Sprite::initWithFile(const std::string& filename)
  2. {
  3. CCASSERT(filename.size()>0,"Invalid filename for sprite");
  4. // 由导演获取纹理缓存,缓存图片,下次需要图片时可直接从texture获取
  5. Texture2D *texture = Director::getInstance()->getTextureCache()->addImage(filename);
  6. if (texture)
  7. {
  8. Rect rect = Rect::ZERO;
  9. rect.size = texture->getContentSize();
  10. return initWithTexture(texture,rect);
  11. }
  12.  
  13. // don't release here.
  14. // when load texture Failed,it's better to get a "transparent" sprite then a crashed program
  15. // this->release();
  16. return false;
  17. }
  1.  


V3.0新特性(官方发表内容):

Sprite 和 SpriteBatchNode

v2.2 2.2版本中推荐的优化游戏方式是将SpriteBatchNode对象设置为Sprite对象的父节点。 虽然使用SpriteBatchNode对象仍然是一个非常好的优化游戏的方式,但是它仍然有一定的限制:

  • Sprite对象的孩子只能是Sprite(否则,Cocos2d-x 会触发断言)
    • Sprite的父节点是SpriteBactchNode时,不能添加ParticleSystem作为Sprite的子节点。
    • 这将导致当SpriteBatchNode时,不能使用ParallaxNode
  • 所有的Sprite对象必须共享相同的纹理ID (否则,Cocos2d-x 会触发断言)
  • Sprite对象使用SpriteBatchNode的混合函数和着色器。

虽然 v3.0 仍然支持SpriteBatchNode(与之前的版本拥有相同的特效和限制),但是我们不鼓励使用它。相反,我们推荐直接使用Sprite,不需要再它作为子节点添加SpriteBatchNode中。

但是,为了能让 v3.0 有更好的表现,你必须要确保你的Sprite对象满足以下条件:

  • 贡献相同的纹理ID(把它们放在一个spritesheet中,就像使用SpriteBatchNode一样)
  • 确保它们使用相同的着色器和混合函数(就像使用SpriteBatchNode一样)

如果这么做,Sprites将会像使用SpriteBatchNode一样的快...(在旧设备上大概慢了10%,在新设备上基本上察觉不出)

v2.2 和 v3.0 最大的区别在于:

    Sprite对象可以有不同的纹理ID。
  • Sprite对象可以有不同种类的Node作为子节点,包括ParticleSystem
  • Sprite对象可以有不同的混合函数和不同的着色器。

但是如果你这么做,渲染器可能无法对它所有的子节点进行批处理(性能较低)。但是游戏仍然可以正常运行,不会触发任何断言。

总结:

  • 保持将所有的精灵放在一张大的 spritesheet 中。
  • 使用相同的混合函数(使用默认)
  • 使用相同的着色器(使用默认)
  • 不要将精灵添加SpriteBatchNode

只有当你需要一些额外的性能上提升(虽然很小),SpriteBatchNode才会是你最后的选择(你需要对它的限制条件很熟悉)。

提示和技巧(摘自官方文档)

  1. 一帧一帧载入游戏资源
  2. 载入纹理时按照从大到小的顺序
  3. 避免高峰内存使用
  4. 使用载入屏幕预载入游戏资源
  5. 需要时释放空闲资源
  6. 收到内存警告后释放缓存资源.
  7. 使用纹理打包器优化纹理大小、格式、颜色深度等
  8. 使用JPG格式要谨慎!
  9. 请使用RGB4444颜色深度16位纹理
  10. 请使用NPOT纹理,不要使用POT纹理
  11. 避免载入超大纹理
  12. 推荐1024*1024 NPOT pvr.ccz纹理集,而不要采用RAW PNG纹理

猜你在找的Cocos2d-x相关文章