Games Old-school Parallax Scrolling

wonder

Expert
Licensed User
Longtime User
In the 8-bit era, most videogame consoles weren't powerful enough to support multiple background layers.
Using a single layer, however, it would be possible to create such an illusion. :)

Here's a small B4J implementation of the concept:
upload_2017-8-28_6-29-15.png


More on the subject:
eJelxAM.gif

http://www.dustmop.io/blog/2015/12/18/nes-graphics-part-3/
 

Attachments

  • background scroll.zip
    234 KB · Views: 645
  • bgscroll.jar
    412.7 KB · Views: 554
Last edited:

sorex

Expert
Licensed User
Longtime User
nice example for those who would like to learn about making games.

Using a single layer, however, it would be possible to create such an illusion. :)

actually you're creating multiple (fake) layers yourself. ;)

the nes one looks impressive altho it's just 9 splits with different scrollings speeds and map offsets.

the more complex parallax does parallax in the same layer by shifting tileset elements.

a really good example that comes in mind is Flimbo's Quest (c64 '90?) altho the first usage of this trick was done years before tho
but with single char tiles or small multi char ones (Parallax (c64 '86) etc).
The benefit here is that you don't lose sprites nor need tricky timed splits.
 

Informatix

Expert
Licensed User
Longtime User
The same thing coded with SGE:
B4X:
Sub Process_Globals
   Private fx As JFX
   Private MainForm As Form
   
   Dim SGE As SGE

   Dim Background As Image
   Dim Regions(3) As sgeRegion
   Dim Actors(6) As sgeActor
End Sub

Sub AppStart (Form1 As Form, Args() As String)
   MainForm = Form1   
   MainForm.Show

   SGE.Initialize("SGE", 10)
   MainForm.RootPane.AddNode(SGE, 0, 0, MainForm.Width, MainForm.Height)   

   Background.Initialize(File.DirAssets, "test1.png")
   Regions(0).Initialize(Background, 0, 0, Background.Width, 90)
   Regions(1).Initialize(Background, 0, 90, Background.Width, 96)
   Regions(2).Initialize(Background, 0, 90+96, Background.Width, 220)

   For i = 0 To 2
     Actors(i).InitializeWithRegion(Regions(i), SGE.Root, "")
     Actors(i + 3).InitializeWithRegion(Regions(i), SGE.Root, "")
     If i > 0 Then
       Actors(i).Y = Regions(i - 1).Y + Regions(i - 1).Height
       Actors(i + 3).Y = Regions(i - 1).Y + Regions(i - 1).Height
     End If
     Actors(i + 3).X = Background.Width
   Next

   SGE.Camera.CenterOn(SGE.Width / 2, SGE.Height / 2)
   SGE.StartLoop
End Sub

Sub SGE_Update(ElapsedSinceLastFrame As Double)
   Dim MoveAmount(3) As Double
   MoveAmount(0) = 0.15 * ElapsedSinceLastFrame
   MoveAmount(1) = 0.1 * ElapsedSinceLastFrame
   MoveAmount(2) = 0.6 * ElapsedSinceLastFrame
   For i = 0 To 2
     Actors(i).X = Actors(i).X - MoveAmount(i)
     Actors(i + 3).X = Actors(i + 3).X - MoveAmount(i)
     If Actors(i).X <= -Background.Width Then
       Actors(i).X = Actors(i + 3).X + Background.Width
     End If
     If Actors(i + 3).X <= -Background.Width Then
       Actors(i + 3).X = Actors(i).X + Background.Width
     End If
   Next
End Sub

Sub SGE_Render(ElapsedSinceLastFrame As Double)
   SGE.Graphics.Clear
   SGE.Root.Draw
End Sub
 

Attachments

  • BackgroundScroll_SGE.zip
    233.5 KB · Views: 492

ilan

Expert
Licensed User
Longtime User
and now with iSpriteKit :)


Code:

B4X:
Sub createBackground(myScence As SKScene)
    'first create Sky
    Dim sky As SKSpriteNode
    sky.Initialize("")
    sky.SpriteNodeWithImageNamed("sky")
    sky.Size = myScence.Size
    sky.Position = Functions.CreatePoint(vpW/2,vpH/2)
    myScence.AddChild(sky)

    'create clouds
    paralexBackLayer(myScence,"clouds",0.12)

    'create mountains
    paralexBackLayer(myScence,"mountains",0.16)

    'create trees
    paralexBackLayer(myScence,"trees",0.06)
End Sub

Sub paralexBackLayer(myScene As SKScene, spritename As String, speed As Float)
    Dim MoveGroundSprite As SKAction
    MoveGroundSprite.Initialize
    MoveGroundSprite.MoveByX(-vpW,0,speed*vpW)

    Dim ResetGroundSprite As SKAction
    ResetGroundSprite.Initialize
    ResetGroundSprite.MoveByX(vpW,0,0)

    Dim GroundActions As SKAction
    GroundActions.Initialize
    GroundActions.Sequence(Array(MoveGroundSprite,ResetGroundSprite))

    Dim MoveGroundSpritesForever As SKAction
    MoveGroundSpritesForever.Initialize
    MoveGroundSpritesForever.RepeatActionForever(GroundActions)

    For i = 0 To 2 + GameScene.Frame.Width/vpW
        Dim Background As SKSpriteNode
        Background.Initialize("")
        Background.SpriteNodeWithImageNamed(spritename)
        Background.Size = Functions.CreateSize(vpW,vpH)
        Background.Position = Functions.CreatePoint(i * Background.Size.Width,Background.Size.Height/2)
        Background.RunAction(MoveGroundSpritesForever)
        myScene.AddChild(Background)
    Next
End Sub

Create:

B4X:
createBackground(GameScene) 'Create Parallax Scrolling

btw. there is no Render.Call in iSpriteKit. you create all your Nodes once and add actions to them. then you just add them to your Scene and they will run until you remove them from your scene. simple :)
 
Last edited:

ilan

Expert
Licensed User
Longtime User
Btw i like more the effect where he picks up the power and background change to dark.

This little things make those game special. I would never thought about such an effect (change back when pick up power).

I like it. Can you tell me the name of the game?
 

Informatix

Expert
Licensed User
Longtime User
and now with iSpriteKit :)


Code:

B4X:
Sub createBackground(myScence As SKScene)
    'first create Sky
    Dim sky As SKSpriteNode
    sky.Initialize("")
    sky.SpriteNodeWithImageNamed("sky")
    sky.Size = myScence.Size
    sky.Position = Functions.CreatePoint(vpW/2,vpH/2)
    myScence.AddChild(sky)

    'create clouds
    paralexBackLayer(myScence,"clouds",0.12)

    'create mountains
    paralexBackLayer(myScence,"mountains",0.16)

    'create trees
    paralexBackLayer(myScence,"trees",0.06)
End Sub

Sub paralexBackLayer(myScene As SKScene, spritename As String, speed As Float)
    Dim MoveGroundSprite As SKAction
    MoveGroundSprite.Initialize
    MoveGroundSprite.MoveByX(-vpW,0,speed*vpW)

    Dim ResetGroundSprite As SKAction
    ResetGroundSprite.Initialize
    ResetGroundSprite.MoveByX(vpW,0,0)

    Dim GroundActions As SKAction
    GroundActions.Initialize
    GroundActions.Sequence(Array(MoveGroundSprite,ResetGroundSprite))

    Dim MoveGroundSpritesForever As SKAction
    MoveGroundSpritesForever.Initialize
    MoveGroundSpritesForever.RepeatActionForever(GroundActions)

    For i = 0 To 2 + GameScene.Frame.Width/vpW
        Dim Background As SKSpriteNode
        Background.Initialize("")
        Background.SpriteNodeWithImageNamed(spritename)
        Background.Size = Functions.CreateSize(vpW,vpH)
        Background.Position = Functions.CreatePoint(i * Background.Size.Width,Background.Size.Height/2)
        Background.RunAction(MoveGroundSpritesForever)
        myScene.AddChild(Background)
    Next
End Sub

Create:

B4X:
createBackground(GameScene) 'Create Parallax Scrolling

btw. there is no Render.Call in iSpriteKit. you create all your Nodes once and add actions to them. then you just add then to your Scene and they will run until you remove them from you scene. simple :)
It seems that you use four different images in your code. It's not what's done in the B4J code above. Wonder wanted to demonstrate how you can use a single image to create the illusion of multiple background layers, moving independently. If you use different images, there's no trick.
 

ilan

Expert
Licensed User
Longtime User
Do you render parts of an image with different speed like loading parts from atlas file and create textures of them and render with different speed?
 

ilan

Expert
Licensed User
Longtime User
By using regions. If you use atlas with libgdx, you're already familiar with the concept. You load a single texture in memory and you draw different parts of it.

Ok so the only benefits would be to have a smaller app size? Because instead of having 20-30 images you have one and load regions from it to a texture correct?

Is there any other benefit?
 

Informatix

Expert
Licensed User
Longtime User
A GPU has a limited number of texture units. In libGDX, only two of them are used if I remember well (0 and 1).
It's also recommended to use textures sized with a power of 2 because that helps a lot the GPU (no fraction, no rounding, no special handling).
 

ilan

Expert
Licensed User
Longtime User
A GPU has a limited number of texture units. In libGDX, only two of them are used if I remember well (0 and 1).

Sorry but what does it means?

I use a lot textures in my game (pixel knight) and i never had a problem rendering them.

I did have problems if i used a to large spritesheet and tried to split it to regions and render it. If it was to big i get an error. (Memory related i think)

Is there a limited number of different images i can draw in a render cycle?
 

Informatix

Expert
Licensed User
Longtime User
Is there a limited number of different images i can draw in a render cycle?
Yes, there's a limited number of meshes (which are the objects using textures that you render) in libgdx. But it's above 5000 if I remember well and you're unlikely to reach such a limit. libGDX is fast and our Android devices are optimized for OpenGL so we don't have to worry about these things except in some cases (my game Diktatour was one of these cases because of the heavy number of different sprites and animations at once on screen). When you start counting your assets by hundreds, then it's probably time to use an atlas instead of individual files. If your sprites are very big, you can also see a difference.
For your opaque images (e.g backgrounds), you can improve dramatically the speed of your rendering in libGDX by using the KTX format instead of Jpeg. A demo proves the huge difference.

I did have problems if i used a to large spritesheet and tried to split it to regions and render it. If it was to big i get an error. (Memory related i think)
Anyway, a texture should not be bigger than 2048x2048 to be compatible with all devices. Note that an atlas can contain more than one page.
 

sorex

Expert
Licensed User
Longtime User
it only works as-is because the sky is a single gradient stretched over the entire width.

put a sun there and he is forced to seperate the clouds from the image :)
(unless the sun is a seperate sprite and drawn before the clouds)
 
Top