How we can stop an action on certain frame (texture)? I want to make an animation which stops after the touch is ended. I searched trough the docs, but with no luck. The only thing I have found is methods removeAllActions and removeActionForKey: but I don't want to completely remove an action, I just want to stop it at the right texture - the current texture when the animating signal is stopped.
//This is from the scene init method
NSMutableArray *frames = [NSMutableArray array];
SKTextureAtlas *atlas = [SKTextureAtlas atlasNamed:#"hero-movement"];
int numImages = atlas.textureNames.count;
for (int i=1; i <= numImages; i++) {
NSString *texture = [NSString stringWithFormat:#"animation_00%d", i];
SKTexture *t = [atlas textureNamed:texture];
[frames addObject:t];
}
self.animation = frames;
And this is the method which I use in touchesBegan to start animation:
-(void)move :(float)delay
{
//This is our general runAction method to make our bear walk.
//By using a withKey if this gets called while already running it will remove the first action before
//starting this again.
[self.hero runAction:[SKAction repeatActionForever:[SKAction animateWithTextures:self.animation
timePerFrame:delay
resize:NO
restore:YES]] withKey:#"move"];
return;
}
You can use the speed property to pause/resume an action:
SKAction *action = [_hero actionWithKey:#"move"];
action.speed = 0; // to pause
or
action.speed = 1; // to resume
n init method am starting one CCAnimation. its fine. at touch ended method am stoping the animation. at the time of stop i need to get the current image of animate.
Player = CCSprite::create("AngleSelector1.png");
Player->setPosition( ccp(size.width / 2, size.height/2) );
this->addChild(Player);
//Animation
CCAnimation *animate = CCAnimation::create();
for (int i = 1; i <=10; i++)
{
char frameName[128] = {0};
sprintf(frameName, "AngleSelector%d.png", i);
animate->addSpriteFrameWithFileName(frameName) ;
}
animate->setDelayPerUnit(0.35f); // This animation contains 3 frames, will continuous 2.8 seconds.
animate->setRestoreOriginalFrame(true); // Return to the 1st frame after the 3rd frame is played.
CCAnimate *animaction = CCAnimate::create(animate);
CCRepeatForever *rt = CCRepeatForever::create(animaction);
Player->runAction(rt);
this->setTouchEnabled(true);
void HelloWorld::ccTouchesEnded(CCSet* touches, CCEvent* event)
{
Player->stopAllActions();
}
i solved using following code. image index will return your current display frame
CCTexture2D* tex = Player->getTexture();
int imageIndex = 0;
for (int i=0; i< animate->getFrames()->count(); i++) {
CCAnimationFrame *frame = (CCAnimationFrame*)animate->getFrames()->objectAtIndex(i);
CCTexture2D *tex2 = frame->getSpriteFrame()->getTexture();
if (tex->isEqual(tex2)) {
imageIndex = i;
break;
}
}
For context, I'm making a brick-breaker game in iOS and want the bricks I create to all move to the left and right repeatedly as the ball bounces around the scene.
I have a method in my code that I call once when the scene is presented that adds a couple of rows of sprite nodes to the scene as bricks, as shown below
-(void) addBricks:(CGSize)size {
// add a new row of bricks
for (int j = 1; j <= 3; j++) {
// add top column of bricks
for (int i = 0; i < 5; i++) {
SKSpriteNode *brick = [SKSpriteNode spriteNodeWithImageNamed:#"brick"];
brick.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:brick.frame.size];
brick.physicsBody.dynamic = NO;
brick.physicsBody.categoryBitMask = brickCategory;
int xPos = ((brick.frame.size.width / 2) + 20) + ((brick.frame.size.width / 2) + 30) * i;
int yPos = size.height - (30 * j);
// generate the sequence that the brick will perform
SKAction *moveHorizontal = [SKAction moveToX:xPos + 20 duration:1.0f];
moveHorizontal.timingMode = SKActionTimingEaseOut;
SKAction *moveBack = [moveHorizontal reversedAction];
SKAction *wait = [SKAction waitForDuration:0.4f];
SKAction *backAndForth = [SKAction sequence:#[moveHorizontal, wait, moveBack, wait]];
SKAction *repeatHorizMove = [SKAction repeatActionForever:backAndForth];
brick.position = CGPointMake(xPos, yPos);
// add the brick
[self addChild:brick];
// make it move back and forth
[brick runAction:repeatHorizMove];
}
}
}
My problem is that, while all of the bricks will move to the right when the scene starts, they don't move back and repeat the action over and over like I want them to. I think it may have something to do with giving them all the same name when they are programmatically created, but I know the scene retains all of the bricks despite them having the same name.
How can I get it so that all of the SpriteNodes (bricks) that I create keep moving back and forth forever?
The moveToX action is not reversible. Always check the reference for comments like this.
This should fix it:
SKAction *moveHorizontal = [SKAction moveToX:xPos + 20 duration:1.0f];
moveHorizontal.timingMode = SKActionTimingEaseOut;
SKAction *moveBack = [SKAction moveToX:xPos duration:1.0];
My project creates a bomb, an explosion, then checks for collisions in the explosions and finally delete the bombs that didn't get hit in a collision. This is explained in more detail here. The following code does this.
-(void)placeBomb
{
NSLog(#"Bomb placed");
_circle = [[CCSprite alloc]initWithFile:#"Circle.png"];
CGPoint circle0position = ccp(_cat.position.x , _cat.position.y);
CGPoint c0TileCoordt = [self tileCoordForPosition:circle0position];
CGPoint c0TileCoord = [self positionForTileCoord:c0TileCoordt];
_circle.position = c0TileCoord;
[self addChild:_circle];
id fade = [CCScaleTo actionWithDuration:3.5 scale:0];
[_circle runAction:fade];
double delayInSeconds = 3.0;
dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delayInSeconds * NSEC_PER_SEC));
dispatch_after(popTime, dispatch_get_main_queue(), ^(void){
[self explosionFromPoint:c0TileCoordt withSprite:_circle];
});
}
- (BOOL)isLocationBombable:(CGPoint)tileCoord;
{
if ([self isValidTileCoord:tileCoord] && ![self isWallAtTileCoord:tileCoord])
{
return YES;
}
else
{
return NO;
}
}
-(void)explosionFromPoint:(CGPoint)explosionPoint withSprite:(CCSprite*)sprite;
{
//int
explosionLenght += 1;
if (explosionLenght >= 7) //Just for testing purposes, don't have a way to increase it naturally.
{
explosionLenght = 1;
}
BOOL topB = YES;
BOOL leftB = YES;
BOOL bottomB = YES;
BOOL rightB = YES;
int bombX = (explosionPoint.x + 1);
int bombY = (explosionPoint.y + 1);
int bombNegX = (explosionPoint.x - 1);
int bombNegY = (explosionPoint.y - 1);
CGPoint top = ccp(explosionPoint.x, bombY);
CGPoint left = ccp(bombNegX, explosionPoint.y);
CGPoint bottom = ccp(explosionPoint.x, bombNegY);
CGPoint right = ccp(bombX, explosionPoint.y);
if (![self isLocationBombable:top])
{topB = NO;}
if (![self isLocationBombable:left])
{leftB = NO;}
if (![self isLocationBombable:bottom])
{bottomB = NO;}
if (![self isLocationBombable:right])
{rightB = NO;}
for (int i = 0; i <= explosionLenght; i++) {
int bombX = (explosionPoint.x + i);
int bombY = (explosionPoint.y + i);
int bombNegX = (explosionPoint.x - i);
int bombNegY = (explosionPoint.y - i);
CGPoint top = ccp(explosionPoint.x, bombY);
CGPoint left = ccp(bombNegX, explosionPoint.y);
CGPoint bottom = ccp(explosionPoint.x, bombNegY);
CGPoint right = ccp(bombX, explosionPoint.y);
CCSprite *circleTop = [[CCSprite alloc]initWithFile:#"Circle.png"];
CCSprite *circleLeft = [[CCSprite alloc]initWithFile:#"Circle.png"];
CCSprite *circleBottom = [[CCSprite alloc]initWithFile:#"Circle.png"];
CCSprite *circleRight = [[CCSprite alloc]initWithFile:#"Circle.png"];
int scaleTime = 5;
if ([self isLocationBombable:top] && topB == YES)
{
circleTop.position = [self positionForTileCoord:top];
[self addChild:circleTop];
id fadeTop = [CCSequence actionOne:[CCMoveTo actionWithDuration:1 position:circleTop.position] two:[CCScaleTo actionWithDuration:scaleTime scale:0]];
[circleTop runAction:fadeTop];
}
if ([self isLocationBombable:left] && leftB == YES)
{
circleLeft.position = [self positionForTileCoord:left];
[self addChild:circleLeft];
id fadeLeft = [CCSequence actionOne:[CCMoveTo actionWithDuration:1 position:circleLeft.position] two:[CCScaleTo actionWithDuration:scaleTime scale:0]];
[circleLeft runAction:fadeLeft];
}
if ([self isLocationBombable:bottom] && bottomB == YES)
{
circleBottom.position = [self positionForTileCoord:bottom];
[self addChild:circleBottom];
id fadeBottom = [CCSequence actionOne:[CCMoveTo actionWithDuration:1 position:circleBottom.position] two:[CCScaleTo actionWithDuration:scaleTime scale:0]];
[circleBottom runAction:fadeBottom];
}
if ([self isLocationBombable:right] && rightB == YES)
{
circleRight.position = [self positionForTileCoord:right];
[self addChild:circleRight];
id fadeRight = [CCSequence actionOne:[CCMoveTo actionWithDuration:1 position:circleRight.position] two:[CCScaleTo actionWithDuration:scaleTime scale:0]];
[circleRight runAction:fadeRight];
}
}
[currentBombs addObject:sprite];
int a = [currentBombs count];
NSLog(#"cBCount: %i",a);
NSLog(#"Explosion done, call checkdamage");
[self schedule:#selector(checkDamageForBomb)];
[self performSelector:#selector(removeSprite:) withObject:sprite afterDelay:5];
}
-(void)removeSprite:(CCSprite *)sprite{
int aa = [currentBombs count];
NSLog(#"removeSprite startcall cbc: %i",aa);
if([currentBombs containsObject:sprite])
{
NSLog(#"Found sprite in array, deleted!");
[currentBombs removeObject:sprite];
int a = [currentBombs count];
NSLog(#"containObject cbc: %i",a);
}
else {
NSLog(#"Didn't find the object in array, didn't delete!");
int a = [currentBombs count];
NSLog(#"elseCO cbc: %i",a);
}
if (currentBombs.count == 0)
{
[self stopCheckDamage];
}
}
-(void)stopCheckDamage{
NSLog(#"StopCheckDamage");
[self unschedule:#selector(checkDamageForBomb)];
}
-(void)checkDamageForBomb{
for (CCSprite* bomb in currentBombs)
{
CGPoint bombPos = [self tileCoordForPosition:bomb.position];
for (int i = 0; i <= explosionLenght; i++) {
CGPoint playerPos = [self tileCoordForPosition:_cat.position];
int bombX = (bombPos.x + i);
int bombY = (bombPos.y + i);
int bombNegX = (bombPos.x - i);
int bombNegY = (bombPos.y - i);
CGPoint centre = bombPos;
CGPoint top = ccp(centre.x, bombY);
CGPoint left = ccp(bombNegX, centre.y);
CGPoint bottom = ccp(centre.x, bombNegY);
CGPoint right = ccp(bombX, centre.y);
//pastebin.com/biuQBfnv
if (CGPointEqualToPoint(top, playerPos) || CGPointEqualToPoint(left, playerPos) || CGPointEqualToPoint(bottom, playerPos) || CGPointEqualToPoint(right, playerPos))
{
playerHits += 1;
NSLog(#"Player hit %i",playerHits);
[currentBombs removeObject:bomb];
break;
}
}
}
}
My problem is with the -(void)removeSprite:(CCSprite *)sprite{method. This is supposed to delete only the one it got called with, but instead it kills them all, as you can see in this log.
15:14:02.499 Tile[1549:c07] Bomb placed
15:14:03.816 Tile[1549:c07] Bomb placed
15:14:05.501 Tile[1549:c07] cBCount: 1
15:14:05.501 Tile[1549:c07] Explosion done, call checkdamage
15:14:06.818 Tile[1549:c07] cBCount: 2
15:14:06.819 Tile[1549:c07] Explosion done, call checkdamage
15:14:06.819 Tile[1549:c07] CCScheduler#scheduleSelector. Selector already scheduled. Updating interval from: 0.00 to 0.00
15:14:10.503 Tile[1549:c07] removeSprite startcall cbc: 2 // has 2
15:14:10.503 Tile[1549:c07] Found sprite in array, deleted! //Line above and under
15:14:10.504 Tile[1549:c07] containObject cbc: 0 //Deleted 2, supposed to kill 1
15:14:10.505 Tile[1549:c07] StopCheckDamage
15:14:11.820 Tile[1549:c07] removeSprite startcall cbc: 0
15:14:11.820 Tile[1549:c07] Didn't find the object in array, didn't delete!
15:14:11.821 Tile[1549:c07] elseCO cbc: 0
15:14:11.821 Tile[1549:c07] StopCheckDamage
If you look at the log location in the code above, you will see that it deletes two instead of the one I wanted to. How can I prevent this behaviour or customise it to only kill the proper sprite?
Edit: To clarify
I thought that as I use the spritein the -(void)explosionFromPoint:(CGPoint)explosionPoint withSprite:(CCSprite*)sprite; this would somehow just give an unique ID to the object and know which one I was talking about. I'm new to coding.
You have the same sprite in the array twice. Both entries are removed. If you're going to use removeObject you need to create multiple sprite objects. Otherwise you need to use removeObjectAtIndex.
The problem as #HotLicks mentioned is you push the same instance in the array.
You should use a local CCSprite instance so your scheduled calls will use different instances. The reason for adding the same instance twice is because placeBomb is called twice before anything happens and in the second call you override the first instance you created. Since _circle is a pointer by the time both scheduled tasks will be called _circle will point to the same instance in both.
So change :
_circle = [[CCSprite alloc]initWithFile:#"Circle.png"];
To :
CCSprite *circle = [[CCSprite alloc]initWithFile:#"Circle.png"];
and update the rest of the method with circle and not _circle.
There is something that is going wrong in your code. Try checking something like that when you remove object from array do this:
if([currentBombs count]>1 )
[currentBombs removeObjectAtIndex:1];
If your code works fine. that is only one object removed from your array. Then I suggest you to check your removeSprite method print sprite object to check what's going wrong.
I think you can use tag values if you using same sprite objects.
You probably added the same (indiviudal) sprite twice?
Instead of logging a variable that has the count, you can log the object itself. It will print the value of its description. That will print out the class and address for all NSObject subclasses per default. Actually NSMutableArray (and similar NS... classes) print quite well.
NSLog ("%#",myObj);
Doing so you probably see more clearly what really happens.
Don't use class variable while creating bombs and try....
CCSprite * _circle = [[CCSprite alloc]initWithFile:#"Circle.png"];
I am trying to add several labels that appear sequentially with a time delay between each. The labels will display either 0 or 1 and the value is calculated randomly. I am running the following code:
for (int i = 0; i < 6; i++) {
NSString *cowryString;
int prob = arc4random()%10;
if (prob > 4) {
count++;
cowryString = #"1";
}
else {
cowryString = #"0";
}
[self runAction:[CCSequence actions:[CCDelayTime actionWithDuration:0.2] ,[CCCallFuncND actionWithTarget:self selector:#selector(cowryAppearWithString:data:) data:cowryString], nil]];
}
the method that makes the labels appear is this:
-(void)cowryAppearWithString:(id)sender data:(NSString *)string {
CCLabelTTF *clabel = [CCLabelTTF labelWithString:string fontName:#"arial" fontSize:70];
CGSize screenSize = [[CCDirector sharedDirector] winSize];
clabel.position = ccp(200.0+([cowries count]*50),screenSize.height/2);
id fadeIn = [CCFadeIn actionWithDuration:0.5];
[clabel runAction:fadeIn];
[cowries addObject:clabel];
[self addChild:clabel];
}
The problem with this code is that all the labels appear at the same moment with the same delay. I understand that if i use [CCDelayTime actionWithDuration:0.2*i] the code will work. But the problem is that i might also need to iterate this entire for loop and have the labels appear again after they have appeared the first time. how is it possible to have actions appear with delay and the actions dont always follow the same order or iterations???
Maybe i did not really understand what you want to do. But if you need some control when your labels appear (to iterate something) make something like this:
-(void) callback
{
static int counter = 0;
//create your label and label action here
// iterate through your labels if required
counter++;
if (counter < 6)
{
double time = 0.2;
id delay = [CCDelayTime actionWithDuration: time];
id callbackAction = [CCCallFunc actionWithTarget: self selector: #selector(callback)];
id sequence = [CCSequence actions: delay, callbackAction, nil];
[self runAction: sequence];
}
else
{
//calculate the result and run callback again if required
//don't forget to write counter = 0; if you want to make a new throw
}
}
The problem is that your are scheduling all the actions to fire off at the same time.
Changing
[self runAction:[CCSequence actions:[CCDelayTime actionWithDuration:0.2] ,[CCCallFuncND actionWithTarget:self selector:#selector(cowryAppearWithString:data:) data:cowryString], nil]];
for
[self runAction:[CCSequence actions:[CCDelayTime actionWithDuration:0.2 * i] ,[CCCallFuncND actionWithTarget:self selector:#selector(cowryAppearWithString:data:) data:cowryString], nil]];
should fix your problem