Best practice for a prallax scrolling background (and layers in the mid- and foreground)?

Can anyone tell me if there is a “best practice” tutorial or sample for creating a high performance parallax scrolling background?

Something using image parts which can be reused when they move out of one side of the screen by moving them in from the other side of the screen again.

My own basic solutions are good, but performance is telling me not good enough :slight_smile:

Thank you for your help!

Hi @d.mach,

Will your game use the physics engine or not?

Brent

It would be nice to solve the parallax without using physics (but using physics for some character effects later, like gravity and stuff)

Thx!

Hi Lori,

Well, as I like to say sometimes, if you’re using physics for something, use it for whatever makes sense! :slight_smile:

Meaning, in this case (and why I asked if you’re using physics), there’s one very convenient way to handle scrolling objects that move off the screen and need to be “recycled” and put back on the other side. It basically involves putting a big rectangular physics object that covers the entire screen (and actually goes outside the bounds by a small amount), and setting that object to a sensor object. Then, you set up collision detection between that big sensor and the scrolling objects, and when you detect an “ended” phase on the scrolling objects, you know that object just “exited the screen” because it travelled off/outside the sensor. At that point, you simply move it to the other side of the screen, and the process repeats.

Of course, that means all of your scrolling objects must be physical bodies too, so if you don’t want that, then this approach won’t work. In that case, you may want to consider using transitions and the “onComplete” listener to place them on the other side of the screen, then start the transition again.

Hope this helps,

Brent

Thx for the info Brent. One question remains… is there a “best solution” to move objects out of screen (like for example big background images) to a pixel exact position, so the transitions of those kind of “Tiles” are perfect? For example: When working with screen sized images put side by side into a group and then transitioning the group, so the first image will leave the visible area on screen and has to be put on the other side of the images to reenter the screen from the other side things can get slow when working with full sized retina images. AND how can such an image put exactly to the seam of the other image (while the background is moving). I once tried something like this and always had a small gap between images then. And performance with a lot of screen sized background images wasn’t so good.

How is this kind of scrolling done in for example games like Jetpack Joyride?

Hi Daniela,

You should check out one of our samples which shows two background images “swapping” for an endless scrolling effect. It’s located in your local Corona application folder:

SampleCode > Physics > LiquidFun-Transparency

It also goes hand-in-hand with a tutorial which describes the background scrolling process:

https://coronalabs.com/blog/2015/09/24/tutorial-creating-awesome-water-with-liquidfun-snapshots-and-filters/

Hope this helps,

Brent

P.S. - sorry I called you by the wrong name a couple posts ago. You’ve been a Corona user long enough that I should know better. :slight_smile:

Brent

Thx for the tip and links!

No problem regarding the wrong name… happens to me all the time! :wink:

Hi Brent and d.mach, 

I have searched all over for some help regarding my scrolling issue. I decided that given this was a recent post that it might be best to post here rather than start a new topic as i thinks its relevant. 

I have gone the non-physics parallax scrolling method, using a group at each layer and moving the group. Inserting objects into those groups at the right place, as the current background edge reaches the screen edge (with slight buffer). I keep track of the left and right most objects using pointers, which i update as required.  I thought this would be better than inserting the objects into a table and separately scrolling them.

What seems to be happening is that as my character moves right, my code works for one addition extension of the scrolling background but then spawns uncontrollably, as if the condition of the values being offscreen are no longer valid. I have spend hours trying to figure it out and debug. I suspect that it might be garbage collection cleaning up images off screen and thus reducing my group width or something but I don’t know. Could be something really simple that i am missing. 

Heres a post of my truncated code, which i hope is enough to provide an understanding of whats happening. Your help would be really appreciated. 

[lua]

    

self.parallaxEnabled = false

self.layers = {}

function scroll:createParallaxLayer(params)

    local l = params.l

    local distRatio = params.distRatio

    local parentGroup = params.parentGroup

    

    – create the layer

    self.layers[l] = {}

    local layer = self.layers[l]

    

    layer.objects = display.newGroup()

    – layer.objectTable = {} – create the table for objects

    layer.distanceRatio = distRatio

    parentGroup:insert(layer.objects)

end

function scroll:addParallaxObject(layer, object)

    local layer = self.layers[layer]

    – if theres no left object then assign the objec to the left mostobject

    if (layer.leftObject == nil) then

        – print(“set left Object layer:”,layer, object)

        layer.leftObject = object

    end

    if (layer.rightObject == nil) then

        – print(“set right Object layer:”,layer, object)

        layer.rightObject = object

    end

    layer.objects:insert(object)    – display group method

    table.insert( layer.objectTable , object )    – table method, not sure if required

    – need to add code to see if object has been added as the new left object or within existing bounds. 

    – but you get the picture

end

scroll.enterFrame = function ( self, event 

     – move objects

    if (self.objects ~= nil) then

        for i = 1, table.maxn(self.objects) do

            if self.objects[i].x ~= nil then

                – adjust the speed here

                self:modifyObjectSpeed(self.objects[i],self.speed )

            end

       end

    end

    

– move parallax layers 

    local layers = self.layers

    local screenScrollBuffer = 20

    local initialImageX = 2000

    if self.parallaxEnabled and (layers ~= nil) then

              for i=1,#layers do

                local layer = layers[i]

                  if layer ~= nil then

                      – scroll acrording to parallax

                    layer.objects.x = layer.objects.x - self.speed * layer.distanceRatio

    

                    – ### HERES WHERE I SEEM TO HIT PROBLEMS ###

                    if (i == 5)  then – start with one layer as test

                        – get the rightMost Object. Finds its content position on screen, and check its rightMost Edge

                        – this should deal with any screen size/image size by dealling the calculated edge 

                        local rightObject = layer.rightObject

                        local rightObjectX, rightObjectY = rightObject:localToContent( 0, 0 ) – 0,0 centre of the object

                        local rightMostEdge = rightObjectX + 0.5 * rightObject.contentWidth

                        

                        – if rightMost Edge less than display plus buffer amount, create a new image. 

                        – Position it at the right edge and insert into scroll group

                        if rightMostEdge <= display.contentWidth + screenScrollBuffer  then

                            local newRightObject = display.newImage(  “graphics/background/layer_01_2048x1546.png”, initialImageX, display.contentHeight/2)

                            newRightObject.x = rightMostEdge + 0.5 * newRightObject.contentWidth

                            layer.rightObject = newRightObject

                            scroll:addParallaxObject(i, newRightObject)

                        end

                    end

                end

            end

    end

end

scroll:createParallaxLayer({parentGroup = parentGroup, l=5, distRatio = 1.2})

local object00 = display.newImage(  “graphics/background/layer_01_2048x1546.png”,cX,cY )

scroll:addParallaxObject(5, object00)

[/lua]

Hi @d.mach,

Will your game use the physics engine or not?

Brent

It would be nice to solve the parallax without using physics (but using physics for some character effects later, like gravity and stuff)

Thx!

Hi Lori,

Well, as I like to say sometimes, if you’re using physics for something, use it for whatever makes sense! :slight_smile:

Meaning, in this case (and why I asked if you’re using physics), there’s one very convenient way to handle scrolling objects that move off the screen and need to be “recycled” and put back on the other side. It basically involves putting a big rectangular physics object that covers the entire screen (and actually goes outside the bounds by a small amount), and setting that object to a sensor object. Then, you set up collision detection between that big sensor and the scrolling objects, and when you detect an “ended” phase on the scrolling objects, you know that object just “exited the screen” because it travelled off/outside the sensor. At that point, you simply move it to the other side of the screen, and the process repeats.

Of course, that means all of your scrolling objects must be physical bodies too, so if you don’t want that, then this approach won’t work. In that case, you may want to consider using transitions and the “onComplete” listener to place them on the other side of the screen, then start the transition again.

Hope this helps,

Brent

Thx for the info Brent. One question remains… is there a “best solution” to move objects out of screen (like for example big background images) to a pixel exact position, so the transitions of those kind of “Tiles” are perfect? For example: When working with screen sized images put side by side into a group and then transitioning the group, so the first image will leave the visible area on screen and has to be put on the other side of the images to reenter the screen from the other side things can get slow when working with full sized retina images. AND how can such an image put exactly to the seam of the other image (while the background is moving). I once tried something like this and always had a small gap between images then. And performance with a lot of screen sized background images wasn’t so good.

How is this kind of scrolling done in for example games like Jetpack Joyride?

Hi Daniela,

You should check out one of our samples which shows two background images “swapping” for an endless scrolling effect. It’s located in your local Corona application folder:

SampleCode > Physics > LiquidFun-Transparency

It also goes hand-in-hand with a tutorial which describes the background scrolling process:

https://coronalabs.com/blog/2015/09/24/tutorial-creating-awesome-water-with-liquidfun-snapshots-and-filters/

Hope this helps,

Brent

P.S. - sorry I called you by the wrong name a couple posts ago. You’ve been a Corona user long enough that I should know better. :slight_smile:

Brent

Thx for the tip and links!

No problem regarding the wrong name… happens to me all the time! :wink:

Hi Brent and d.mach, 

I have searched all over for some help regarding my scrolling issue. I decided that given this was a recent post that it might be best to post here rather than start a new topic as i thinks its relevant. 

I have gone the non-physics parallax scrolling method, using a group at each layer and moving the group. Inserting objects into those groups at the right place, as the current background edge reaches the screen edge (with slight buffer). I keep track of the left and right most objects using pointers, which i update as required.  I thought this would be better than inserting the objects into a table and separately scrolling them.

What seems to be happening is that as my character moves right, my code works for one addition extension of the scrolling background but then spawns uncontrollably, as if the condition of the values being offscreen are no longer valid. I have spend hours trying to figure it out and debug. I suspect that it might be garbage collection cleaning up images off screen and thus reducing my group width or something but I don’t know. Could be something really simple that i am missing. 

Heres a post of my truncated code, which i hope is enough to provide an understanding of whats happening. Your help would be really appreciated. 

[lua]

    

self.parallaxEnabled = false

self.layers = {}

function scroll:createParallaxLayer(params)

    local l = params.l

    local distRatio = params.distRatio

    local parentGroup = params.parentGroup

    

    – create the layer

    self.layers[l] = {}

    local layer = self.layers[l]

    

    layer.objects = display.newGroup()

    – layer.objectTable = {} – create the table for objects

    layer.distanceRatio = distRatio

    parentGroup:insert(layer.objects)

end

function scroll:addParallaxObject(layer, object)

    local layer = self.layers[layer]

    – if theres no left object then assign the objec to the left mostobject

    if (layer.leftObject == nil) then

        – print(“set left Object layer:”,layer, object)

        layer.leftObject = object

    end

    if (layer.rightObject == nil) then

        – print(“set right Object layer:”,layer, object)

        layer.rightObject = object

    end

    layer.objects:insert(object)    – display group method

    table.insert( layer.objectTable , object )    – table method, not sure if required

    – need to add code to see if object has been added as the new left object or within existing bounds. 

    – but you get the picture

end

scroll.enterFrame = function ( self, event 

     – move objects

    if (self.objects ~= nil) then

        for i = 1, table.maxn(self.objects) do

            if self.objects[i].x ~= nil then

                – adjust the speed here

                self:modifyObjectSpeed(self.objects[i],self.speed )

            end

       end

    end

    

– move parallax layers 

    local layers = self.layers

    local screenScrollBuffer = 20

    local initialImageX = 2000

    if self.parallaxEnabled and (layers ~= nil) then

              for i=1,#layers do

                local layer = layers[i]

                  if layer ~= nil then

                      – scroll acrording to parallax

                    layer.objects.x = layer.objects.x - self.speed * layer.distanceRatio

    

                    – ### HERES WHERE I SEEM TO HIT PROBLEMS ###

                    if (i == 5)  then – start with one layer as test

                        – get the rightMost Object. Finds its content position on screen, and check its rightMost Edge

                        – this should deal with any screen size/image size by dealling the calculated edge 

                        local rightObject = layer.rightObject

                        local rightObjectX, rightObjectY = rightObject:localToContent( 0, 0 ) – 0,0 centre of the object

                        local rightMostEdge = rightObjectX + 0.5 * rightObject.contentWidth

                        

                        – if rightMost Edge less than display plus buffer amount, create a new image. 

                        – Position it at the right edge and insert into scroll group

                        if rightMostEdge <= display.contentWidth + screenScrollBuffer  then

                            local newRightObject = display.newImage(  “graphics/background/layer_01_2048x1546.png”, initialImageX, display.contentHeight/2)

                            newRightObject.x = rightMostEdge + 0.5 * newRightObject.contentWidth

                            layer.rightObject = newRightObject

                            scroll:addParallaxObject(i, newRightObject)

                        end

                    end

                end

            end

    end

end

scroll:createParallaxLayer({parentGroup = parentGroup, l=5, distRatio = 1.2})

local object00 = display.newImage(  “graphics/background/layer_01_2048x1546.png”,cX,cY )

scroll:addParallaxObject(5, object00)

[/lua]