Syncing sprite animations in cocos2d - cocos2d-iphone

Every now and then I add some pulsating sprites to my scene like so:
CCSpriteBatchNode *batch = (CCSpriteBatchNode*) [scene getChildByTag: foo1];
sprite = [CCSprite spriteWithBatchNode:batch rect:CGRectMake(0, 0, 128, 128)];
sprite.position = foo2
CCTintTo *a = [CCTintTo actionWithDuration: .5 red:128 green: 128 blue: 128];
CCTintTo *b = [CCTintTo actionWithDuration: .5 red:255 green: 255 blue: 255];
[sprite runAction:[CCRepeatForever actionWithAction:
[CCSequence actionOne: a two: b]]];
[batch addChild: sprite];
I would like to have all my sprites pulsating in sync, how do I go about this?

hmmm ... not easy. The only way i'd see of doing that is to schedule a 'flasherRamp' like so:
in .h
NSMutableArray *flashers;
in .m, init method
flashers = [[NSMutableArray array] retain]; // choose your own ARC flavor, if you retain
// dont forget to release in dealloc
[self schedule:#selector(flasherRamp) interval:1.0f];
in .m, where you create the sprite
foo2.visible=NO;
[flashers addObject foo2];
finally
-(void) flasherRamp {
for (CCSprite *flasher in flashers) {
CCTintTo *a = [CCTintTo actionWithDuration: .5 red:128 green: 128 blue: 128];
CCTintTo *b = [CCTintTo actionWithDuration: .5 red:255 green: 255 blue: 255];
[flasher runAction:[CCRepeatForever actionWithAction:
[CCSequence actionOne: a two: b]]];
flasher.visible=YES;
}
[flashers removeAllObjects];
}
ps. there could be some drift eventually, depending on how long this goes on for.
pps. from a usability perspective, this may not be a good idea if there is some causality between the appearance of the flashing sprites and some 'asynchronous' gaming event that may induce a delay of up to 1 second between the triggering event and the actual appearance of the flasher.
ob cit. : coded from memory, not tested, but should be close.

I would avoid using CCRepeatForever in this case.
Create an enum defining the current tint state (tintGray, tintWhite, tintDone), then create a scheduled selector that checks the state.
Once the state is complete, repeat the actions, but for every child of the batchnode (assuming those are the only children).
To schedule the selector, place the following in your init or other loading method:
// be sure to schedule the interval at a fast enough rate
[self schedule:#selector(tick:) interval:0.1f];
Then define the method as follows:
-(void)tick:(ccTime)dt
{
if(tintState == tintDone)
{
[self unschedule:#selector(tick:)];
[self tinter];
}
}
Then to schedule the tint actions for all sprites:
-(void)tinter
{
// could init and reuse this somewhere else to save on allocs
CCSequence *actions = [CCSequence actions:
[CCCallBlockN actionWithBlock:^(CCNode* node)
{
tintState = tintGray;
}],
[CCTintTo actionWithDuration: .5 red:128 green: 128 blue: 128],
[CCCallBlockN actionWithBlock:^(CCNode* node)
{
tintState = tintWhite;
}],
[CCTintTo actionWithDuration: .5 red:255 green: 255 blue: 255],
[CCCallBlockN actionWithBlock:^(CCNode* node)
{
tintState = tintDone;
}],
nil];
CCSpriteBatchNode *batch = (CCSpriteBatchNode*) [scene getChildByTag: foo1];
for (CCSprite *flasher in batch.children)
{
[flasher stopAllActions];
[flasher runAction:actions];
}
// reschedule tick checking
[self schedule:#selector(tick:) interval:0.1f];
}
Obviously this isn't perfect, as the flag will be driven by the first sprite to complete the tinting, but the delay should be negligible. If you want to make sure they all finish, just change the flag to a running count of the number of sprites, so "tinter" only gets called once tintState is equal to the number of sprites in the batchnode.

Related

How to create a stroke on a CCLabelTTF

I have been looking for the past couple of hours for a way to create a stroke around labels in cocos2d but so far all I have come up with is this: CCLabelTTF Font Stroke Demo this is just what I need but the stroke looks very blocky, and I am needing something that looks smoother. Is there any way to turn on some kind of antialiasing for the stroke or maybe there is some other way to create a stroke. Any help would be appreciated.
To create a stroke:
-(CCRenderTexture*) createStroke: (CCLabelTTF*) label size:(float)size color:(ccColor3B)cor
{
CCRenderTexture* rt = [CCRenderTexture renderTextureWithWidth:label.texture.contentSize.width+size*2 height:label.texture.contentSize.height+size*2];
CGPoint originalPos = [label position];
ccColor3B originalColor = [label color];
BOOL originalVisibility = [label visible];
[label setColor:cor];
[label setVisible:YES];
ccBlendFunc originalBlend = [label blendFunc];
[label setBlendFunc:(ccBlendFunc) { GL_SRC_ALPHA, GL_ONE }];
CGPoint bottomLeft = ccp(label.texture.contentSize.width * label.anchorPoint.x + size, label.texture.contentSize.height * label.anchorPoint.y + size);
//CGPoint positionOffset = ccp(label.texture.contentSize.width * label.anchorPoint.x - label.texture.contentSize.width/2,label.texture.contentSize.height * label.anchorPoint.y - label.texture.contentSize.height/2);
//use this for adding stoke to its self...
CGPoint positionOffset= ccp(-label.contentSize.width/2,-label.contentSize.height/2);
CGPoint position = ccpSub(originalPos, positionOffset);
[rt begin];
for (int i=0; i<360; i+=60) // you should optimize that for your needs
{
[label setPosition:ccp(bottomLeft.x + sin(CC_DEGREES_TO_RADIANS(i))*size, bottomLeft.y + cos(CC_DEGREES_TO_RADIANS(i))*size)];
[label visit];
}
[rt end];
[[[rt sprite] texture] setAntiAliasTexParameters];//THIS
[label setPosition:originalPos];
[label setColor:originalColor];
[label setBlendFunc:originalBlend];
[label setVisible:originalVisibility];
[rt setPosition:position];
return rt;
}
Usage:
CCRenderTexture* myStroke = [self createStroke:myCCLabelTTF size:myStrokeSize color:ccYELLOW];
[myCCLabelTTF addChild:myStroke z:-1 tag:kTagStroke];
And to increase smoothness, modify the following function to fit your needs (decrease the +60 increment to perhaps +30). Just note that the more iterations, the more more time spent rendering, which will negatively affect performance:
for (int i=0; i<360; i+=60) // you should optimize that for your needs
{
[label setPosition:ccp(bottomLeft.x + sin(CC_DEGREES_TO_RADIANS(i))*size, bottomLeft.y + cos(CC_DEGREES_TO_RADIANS(i))*size)];
[label visit];
}

Cocos2d/Box2d strange behavior for kinematic bodies

I'm developing a very simple game. Here's my enemy class named Machine's behavior code:
#import "Machine.h"
#implementation Machine
+(id)machineWithWorld:(b2World*)world position:(CGPoint)pos
{
return [[[self alloc] initWithWorld:world position:pos] autorelease];
}
-(id)initWithWorld:(b2World*)world position:(CGPoint)pos
{
if(self = [super initWithShape:[AppDelegate renameFrameForIpad:#"machine"] inWorld:world])
{
size = [CCDirector sharedDirector].winSize;
self.body->SetTransform([Helper toMeters:pos], 0.0);
self.body->SetType(b2_staticBody);
safetyCounter = 5;
[self schedule:#selector(machineSafetyCounter)];
movementWidthInMeters = (size.width-self.contentSize.width)/PTM_RATIO;
linearSpeed = 0.5;
[self schedule:#selector(startMoving) interval:1.5];
}
return self;
}
#pragma mark<Machine Behavior>
-(void)startMoving
{
[self unschedule:_cmd];
float distanceFromCenterInMeters = (size.width/2 - self.position.x)/PTM_RATIO;
float interval = ABS(distanceFromCenterInMeters/linearSpeed);
if(interval < 0.01f)
interval = 0.02f;
b2Vec2 motionDirection = (distanceFromCenterInMeters > 0.0f) ? b2Vec2(1.0, 0.0) : b2Vec2(-1.0, 0.0);
self.body->SetType(b2_kinematicBody);
self.body->SetLinearVelocity(linearSpeed*motionDirection);
[self schedule:#selector(startMotionFromBeginning) interval:interval-0.01];
CCLOG(#"startMoving distance-->%f, interval-->%f", distanceFromCenterInMeters, interval);
}
-(void)startMotionFromBeginning
{
[self unschedule:_cmd];
float interval = (movementWidthInMeters/2)/linearSpeed;
self.body->SetLinearVelocity(0.5*b2Vec2(1.0, 0.0));
[self schedule:#selector(moveRTL) interval:interval-0.01];
[self schedule:#selector(checkIfHelmetIsBelowMachine) interval:0.1];
CCLOG(#"startMotionFromBeginning interval-->%f", interval);
}
-(void)moveRTL
{
[self unschedule:_cmd];
float interval = movementWidthInMeters/linearSpeed;
self.body->SetLinearVelocity(0.5*b2Vec2(-1.0, 0.0));
[self schedule:#selector(moveLTR) interval:interval-0.01];
CCLOG(#"moveRTL interval-->%f", interval);
}
-(void)moveLTR
{
[self unschedule:_cmd];
float interval = movementWidthInMeters/linearSpeed;
self.body->SetLinearVelocity(0.5*b2Vec2(1.0, 0.0));
[self schedule:#selector(moveRTL) interval:interval-0.01];
CCLOG(#"moveLTR interval-->%f", interval);
}
-(void)checkIfHelmetIsBelowMachine
{
[self unschedule:_cmd];
Helmet* helmet = (Helmet*)[[[[[CCDirector sharedDirector] runningScene] children] objectAtIndex:0] getChildByTag:kTagHelmet];
float helmetPosX = helmet.position.x;
if((self.position.x > helmetPosX) && (self.position.x < helmetPosX+helmet.contentSize.width))
{
[self unscheduleAllSelectors];
[self schedule:#selector(machineSafetyCounter) interval:0.1];
[self schedule:#selector(startMovingDownwards) interval:0.0];
return;
}
[self schedule:_cmd interval:0.1];
}
-(void)startMovingDownwards
{
[self unschedule:_cmd];
self.body->SetLinearVelocity(0.25*b2Vec2(0.0, -1.0));
[self schedule:#selector(stopMovingDownwards) interval:1.0];
CCLOG(#"startMovingDownwards");
}
-(void)stopMovingDownwards
{
[self unschedule:_cmd];
self.body->SetLinearVelocity(b2Vec2(0.0, 0.0));
[self schedule:#selector(startMoving) interval:0.2];
CCLOG(#"stopMovingDownwards");
}
All I have done is following:
1) The body is static initially and is positioned at ccp(size.width*0.5, size.height*0.75).
2) After 1.5 seconds, It becomes kinematic and starts moving with a linear speed of 0.5 m/s.
3) It checks it's current distance (from screen width center keeping height same), evaluates the time needed to reach that spot, and then starts moving in that direction horizontally.
4) After reaching that spot, it starts it's signature motion, it starts moving from Left to right, if at any time helmet(another game object) passes underneath it, it starts moving down and stops after 1.0 seconds, then whole cycle repeats.
5) It moves LTR & RTL until it starts to move down when it finds the helmet underneath it.
Now the problem is, sometimes the behavior is exactly same as expected.
And many many times, it starts moving upwards and I have never set the y bit for motion vector in positive direction.
After initializing the body, you should not change its bodyType. When you first make the body, set it as kinematic so you don't run into problems later when trying to change it. Is the body attached to a CCSprite? If so, you should make the class a subclass of CCSprite so you can use [super initWithFile:(NSString *) world:(b2World *)] or some method of your choosing to facilitate your endeavors.

progress time bar in cocos2d disappers after restart game

I've created a quiz game, that implements a time bar. In the first play it's all right, but if, after gameover, the player tap "restart", the game goes on properly, but the time bar disappears!
Here my code from GameOverLayer to Game:
-(void) restart {
[[CCDirector sharedDirector] replaceScene:[HelloWorldLayer node]];
}
Here the function to create a new question
-(void)creaDomanda{
//bar
CCProgressFromTo *to1 = [CCProgressFromTo actionWithDuration:MaxTime from:100 to:0];
bar = [CCProgressTimer progressWithFile:#"barra.png"];
bar.type = kCCProgressTimerTypeHorizontalBarLR;
[bar setPosition:ccp(size.width - 250 , size.height - 18)];
int randomValue = (arc4random() % 4) + 1;
NSString *stringa = [NSString stringWithFormat:#"Domanda%i", randomValue];
dictionary = [plistData objectForKey:stringa];
domanda = [dictionary valueForKey:#"Titolo"];
labelDomanda = [CCLabelTTF labelWithString:domanda fontName:#"Marker Felt" fontSize:24];
labelDomanda.position = ccp( size.width /2 , 400 );
[self addChild: labelDomanda];
int rispostaEsatta = [[dictionary valueForKey:#"Soluzione"] intValue];
menu = [CCMenu menuWithItems:nil];
for (int i = 1; i<5;i++)
{
if(rispostaEsatta == i){
item = [CCMenuItemFont itemFromString:[dictionary valueForKey:
[NSString stringWithFormat:#"Risposta%i",i] ]
target:self selector:#selector(corretto)];
}else{
item = [CCMenuItemFont itemFromString:[dictionary valueForKey:
[NSString stringWithFormat:#"Risposta%i",i] ]
target:self selector:#selector(sbagliato)];
}
[menu addChild:item];
}
//[..]
[self addChild:menu];
[self addChild:bar];
[bar runAction:to1];
}
And here one of the correct/wrong method (similar) that after all, create a new question:
-(void)sbagliato{
CCLOG(#"Sbagliato");
if (menu) [self removeChild:menu cleanup:YES];
if (labelDomanda) [self removeChild:labelDomanda cleanup:YES];
if (bar) [self removeChild:bar cleanup:YES];
labelRisultato = [CCLabelTTF labelWithString:#"Hai sbagliato!" fontName:#"Marker Felt" fontSize:24];
[labelRisultato setColor:ccc3(255, 1, 1)];
labelRisultato.position = ccp(size.width / 2, 280);
[self addChild:labelRisultato];
[self gameOver:2 punteggio:0];
// Richiamiamo il metodo per eliminare la label dopo 0,3 secondi
[self performSelector:#selector(eliminaLabel) withObject:nil afterDelay:0.5];
increment = increment - 20;
[pointLabel setString: [NSString stringWithFormat: #"Punti: %i", increment]];
// new question
[self performSelector:#selector(creaDomanda) withObject:nil afterDelay:0.5];
}
Can anyone explain to me please why when I restart the time bar desappers?
Thank You
My best guess:
The CCProgressFromTo action is still running. Since it progresses down to 0, the CCProgressTimer eventually doesn't display any part of it anymore. This may continue even if you run another CCProgressFromTo action on the progress timer.
Solution: be sure to stop any running CCProgressFromTo actions before running another.
If that doesn't fix it, then I imagine the CCProgressTimer needs to be reset by setting percentage back to 100.

cocos2d remove tint

I'm trying to implement a highlight animation to my sprites. The sprite should highlight to a given color and gradually reverse back to its original colors, with the following code:
- (void)highlight {
CCTintTo *tintAction = [CCTintTo actionWithDuration:0.1 red:255 green:255 blue:255];
CCTintTo *tintBackAction = [tintAction reverse];
CCSequence *sequence = [CCSequence actions: tintAction, tintBackAction, nil];
[self runAction:sequence];
}
Now this function raises an exception as CCTintTo doesn't seem to implement 'reverse', which makes sense. Is there any other way to implement removal of an added tint over an interval, using a CCAction?
CCSprite's default color is ccWhite, {255, 255, 255}, so if you
want to make sprite lighter, you'll have to subclass CCSprite/write shader to use additive coloring.
Just tint it back:
CCColor3B oldColor = sprite.color;
CCTintTo *tintTo = [CCTintTo actionWithDuration:t red:r green:g blue:b];
CCTintTo *tintBack = [CCTintTo actionWithDuration:t red:oldColor.r green:oldColor.g blue:oldColor.b];
[sprite runAction:[CCSequence actions: tintTo, tintBack, nil]];
You can store previous color before start tint, then just create CCTintTo with initial RGB values.
For Cocos2dx (C++)
ccColor3B oldColor = sprite->getColor();
CCTintTo* action = CCTintTo::create(0.5f, 127, 255, 127);
CCTintTo* actionReverse = CCTintTo::create(0.5f, oldColor.r, oldColor.g, oldColor.b);;
sprite->runAction(CCSequence::create(action, actionReverse, NULL));
Works fine Kreiri, thanks! I already gave a plus to you:).

fading frames in and out

I have a set of frames that I need to show in a sequence one after the other but instead of simply erasing the previous frame and drawing the next one, I need to fade out the previous frame and fade in the new one simultaneously.
What is the best way to achieve this in cocos2d?
- (void) showFirstSpriteWithFade
{
if( [m_sprites count] > 0 )
{
CCSprite* spriteToShow = [m_sprites objectAtIndex: 0];
[m_sprites removeObjectAtIndex: 0];
id showAction = [CCSequence actions: [CCFadeIn actionWithDuration: fadeInDuration],
[CCFadeOut actionWithDuration: fadeOutDuration],
[CCCallFunc actionWithTarget: self selector:#selector(showFirstSpriteWithFade)],
nil];
[spriteToShow runAction: showAction];
}
}
this will work if you wil store all your sprites in array m_sprites. in this case all sprites must be added to the parent to be shown one by one. you can improve this code if you want, for example, use just one sprite and change it's texture each time.
if you wanna show pictures forever, you can try something like this
- (void) showNextSpriteWithFade
{
m_shownSpriteIndex++;
if( m_shownSpriteIndex == [m_sprites count] )
{
m_shownSpriteIndex = 0;
}
CCSprite* spriteToShow = [m_sprites objectAtIndex: m_shownSpriteIndex];
id showAction = [CCSequence actions: [CCFadeIn actionWithDuration: fadeInDuration],
[CCFadeOut actionWithDuration: fadeOutDuration],
[CCCallFunc actionWithTarget: self selector:#selector(showNextSpriteWithFade)],
nil];
[spriteToShow runAction: showAction];
}