How to create a stroke on a CCLabelTTF - cocos2d-iphone

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];
}

Related

How to add multiple sprites with time interval of 3 seconds and run same CCAction for all in cocos2d

Please help me as am fed with the searches and new to cocos 2d. Only am getting is the last sprite got moving if i schedule addRed and i want all the sprites moving randomly in the screen. Any help will be Appreciated and thanks in advance.
-(void)addRed
{
redSprite = [CCSprite spriteWithImageNamed:#"Red.png"];;
CGSize screenSize = [[CCDirector sharedDirector] viewSize];
[redSprite setPosition:CGPointMake(screenSize.width/2, screenSize.height/2)];
[self resizeSprite:redSprite toWidth:80 toHeight:80];
[self addChild:redSprite z:10];
[self gameStart];
}
- (void)gameStart {
// Create the actions
CGSize result = [[UIScreen mainScreen] bounds].size;
CGPoint nextPoint;
if (redSprite.position.x == result.width-40.0) {
nextPoint.x = redSprite.position.x - kAccelXAxis;
backx = YES;
}
else {
if (redSprite.position.x == 40.0) {
nextPoint.x = redSprite.position.x + kAccelXAxis;
backx = NO;
}
else {
if (backx) {
nextPoint.x = redSprite.position.x - kAccelXAxis;
}
else
{
nextPoint.x = redSprite.position.x + kAccelXAxis;
}
}
}
if (redSprite.position.y == 40.0) {
nextPoint.y = redSprite.position.y + kAccelYAxis;
backy = YES;
}
else {
if (redSprite.position.y == result.height-40.0) {
nextPoint.y = redSprite.position.y - kAccelYAxis;
backy = NO;
}
else {
if (backy) {
nextPoint.y = redSprite.position.y + kAccelYAxis;
}
else
{
nextPoint.y = redSprite.position.y - kAccelYAxis;
}
}
}
CCAction *myAction = [CCActionSequence actions:[CCActionMoveTo actionWithDuration:0.01 position:nextPoint], [CCActionCallFunc actionWithTarget:self selector:#selector(gameStart)], [CCActionCallFunc actionWithTarget:self selector:#selector(updateCol:)],nil];
[myAction setTag:10];
[redSprite runAction:myAction];
}
Try creating a updater with an interval of 3 sec for creating your sprites. For your created sprites, use a CCRepeatForever which will generate your next positions for your sprite.
For your case, I also thought about using a custom CCSprite to store your next position to move.
You will need something like...
[self schedule:#selector(createSprite) interval:(3)]; //to schedule your sprite generator
// Your sprite generator with action
-(void)createSprite{
CCCustomRed *red = [CCCustomRed spriteWithFile:#"red.png"];
[self addChild:red];
CCCallFuncN *changePos = [CCCallFuncN actionWithTarget:self selector:#selector(setRandomPos:)];
CCMoveTo *move = [CCMoveTo actionWithDuration:1 position:red.nextPosition];
CCDelayTime *delay = [CCDelayTime actionWithDuration:1.0];
[red runAction:[CCRepeatForever actionWithAction:[CCSequence actions:move,changePos,delay,nil]]];
}
//Generate a random pos as your sprite next move
-(void)setRandomPos:(id)sender{
CCCustomRed *red = (CCCustomRed *) sender;
CGSize screenSize = [[CCDirector sharedDirector] winSize];
red.nextPosition = ccp(arc4random()%screenSize.width,arc4random()%screenSize.height);
}
Hope it helps :)

Move a whole animation sprite sheet

I have some sprite-sheet that i have to animate forever , and i would like to add it as a CCLayer
to my scene .
Later on , i have to move this whole animation sprite on the screen.
So, for example, i have some animation of a dog walking, from sprite sheet, this one is running forever. than i want to be able to move this dog on screen while animating.
What is the best way to do this ? (or the right way)
This is how i animate the frames :
CCSprite *boom;
boom = [CCSprite spriteWithSpriteFrameName:[NSString stringWithFormat:#"%#_00000.png",file]];
boom.position=touch;
[self addChild:boom];
NSMutableArray *animFrames = [NSMutableArray array];
for(int i = 0; i < 5; i++)
{
CCSpriteFrame *frame = [[CCSpriteFrameCache sharedSpriteFrameCache] spriteFrameByName:[NSString stringWithFormat:
#"%#_0000%i.png",file,i]];
[animFrames addObject:frame];
}
CCAnimation* boxAnimation = [CCAnimation animationWithSpriteFrames:animFrames delay:0.075f];
CCAnimate * boxAction = [CCAnimate actionWithAnimation:boxAnimation];
CCAction *call=[CCCallBlock actionWithBlock:^{[self removeFromParentAndCleanup:YES];}];
CCAction * sequence=[CCSequence actions:boxAction,[CCHide action],call,nil];
[boom runAction:sequence];
return self;
How would you move this whole thing ?
There are a few ways to do this. If you are not preocupied with collision detection, then one way would be to :
CGPoint egressPosition = ccp(0,0); // figure this out in your app
float moveDuration = 1.5f ; // whatever time you compute for desired speed and distance
id move = [CCMoveTo actionWithDuration:moveDuration position:egressPosition];
id spawn = [CCSpawn actions:sequence,move,nil];
[boom runAction:spawn];
otherwise, using your code as is
[self schedule:#selector(moveBox:)]; // optional, you could do this in update method
[boom runAction:sequence];
-(void) moveBoom:(CCTime) dt {
CGPoint newPosition;
delta = ccp(dt*speedX,dt*speedY); // crude , just to get the idea
newPosition = ccpAdd(boom.position,delta);
// here you can figure out collisions at newPosition before the collision
// and do whatever seems appropriate
boom.position = newPosition;
}

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. Rotate point around another point without hard math calculations?

I haven't seen easy examples about rotaion around a specified point. I tried something like this but it doesn't work:
//CCNode *node is declared
//In a function of a subclass of CCSprite
- (void)moveWithCicrlce
{
anchorNode = [CCNode node];
anchorNode.position = ccpSub(self.position, circleCenter);
anchorNode.anchorPoint = circleCenter;
[anchorNode runAction:[CCRotateBy actionWithDuration:1 angle:90]];
[self runAction:[CCRepeatForever actionWithAction:[CCSequence actions:[CCCallFunc actionWithTarget:self selector:#selector(rotate)], [CCDelayTime actionWithDuration:0.1], nil]]];
}
- (void)rotate
{
self.position = ccpAdd(anchorNode.position, anchorNode.anchorPoint);
}
Here's how you can rotate a node (sprite etc) around a certain point P (50,50) with a radius (distance from P) of 100:
CCNode* center = [CCNode node];
center.position = CGPointMake(50, 50);
[self addChild:center];
// node to be rotated is added to center node
CCSprite* rotateMe = [CCSprite spriteWithFile:#"image.png"];
[center addChild:rotateMe];
// offset rotateMe from center by 100 points to the right
rotateMe.position = CGPointMake(100, 0);
// perform rotation of rotateMe around center by rotating center
id rotate = [CCRotateBy actionWithDuration:10 rotation:360];
[center runAction:rotate];
My approximate solution:
#interface Bomb : NSObject {
CCSprite *center;
}
...
#end
and some methods:
- (void)explode
{
BombBullet *bullet = [BombBullet spriteWithFile:#"explosion03.png"];
[[[CCDirector sharedDirector] runningScene] addChild:bullet];
center = [CCSprite spriteWithTexture:bullet.texture];
center.position = explosionPoint;
center.anchorPoint = ccp(-0.5, -0.5);
center.visible = NO;
[[[CCDirector sharedDirector] runningScene] addChild:center];
[center runAction:[CCRotateBy actionWithDuration:1 angle:360]];
CCCallFunc *updateAction = [CCCallFuncN actionWithTarget:self selector:#selector(update:)];
[bullet runAction:[CCRepeatForever actionWithAction:[CCSequence actions:updateAction, [CCDelayTime actionWithDuration:0.01], nil]]];
}
- (void)update:(id)sender
{
BombBullet *bombBullet = (BombBullet *)sender;
bombBullet.rotation = center.rotation;
bombBullet.position = ccpAdd(center.position, center.anchorPointInPoints);
bombBullet.position = ccpAdd(bombBullet.position, ccp(-bombBullet.contentSize.width / 2, -bombBullet.contentSize.height / 2));
bombBullet.position = ccpRotateByAngle(bombBullet.position, center.position, bombBullet.rotation);
}
of course I should add sprite deleting.

On CCBitmapFontAtlas effect (Display on by one)

I'm trying to make a effect using the CCBitmapFontAtlas, here is what I want:
The string say "ABCDEFG" being dispayed one by one, each one won't be displayed
until the one before is completely displayed.
And here is what I tried:
-(id) init
{
if( (self=[super init] )) {
label = [CCBitmapFontAtlas bitmapFontAtlasWithString:#"ABC" fntFile:#"bitmapFontTest.fnt"];
[self addChild:label];
CGSize s = [[CCDirector sharedDirector] winSize];
label.position = ccp(s.width/2, s.height/2);
label.anchorPoint = ccp(0.5f, 0.5f);
label.visible = NO; //hide it first
[[CCTouchDispatcher sharedDispatcher] addTargetedDelegate:self priority:0 swallowsTouches:YES];
}
return self;
}
-(BOOL) ccTouchBegan:(UITouch *)touch withEvent:(UIEvent *)event
{
CCSprite AChar = (CCSprite) [label getChildByTag:0];
CCSprite BChar = (CCSprite) [label getChildByTag:1];
CCSprite CChar = (CCSprite) [label getChildByTag:2];
id fade_in = [CCFadeIn actionWithDuration:3];
label.visible = YES;
[AChar runAction:fade_in];
[BChar runAction:fade_in];
[CChar runAction:fade_in];
return YES;
}
The effect is the "ABC" will fade in once I touched the screen, then I tried to use the
CallFuncND to call the next string to fade in while the current string is displayed.
But this seems to make things very complex.
Is there a easier way to get this effect done?
Any suggestion will be appreciate.
I feel like you are going in the right direction with this one. You could have each letter be a separate sprite and store them in an array and then run them each one by one.
The call function can be started by:
[self displayNextSprite:spriteArray nextIndex:0];
And the function is:
// Warning, This assumes you are not passing it an empty array, you may want to put in a check for that
-(void)displayNextSprite:(NSMutableArray*)spriteArray nextIndex:(NSUInteger)nextIndex
{
CCSprite *nextSprite = [spriteArray objectAtIndex:nextIndex];
id action1 = [CCFadeIn actionWithDuration:3];
// or = [CCPropertyAction actionWithDuration:3 key:#"opacity" from:0 to:255];
// The last letter
if(nextIndex == ([spriteArray count] - 1))
{
[nextSprite runAction:action1];
}
else // continue to the next letter
{
id callFunc = [CCCallFunc actionWithTarget:self selector:#selector(displayNextSprite:spriteArray nextIndex:nextIndex+1)];
id sequence = [CCSequence actionOne:action1 two:callFunc];
[nextSprite runAction:sequence];
}
}