Welcome to the Treehouse Community

Want to collaborate on code errors? Have bugs you need feedback on? Looking for an extra set of eyes on your latest project? Get support with fellow developers, designers, and programmers of all backgrounds and skill levels here with the Treehouse Community! While you're at it, check out some resources Treehouse students have shared here.

Looking to learn something new?

Treehouse offers a seven day free trial for new students. Get access to thousands of hours of content and join thousands of Treehouse students and alumni in the community today.

Start your free trial

iOS

Stone Preston
Stone Preston
42,016 Points

Rock blaster animation issue

Im trying to do the sprite kit extra credit project and am running into an issue with shrinking the ship down after the user starts the game. it shrinks fine, but when I replace the morph texture with the small ship texture, the ship jumps a bit. any ideas on how I can fix this Amit Bijlani or Ben Jakuben?

here is a zip file containing my project

here is a video of whats happening.

below is the code im using to animate the ship

- (void)shrinkAndMoveToPosition:(CGPoint)position {

    SKTexture *texture = [SKTexture textureWithImageNamed:@"ship-small_01"];

    float xScale = (texture.size.width ) / self.size.width;
    float yScale = (texture.size.height) / self.size.height;

    SKAction *move = [SKAction moveTo:position duration:.5];
    SKAction *scale = [SKAction scaleXTo:xScale y:yScale duration:0.5];

    SKAction *moveAndScale = [SKAction group:@[move, scale]];
    [self runAction:moveAndScale completion:^{



        NSArray *textures = @[[SKTexture textureWithImageNamed:@"ship-small_01"],
                              [SKTexture textureWithImageNamed:@"ship-small_02"],
                              [SKTexture textureWithImageNamed:@"ship-small_03"],
                              [SKTexture textureWithImageNamed:@"ship-small_04"]];


        SKAction *animate = [SKAction animateWithTextures:textures timePerFrame:0.2 resize:YES restore:NO];


        [self runAction:[SKAction repeatActionForever:animate]];

    }];

}

also, im only able to get a smooth background animation using the first 3 images. if I use them all there appear to be some "breaks" in the animation where it just doesnt look like the images fit together.

here is a video of it. you can see about every 8 seconds or so there is a spot in the animation that doesnt look (around 9 seconds into the video) right where part of the background is light and then its darker on the other image so there is a "line"

below is my code for my background animation:

+ (instancetype)backgroundNodeAtPosition:(CGPoint)position {

    SPBackgroundNode *background = [self node];
    background.name = @"Background";

    for (int i = 0; i < 19; i++) {


        NSString *imageName = [NSString stringWithFormat:@"bg-stars_%02d",i];

        SKSpriteNode *bg = [SKSpriteNode spriteNodeWithImageNamed:imageName];
        bg.name = @"bg";

        bg.position = CGPointMake(position.x, i * bg.size.height);

        [background addChild:bg];


    }

    background.backgroundCount = background.children.count;


    return  background;
}

- (void)scrollBackground {


    [self enumerateChildNodesWithName:@"bg" usingBlock:^(SKNode *node, BOOL *stop) {

        SKSpriteNode *bg = (SKSpriteNode *)node;
        [bg runAction:[SKAction moveBy:CGVectorMake(0, -30) duration:0]];

        //Checks if bg node is completely scrolled of the screen, if yes then put it at the end of the other node

        if (bg.position.y <= -bg.size.height) {

            bg.position = CGPointMake(bg.position.x , bg.position.y + bg.size.height * self.backgroundCount);

        }
    }];

}

if I only use the first 3 images instead of looping through all 19 it looks fine though

6 Answers

Amit Bijlani
STAFF
Amit Bijlani
Treehouse Guest Teacher

To fix the shrinking issue I modified the ShipNode to subclass SKNode so that it is easier to swap out child nodes. The problems with doing it with one single SpriteNode is that the sizes of the sprites differ and so you end up resizing and aligning everything. It is easier to swap out the child nodes and I always prefer the path of least resistance.

Here are the two implementation files:

ShipNode.h

@interface SPShipNode : SKNode

+ (instancetype)shipAtPosition:(CGPoint)position;
- (void)animateMorph ;
- (void)shrinkAndMoveToPosition:(CGPoint)position ;

@end

ShipNode.m

@implementation SPShipNode

+ (instancetype)shipAtPosition:(CGPoint)position {

    SPShipNode *ship = [self node];
    ship.name = @"Ship";

    SKSpriteNode *morphShip = [SKSpriteNode spriteNodeWithImageNamed:@"ship-morph0001"];
    morphShip.name = @"ShipAnimation";
    morphShip.position = position;
    [ship addChild:morphShip];
    [ship animateMorph];

    return ship;
}

- (void)animateMorph {

    SKSpriteNode *morphShip = (SKSpriteNode*)[self childNodeWithName:@"ShipAnimation"];


    NSMutableArray *textures = [NSMutableArray arrayWithCapacity:40];

    for (int i = 1; i < 40; i++) {

        NSString *imageName = [NSString stringWithFormat:@"ship-morph%04d", i];
        SKTexture *texture = [SKTexture textureWithImageNamed:imageName];
        [textures addObject:texture];
    }

    SKAction *morphAnimation = [SKAction animateWithTextures:textures timePerFrame:0.03f];
    [morphShip runAction:morphAnimation];

}


- (void)shrinkAndMoveToPosition:(CGPoint)position {

    SKSpriteNode *morphShip = (SKSpriteNode*)[self childNodeWithName:@"ShipAnimation"];
    SKSpriteNode *bigShip = [SKSpriteNode spriteNodeWithImageNamed:@"ship-big"];
    bigShip.name = @"ShipAnimation";
    bigShip.position = CGPointMake(morphShip.position.x,morphShip.position.y-50);
    [morphShip removeFromParent];
    [self addChild:bigShip];

    SKTexture *texture = [SKTexture textureWithImageNamed:@"ship-small_01"];
    SKAction *move = [SKAction moveTo:position duration:.5];
    SKAction *scale = [SKAction resizeToWidth:texture.size.width height:texture.size.height duration:0.5];
    SKAction *moveAndScale = [SKAction group:@[move, scale]];
    [bigShip runAction:moveAndScale completion:^{

        NSArray *textures = @[[SKTexture textureWithImageNamed:@"ship-small_01"],
                              [SKTexture textureWithImageNamed:@"ship-small_02"],
                              [SKTexture textureWithImageNamed:@"ship-small_03"],
                              [SKTexture textureWithImageNamed:@"ship-small_04"]];


        SKAction *animate = [SKAction animateWithTextures:textures timePerFrame:0.2 resize:YES restore:NO];


        [bigShip runAction:[SKAction repeatActionForever:animate]];

    }];
}

@end

thanks Amit Bijlani !! It looks a lot better now! Would you have any idea why the FPS drops when starting the app and the morph animation runs slow on my iPhone 5? I created an SKTextureAtlas in my animateMorph method that looks like:

SKTextureAtlas *spritesAtlas = [SKTextureAtlas atlasNamed:@"sprites2"];

and in the for loop I used:

SKTexture *texture = [spritesAtlas textureNamed:imageName];

but it's still just as laggy.

Amit Bijlani
STAFF
Amit Bijlani
Treehouse Guest Teacher

The problem is with the morph animation which is 640x1136 so when you scale it you get a lot of white space around it. The actual ship is much smaller when you scale it down. The trick is to create an image that you swap out before the animation starts. I created another image called "ship-big".

I did a few things to make this work:

  • Made sure all the assets have an @2x prefix. You can download the assets again to add this quickly to your project.

  • Changed the constructor SPShipNode to remove the halving:

+ (instancetype)shipAtPosition:(CGPoint)position {

    SPShipNode *ship = (SPShipNode *)[SPShipNode spriteNodeWithImageNamed:@"ship-morph0001"];

    ship.name = @"Ship";
    ship.size = CGSizeMake(ship.size.width, ship.size.height);
    ship.position = position;
    [ship animateMorph];

    return ship;
}
  • Added the "ship-big" image to the project.

  • Modified the shrinkAndMoveToPosition method.

- (void)shrinkAndMoveToPosition:(CGPoint)position {

    SKTexture *texture = [SKTexture textureWithImageNamed:@"ship-small_01"];

    self.texture = [SKTexture textureWithImageNamed:@"ship-big"];

    SKAction *move = [SKAction moveTo:position duration:.5];
    SKAction *scale = [SKAction resizeToWidth:texture.size.width height:texture.size.height duration:0.5];
    SKAction *moveAndScale = [SKAction group:@[move, scale]];
    [self runAction:moveAndScale completion:^{

        NSArray *textures = @[[SKTexture textureWithImageNamed:@"ship-small_01"],
                              [SKTexture textureWithImageNamed:@"ship-small_02"],
                              [SKTexture textureWithImageNamed:@"ship-small_03"],
                              [SKTexture textureWithImageNamed:@"ship-small_04"]];


        SKAction *animate = [SKAction animateWithTextures:textures timePerFrame:0.2 resize:YES restore:NO];        

        [self runAction:[SKAction repeatActionForever:animate]];             
    }];       
}
Stone Preston
Stone Preston
42,016 Points

hmm that fixes the jumping issue at the end, however now its super large at the beginning of the animation, then scales down correctly. here is what im seeing now

Robert Bojor
PLUS
Robert Bojor
Courses Plus Student 29,439 Points

After checking the Rock Blaster images it looks like there are multiple animations for stars in the same atlas... Various speeds as I understand the images, so you are not supposed to use all 19 for a continuous animation...

Why not use the movie provided? and loop that?

Robert Bojor
PLUS
Robert Bojor
Courses Plus Student 29,439 Points

Hi Stone,

The background doesn't seem to loop correctly when you use all 19 of them. Have a look at 18 and then switch to 00 and you will see the difference.

Something else you can try is to create a longer image that tiles perfectly and use 2 nodes comprised of the same image and once the first one is almost scrolled, add the second one on top, let it scroll and add another one on top when it's almost scrolled, and so on.

I've tried this with horizontal scrolling and a bit of parallax and it works like a charm. In my case the images were exactly the screen's width and had 2 from the start moving and repeating forever.

-(void) setupClouds:(CGSize)size {
    clouds1 = [SKSpriteNode spriteNodeWithImageNamed:@"clouds"];
    clouds1.anchorPoint = CGPointMake(0, 0);
    clouds1.position = CGPointMake(0, 105);
    clouds1.name = @"clouds1";
    [self addChild:clouds1];
    SKAction *moveClouds1 = [SKAction repeatActionForever:[SKAction moveByX:-self.frame.size.width-clouds1.size.width y:0 duration:160]];
    [clouds1 runAction:moveClouds1];
    clouds2 = [SKSpriteNode spriteNodeWithImageNamed:@"clouds"];
    clouds2.anchorPoint = CGPointMake(0, 0);
    clouds2.position = CGPointMake(size.width, 105);
    clouds2.name = @"clouds2";
    [self addChild:clouds2];
    SKAction *moveClouds2 = [SKAction repeatActionForever:[SKAction moveByX:-self.frame.size.width-clouds2.size.width y:0 duration:160]];
    [clouds2 runAction:moveClouds2];
}
-(void) setupSea:(CGSize)size {
    sea1 = [SKSpriteNode spriteNodeWithImageNamed:@"sea"];
    sea1.anchorPoint = CGPointMake(0, 0);
    sea1.position = CGPointMake(0, 92);
    sea1.name = @"sea1";
    [self addChild:sea1];
    SKAction *moveSea1 = [SKAction repeatActionForever:[SKAction moveByX:-self.frame.size.width-sea1.size.width y:0 duration:120]];
    [sea1 runAction:moveSea1];
    sea2 = [SKSpriteNode spriteNodeWithImageNamed:@"sea"];
    sea2.anchorPoint = CGPointMake(0, 0);
    sea2.position = CGPointMake(size.width, 92);
    sea2.name = @"sea2";
    [self addChild:sea2];
    SKAction *moveSea2 = [SKAction repeatActionForever:[SKAction moveByX:-self.frame.size.width-sea2.size.width y:0 duration:120]];
    [sea2 runAction:moveSea2];
}
-(void) setupGround:(CGSize)size {
    ground1 = [SKSpriteNode spriteNodeWithImageNamed:@"land"];
    ground1.anchorPoint = CGPointMake(0, 0);
    ground1.position = CGPointMake(0, 0);
    ground1.name = @"ground1";
    [self addChild:ground1];
    SKAction *moveGround1 = [SKAction repeatActionForever:[SKAction moveByX:-self.frame.size.width-ground1.size.width y:0 duration:80]];
    [ground1 runAction:moveGround1];
    ground2 = [SKSpriteNode spriteNodeWithImageNamed:@"land"];
    ground2.anchorPoint = CGPointMake(0, 0);
    ground2.position = CGPointMake(size.width, 0);
    ground2.name = @"ground2";
    [self addChild:ground2];
    SKAction *moveGround2 = [SKAction repeatActionForever:[SKAction moveByX:-self.frame.size.width-ground2.size.width y:0 duration:80]];
    [ground2 runAction:moveGround2];
}

It's the background images fault, if you select all of them and open them in preview they completely change every 3 frames. Going to try to make my own instead

Stone Preston
Stone Preston
42,016 Points

yeah thats what I was thinking. looks like its the transition from the dark to the light background that is causing the issues. im just gonna use the first 3 frames I guess

Stone Preston I was able to make 19 slides in 15 minutes, it's really easy! I would suggest doing that in photoshop because if you only run 3 frames it's going to run at an insanely quick speed or appear that way, and when you have asteroids going much slower it might look a bit off. When you make it by hand, all you do is make a small streak of light on a dark blue 640x1136 canvas and copy and paste that streak in an unorganized row across the canvas on a small portion of it, and make that a group and copy and paste that group until it fills the canvas. Then, group that and move it an inch downwards and save it as 00. then move it an inch and save and keep doing that until you get back to where you started! good luck!

Stone Preston Also, is it alright if I use that morph effect that Amit helped you out with?

Stone Preston
Stone Preston
42,016 Points

sure go ahead. although I cant get the code/image he posted to work. the end of the shrink works great, but the beginning when it first starts to shrink down it gets super big (like almost as big as the screen) before it starts to shrink

Stone Preston thanks! hmm thats strange, i'll do some testing and let you know what I find. Have you posted on stack overflow?

Stone Preston
Stone Preston
42,016 Points

yep. didnt get much help, ill make another post there and apple dev forums if I cant get it working soon

Stone Preston my morph animation is so lagging when starting the game tho

Stone Preston
Stone Preston
42,016 Points

Eliot Winchell mine is too on the simulator. when running on the actual device its fine. any luck with the animation? ive put the shrink animation on the back burner until I get everything else finished.

Stone Preston that's weird, I always test on my phone and it lags... Not yet