原文链接地址:http://www.iphonegametutorials.com/2010/09/14/cocos2d-sprite-tutorial-part-3/
我们在第2部分教程中已经介绍了如何让dragon沿着8个不同的方向移动,并且播放相应的动画,同时,移动过程可以由用户touch屏幕来控制。cocos2d很酷吧!好了,今天我们将多干点活,我们将创建一大批村民--实际上是N个村民。我们会使用我们已经学习过的技术,从spritesheet里面加载精灵,同时建立相应的精灵动画。
这里有本教程的完整源代码。
那么,我们到底要做成什么样子呢---看了下面的图就明白了:
上面加载了好多村民,但是,屏幕的帧速率仍然是60 fps。这是因为我们做了优化。那么,究竟是如何做的呢?让我们马上开始学习吧。
首先,我们要创建我们的adventurer (冒险者)类。---它里面存储了我们的移动和行走动画的精灵实例。在屏幕上每一个冒险家,我们都会为之创建一个adventurer 类的实例。
Adventurer.h
#import "cocos2d.h"@interface Adventurer : CCNode { CCSprite *_charSprite; CCAction *_walkAction; CCAction *_moveAction; BOOL _moving;}@property (nonatomic, retain) CCSprite *charSprite;@property (nonatomic, retain) CCAction *walkAction;@property (nonatomic, retain) CCAction *moveAction;@end
如果你愿意的话,你也可以从CCSprite继承,然后我们可以调用initWithFile方法来初始化我们的Adventure 类。但是,我更喜欢从CCNode继承,然后包含一个CCSprite的实例。
Adventurer.m
#import "Adventurer.h"@implementation Adventurer@synthesize charSprite = _charSprite;@synthesize moveAction = _moveAction;@synthesize walkAction = _walkAction;-(id) init{ self = [super init];if (!self) {return nil; }return self;}- (void) dealloc{ self.charSprite = nil; self.walkAction = nil; self.moveAction = nil; [super dealloc];}@end
很简单的init函数,同时我们还定义了一个dealloc方法。(译者:大家一定要养成一个好习惯,定义init就马上定义dealloc,“创建-销毁”要成对,这个很重要,能减少很多内存问题。stackoverflow上面,有人直接把dealloc方法放在.m文件的最开头,作用不言而喻!ios内存有限啊!)---上面这段代码,我们再熟悉不过了。这里创建了一个非常简单的类,但是,也给我们一些提示,如何为游戏主角创建class。这里把所有的属性都定义了retain说明符,同时在dealloc方法里面调了self.xxx = nil来释放内存。这样就把内存管理与property关联起来了。objc的引用计数已经为我们程序员减少了对于内存管理的烦恼,因此,只需要养成良好的习惯,就可以减少大量与内存有关的问题发生。
现在,我们拥有角色了,让我们来使用之。。。先回到“PlayLayer.h” ,然后做下面一些变更:
#import "cocos2d.h"#import "SceneManager.h"#import "Adventurer.h"@interface PlayLayer : CCLayer { CCTexture2D *_texture; CCSpriteSheet *_spriteSheet; NSMutableArray *_charArray;}@property (nonatomic, assign) CCTexture2D *texture;@property (nonatomic, assign) CCSpriteSheet *spriteSheet;@property (nonatomic, retain) NSMutableArray *charArray;@end
我们先导入 “Adventurer.h”,然后定义了3个实例变量。第一个变量 “_texture”用来加载adventurer 精灵表单。第二变量 “_spritesheet”是把我们将要创建的精灵都进行“批处理”,使之提高效率。最后,我们想要追踪所有的adventurers,所以,我们定义了一个“_charArray”.数组。同时我们为每一个实例变量都声明了属性,这样我们就可以在PlayLayer.m间接使用了。(另一种方法是定义tag,在init方法里面指定tag,然后在其它方法里面就可以用self getChildByTag:tag来获得想要的孩子了)
OK,现在我们有一堆类了。不过别担心,我们会在后面把它逐步分开讲解--首先,先让我们实现PlayLayer.m:
PlayLayer.m
#import "PlayLayer.h"#import "Adventurer.h"@implementation PlayLayer@synthesize texture = _texture;@synthesize spriteSheet = _spriteSheet;@synthesize charArray = _charArray;enum { kTagSpriteSheet =1,};-(id) init{ self = [super init];if (!self) {return nil; } CCSprite *background = [CCSprite spriteWithFile:@"Terrain.png"]; background.position = ccp(160, 240); [self addChild:background]; _texture = [[CCTextureCache sharedTextureCache] addImage:@"adventurer.png"]; _spriteSheet = [CCSpriteSheet spriteSheetWithTexture:self.texture capacity:100]; [self addChild:_spriteSheet z:0 tag:kTagSpriteSheet]; self.charArray = [[NSMutableArray alloc] init]; [self schedule:@selector(gameLogic:) interval:1.0f];return self;}-(void)addAdventurer { NSLog(@"Add Adventurer"); NSMutableArray *animFrames = [NSMutableArray array]; [animFrames removeAllObjects];for (int i =0; i <9; i++) { CCSpriteFrame *frame = [CCSpriteFrame frameWithTexture:self.texture rect:CGRectMake(i*16, 0, 16, 29) offset:CGPointZero]; [animFrames addObject:frame]; } Adventurer * adventurer = [[[Adventurer alloc] init] autorelease];if (adventurer != nil) { CCSpriteFrame *frame1 = [CCSpriteFrame frameWithTexture:self.texture rect:CGRectMake(0, 0, 19, 29) offset:CGPointZero]; adventurer.charSprite = [CCSprite spriteWithSpriteFrame:frame1]; CGSize s = [[CCDirector sharedDirector] winSize];int minY = adventurer.charSprite.contentSize.height/2;int maxY = s.height - adventurer.charSprite.contentSize.height/2;int rangeY = maxY - minY;int actualY = (arc4random() % rangeY) + minY;int minX =-300;int maxX =0;int rangeX = maxX - minX;int actualX = (arc4random() % rangeX) + minX; adventurer.charSprite.position = ccp(actualX, actualY); CCAnimation *animation = [CCAnimation animationWithName:@"walk" delay:0.2f frames:animFrames]; CCAnimate *animate = [CCAnimate actionWithAnimation:animation restoreOriginalFrame:NO]; CCSequence *seq = [CCSequence actions: animate, nil]; adventurer.walkAction = [CCRepeatForever actionWithAction: seq ]; id actionMove = [CCMoveTo actionWithDuration:10.0f position:ccp(s.width +200,actualY)]; id actionMoveDone = [CCCallFuncND actionWithTarget:self selector:@selector(spriteMoveFinished:data:)data:adventurer]; adventurer.moveAction = [CCSequence actions:actionMove, actionMoveDone, nil]; [adventurer.charSprite runAction:adventurer.walkAction]; [adventurer.charSprite runAction:adventurer.moveAction]; [self addChild:adventurer.charSprite]; [_charArray addObject:adventurer]; }}-(void)gameLogic:(ccTime)dt { [self addAdventurer];}-(void)spriteMoveFinished:(id)sender data:adv{ Adventurer * adventurer = (Adventurer*)adv; CGSize s = [[CCDirector sharedDirector] winSize];int minY = adventurer.charSprite.contentSize.height/2;int maxY = s.height - adventurer.charSprite.contentSize.height/2;int rangeY = maxY - minY;int actualY = (arc4random() % rangeY) + minY;int minX =-300;int maxX =0;int rangeX = maxX - minX;int actualX = (arc4random() % rangeX) + minX; adventurer.charSprite.position = ccp(actualX, actualY); [adventurer stopAction:adventurer.moveAction]; [adventurer.charSprite runAction:adventurer.moveAction];}- (void) dealloc{ [[CCSpriteFrameCache sharedSpriteFrameCache] removeUnusedSpriteFrames]; [self.charArray removeAllObjects]; [super dealloc];}@endOK,千万别头疼--接下来我会一点点分解:
-(id) init{ self = [super init];if (!self) {return nil; } CCSprite *background = [CCSprite spriteWithFile:@"Terrain.png"]; background.position = ccp(160, 240); [self addChild:background]; _texture = [[CCTextureCache sharedTextureCache] addImage:@"adventurer.png"]; _spriteSheet = [CCSpriteSheet spriteSheetWithTexture:self.texture capacity:100]; [self addChild:_spriteSheet z:0 tag:kTagSpriteSheet]; self.charArray = [[NSMutableArray alloc] init]; [self schedule:@selector(gameLogic:) interval:1.0f];return self;}
好,首先看到“init”函数,它和我们之前的adventurer 类一样,先调super init,如果失败的话,就直接返回nil。然后我们添加了一张背景图片叫做"Terrain.png"并把它放置在屏幕的中心(因为我们知道图片的默认中心点anchorPoint是0.5,0.5)。然后直接把它加到当前层中。
接下来,我们初始化纹理和spritesheet--首先把"adventurer.png"加载到CCTexture2D变量中,然后使用spriteSheetWithTexture来建立一个精灵表单。(我们也可以用spriteSheetWithFile这个函数来建立spritesheet,但是,我想向你展示另外一种方法)。然后,我们把spritesheet加到CCLayer中。之后,我们所有的精灵,如果作为孩子加到spritesheet中的话,就可以得到“批处理”。
最后,我们初始化NSMutableArray ,同时触发一个回调函数gamelogic,它会每隔1秒钟回调一次。函数如下所示:
-(void)gameLogic:(ccTime)dt { [self addAdventurer];}
我们将使用这个函数,每隔一秒钟创建一个新的adventurer 对象。
接下来,看看AddAventurer函数。这个函数不仅仅创建一个新的角色,同时还会使之移动并播放相应方向行走的动画。
-(void)addAdventurer { NSLog(@"Add Adventurer"); NSMutableArray *animFrames = [NSMutableArray array]; [animFrames removeAllObjects];for (int i =0; i <9; i++) { CCSpriteFrame *frame = [CCSpriteFrame frameWithTexture:self.texture rect:CGRectMake(i*16, 0, 16, 29) offset:CGPointZero]; [animFrames addObject:frame]; }上面的代码我们之前已经见过了,我们只是为walking动画存储了9个动画帧(CCSpriteFrames) 。
Adventurer * adventurer = [[[Adventurer alloc] init] autorelease];if (adventurer != nil) { CCSpriteFrame *frame1 = [CCSpriteFrame frameWithTexture:self.texture rect:CGRectMake(0, 0, 19, 29) offset:CGPointZero]; adventurer.charSprite = [CCSprite spriteWithSpriteFrame:frame1];接下来,我们创建一个新的adventurer 实例,然后把charSprite成员初始化为第一个动画帧,调用的方法是spriteWithSpriteFrame。
int minY = adventurer.charSprite.contentSize.height/2;int maxY = s.height - adventurer.charSprite.contentSize.height/2;int rangeY = maxY - minY;int actualY = (arc4random() % rangeY) + minY;int minX =-300;int maxX =0;int rangeX = maxX - minX;int actualX = (arc4random() % rangeX) + minX;adventurer.charSprite.position = ccp(actualX, actualY);好了,即使我们的精灵按照粒子数量去增加,所有的精灵刚开始的位置都是在左下角,除非我们人为改变它们的位置。因此,上面的代码就是产生一个随机坐标,同时又要保证这个随机坐标在屏幕范围之内。然后把这个随机坐标点赋值给adventurer。
CCAnimation *animation = [CCAnimation animationWithName:@"walk" delay:0.2f frames:animFrames]; CCAnimate *animate = [CCAnimate actionWithAnimation:animation restoreOriginalFrame:NO]; CCSequence *seq = [CCSequence actions: animate, nil]; adventurer.walkAction = [CCRepeatForever actionWithAction: seq ]; id actionMove = [CCMoveTo actionWithDuration:10.0f position:ccp(s.width +200,actualY)]; id actionMoveDone = [CCCallFuncND actionWithTarget:self selector:@selector(spriteMoveFinished:data:)data:adventurer]; adventurer.moveAction = [CCSequence actions:actionMove, actionMoveDone, nil]; [adventurer.charSprite runAction:adventurer.walkAction]; [adventurer.charSprite runAction:adventurer.moveAction]; [self addChild:adventurer.charSprite]; [_charArray addObject:adventurer]; }
addAdventurer方法的最后一个部分就是处理角色在屏幕上面的行走和移动。我们把之前存储CCSpriteFrame 的animFrames拿过来,然后把它转换成动画。(每个动画帧之间的间隔是0.2秒,整个动画差不多就要2秒的时间来运行完)。然后我们把这个动画放到一个sequence 动作中(使用CCSequence 类),最后,我们使用CCRepeatForever创建walkAction,并把它赋值给adventurer。
我们现在已经可以让角色有行走的动画了,但是,我们还想让它实际移动。所以,我们需要创建另外一个action,叫做CCMoveTo 。并且使用CCSequence类把它与一个回调函数关联起来。当CCMoveTo结束的时候,就运行CCCallFuncND指定的回调函数。
-------------------Side Note: 如果你想指定带一个参数的函数,那么就使用CCCallFuncN--它会把CCSprite本身传递进去,通过sender参数传递:
id actionMoveDone = [CCCallFuncN actionWithTarget:self selector:@selector(spriteMoveFinished:)];
如果你不想让任何参数传递的话,就使用CCCallFunc函数。-------------------
现在,我们还剩下一件事情没有涉及了,就是之前CCMove结束的时候,通过CCCallFuncND指定的回调函数,如下所示:
-(void)spriteMoveFinished:(id)sender data:adv{ Adventurer * adventurer = (Adventurer*)adv; CGSize s = [[CCDirector sharedDirector] winSize];int minY = adventurer.charSprite.contentSize.height/2;int maxY = s.height - adventurer.charSprite.contentSize.height/2;int rangeY = maxY - minY;int actualY = (arc4random() % rangeY) + minY;int minX =-300;int maxX =0;int rangeX = maxX - minX;int actualX = (arc4random() % rangeX) + minX; adventurer.charSprite.position = ccp(actualX, actualY); [adventurer stopAction:adventurer.moveAction]; [adventurer.charSprite runAction:adventurer.moveAction];}
这里再重复解释上面的代码的话,就有点烦人了。简言之,就是在CCMoveTo结束之后,随机再生成一个x,y坐标,然后让advertuere移动到这个位置去,再开始行走的动画。
最后,别忘了我们的dealloc方法:
- (void) dealloc{ [[CCSpriteFrameCache sharedSpriteFrameCache] removeUnusedSpriteFrames]; [self.charArray removeAllObjects]; [super dealloc];}
上面把不再使用的纹理全部移除,并且把数组里面的对象移除掉。一定不要忘了调用super dealoc方法!
如果大家发现教程有什么问题或者笔误,请在下方留言,让我知道,谢谢!
如果你觉得还缺少了些什么,也请在下方留言。
下篇教程见!~
著作权声明:本文由http://www.cnblogs.com/andyque翻译,欢迎转载分享。请尊重作者劳动,转载时保留该声明和作者博客链接,谢谢!
转载于:https://www.cnblogs.com/zilongshanren/archive/2011/07/20/2111563.html
