这篇文章主要会介绍一些cocos2d的基础类,以及他们的用途。
cocos2d中,大量使用了单例(singleton)模式,单例其实就是一个普通的类,但是它在整个应用程序生命周期内只实例化一次,cocos2d中,要访问单例对象,基本上都是使用shared开头的方法(目前为止,我没有发现过有不这样使用的单例)。如果你还没看懂单例是什么,那么看看下面这个例子你就知道了。
static MyManager *shareManager = nil; +(MyManager) sharedManager { if(shareManager == nil) { shareManager = [[MyManager alloc] init]; } return shareManager; }
我觉得单例应该是包含了一些公用的方法,而且调用这些方法,不会去修改单例的属性,否则其他的类来调用单例的时候,所遇到的结果就是未知的了。说完单例,我们先来说说一个重要的单例类——CCDirector。CCDiretor类是Cococs2d游戏引擎的核心,它存储了cocos2d种大量的全局配置信息,而且管理着所有的cocos2d场景。Dirctor的主要作用有一下几点:
- 切换场景
- 存储cocos2d的配置信息
- 访问试图(包含OpenGL、UIView、UIWindow)
- 暂停、恢复以及终止游戏
- 在UIKit和OpenGL之间转换坐标
- CCNode类
接下来,说说CCNode类。cocos2d中,所有的节点都继承自CNode类,它是一个没有具体显示的抽象类,仅用于定义所有的公共属性和方法。首先我们来看一下cocos文档里面列举出来的类的继承关系,可以从中发现CCScene、CCSprite都继承自CCNode,以前CCLayer也应该是继承自CCNode,但是在1.0.1的文档中查不到这个类,先记录下,等下再去查。
再来看看这个类的部分公共函数,里面包含了各种对节点的操作,增加、删除、获取节点、调度(即隔多少秒执行一次,稍后会详细说、取消调度、开始播放动作、停止动作等):
(void) | - addChild: |
(void) | - addChild:z: |
(void) | - addChild:z:tag: |
(void) | - removeFromParentAndCleanup: |
(void) | - removeChild:cleanup: |
(void) | - removeChildByTag:cleanup: |
(void) | - removeAllChildrenWithCleanup: |
(CCNode *) | - getChildByTag: |
(void) | - reorderChild:z: |
(void) | - cleanup |
(void) | - draw |
(void) | - visit |
(void) | - transform |
(void) | - transformAncestors |
(CGRect) | - boundingBox |
(CGRect) | - boundingBoxInPixels |
(CCAction *) | - runAction: |
(void) | - stopAllActions |
(void) | - stopAction: |
(void) | - stopActionByTag: |
(CCAction *) | - getActionByTag: |
(NSUInteger) | - numberOfRunningActions |
(void) | - scheduleUpdate |
(void) | - scheduleUpdateWithPriority: |
(void) | - schedule: |
(void) | - schedule:interval: |
(void) | - unschedule: |
(void) | - unscheduleAllSelectors |
(void) | - resumeSchedulerAndActions |
(void) | - pauseSchedulerAndActions |
- CCScene类
一个CCScene对象往往是场景图种的第一个节点。通常来说,CCScene节点的第一层子节点一定是CCLayer的子类,而CCScene对象本身,通常是利用CCLayer对象种的静态方法+(id)scene来创建,而且游戏中的各个对象,也通常是由这些子节点(CCLayer)来保存,而不是CCScene本身来保存,这样做的好处,会在CCLayer部分介绍。
1、场景类跟app建立关系上
我们可以把要显示的第一个场景,加在AppDelegate中applicationDidFinishLaunching方法的最后,类似如下:
[[CCDirector sharedDirector] runWithScene:[HelloWorld scene]];
HelloWold类是一个继承自CCLayer的类,scene是其中的一个静态方法,用来将layer加入scene里面,如下所示:
+(id)scene { CCScene *scene = [CCScene node]; CCLayer *layer = [HelloWord node]; [scene addChild:layer]; return scene; }
2、内存使用
当进行场景替换的时候,cocos2d会把自己占用的内存清理干净,它会删除所有的节点,停止所有的动作,并且对所有用选择器选中的方法取消调度。但是,由于在进行场景替换时,新场景往往在旧场景释放之前就被加载到内存了,这会导致内存负荷瞬间加大,这个问题在使用场景转换动画的时候,显得格外明显。这时候,场景首先会被创建,然后过渡效果运行,一直要到过度效果运行完毕之后,旧场景才会从内存中释放。
由于场景替换的时候,会停止所有的动作,那么可否在播放场景过渡动画前先把前一个场景截屏,然后释放掉前一个场景再播放动画,这样的话,就能节省不少内存。
3、场景的推进和弹出
cocos2d种有pushScene和popScene这两个有用的方法,这两个方法用来在不释放旧场景内存的情况下运行新场景,可以加快场景替换的速度。由于很多场景可以互相叠加的存在于内存之中,很容易就会忘记弹出一个场景,或者对于同一个场景弹出太多遍。学过堆栈的同学,应该知道这种情况的危险性。但是这个用来切换setting还是不错的选择,因为setting一般都不怎么占内存,当然,特殊情况除外。
- CCTransitonScene
场景过渡动画有时候能为游戏添色不少。在cocos2d中,要使用过度动画还是比较简单的,只要在场景转换时添加两行代码就行了。比如下面这个淡入淡出效果:
CCFadeTransition *tran = [CCFadeTransition transitionWithDuration:1
scene:[HelloWorld scene]
withColor:ccWHITE];
[[CCDirector sharedDirector] replaceScene:tran];
CCTransitonScene定义了很多的场景转换动画,看下下面这张类图应该能很清楚的知道:
- CCLayer
开始在CCScene里面提到过,CCLayer本质是对节点进行分组,游戏中的各个对象,一般是用CCLayer来保存的。这样的好处是,可以很轻松的修改层的属性或者在该层上运行一个动作来影响层上的所有子节点。例如你可以对某一层施加一个动作,然后这个动作会对该层上的所有对象产生影响。当然,不使用层也能达到这样的效果,只要对每个对象分别进行操作即可,但是显然,不使用层的做法是非常低效的。
CCLayer能够接收触摸事件和加速剂事件,但是接收触摸或者加速计事件的开销是很大的,这也就导致有人说使用多个层会影响性能。实际上你可以使用任意多个蹭,与其他的节点相比,他们对于性能并没有太大的影响。如果太多的层需要接收和处理触摸或者加速计事件,可以只用一个蹭来接收和处理这些输入,然后在必要的情况下,通过这个层将输入事件通知给其他节点或者类。
1、接收触摸事件
使用self.isTouchEnabled = YES来显示启动接收触摸事件,一般会在init方法中开启此功能。下面列出几个常用单点触控的触摸事件:
a)当手指接触到屏幕时被调用:
-(BOOL) ccTouchBegan:(UITouch *)touch withEvent:(UIEvent *)event
b)当手指离开屏幕时候被调用:
-(void) ccTouchEnded:(UITouch *)touch withEvent:(UIEvent *)event
c)当触摸时间被取消时调用:
-(void) ccTouchCancelled:(UITouch *)touch withEvent:(UIEvent *)event
d)当手指在屏幕上移动的时候被调用:
-(void) ccTouchMoved:(UITouch *)touch withEvent:(UIEvent *)event
在使用单点触摸之前,要向层中添加以下方法来启用有针对性的触摸处理:
-(void) registerWithTouchDispatcher { [[CCTouchDispatcher sharedDispatcher] addTargetedDelegate:self priority:INIT_MIN + 1 swallowsTouches:YES]; }
CCTouchBegan返回一个bool值,如果返回YES,就意味着不想让这个触摸被传送到其他优先级更低的有针对性的触摸处理,也就相当于你直接吞噬掉了这个触摸事件。
由于触摸事件是由Cocoa Touch API接收的,因此一定要吧触摸位置转换成cocos2d所用的OpenGL坐标:
-(CGPoint) locationFromTouch:(UITouch *)touch
{
CGPoint touchLocation = [touch locationInView:[touch view]];
return [[CCDirector sharedDirector] convertToGL:touchLocation];
}
2、接收加速计事件
同样,需要self.isAccelerometerEnabled = YES来显示启动加速计来接收加速计事件,但是这个事件的处理比触摸时间就简单多了,只需要向层里面添加一个特定方法来接收加速计事件:
- (void)accelerometer:(UIAccelerometer*)accelerometer didAccelerate:(UIAcceleration*)acceleration { //可以利用以下参数来决定三维中任意方向的加速度 //acceleration.x acceleration.y acceleration.z }
- CCSprite
CCSprite是cocos2d种最为常用的类,它用一副图像将精灵显示到屏幕上。要创建一个精灵很简单,比如你的工程的Resources分组下有一张叫做monster.png的图,那么只需要使用如下方法,就能将精灵显示在层上:
CCSpirt *sprite = [[CCSprite spriteWithFile:@"monster.png"]]; [self addChild:sprite];
通过上面的操作后,cocos2d内部会把该图片加载到CCTexture2D类的图像资源中。在这里,顺便提一下,由于ios设备只支持尺寸为“2的n次幂”的纹理,即图片的长宽只能伟:2、4、8等像素。如果你有一张贴图,大小为260*260像素32位色的图片,那么就比较悲剧了。你觉得它在内存中应该只占用260*260*4=270KB左右的空间,单实际上,它占用了512*512*4=1MB的内存。
CCSprite还有一个比较重要的就是位置问题,想象下现实生活中,如果你要把一张照片钉在墙上某个问题,你会怎么做?首先,你会把图钉插在照片上的某个点(在cocos2d中称为anchorPoint),然后你会确定要把这个图钉订在墙上的某个位置(在cocos2d种称为positon),这两个点就能确定照片在墙上的位置了。比如你想把照片放到左下角,那么你可以选择将图钉钉在照片的左下角(0,0),然后钉在墙的左下角(0,0).或者,你可以选择把图钉定在照片的右上角(1,1),然后把照片订在墙的(照片长,照片宽)的位置。auchorPoint表示的其实是一个百分比,用来标明相对于图片左下角的(长*百分比,宽*百分比)像素的位置,比如auchorPoint为(0.5, 0.5), 那么在图片的坐标系里,它标明的位置就应该是(长*0.5, 宽*0.5)的位置,也就是图片的中心点。最好好anchorPoint设置成(0.5, 0.5),也就是在图片的中心,这样,当你进行旋转、缩放等动作的时候,会比较方便。幸运的是,anchorPoint的默认位置就是(0.5,0.5).
- CCLabelTTF
这个类的作用就是在屏幕上显示文本。cocos2d内部会以制定的字体作为参数创建一个CCTexture2D对象,也就是一张纹理,然后再用改纹理渲染出最后显示的文本.因为每次文本发生改变,就要做一次上述工作,所以cocos2d的文档中也建议改用CCLabelAtlas或者CCLabelBMFont代替。
你可能会发现,每次修改标签上的文本时候,这些文本都会自动中对齐,如果要改成左对齐,或者右对齐这些,只需要改变anchorPoint的属性就可以了。
- 动作
动作是可以用来让节点执行诸如移动、旋转、缩放、变色、消失等很多动作。由于他们能作用在所有的节点上,因此可以对精灵、标签甚至菜单或整个场景施加动作。动作在完成后,会自动从节点上清除并释放它所占用的内存。
动作又分为即时动作和延时动作。延时动作就是我们一般理解上的动作,比如让一个精灵移动到哪里。即时动作,一般就类似于设置精灵的属性等,及时动作平时看起来是没有多大意义的,一般要配合后文所述的动作序列。
1)重复动作
这个很容易理解,就是让一个动作不停的重复,可以用这个方法创建无限循环的动画。使用起来也很简单,例如下面这个让一个节点不停的旋转:
CCRotateBy* rotateBy = [CCRotateBy actionWithDuration:2 angle:360]; CCRepeatForever *repeat = [CCRepeatForever actionWithAction:rotateBy]; [myNode runAction:repeat];
还有一个类CCRepeat是让一个动画重复多少次,函数原型如下:
actionWithAction:(CCFiniteTimeAction *)action times:(NSUInteger) times;
2、流畅动作(CCEaseAction)
一般的动作,比如你定义了一个物体是向哪个方向移动,那么它就会匀速的过去,但是很显然,我们喜欢更有点变化的动作,比如加速进入,然后迅速一段时间,再减速停止,流畅动作就是用来做这个的。
3、动作序列
CCSequence,这个比较好理解,就是定义一组动作,然后让他们按照这个顺序执行。