I started playing around with Cocos2D and I figured out to do a sprite animation with sprite sheets.
Now I have a little robot walking around on my screen. But I am wondering how I can put one animation into another.
For example, my robot has a walking animation and I want to move his arms separately. But his shoulders will rise and fall during walking and the arms should move relative to his shoulder position.
My walk animation has five sprites and I have nine different arm positions. Now, I could add the arms to every single walking sprite for all nine arm positions. but then I would have 45 Images instead of 14.
That's a good question (finally!).
What I would do is separate your character into different sprites (you already did this) that are animatable on their own.
Then, whenever a frame of that animation is presented, I would have a block of code executed that modified the position of the arms animation, so it matches the shoulders.
To execute that block of code, you'd need Cocos 1.1 or better, since they added the CCAnimationFrame there, however, those frames can only execute code via an NSNotification, so I made an improvement so we can set a block on a frame, and that block would be executed whenever that frame is displayed.
Just find CCAnimation.h and modify the CCAnimationFrame interface to look like this:
typedef void(^FrameBlock)(CCSprite *sprite);
/** CCAnimationFrame
A frame of the animation. It contains information like:
- sprite frame name
- # of delay units.
- offset
#since v1.1
*/
#interface CCAnimationFrame : NSObject <NSCopying>
{
CCSpriteFrame* spriteFrame_;
float delayUnits_;
NSDictionary *userInfo_;
FrameBlock frameBlock;
}
/** CCSpriteFrameName to be used */
#property (nonatomic, readwrite, retain) CCSpriteFrame* spriteFrame;
/** how many units of time the frame takes */
#property (nonatomic, readwrite) float delayUnits;
/** A CCAnimationFrameDisplayedNotification notification will be broadcasted when the frame is displayed with this dictionary as UserInfo. If UserInfo is nil, then no notification will be broadcasted. */
#property (nonatomic, readwrite, retain) NSDictionary *userInfo;
/** If the block is not NULL, it will be executed when the frame becomes visible **/
#property (nonatomic, readwrite, copy) FrameBlock frameBlock;
/** initializes the animation frame with a spriteframe, number of delay units and a notification user info */
-(id) initWithSpriteFrame:(CCSpriteFrame*)spriteFrame delayUnits:(float)delayUnits userInfo:(NSDictionary*)userInfo;
-(id) initWithSpriteFrame:(CCSpriteFrame*)spriteFrame delayUnits:(float)delayUnits block:(FrameBlock)block;
#end
And then open CCAnimation.m and make sure the CCAnimationFrame implementation looks like this:
#implementation CCAnimationFrame
#synthesize spriteFrame = spriteFrame_, delayUnits = delayUnits_, userInfo=userInfo_;
#synthesize frameBlock;
-(id) initWithSpriteFrame:(CCSpriteFrame *)spriteFrame delayUnits:(float)delayUnits userInfo:(NSDictionary*)userInfo
{
if( (self=[super init]) ) {
self.spriteFrame = spriteFrame;
self.delayUnits = delayUnits;
self.userInfo = userInfo;
}
return self;
}
-(id) initWithSpriteFrame:(CCSpriteFrame*)spriteFrame delayUnits:(float)delayUnits block:(FrameBlock)block{
self = [self initWithSpriteFrame:spriteFrame delayUnits:delayUnits userInfo:nil];
if(self){
[self setFrameBlock:block];
}
return self;
}
-(void) dealloc
{
CCLOGINFO( #"cocos2d: deallocing %#", self);
[spriteFrame_ release];
[userInfo_ release];
[super dealloc];
}
-(id) copyWithZone: (NSZone*) zone
{
CCAnimationFrame *copy = [[[self class] allocWithZone: zone] initWithSpriteFrame:[[spriteFrame_ copy] autorelease] delayUnits:delayUnits_ userInfo:[[userInfo_ copy] autorelease] ];
return copy;
}
-(NSString*) description
{
return [NSString stringWithFormat:#"<%# = %08X | SpriteFrame = %08X, delayUnits = %0.2f >", [self class], self, spriteFrame_, delayUnits_ ];
}
#end
Then, when creating the animation frames, add a block to them in which you set the position of the arms to match the shoulders.
I hope it helps.
Related
I have an animated sprite using CCSpriteFrameCache that represents my game character. I want to add a text label as a child of this sprite to represent the (dynamic) character name, however I run into complications with 'CCSprite is not using the same texture id'. Because the text is dynamic, I can't include it in the sprite sheet the character is using. What is the best approach to get this text overlay on to my character?
I tend to use a CCNode derivative for this, that also implements the RGBA protocol. You can play the animation from inside the node, and add the label to the node (as well as others like a health bar), and then move the node, make it visible (or not), use some animation like fading in and out the entire content.
Composition over inheritance is true in this context.
You can see my code which should be a template for you to implement on your own.
#interface Player : CCNode
+ (instancetype)playerWithName:(NSString*)name sprite:(CCSprite*)sprite
- (id)initWithName:(NSString*)name sprite:(CCSprite*)sprite;
#property (nonatomic, retain) CCSprite* sprite;
#property (nonatomic, copy) NSString* name;
#end
#implementation Player {
BOOL _nameLabelAdded;
BOOL _spriteAdded;
}
#define LABEL_NAME_TAG 0e1
#synthesize sprite = _sprite, name = _name;
+ (instancetype)playerWithName:(NSString*)name sprite:(CCSprite*)sprite {
return [[[self alloc] initWithName:name sprite:sprite] autorelease];
}
- (id)initWithName:(NSString*)name sprite:(CCSprite*)sprite {
if (self = [super init]) {
//NOT TESTED..
self.name = name;
self.sprite = sprite;
}
return self;
}
- (void)setSprite:(CCSprite*)theSprite {
if (theSprite != _sprite) {
[_sprite release];
_sprite = [theSprite retain];
if (!_spriteAdded) {
/* initialize position etc here */
[self addChild:_sprite];
_spriteAdded = YES;
}
}
}
- (void)setName:(NSString*)theName {
if (![_name isEqualToString:theName]) {
[_name release];
_name = [theName copy];
if (![_name isEqualToString:#""]) {
CCLabelTTF* name_label;
if (!_nameLabelAdded) {
name_label = [CCLabelTTF labelWithString:_name];
/* init the name_label */
[self addChild:name_label z:1 tag:LABEL_NAME_TAG];
_nameLabelAdded = YES;
} else {
name_label = (CCLabelTTF*)[self childWithTag:LABEL_NAME_TAG];
name_label.text = name;
}
}
}
}
#end
I had this situation on my last released game.
The best way to solve this problem was to create a parent node and add the label and sprite as child. It's better to manage the node separately, mainly because you can adjust the position of both without interfering with each other.
Node hierarchy:
- Node parent
- Sprite with CCSpriteFrameCache
- Label
I am developing game in Cocos2d. In it background of scene moving constantly with x - axis.
I want to understand how can i do it because i am new in cocos2d.
thanks in advance
Take a look to my code - here are the sources. Find the "moving" technique in HelloWorldLayer.m. I have taken a 360 panorama picture and put it to move continuously from right to left, in fullscreen.
In your YourClass.h file:
#import "cocos2d.h"
#interface YourClass : CCLayer
+(CCScene *) scene;
#end
In your YourClass.m file:
#import "YourClass.h"
#implementation YourClass
CCSprite *panorama;
CCSprite *appendix;
//_____________________________________________________________________________________
+(CCScene *) scene
{
// 'scene' is an autorelease object.
CCScene *scene = [CCScene node];
// 'layer' is an autorelease object.
YourClass *layer = [YourClass node];
// add layer as a child to scene
[scene addChild: layer];
// return the scene
return scene;
}
//_____________________________________________________________________________________
// on "init" you need to initialize your instance
-(id) init
{
// always call "super" init
// Apple recommends to re-assign "self" with the "super" return value
if( (self=[super init])) {
panorama = [CCSprite spriteWithFile: #"panorama.png"];
panorama.position = ccp( 1709/2 , 320/2 );
[self addChild:panorama];
appendix = [CCSprite spriteWithFile: #"appendix.png"];
appendix.position = ccp( 1709+480/2-1, 320/2 );
[self addChild:appendix];
// schedule a repeating callback on every frame
[self schedule:#selector(nextFrame:)];
}
return self;
}
//_____________________________________________________________________________________
- (void) nextFrame:(ccTime)dt {
panorama.position = ccp(panorama.position.x - 100 * dt, panorama.position.y);
appendix.position = ccp(appendix.position.x - 100 * dt, appendix.position.y);
if (panorama.position.x < -1709/2) {
panorama.position = ccp( 1709/2 , panorama.position.y );
appendix.position = ccp( 1709+480/2-1, appendix.position.y );
}
}
// on "dealloc" you need to release all your retained objects
- (void) dealloc
{
// in case you have something to dealloc, do it in this method
// in this particular example nothing needs to be released.
// cocos2d will automatically release all the children (Label)
// don't forget to call "super dealloc"
[super dealloc];
}
//_____________________________________________________________________________________
#end
Play a little with this code and you will get what you need.
I was wondering on how do I increase the interval over time so I can add target. I am still new to cocos2d.
[self schedule:#selector(gameLogic:) interval:0.7];
-(void)gameLogic:(ccTime)dt {
[self addTarget];
}
Why not declare a simple property (int, float, etc.) to hold the number of times your method has been called, and increment it when you call the method itself? That way, it's just a multiplication problem:
//.h
...
#property (nonatomic, assign) int iterations;
//.m
#synthesize iterations = iterations_;
[self schedule:#selector(gameLogic:) interval:0.7*iterations_];
-(void)gameLogic:(ccTime)dt {
[self addTarget];
iterations_++;
}
float interval = .7;
-(id)init{
...
[self scheduleOnce:#selector(gameLogic:) delay:interval]; //Check the name of the method, I'm not 100% sure about it
...
}
-(void)gameLogic:(ccTime)dt {
[self addTarget];
interval += dt; //Or whatever you want to increase it by
[self scheduleOnce:#selector(gameLogic:) delay:interval]; //Check the name of the method, I'm not 100% sure about it
}
fresh to objC and cocos2d :)
i'm following "learn cocos2d game development with iOS5", in chapter4, there is a "DoodleDrop" game.
define some variable in GameScene.h like this
#interface GameScene : CCLayer
{
CCSprite *player;
CGPoint playerVelocity;
CCArray *spiders;
CGSize screenSize;
int dropedSpidersCount;
float duration;
}
+ (CCScene *)scene;
#end
in GameScene.m the init method looks like this
- (id)init
{
if (self = [super init]) {
duration = 4.0;
[self createPlayer];
[self createSpiders]; // spiders were inited here.
[self resetSpiders];
[self schedule:#selector(chooseSpider:) interval:0.7];
}
return self;
}
while in chooseSpider, i cannot access spiders, xcode broke
in other methods, spiders or duration just behave normally, why does this happens?
gist code added
https://gist.github.com/2940466
After inspecting your code, I suggest you to try this fix:
- (void)createSpiders
{
CCSprite *tempSpider = [CCSprite spriteWithFile:#"spider.png"];
CGSize spiderSize = [tempSpider texture].contentSize;
int spiderCount = screenSize.width / spiderSize.width;
spiders = [[CCArray arrayWithCapacity:spiderCount] retain];
for (int i = 0; i < spiderCount; i++) {
CCSprite *spider = [CCSprite spriteWithFile:#"spider.png"];
[self addChild:spider];
[spiders addObject:spider];
}
}
where the only difference is in the line:
spiders = [[CCArray arrayWithCapacity:spiderCount] retain];
Indeed, if you do not retain you spiders object, it will be autoreleased at the next run loop iteration.
OLD ANSWER:
Without seeing more code it is not possible to say exactly what is happening, but it seems that in the interval between creating the spiders and the actual execution of chooseSpiders, your spiders array gets deallocated.
As a quick try, I would suggest adding:
[spiders retain];
before calling
[self schedule:#selector(chooseSpider:) interval:0.7];
and see wether the crash keeps happening.
if you provide more code, it could be possible to help you further.
I am currently using the cocos2d Director for controlling my animation with the pause, resume, and stopAnimation methods. Is it also possible to use the Director to return the time that the animation has played?
I am currently using this method:
-(void)stopAnimation:(id)sender {
//Timer initialized elsewhere: startTimer = [NSDate timeIntervalSinceReferenceDate];
//Do other method stuff here
[[Director sharedDirector] stopAnimation];
stopTimer = [NSDate timeIntervalSinceReferenceDate];
elapsedTime = (stopTimer - startTimer);
NSLog(#"elapsedTime = %f", elapsedTime);
}
I looked through the Director source and didn't see anything that would help you. I did notice that your code, as written, doesn't take into account the time your animation was paused or when other scenes were playing.
If that is a concern you can keep track of the elapsed time in a tick method that you schedule in your Scene or Layer.
MyLayer.h
#interface MyLayer : Layer {
ccTime totalTime;
}
#property (nonatomic, assign) ccTime totalTime;
MyLayer.m
-(id)init
{
if( (self = [super init]) )
{
[self schedule:#selector(update:)];
}
return self;
}
// deltaTime is the amount of running time that has passed
// since the last time update was called
// Will only be called when the director is not paused
// and when it is part of the active scene
-(void)update:(ccTime)deltaTime
{
totalTime += deltaTime;
}