Parallax Scrolling in SpriteKit made easy

After being developing for some time with the new 2D game engine SpriteKit, I have to admit that I’m in love with it. I find it really simple to use, easy to learn and very powerful. While developing my new game, I was faced with the need to introduce parallax scrolling to give a sense of depth to the game. I have published a class on Github to easily add Parallax Backgrounds in any SpriteKit game, so if you are, like me, an iOS developer interested in SpriteKit games, here is how you can implement your own parallax effect for your SpriteKit games.

Parallax Scrolling is a 2D effect used to create a fake sense of 3D depth in a 2D game. It is based on a trick of the eye that happens when you are traveling, i.e: in a car. As you move forwards, the closest items seem to move faster than the distant ones, as if there were several layers moving at different speeds, the closer the faster. The parallax effect tries to clone this trick by setting several images acting as background layers, and animating all of them in the same direction, but at different speeds.


Now let’s create our own Parallax Scrolling effect for our SpriteKit App. First we will create a new project in Xcode. Choose iOS -> Application -> SpriteKit Game.


Pick a name for the project, like “ParallaxBackground” and a prefix for your classes, like “PB”, and Xcode will set the initial environment and needed classes for the development of a SpriteKit app, as show here:


First, we need to do some changes to clean and set everything. We need to edit PBViewController and move the code that initializes the SKScene from viewDidLoad (where Xcode originally puts it) to viewWillLayoutSubviews. Why? because in viewDidLoad, our view still has not been presented to screen, so a lot of properties have not been set, like size, for example. It is way better to initialize the scene when our view is ready to be shown and everything has been set. To avoid the view appearing on the screen and then initializing the SKScene with a noticeable flash, we will use viewWillLayoutSubviews (called prior to viewDidAppear) instead of viewDidAppear. So our PBViewController will be like this:

- (void)viewDidLoad
    [super viewDidLoad];

- (void) viewWillLayoutSubviews
    [super viewWillLayoutSubviews];

    // Configure the view.
    SKView * skView = (SKView *)self.view;
    if (!skView.scene) { // because viewWillLayoutSubviews is called twice!
        skView.showsFPS = YES;
        skView.showsNodeCount = YES;

        // Create and configure the scene.
        SKScene * scene = [[PBMyScene alloc] initWithSize:skView.bounds.size];
        scene.scaleMode = SKSceneScaleModeAspectFill;

        // Present the scene.
        [skView presentScene:scene];

Now we should delete the Spaceship.png and edit the touchesBegan:withEvent: method, so no spaceship is shown if we touch the screen. Then, we can start writing our parallax scrolling code.

We will start by creating a class that will represent our Parallax Scrolling entity. This entity will have several backgrounds, from foreground to background. To create the illusion of a never-ending movement, we will clone each background and put this copy next to the original. If we design our backgrounds in a way that the edges of the image create a sense of continuity, and we move them in the same direction at the same speed, the image will be seen as a continuous flow. Thus, we will have two identical images together moving as a unity for each background layer, and we will make sure that when a image escapes completely from the scene from one of the sides, we reset its coordinates so it is “entering” the scene again.


Thus, our class will have a NSArray of backgrounds and a NSArray of cloned backgrounds, and will combine them to create the parallax layers. As each layer will move at a different speed. We will also specify a direction for the moving images, and a global size (that will be the size of the scene). We add a new Objective-C class and select as superclass SKSpriteNode. We will call this class PBParallaxScrolling. Then, in PBParallaxScrolling.m, we will add a private interface with all the properties:


@interface PBParallaxScrolling ()

/** The array containing the set of SKSpriteNode nodes representing the different backgrounds */
@property (nonatomic, strong) NSArray * backgrounds;

/** The array containing the set of duplicated background nodes that will appear when the background starts sliding out of the screen */
@property (nonatomic, strong) NSArray * clonedBackgrounds;

/** The array of speeds for every background */
@property (nonatomic, strong) NSArray * speeds;

/** Number of backgrounds in this parallax background set */
@property (nonatomic) NSUInteger numberOfBackgrounds;

/** The movement direction of the parallax backgrounds */
@property (nonatomic) PBParallaxBackgroundDirection direction;

/** The size of the parallax background set */
@property (nonatomic) CGSize size;


The PBParallaxBackgroundDirection will define the direction for the movement of the backgrounds. We will define it on PBParallaxScrolling.h:

typedef enum {
   kPBParallaxBackgroundDirectionUp = 0,
} PBParallaxBackgroundDirection;

Depending on the direction, we will apply different values to the coordinates to move and reset our backgrounds. Next, we will define the two methods needed for our class: one for initializing the parallax scrolling and another one for moving it. We do this on PBParallaxScrolling.h:

- (id) initWithBackgrounds: (NSArray *) backgrounds size: (CGSize) size direction: (PBParallaxBackgroundDirection) direction fastestSpeed: (CGFloat) speed andSpeedDecrease: (CGFloat) differential;

- (void) update: (NSTimeInterval) currentTime;

And implement them on PBParallaxScrolling.h. The method initWithBackgrounds:size:direction:fastestSpeed:andSpeedDecrease: (wow!) will receive all the needed params to initialize the parallax. We will allow a NSArray of backgrounds containing either NSStrings (with the name of the images to be used), UIImages, SKTexture or the SKSpriteNodes. We will convert each of these into SKSpriteNodes to be added to our class.

for (id obj in backgrounds) {

   // determine the type of background
   SKSpriteNode * node = nil;

   if ([obj isKindOfClass:[UIImage class]]) {
      node = [[SKSpriteNode alloc] initWithTexture:[SKTexture textureWithImage:(UIImage *) obj]];
   } else if ([obj isKindOfClass:[NSString class]])  {
      node = [[SKSpriteNode alloc] initWithImageNamed:(NSString *) obj]; 
   } else if ([obj isKindOfClass:[SKTexture class]]) {
      node = [[SKSpriteNode alloc] initWithTexture:(SKTexture *) obj];
   } else if ([obj isKindOfClass:[SKSpriteNode class]]) {
      node = (SKSpriteNode *) obj;
   } else continue;
   calculate node position, create clon and adjust clon's position.

Now we must determine the node’s position and create a duplicate to attach to the original background. Depending on the direction of the movement, we will place this duplicate in one of the edges.

// create the duplicate and insert both at their proper locations.
node.zPosition = self.zPosition - (zPos + (zPos * bgNumber));
node.position = CGPointMake(0, self.size.height);

SKSpriteNode * clonedNode = [node copy];
CGFloat clonedPosX = node.position.x, clonedPosY = node.position.y;

switch (direction) { // calculate clone's position
   case kPBParallaxBackgroundDirectionUp:
      clonedPosY = 0;
   case kPBParallaxBackgroundDirectionDown:
      clonedPosY = node.size.height * 2;
   case kPBParallaxBackgroundDirectionRight:
      clonedPosX = - node.size.width;
   case kPBParallaxBackgroundDirectionLeft:
      clonedPosX = node.size.width;

Now we need to set the speeds for the node and the cloned node. Going from foreground (the closest layer) to background (the most distant layer) means we need to decrease the speed of every layer by a measure. We will use the speed decrease differential to reduce the speed between 0% (no speed reduction) when differential=0 to 100% (half speed) when differential=1.

// add the velocity for this node and adjust the next current velocity.
[spds addObject:[NSNumber numberWithFloat:currentSpeed]];
currentSpeed = currentSpeed / (1 + differential);

So now we just need to add this two nodes to the scene.

// add to the scene
[self addChild:node];
[self addChild:clonedNode];

We will do this for every background, creating all the layers. Then, we would need to update the positions of the backgrounds on every iteration of the game loop. The method will, depending on the direction of the movement, adjust the position for every background and its clon:

- (void) update:(NSTimeInterval)currentTime {
   for (NSUInteger i = 0; i < self.numberOfBackgrounds; i++) {
      // determine the speed of each node
      CGFloat speed = [[self.speeds objectAtIndex:i] floatValue];

      // adjust positions
      SKSpriteNode * bg = [self.backgrounds objectAtIndex:i];
      SKSpriteNode * cBg = [self.clonedBackgrounds objectAtIndex:i];
      CGFloat newBgX = bg.position.x, newBgY = bg.position.y, newCbgX = cBg.position.x, newCbgY = cBg.position.y;

      // position depends on direction.
      switch (self.direction) {
         case kPBParallaxBackgroundDirectionUp:
            newBgY += speed;
            newCbgY += speed;
            if (newBgY >= (bg.size.height * 2)) newBgY = -(bg.size.height * 2);
            if (newCbgY >= (cBg.size.height * 2)) newCbgY = -(cBg.size.height * 2);

         case kPBParallaxBackgroundDirectionDown:
            newBgY -= speed;
            newCbgY -= speed;
            if (newBgY <= 0) newBgY += (bg.size.height * 2);
            if (newCbgY <= 0) newCbgY += (cBg.size.height * 2);

         case kPBParallaxBackgroundDirectionRight:
            newBgX += speed;
            newCbgX += speed;
            if (newBgX >= bg.size.width) newBgX -= 2*bg.size.width;
            if (newCbgX >= cBg.size.width) newCbgX -= 2*cBg.size.width;

         case kPBParallaxBackgroundDirectionLeft:
            newBgX -= speed;
            newCbgX -= speed;
            if (newBgX <= -bg.size.width) newBgX += 2*bg.size.width;
            if (newCbgX <= -cBg.size.width) newCbgX += 2*cBg.size.width;

      // update positions with the right coordinates.
      bg.position = CGPointMake(newBgX, newBgY);
      cBg.position = CGPointMake(newCbgX, newCbgY);

We will need to call to PBParallaxScrolling’s update in our PBMyScene update’s method:

-(void)update:(CFTimeInterval)currentTime {
   [self.parallaxBackground update:currentTime];

The resulting flow will appear as if we are moving in a 3D environment.


You can get the full Xcode from my Github repository.

  • Share:


Link DeTestare
January 7, 2014 At 3:27 pm

Your mode of describing the whole thing in this
piece of writing is in fact pleasant, all be
capable of without difficulty understand it, Thanks a lot.

February 12, 2014 At 3:27 pm

Thanks for the code. On your code you have this kParallaxBackgroundAntiFlickeringAdjustment constant. Can you explain what it does? Thanks.

April 19, 2014 At 3:27 pm

Is it iPad-only?

In Xcode, no iPhone-simulator is visible?


April 23, 2014 At 3:27 pm

good afternoon.? I am a beginner in xcode. how to remodel your parallax for iphone landscape. thank you very much.

April 23, 2014 At 3:27 pm

Good afternoon. as a force the size and position of the array of images? Thank you.

    April 23, 2014 At 3:27 pm


    I’m afraid I did not completely understand your question. The size of the parallax background is equal to the size of the background images. All background images must be the same size.


      April 24, 2014 At 3:27 pm

      the matter is that if you run your parallax images with a size 3000×2000 emulator siphon in the landscape, the images enlarged and not amenable to reduce the size of the screen. I am a beginner and do not understand how little they fit the screen size. thank you

        April 24, 2014 At 3:27 pm

        If you are working on a iPhone simulator, your images should be 640×1136 / 640×960 or viceversa, no 3000×2000. If you are a beginner I suggest you to start with a SpriteKit without parallax and build it later when you have a deeper understanding of the system.

          April 24, 2014 At 3:27 pm

          Reduce the size of pictures. 0 effect. they increased! how to make the original size?

          April 24, 2014 At 3:27 pm

          Sorry, I am not teaching you how to program. Learn a little more, then follow the usage guide, watch the example, and you will eventually make it work. Best of lucks.

May 12, 2014 At 3:27 pm

Thanks for this very useful class and example. I enjoyed the tutorial, but also enjoyed your conversation with the numpty who seems to want you to teach him how to code. lol ;)

    May 12, 2014 At 3:27 pm

    Thanks to you, Simon. Yes, I try to be helpful, but sometimes it bothers me when people seem to ask us to give them the code directly, without even trying to learn and improve as developers.

May 31, 2014 At 3:27 pm

I’d like to add subnodes to one of the parallax nodes just before the parallax node is actually visible. Where would you suggest I implement this? Ie. I have skphysics bodies that that I want the player to interact with on the parallax layer.

    May 31, 2014 At 3:27 pm


    I would probably implement a new initialization method in the PBParallaxScrolling class. You could send this method an NSDictionary of SKSpriteNodes, where the keys would be the parallax layer number, and the content would be an NSArray with the SKSpriteNodes. This initWith… andAttachedObjects: method would add this SKSpriteNodes to the appropriate layer. Don’t forget to give a physic body to those objects (no gravity, but a contact and collision mask) to interact with the player or other nodes in the scene.

    I just hope this subnodes you are talking about are not green pipes, though ;)

    Good luck.

July 4, 2014 At 3:27 pm

Great tutorial.

I have tried the PBParallaxScrolling classes and works as described.

Before adding any parallax effect, the app is running at 60 fps and everything is working fine. But after adding the parallax effect using the PBParallaxScrolling class, the app is running at 15 fps and it is sluggish.

Any idea or some sort of setting to make it run at a higher fps?


    July 4, 2014 At 3:27 pm

    Never experienced this fps drop you describe. Are you testing this in the simulator?

      July 4, 2014 At 3:27 pm

      Yes i was running it on a simulator and it was slow.

      I ran it on an idevice and the fps is correct at 60 so everything is good.


Cameron Frank
July 21, 2014 At 3:27 pm

First of all: phenomenal class! Great work. By far the best, more straightforward parallax implementation I’ve come across. I am having one weird thing though. When I’m using @2x images, it seems like the cloned image isn’t appearing in the scene until the anchor point of the original image falls off the screen(I’m using the left direction) I feel like one quick fix would be to make the @2x image at least 1280px wide, but I’ve done a fair amount of graphics work so far, that I’d rather not have to redo haha. Any idea what would cause this? My first thought was to try and move the anchor point of the backgrounds to the bottom left corner, and then adjust their placement accordingly, but I don’t want to break your class inadvertently. Any thoughts? Thanks!

    Ignacio Nieto Carvajal
    July 30, 2014 At 3:27 pm

    Hi Cameron, glad to know you are using the class and finding it useful. That’s a weird issue you are facing. The Parallax is configured so that the images must be the same size of the scene, so when the background is about to end, the new one comes from the edge of the scene.

    I guess that your problem is that your scene (SKScene) is probably shorter than your images (i.e: scene 1024 width, image 1280 width), so the new image enters midway. Try setting your SKScene size to fit the images size. Hope that helps!

July 22, 2014 At 3:27 pm

Hi there!
I’m working with the simulator with an iPad and iPad – retina. I have found that the scrolling with the iPad works perfectly but it is very choppy when I am using it with the retina. Is this actually an issue or is it just a problem with the simulator?

    Ignacio Nieto Carvajal
    July 30, 2014 At 3:27 pm

    The simulator doesn’t have hardware graphics acceleration, you cannot properly test anything SpriteKit related in the simulator. May I suggest you to learn a little more before actually trying to build a game. Thanks!

Parallax Scrolling SpriteKit
September 6, 2014 At 3:27 pm

[…] Parallax Scrolling […]

September 7, 2014 At 3:27 pm


Will you be covering this tutorial in swift?

    Ignacio Nieto Carvajal
    September 7, 2014 At 3:27 pm

    Hi, not probably, as adapting the code to swift is really trivial, and you can still use it as is in your swift project. Thanks for your interest.

February 27, 2015 At 3:27 pm


Thanks a lot for this explanation. You really helped me a lot.

November 5, 2015 At 3:27 pm

Do you know why when I use a larger image the image appears black. At the minute I am using an image with a width of 6000px but I don’t know if this is technically allowed.

    Ignacio Nieto Carvajal
    November 10, 2015 At 3:27 pm

    Hi Oliver. You need to use images with the exact size of the device.

February 20, 2016 At 3:27 pm

Seriously greate tutorial on the internet that exactly I want. Thanks for this post.

Create a Stunning Moving Picture to Share With the World | SmartWorld
December 24, 2016 At 3:27 pm

[…] Image Credit: DigitalLeaves […]

Create a Stunning Moving Picture to Share With the World
December 24, 2016 At 3:27 pm

[…] Image Credit: DigitalLeaves[5] […]

Create a Stunning Moving Picture to Share With the World | danilnews
December 24, 2016 At 3:27 pm

[…] The effect behind this Photoshop marvel, dubbed the 2.5D effect (otherwise known as a parallax effect) uses clever Photoshop zoom functions to create a realistic parallax effect from your still photos. /**/ Image Credit: DigitalLeaves […]

Create A Stunning Moving Picture To Share With The World – AMIBAC
June 18, 2018 At 3:27 pm

[…] Image Credit: DigitalLeaves […]

Leave a Comment

sing in to post your comment or sign-up if you dont have any account.