Cocos2D-X learning 4

废话

今天和王爷一起去传说中的车库咖啡见了一个创业团队,跟他们有了一个小时左右的交流时间,交流的详情就不说了,总之,让我和王爷多了一个很好的话题,在回家的路上,我们就这个话题进行了友好、亲切而又愉快的交流,受益匪浅。
不过,最糟心的是,跑这一趟我竟然感冒了,主要是天气热,出了很多汗,而在地铁通道里风太大,晾了汗了,纠结死我了,明天还要去见一个离得比较近的创业团队,我在犹豫到底还要不要去。

概述

今天随便说说在Cocos2D-X开发中,关于对CCSprite方面的优化,总得来说就是尽可能减少渲染次数,这样可以使游戏运行更有效率,能够空出更多的CPU时间做其它的事情,以增加游戏的流畅与体验。

正文

Cocos2D为图片资源批量加载提供了CCSpriteFrameCache类,此类可以通过使用Sprite Sheet文件来减少图片加载次数,简单来说,就是把多个图片拼成一个大图片,之后通过坐标在大文件上取到相应的图片,这个技术被广泛使用在网页设计及游戏开发中。在这里我使用了一个叫做Zwoptex的软件自动生成大图片及相应的.plist,软件的具体使用方法就不在这里细说了(另外,朋友介绍说Texture Packer是可以更好的做这件事的工具,改天尝试一下)。

另外,Cocos2D还提供了一个CCSpriteBatchNode类,这个类主要作用是,当相同图片资源被重复使用时,通过这个类可以减少使相同的图片只渲染一次,具体做法看下面的代码。
PS: 代码基于上一篇BLOG,只对关键部分进行说明。

GameSceneDH.h
1
2
3
4
5
6
7
8
9
10
11
12
13
14
#ifndef __StudyCocos2D__GameSceneDH__
#define __StudyCocos2D__GameSceneDH__

#include <iostream>
#include "cocos2d.h"

using namespace cocos2d;

class GameSceneDH: public CCLayer {
public:
static CCScene *scene();
virtual bool init();
CREATE_FUNC(GameSceneDH);
};
GameSceneDH.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
//
// GameSceneDH.cpp
// StudyCocos2D
//
// Created by Toby Lee on 14-3-30.
//
//

#include "GameSceneDH.h"
CCScene * GameSceneDH::scene()
{
CCScene *scene = CCScene::create();
CCLayer *layer = GameSceneDH::create();
scene->addChild(layer);
return scene;
}

bool GameSceneDH::init()
{
if (!CCLayer::init()) {
return false;
}
CCSize winSize = CCDirector::sharedDirector()->getWinSize();

CCSprite *background = CCSprite::create("大唐边境.jpg");
CCSize backgroundSize = background->getContentSize();
background->setScale(winSize.width / backgroundSize.width);
background->setPosition(ccp(winSize.width/2, winSize.height/2));
this->addChild(background);

CCSpriteFrameCache *frameCache = CCSpriteFrameCache::sharedSpriteFrameCache();
frameCache->addSpriteFramesWithFile("DH.plist");

CCSpriteFrame *frameDH0, *frameDH1, *frameDH2, *frameDH3, *frameDH4, *frameDH5;
frameDH0 = frameCache->spriteFrameByName("DH (0).png");
frameDH1 = frameCache->spriteFrameByName("DH (1).png");
frameDH2 = frameCache->spriteFrameByName("DH (2).png");
frameDH3 = frameCache->spriteFrameByName("DH (3).png");
frameDH4 = frameCache->spriteFrameByName("DH (4).png");
frameDH5 = frameCache->spriteFrameByName("DH (5).png");

frameDH0->autorelease();
frameDH1->autorelease();
frameDH2->autorelease();
frameDH3->autorelease();
frameDH4->autorelease();
frameDH5->autorelease();

CCSprite *sp0 = CCSprite::createWithSpriteFrame(frameDH0);
sp0->setPosition(ccp(50, 150));
this->addChild(sp0);
CCSprite *sp1 = CCSprite::createWithSpriteFrame(frameDH1);
sp1->setPosition(ccp(140, 150));
this->addChild(sp1);
CCSprite *sp2 = CCSprite::createWithSpriteFrame(frameDH2);
sp2->setPosition(ccp(230, 150));
this->addChild(sp2);
CCSprite *sp3 = CCSprite::createWithSpriteFrame(frameDH3);
sp3->setPosition(ccp(320, 150));
this->addChild(sp3);
CCSprite *sp4 = CCSprite::createWithSpriteFrame(frameDH4);
sp4->setPosition(ccp(410, 150));
this->addChild(sp4);
CCSprite *sp5 = CCSprite::createWithSpriteFrame(frameDH5);
sp5->setPosition(ccp(500, 150));
this->addChild(sp5);

return true;
}

以上代码中,第25-29行为游戏添加了背景,这个不在本文的写作目的之中,其中27、28行是将背景的大小和位置调整到铺满整个窗口。
第31、32行利用.plist文件初始化了一个SpriteSheet图片,CCSpriteFrameCache类全局有效以单例的形式使用,但是为了方便后面使用,这里单独声明了一个CCSpriteFrameCache对象。
从34行开始到第40行,声明一组CCSpriteFrame对象,并通过文件名在CCSpriteFrameCache类中初始化。从49行到66行,通过已经创建的``CCSpriteFrame对象创建一些相应的CCSprite`实例,并做位置做相应的调整后加入到场景中。
在这里为了便于说明,尽可能少的牵涉到无关内容,对代码本身并没做优化。
进行CCSpriteBatchNode优化前
注意看界面左下角的帧率信息。

下面,我们在代码中通过CCSpriteBatchNode对程序进行优化,代码如下:

GameSceneDH.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
//
// GameSceneDH.cpp
// StudyCocos2D
//
// Created by Toby Lee on 14-3-30.
//
//

#include "GameSceneDH.h"
CCScene * GameSceneDH::scene()
{
CCScene *scene = CCScene::create();
CCLayer *layer = GameSceneDH::create();
scene->addChild(layer);
return scene;
}

bool GameSceneDH::init()
{
if (!CCLayer::init()) {
return false;
}
CCSize winSize = CCDirector::sharedDirector()->getWinSize();

CCSprite *background = CCSprite::create("大唐边境.jpg");
CCSize backgroundSize = background->getContentSize();
background->setScale(winSize.width / backgroundSize.width);
background->setPosition(ccp(winSize.width/2, winSize.height/2));
this->addChild(background);

CCSpriteFrameCache *frameCache = CCSpriteFrameCache::sharedSpriteFrameCache();
frameCache->addSpriteFramesWithFile("DH.plist");
CCSpriteBatchNode *batchNode = CCSpriteBatchNode::create("DH.png");
this->addChild(batchNode);

CCSpriteFrame *frameDH0, *frameDH1, *frameDH2, *frameDH3, *frameDH4, *frameDH5;
frameDH0 = frameCache->spriteFrameByName("DH (0).png");
frameDH1 = frameCache->spriteFrameByName("DH (1).png");
frameDH2 = frameCache->spriteFrameByName("DH (2).png");
frameDH3 = frameCache->spriteFrameByName("DH (3).png");
frameDH4 = frameCache->spriteFrameByName("DH (4).png");
frameDH5 = frameCache->spriteFrameByName("DH (5).png");

frameDH0->autorelease();
frameDH1->autorelease();
frameDH2->autorelease();
frameDH3->autorelease();
frameDH4->autorelease();
frameDH5->autorelease();

CCSprite *sp0 = CCSprite::createWithSpriteFrame(frameDH0);
sp0->setPosition(ccp(50, 150));
batchNode->addChild(sp0);
CCSprite *sp1 = CCSprite::createWithSpriteFrame(frameDH1);
sp1->setPosition(ccp(140, 150));
batchNode->addChild(sp1);
CCSprite *sp2 = CCSprite::createWithSpriteFrame(frameDH2);
sp2->setPosition(ccp(230, 150));
batchNode->addChild(sp2);
CCSprite *sp3 = CCSprite::createWithSpriteFrame(frameDH3);
sp3->setPosition(ccp(320, 150));
batchNode->addChild(sp3);
CCSprite *sp4 = CCSprite::createWithSpriteFrame(frameDH4);
sp4->setPosition(ccp(410, 150));
batchNode->addChild(sp4);
CCSprite *sp5 = CCSprite::createWithSpriteFrame(frameDH5);
sp5->setPosition(ccp(500, 150));
batchNode->addChild(sp5);

return true;
}

代码中,第33-34行,创建了一个CCSpriteBatchNode对象,并将其加入场景,在51-68行中,所有的CCSprite对象全部加入到CCSpriteBatchNode对象,此时,所有的CCSprite将不再重新渲染,如下图:
经过CCSpriteBatchNode优化后
与之前的截图相比,渲染次数减少到2,说明这样的优化方法是可行的。

说明:CCSprite对象必须加入到CCSpriteBatchNode中才能起到优化作用,如果直接将CCSprite加入到场景中,则不会重用CCSpriteBatchNode的渲染结果。这里可以理解为,CCSpriteBatchNode是一个虚拟的容器,将此容器加入到场景后,再将CCSprite加入其中,由CCSpriteBatchNode对其进行管理,另外,如果CCSprite使用的资源不包含在CCSpriteBatchNode初始化的资源中,则程序会出现异常。

结束

程序的优化方法有很多种,但是并不是所有的优化方法都适用于当前的应用场景 ,这也牵涉到优化的性价比的问题,对程序进行适度的优化才是最正确的。

另:由于本人是初学跟前端有关的开发,特别是游戏前端,因此,在我所讨论的内容中难免有所偏颇,希望能够得到大家的指正,谢谢。