3.2 CCNode节点类
CCNode是Cocos2D中最重要的类,同时也是所有节点的基类。它是一个抽象类,没有视觉表现,定义了所有节点都通用的属性和方法。
在Cocos2D中,所有要绘制到屏幕的对象,或是自身包含要绘制到屏幕中的对象,都属于CCNode类。最重要的几个CCNode类分别是CCScene、CCLayer、CCSprite、CCMenu,这也是我们本章要重点学习的内容。
CCNode的主要作用:
包含其他的CCNode节点(addChild、getChildByTag、removeChild等方法);
通过定时器预定消息(schedule、unschedule等方法);
执行动作(runAction、stopAction等方法)。
注意 作为抽象类,CCNode是没有自身纹理的。
3.2.1 CCNode类的属性
CCNode类的常见属性包括以下各种。
(1)anchorPoint
anchorPoint表示锚点。节点的变形和定位操作都要依据锚点来确定。锚点的变量类型是CGPoint。
锚点值通常在0~1之间,(0,0)代表左下角,而(1,1)代表右上角。默认CCNode节点锚点值是(0,0),也就是节点左下角。另外,还有anchorPointInPoints属性,区别在于其采用绝对像素值为单位。
(2)camera
camera用于设置游戏中的视角,利用OpenGL ES中的gluLookAt()。变量类型是CCCamera。
只有在创建3D效果时才会用到camera属性。如果使用了该属性,就不能再同时使用rotation、scale、position这些属性。
注意 Cocos2D不推荐直接使用camera。如果需要实现一个视差滚动的背景(人物向前移动时,游戏场景向身后移动,并且远近不同的背景移动速度还不一样,近的物体移动速度快,远的物体移动速度慢,即所谓的视差),通过移动camera来实现会变得更复杂,而且会使Cocos2D本身的一些功能特性遭受破坏。推荐的作法是使用CCParallaxNode,后面章节中我们会向大家演示如何做。
(3)children
children为节点的子节点数组。变量类型是CCArray。
CCArray是专门为了性能优化而设计的类,它的功能和NSMutablArray差不多。推荐在Cocos2D中尽量使用CCArray替代NSMutableArray。
(4)contentSize
contentSize为未经转换的节点大小,以points(点值)为单位。contentSize的变量类型是CGSize,默认值为(0,0)。
无论节点如何缩放或旋转,contentSize属性值始终不变。所有的节点都有大小,而Layer(层)和Screen(场景)大小就是屏幕大小。
contentSize主要用于碰撞检测,我们可以根据节点当前所在位置以及节点纹理本身的大小来计算碰撞矩形区域,具体代码如代码清单3-2所示。
代码清单3-2 计算碰撞矩形区域
-(CGRect) rectOfSprite:(CCSprite*)sprite{
return CGRectMake(sprite.position.x - sprite.contentSize.width / 2,
sprite.position.y - sprite.contentSize.height /2,
sprite.contentSize.width, sprite.contentSize.height);
}
(5)glServerState
glServerState表示OpenGLES服务器端状态,从Cocos2D v2.0版本开始使用。变量类型是ccGLServerState,可参考ccGLState.h。
(6)grid
在对节点应用某些特殊效果时会用到此属性。变量类型是CCGridBase。
(7)isRelativeAnchorPoint
该属性为YES时,节点的变形将基于锚点进行。变量类型是BOOL。
对于精灵、标签和其他可调整大小的节点会默认启用该属性;而对于场景、层和其他“全屏”对象,会默认禁用该属性。
(8)isRunning
isRunning用于判断当前节点是否在运行。变量类型是BOOL。
(9)orderOfArrival
当使用同样的z值对子节点进行排序的时候,用该属性来决定子节点的顺序,不要手动更改该数值。变量类型是NSUInteger。
(10)parent
parent表示对节点父节点的弱引用,通常很少用到。变量类型是CCNode。
(11)position
position表示节点在屏幕中的位置。变量类型是CGPoint,以points(点值)为单位,屏幕的左下角坐标是(0,0)。
此外,positionInPixels属性同样表示节点在屏幕中的位置,不过单位为pixels(像素值),变量类型是CGPoint,默认值为(0,0)。
(12)rotation
rotation表示节点沿顺时针方向旋转的角度。变量类型是float,默认值为0。
(13)scale
scale表示节点的缩放比例。变量类型是float,有三个属性,默认值为(scaleX=1,scaleY=1)。
scale:节点沿x和y坐标轴的整体缩放比例。
scaleX:节点沿x轴的缩放比例。
scaleY:节点沿y轴的缩放比例。
(14)shaderProgram
shaderProgram表示着色程序。变量类型是GLProgram。
该属性从Cocos2D v2.0版本开始使用,详细信息可参考GLProgram.h。后面会专门用一章介绍如何为CCNode编写定制的shader程序。
(15)skew
skew表示节点的变形角度。变量类型是float。
skewX:沿x轴方向顺时针切向畸变角度(y轴和节点形状左边缘之间的角度),默认值为0。
skewY:沿y轴方向逆时针切向畸变角度(x轴和节点形状底边的角度),默认值为0。
(16)tag
tag用于识别节点的标识值。变量类型是NSInteger。
无论是创建、引用还是删除节点,tag值都有用武之地。比如,tag参数允许通过getChildByTag方法获取指定节点。如果有多个节点拥有相同tag数值,getChildByTag会将找到的第一个节点返回,将不再访问其他节点,所以要确保为节点指定独有的tag数值。
tag在很多应用场景以及Cocos2D游戏编程过程中都起到重要作用。最简单的应用就是在其他scheduler的selector函数里通过getChildByTag得到对应tag节点,这样无需在类的定义里声明过多的节点弱引用(weak ref)。此外,tag还有一个重要的应用场景。多个CCMenuItem共用一个selector时,可以给menuItem赋予不同tag,然后在selector里根据tag值判断玩家具体触碰的是哪一个按钮。
(17)userData
userData为用户自定义的数据指针对象。变量类型是void。
userData经常用于selector之间的数据传递,或者其他需要节点携带传递数据的场合。
(18)visible
visible用于判断节点是否可见。变量类型是BOOL,默认值为YES。
(19)zOrder
zOrder表示节点相对于其“兄弟”节点(拥有共同的父节点)Z顺序值。变量类型是NSInteger。
对于像CCSprite这样有视觉呈现的节点,该参数决定了节点的绘制顺序,拥有最小Z值的节点会首先被绘制,拥有最大Z值的节点最后被绘制。如果多个节点拥有相同Z值,绘制顺序将由它们的添加顺序来决定。
(20)vertexZ
vertexZ表示真正的OpenGL Z顶点。OpenGL Z顶点和Cocos2D Z顺序的区别如下:
OpenGL Z修改Z顶点,而非父节点和子节点间的Z顺序;
OpenGL Z可能需要设置2D投影;
如果所有节点使用相同的OpenGL Z顶点,Cocos2D Z顺序仍然保持正常。
使用该值可能会破坏Cocos2D中的父节点–子节点Z顺序,所以通常较少使用。
以上只是对CCNode类属性的简单介绍,要真正了解每个属性在开发Cocos2D游戏中的作用,需要在实际开发中逐渐体会。在开发过程中大家可以随时回顾本章查找相关属性。第2章的示例代码运用了一部分CCNode属性,大家可以多多学习并修改代码,体会这些属性的用途。
3.2.2 CCNode类的方法
CCNode类中所实现的方法可以分为三个大类,分别是对子节点的处理、使用定时器预定消息,以及执行动作。此外有些方法不属于以上三大类,下面将一一介绍。
1 . 处理子节点
CCNode类实现了所有添加、获取和删除子节点的方法。以下是一些处理子节点的方法:
1)创建一个新的节点。示例代码如下:
CCNode* childNode = [CCNode node];
请注意,这行代码等同于:
CCNode* childNode = [[[CCNode alloc]init]autorelease];
2)将新节点添加为当前节点的子节点,并设置子节点的Z值和tag值。
如果将该子节点添加到“运行”模式,则立即调用“onEnter”和“onEnterTransitionDidFinish”方法。示例代码如下:
[thisNodeaddChild:childNode z:0 tag:1];
3)使用节点的标识获取子节点。示例代码如下:
CCNode* childNode = [thisNode getChildByTag:1];
4)通过tag删除子节点。
如果cleanup的参数值为YES,则停止任何运行中的动作。示例代码如下:
[thisNode removeChildByTag:1 cleanup:YES];
5)通过节点指针删除子节点。
如果cleanup的参数值为YES,则停止任何运行中的动作。示例代码如下:
[thisNoderemoveChild:childNode cleanup:YES];
6)删除一个节点的所有子节点。
如果cleanup的参数值为YES,则停止任何运行中的动作。示例代码如下:
[thisNoderemoveAllChildrenWithCleanup:YES];
7)从当前节点的父节点删除当前节点。
如果cleanup的参数值为YES,将删除所有的动作和回调方法;如果当前节点没有父节点,则不执行任何操作。示例代码如下:
[thisNoderemoveFromParentAndCleanup:YES];
2 . 执行动作
CCNode可以使用动作(Actions)让节点执行某些动作。本书第4章将专门讲述动作的详细内容,现在我们只需要知道使用动作可以让节点移动、旋转和缩放,以及做一些其他事情。
1)运行某个特定的动作。示例代码如下:
[thisNode runAction:action];
其中action是使用CCAction定义的某个动作。
2)停止所有在该节点上运行的动作。示例代码如下:
[thisNode stopAllActions];
3)停止在该节点上运行的某个特定动作。示例代码如下:
[thisNode stopAction:action];
其中action是使用CCAction定义的某个动作。
4)停止运行的动作列表中的某个特定动作,使用tag标识来区分。示例代码如下:
[thisNode stopActionByTag:123];
5)获取当前运行的动作清单中的某个特定动作,使用tag标识来区分。示例代码如下:
myAction = [thisNode getActionByTag:123];
如果想在以后使用此动作,可以使用这个方法。
6)获取运行动作的数量。
包括正在运行的动作,还包括预定运行的动作(在actionsToAdd和动作数组中的动作),复合动作算一个动作。示例代码如下:
numberofActions = [thisNode numberOfRunningActions];
3 . 预定消息
CCNode节点可以预定消息,其实就是每隔一段时间调用一次方法。在很多游戏中需要节点调用特定的更新方法来处理某些情况,比如碰撞测试等。
1)每帧都调用的更新方法,每个节点只可预定一个update方法。示例代码如下:
-(void) scheduleUpdates
{
[self scheduleUpdate];
}
-(void) update:(ccTime)delta
{
// 游戏的每一帧都会调用该方法
}
其中,delta参数表示该方法的上一次调用到目前所经过的游戏时间。
2)仍然是每帧都会调用的更新方法,每个节点只可预定一个update方法,与1)的区别在于使用该方法可以安排更新方法的优先次序。更新方法的调用是按照优先级数值从小到大的次序。示例代码如下:
// 在节点1中
-(void) scheduleUpdates
{[self scheduleUpdate];
}
// 在节点2中
-(void) scheduleUpdates
{
[self scheduleUpdateWithPriority:1];
}
// 在节点3中
-(void) scheduleUpdates
{
[self scheduleUpdateWithPriority:-1];
}
-(void) update:(ccTime)delta
{
// 游戏的每一帧都会调用该方法
}
所有节点(节点1、2、3)都会调用-(void)update: (ccTime) delta方法。但因为使用了优先级设置,将会首先运行节点3的更新方法;然后是调用节点1的更新方法,因为节点1默认优先级设定为0;节点2的更新方法最后调用,因为它的优先级数值最大。
如果每帧都调用相同的更新方法,上述做法很适用。不过有时需要用到更灵活的更新方法。
3)指定运行特定的更新方法,并设置调用的时间间隔。示例代码如下:
-(void) scheduleUpdates{
[self schedule:@selector(updateTenTimesPerSecond:) interval:0.1f];
}
-(void) updateTenTimesPerSecond:(ccTime)delta
{
//根据时间间隔来调用该方法,每秒10次
}
如果需要每一帧都触发某个方法,应该使用scheduleUpdate方法,并把要定时处理的逻辑放到update方法中。但是update方法只有一个,所以,大部分情况下大家会使用上述代码来触发不同的定时更新逻辑。
4)指定运行特定的更新方法,并设置调用的延迟时间,但只运行一次。示例代码如下:
-(void) scheduleUpdates{
[self schedule:@selector(specialMethod:) delay:2.0f];
}
5)停止节点的某个指定选择器,但该方法不会停止scheduleUpdate中设置的预定更新方法。想要停止scheduleUpdate中设置的预定更新方法,可以使用unscheduleUpdate方法。示例代码如下:
[self unschedule:@selector(updateTenTimesPerSecond:)];
6)停止节点的所有选择器(selector),包括在scheduleUpdate里设置的update选择器,该方法不会影响节点的动作(action)。示例代码如下:
[self unscheduleAllSelectors];
7)用_cmd关键词停止当前方法的预定。示例代码如下:
-(void) scheduleUpdates
{
[self schedule:@selector(tenMinutesElapsed:) interval:600];
}
-(void) tenMinutesElapsed:(ccTime)delta
{
// 用_cmd关键词停止当前方法的预定
[self unschedule:_cmd];
}
4 . 其他方法
(1)-(CGRect)boundingBox
该方法用于获取节点的边框,返回的坐标是相对于其父节点的。以points(点值)为单位,返回CGRect变量类型的值。
boundingBox在进行游戏物体碰撞检测时非常有用,功能类似之前定义的rectOfSprite方法。比如,可以把第2章示例游戏中的碰撞检测部分更新为代码清单3-3。
代码清单3-3 碰撞检测代码更新
-(void) collisionDetection:(ccTime)dt{
CCSprite *enemy;
CCARRAY_FOREACH(_enemySprites, enemy)
{
if (enemy.visible) {
//1.bullet & enemy collision detection
if (_bulletSprite.visible && CGRectIntersectsRect(enemy.boundingBox, _bulletSprite.boundingBox)) {
…//此处省略了部分代码
//2.enemy & player collision detection
CCSprite *playerSprite = (CCSprite*)[self getChildByTag:kTagPalyer];
if (playerSprite.visible &&
playerSprite.numberOfRunningActions == 0
&& CGRectIntersectsRect(enemy.boundingBox, playerSprite.boundingBox)) {
…//此处省略了部分代码
}
}
}
}
(2)-(void)cleanup
该方法用于停止所有运行中的动作和预定方法。
(3)-(CGPoint)convertToNodeSpace:(CGPoint)worldPoint
该方法用于将点坐标转换成节点空间坐标。以points(点值)为单位,返回CGPoint变量类型的值。
(4)-(CGPoint)convertToNodeSpaceAR:(CGPoint)worldPoint
该方法用于将点坐标转换成相对于锚点的节点空间坐标。以points(点值)为单位,返回CGPoint变量类型的值。
(5)-(CGPoint)convertToWorldSpace:(CGPoint)nodePoint
该方法用于将节点空间坐标转换成世界空间坐标。以points(点值)为单位,返回CGPoint变量类型的值。
(6)-(CGPoint)convertToWorldSpace:(CGPoint)nodePoint
该方法用于将节点空间坐标转换成相对于锚点的世界空间坐标。以points(点值)为单位,返回CGPoint变量类型的值。
注意,第2章的示例游戏中,假如发射子弹的需求有所改变,比如只有玩家“点中”飞机才开始发射子弹,实现方式如代码清单3-4所示。
代码清单3-4 发射子弹实现
-(void) ccTouchesEnded:(NSSet *)touches withEvent:(UIEvent *)event{
//修改为必须点选中playerSprite才能够发射子弹
//方法1:因为boundingBox是相对于世界坐标系而言的,所以要用
//self convertTouchToNodeSpace转化成世界坐标系中的坐标
UITouch *touch = [touches anyObject];
CCSprite *playerSprite = (CCSprite*)[self getChildByTag:kTagPalyer];
CGPoint pt;
// pt = [touch locationInView:[touch view]];
// pt = [[CCDirector sharedDirector] convertToGL:pt];
// pt = [self convertToNodeSpace:pt];
//上面三句调用可以简化为下面一句调用
//CGPoint pt = [self convertTouchToNodeSpace:touch];
// if (CGRectContainsPoint(playerSprite.boundingBox, pt)) {
// _isTouchToShoot = YES;
// }
//===================================================================
//方法2
// pt = [touch locationInView:[touch view]];
// pt = [[CCDirector sharedDirector] convertToGL:pt];
// pt = [playerSprite convertToNodeSpace:pt];
//简化为下面的一句代码调用
pt = [playerSprite convertTouchToNodeSpace:touch];
CCLOG(@"pt.x = %f, pt.y = %f",pt.x, pt.y);
if (CGRectContainsPoint(playerSprite.textureRect, pt)) {
_isTouchToShoot = YES;
CCLOG(@"touched!");
}
}
大家可以找到本章示例代码,根据代码注释体验4种检测玩家的触摸点是否在playerSprite区域内的方法,相信会对局部坐标和世界坐标有一定的了解,同时明白boundingBox是相对于节点所处的世界坐标来计算的。通过几个小实验,应该已经掌握boundingBox和textureRect的功能与区别。
(7)-(void)draw
覆盖该方法以绘制自己的节点。在Xcode中打开HelloWorldLayer.m,实现draw方法,如代码清单3-5所示。
代码清单3-5 draw方法实现
-(void) draw{
[super draw];
ccDrawColor4F(255,0,0,0);
glLineWidth(8);
ccDrawLine(ccp(10,10),ccp(200,200));
}
此时编译运行,我们并不能看到这条从(10,10)到(200,200)、红色、宽度为8像素的线。因为程序中添加了背景精灵图片,所以需要把之前添加背景图片的代码注释掉:
//2.add background
// CCSprite *bgSprite = [CCSprite spriteWithFile:@"background.png"];
// bgSprite.position = ccp(winSize.width / 2,winSize.height/2);
// [self addChild:bgSprite z:-100];
编译并运行代码,将会得到如图3-3所示画面。
仔细观察的话大家会发现,在HelloWorldLayer里添加的任何节点,在经过这条红线时都会将红线上的点覆盖。因为Cocos2D的绘制是先调用节点自身的draw,然后再调用它所有子节点的draw方法。
(8)-(id)init
该方法用于初始化节点。
有一点需要注意,我们应该尽量把游戏场景中用到的sprite、label、menu、action、animation等对象在init方法中创建好。如果不想让玩家在进入场景时就看到,可以把些节点的visible属性先设置为NO;在后面需要使用这些预分配的对象时,就可以直接把visbile设置为YES,这样做能提高游戏性能。这种预先分配对象的策略,也叫做lazy loading。第2章示例中的玩家sprite和敌机sprite,以及子弹sprite都采用了这种方式,这种做法有助于提高游戏性能。
(9)- (CGAffineTransform) nodeToParentTransform
该方法返回一个矩阵,可以将节点(本地)空间坐标转换为父节点的空间坐标,该矩阵以像素值为单位。
(10)- (CGAffineTransform) nodeToWorldTransform
该方法返回一个世界坐标的仿射转换矩阵,该矩阵以像素值为单位。
(11)-(void)onEnter
该方法为回调方法,当CCNode节点进入“舞台”时调用。如果进入时带有过渡效果,则在过渡开始时调用。
(12)-(void)onEnterTransitionDidFinish
该方法为回调方法,当CCNode节点进入舞台时调用。如果带有过渡效果,则在过渡完成时调用。
(13)-(void)onExit
该方法为回调方法,当CCNode节点离开舞台时会被调用。如果带有过渡效果,则在过渡完成时调用。
(14)- (void) parentToNodeTransform
该方法返回一个矩阵,可以将父节点的空间坐标转换为节点(本地)空间坐标,该矩阵以像素值为单位。
(15)- (void) pauseSchedulerAndActions
该方法暂停所有预定的选择器方法和动作,由onExit方法在其内部调用。
(16)- (void) reorderChild: (CCNode *) childz:(NSInteger) zOrder
该方法重新设置子节点的次序,该子节点必须已经添加。
(17)- (void) resumeSchedulerAndActions
该方法继续所有预定的选择器方法和动作。该方法由onEnter方法在其内部调用。
(18)- (void) sortAllChildren
使用该方法可以改善游戏性能。该方法在每次绘制前对子节点数组进行排序,除非某个添加的子节点需要在同一帧被清除,不要手动调用该方法。
(19)- (void) transform
该方法根据节点的位置、比例、旋转和其他属性来执行OpenGLES视图–矩阵转换。
(20)- (void) transformAncestors
该方法从其祖先节点执行OpenGLES视图–矩阵转换。
(21)-(void)visit
该方法为递归方法,访问节点的子节点并绘制它们。
(22)- (CGAffineTransform) worldToNodeTransform
该方法返回逆向的世界坐标仿射转换矩阵,以像素值为单位。
以上就是CCNode类的主要方法,详细定义可以查看Cocos2D模板中的CCNode.h文件。