(译)cocos2d精灵教程:第三部分

来源:转载

原文链接地址: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];
}

@end

    OK,千万别头疼--接下来我会一点点分解:

-(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方法!

    如果大家发现教程有什么问题或者笔误,请在下方留言,让我知道,谢谢!

如果你觉得还缺少了些什么,也请在下方留言。

    下篇教程见!~


分享给朋友:
您可能感兴趣的文章:
随机阅读: