My main project needed a better background so I gave it a parallax scroll using texture scrolling.
Basically it scrolls the texture of several sprites with varying texture scales overlaid to form a background.
The scroll direction changes randomly at random intervals
The effect worked so well I thought I'd share it with you, so wrapped what was a few lines of code into a new project, wrapped it up in a few demo routines and commented the hell out of it.
You can change the number of layers, the number of splits and if you want, control one of the parallaxes with a virtual joystick.
It adjusts for rotation and should work at any size.
I've included a few textures to demo the effect.
Hope you find it useful.
Codes a bit longer than I planned so I split it up;
Initialisation
// Settings which work best for my Phone Feel Free to Change or Remove
setVirtualResolution( 480 , 800 )
setSyncRate( 30 , 0)
// Hard to find a good colour for a variety of backgrounds
setPrintColor( 255 , 191 , 0 , 95 )
// from here the program does not change any display settings
// and will use whatever virtual resolution is running
// *** STUFF YOU CAN PLAY ABOUT WITH ***
// NUM_LAYERS (1 to ??) the number of Layers to use.
// NUM_AREAS (1 to ?? ) the number of scrolling areas (images provided for up to four)
// JOY_AREA (1 to areas) sets a scroll area to be controlled by the joystick (0 for none)
// STATUS_TEXT (0/1) 1 enables status text printing
// WORLD_DEMO (0/1) 1 enables some world sprites, which can be moved with a virtual joystick
#constant NUM_LAYERS 8
#constant NUM_AREAS 4
#constant JOY_AREA 0
#constant WORLD_DEMO 0
#constant STATUS_TEXT 0
// MAX_SCROLLX and MAX_SCROLLY set the maximum Scroll Speed in Pixels / Second
// FRAMERATE is used to calculate movement in pixels / frame
// MIN_SCALE sets the scale of the back layer. 0.1 to 0.5 produce good results
// T_ALPHA sets full sprite transparency for areas where the image filename starts in "T_"
#constant MAX_SCROLLX 96.0
#constant MAX_SCROLLY 96.0
#constant FRAMERATE 30.0
#constant MIN_SCALE 0.1
#constant T_ALPHA 127
// Filenames for Images - NUM_AREAS must not be bigger than size of this array
dim imageName[ 8 ] as string = [ "","Starfield512.png","MatrixCode512.png","TileGrid128.png","T_StarBall128.png","T_Cloud256.png","Zag128.png","T_Cute256.png","" ]
// Oh for a data statement !
// *** END OF STUFF YOU CAN PLAY ABOUT WITH ***
// Set up UDT and global to store values
type myType
maxStepX#
maxStepY#
count
lastOrientation
endtype
global my as myType
// convert constant values to maximum scroll speed per frame
my.maxStepX# = MAX_SCROLLX / FRAMERATE
my.maxStepY# = MAX_SCROLLY / FRAMERATE
// Set Orientation to zero so resize occurs on first loop
my.lastOrientation = 0
// array for scaling value for each layer
dim layerScale [ NUM_LAYERS ] as float
if NUM_LAYERS > 1
// Divide by Zero Trap
for layer = 1 to NUM_LAYERS
// Precalculate scaling for layers; 1.0 for first to MIN_SCALE for last
layerScale [ layer ] = 1.0 - (layer - 1.0) * (1.0 - MIN_SCALE) / ( NUM_LAYERS - 1.0 )
next layer
else
layerScale [ NUM_LAYERS ] = 1.0
endif
// array for random layer offset - prevents obvious repeat tiling across layers
dim layerRand[ NUM_LAYERS ] as float
for layer = 1 to NUM_LAYERS
layerRand[ layer ] = random( 0 , 100 ) / 100.0
next layer
// Set up UDT and array for scrolling areas
type areaType
tileImage
tileWidth
tileHeight
scrollX#
scrollY#
speedX#
speedY#
changeCount
filenameText
filenameShadow
endtype
// Create Array for scrolling area parameters
dim myArea[ NUM_AREAS ] as areaType
// Load Images
for area = 1 to NUM_AREAS
myArea[ area ].tileImage = myLoadImage( imageName[ area ] )
next area
// Create Array for Sprites, indexed by area and layer
dim layerSprite [ NUM_AREAS , NUM_LAYERS ]
for area = 1 to NUM_AREAS
// Set changecount to zero so speed change occurs first loop
myArea[ area ].changeCount = 0
// Set initial scroll position half way across the tile
myArea[ area ].scrollX# = myArea[ area ].tileWidth / 2.0
myArea[ area ].scrollY# = myArea[ area ].tileHeight / 2.0
if myArea[ area ].tileImage > 0
// get width of loaded image and set texture repeat on
myArea[ area ].tileWidth = getImageWidth( myArea[ area ].tileImage )
myArea[ area ].tileHeight = getImageHeight( myArea[ area ].tileImage )
SetImageWrapU( myArea[ area ].tileImage , 1 )
SetImageWrapV( myArea[ area ].tileImage , 1 )
else
// to prevent divide by zeros if image not found
myArea[ area ].tileWidth = 64
myArea[ area ].tileHeight = 64
endif
// Initialise layer sprites
for layer = 1 to NUM_LAYERS
// Create Sprite and set depth , position and colour
layerSprite[ area , layer ] = createSprite( myArea[ area ].tileImage )
// Set sprite depth based on layer, layer 1 is at the front
setSpriteDepth( layerSprite[ area , layer ] , 100 + layer )
// If image names starts "T_" treat as trasparent
if upper(left( imageName[ area ] , 2 )) = "T_"
// Set Alpha based on constant and set colour to full
thisAlpha = T_ALPHA
thisCol = 255
else
// If non transparent, set alpha to 255 and darken colour of layers further back
thisAlpha = 255
thisCol = 223.0 * layerScale[ layer ] + 32.0
endif
setSpriteColor( layerSprite[ area , layer ] , thisCol , thisCol , thisCol , thisAlpha )
// FixSprite to screen so it is not affected by moving world offset
fixSpritetoScreen( layerSprite[ area , layer ] , 1 )
next layer
// Create Text For Filename and shadow
myArea[ area ].filenameText = createText( imageName[ area ] )
myArea[ area ].filenameShadow = createText( imageName[ area ] )
fixTextToScreen( myArea[ area ].filenameText , 1 )
fixTextToScreen( myArea[ area ].filenameShadow , 1 )
setTextDepth( myArea[ area ].filenameText , 90 + layer )
setTextColor( myArea[ area ].filenameText , 127 , 239 , 255 , 255 )
setTextDepth( myArea[ area ].filenameShadow , 91 + layer )
setTextColor( myArea[ area ].filenameShadow , 0 , 0 , 0 , 127 )
next area
if WORLD_DEMO > 0
// position some world sprites to show texture scroll independant of view scroll
dim worldSprite [ 63 ]
for i=0 to 63
worldSprite[ i ] = createSprite(0)
fixSpritetoScreen( worldSprite[ i ] , 0 )
setSpriteSize( worldSprite[ i ] , 16 , 16 )
setSpriteOffset( worldSprite[ i ] , 8 , 8 )
thisX = (i mod 8) * 96
thisY = (i / 8) * 96
setSpritePositionByOffset( worldSprite[ i ] , thisX , thisY )
setSpriteDepth( worldSprite[ i ] , 20 )
setSpriteColor( worldSprite[ i ] , random( 63 , 191 ) , random( 63 , 191 ) , random( 63 , 191 ) , 255 )
next i
endif
// If Virtual Joystick needed, add one
if WORLD_DEMO > 0 or JOY_AREA > 0
// Create a Joystick to allow world to be scrolled
addVirtualJoystick( 1 , 0 , 0 , 32 )
endif
Main Loop
do
// Check back key and exit if pressed
if getRawKeyState ( 27 ) then exit
// if joystick is used
if WORLD_DEMO > 0 or JOY_AREA > 0
// Move world offset based on virtual joystick
setViewOffset( getViewOffsetX() + getVirtualJoystickX(1) * 8, getViewOffsetY() + getVirtualJoystickY(1) * 8 )
endif
// If orientation changes (and on first run of loop)
if getOrientation() <> my.lastOrientation
// Call function to set Sprite Size and texture scale to match screen size
resizeSprites()
my.lastOrientation = getOrientation()
endif
// Increment counter
inc my.count , 1
// If we are printing text, then print some text
if STATUS_TEXT > 0
// Print current speed limits, counter and world view offset
print(" Limit X: " + myStr( my.maxStepX# ) + ", Y: " + myStr( my.maxStepX# ))
print(chr(10) + "Count: " + str( my.count ) + ", View: " + str(getViewOffsetX(), 3) + "," + str(getViewOffsetY(),3))
endif
// Cycle through Scroll areas
for area = 1 to NUM_AREAS
if STATUS_TEXT > 0
// Print current Values for checking purposes - uses padding str function below
print( chr(10) + "Section " + str( area )+ ", Change:" + str( myArea[ area ].changeCount ))
print("Offset X: " + myStr( myArea[ area ].scrollX# ) + ", Y: " + myStr( myArea[ area ].scrollX# ))
print(" Speed X: " + myStr( myArea[ area ].speedX# ) + ", Y: " + myStr( myArea[ area ].speedX# ))
endif
// If counter has passed change count for this area and area is not controlled by joystick
if my.count > myArea[ area ].changeCount and area <> JOY_AREA
// Set next change for a random time between 1 and 8 seconds from now
myArea[ area ].changeCount = my.count + random( FRAMERATE , FRAMERATE * 8.0 )
// get a random value between -1.0 and +1.0 using frame rate for value spacing
// then multiply by maximum Step for each axis for new random scroll speed
myArea[ area ].speedX# = ( random( -FRAMERATE , FRAMERATE ) / FRAMERATE ) * my.maxStepX#
myArea[ area ].speedY# = ( random( -FRAMERATE , FRAMERATE ) / FRAMERATE ) * my.maxStepY#
endif
next area
// Call function to Scroll The Layers
ScrollLayers()
sync()
loop
Resize function (used for orientation change)
function resizeSprites()
// Called at start of first loop and when screen orientation changes only
if (getOrientation()-1) / 2 <> (my.lastOrientation-1) / 2 and my.lastOrientation > 0
// If orientation changed from landscape to portrait switch coordinates
setVirtualResolution( getVirtualHeight() , getVirtualWidth())
endif
// Get current display dimensions
thisScreenWidth# = getVirtualWidth()
thisScreenHeight# = getVirtualHeight()
if thisScreenWidth# > thisScreenHeight#
// If screen is wider than it is tall - Split the screen horizontally
thisWidth# = thisScreenWidth# / NUM_AREAS
thisHeight# = thisScreenHeight#
// Sprite position change between layers
thisXStep# = thisWidth#
thisYStep# = 0.0
// Recalculate size of text and virtual Joystick
stickSize = thisScreenHeight# / 4.0
textSize# = thisScreenHeight# / 30.0
else
// otherwise Split the screen vertically
thisWidth# = thisScreenWidth#
thisHeight# = thisScreenHeight# / NUM_AREAS
// Sprite position change between layers
thisXStep# = 0.0
thisYStep# = thisHeight#
// Recalculate size of text and virtual Joystick
stickSize = thisScreenWidth# / 4.0
textSize# = thisScreenWidth# / 30.0
endif
setPrintSize( textSize# )
// If joystick being used
if WORLD_DEMO > 0 and JOY_AREA = 0
// set size and position of virtual Joystick
setVirtualJoyStickPosition( 1 , stickSize / 2, thisScreenHeight# - stickSize / 2)
setVirtualJoyStickSize( 1 , stickSize )
endif
// Loop through scroll areas
for area = 1 to NUM_AREAS
// Calculate sprite offsets
thisX# = (area - 1) * thisXStep#
thisY# = (area - 1) * thisYStep#
// If area under joystick control
if area = JOY_AREA
// set size and position of virtual Joystick to be on this area
setVirtualJoyStickPosition( 1 , thisX# + stickSize / 2, thisY# + thisHeight# - stickSize / 2)
setVirtualJoyStickSize( 1 , stickSize )
endif
if myArea[ area ].tileImage > 0
// If image loaded, calculate scales using image and screen sizes
thisUScale# = getImageWidth( myArea[ area ].tileImage ) / thisWidth#
thisVScale# = getImageHeight( myArea[ area ].tileImage ) / thisHeight#
else
// Otherwise you're not going to see much anyway
thisUScale# = 1.0
thisVScale# = 1.0
endif
// Cycle through layers of this area
for layer = 1 to NUM_LAYERS
// Set sprite position, size and texture scale
setSpritePosition ( layerSprite[ area , layer ] , thisX# , thisY# )
setSpriteSize ( layerSprite[ area , layer ] , thisWidth# , thisHeight# )
setSpriteUVScale( layerSprite[ area , layer ] , thisUScale# * layerScale [ layer ], thisVScale# * layerScale [ layer ])
// Set Text Size and Position
setTextSize( myArea[ area ].filenameText , textSize# )
setTextSize( myArea[ area ].filenameShadow , textSize# )
setTextPosition( myArea[ area ].filenameText , thisX# + 3, thisY# + 3 )
setTextPosition( myArea[ area ].filenameShadow , thisX# + 5, thisY# + 5 )
next layer
next area
endfunction
Scroll function and supplementary bits;
function ScrollLayers()
// Cycle through Scroll Sections
for area = 1 to NUM_AREAS
if area = JOY_AREA
// Layer is controlled with joystick (use view offset)
thisUOffset# = getViewOffsetX() / myArea[ area ].tileWidth
thisVOffset# = getViewOffsetY() / myArea[ area ].tileHeight
else
// otherwise move scroll position for each axis
myArea[ area ].scrollX# = myArea[ area ].scrollX# + myArea[ area ].speedX#
myArea[ area ].scrollY# = myArea[ area ].scrollY# + myArea[ area ].speedY#
// convert scroll position in pixels to texture offset values
thisUOffset# = myArea[ area ].scrollX# / myArea[ area ].tileWidth
thisVOffset# = myArea[ area ].scrollY# / myArea[ area ].tileHeight
endif
// Cycle through layers, changing texture offsets of sprites
for layer = 1 to NUM_LAYERS
setSpriteUVOffset( layerSprite[ area , layer ] , thisUOffset# + layerRand[ layer ], thisVOffset# + layerRand[ layer ] )
next layer
next area
endfunction
// Load image function
function myLoadImage( thisFileName$ )
if getFileExists( thisFileName$ )
// if file exists then load it
thisInt = loadImage( thisFileName$ )
// and set Texture Wrapping On
SetImageWrapU( tileImage , 1 )
SetImageWrapV( tileImage , 1 )
else
// Program is really not that imresssive without an image
thisInt = 0
endif
endfunction thisInt
// Padded STR function ( nnnn.nn including sign)
function myStr( thisValue# )
thisString$ = right( spaces(3) + str(thisValue# , 2) , 7 )
endfunction thisString$
@baxslash,
I know you are working on this very topic for your tutorials, feel free to use anything you find useful, though I can't see much of it being new to you.