Thursday, July 21, 2011

Parallax scrolling in processing and processingJS

A very special secret project that I am working on at CDOT involves the creation of a very large scene- one that extends well beyond the actually visible area. Here is an abstract representation of how that scene is laid out:






















The viewable area is the part of the scene that the user actually sees. You can set this area by calling translate(-screenCornerX, -screenCornerY), where screenCornerX and Y are the co-ordinates of the top left corner of the viewable area, at the start of each draw loop. This moves the entire sketch over so that the elements you want the user to see are placed on their screen.

A smooth scrolling effect can be created by keeping track of the amount of time between draw calls. The millis() function tells you the amount of time between now and the start of the program. Storing this value and subtracting what it is now from what it was in the previous frame tells you the number of milliseconds between frames. You can multiply that value by a scrollRateXPerMillisecond and scrollRateYPerMillisecond to get values to add to screenCornerX and screenCornerY which will cause the screen to scroll at a constant rate over time.

But there is a problem with this approach. You can see in the image that there are foreground objects and background objects. The background objects are intended to appear far, far behind the foreground objects but because they both scroll at the same rate the viewer sees that they are at the same height!
The solution to this problem is to use the technique known as Parallax scrolling.


With parallax scrolling when we are drawing a scene we want to draw the background elements first, so that the foreground elements appear on top of them. But we don't want to translate the full screenCornerX and screenCornerY values over- instead we want to translate(-screenCornerX/10, -screenCornerY/10). We refer to the 10 in that function call as our parallax factor- it's actually a good idea to take the 10 out of there and put it in a constant variable so that our call looks like translate(-screenCornerX/PARALLAX_FACTOR, -screenCornerY/PARALLAX_FACTOR) instead.


You can then draw all the background elements as normal, but because our translate call is different the purple area in this image will end up being our background instead of the green area.
Next we reverse the translation (this is most easily done by calling pushStyle() before the translation and drawing and popStyle() afterwards) and then translate the full screenCorner amounts and draw our foreground elements.


This image shows the results of the new drawing mechanism after the foreground has scrolled 90 pixels down and 90 pixels to the right. The background area only scrolled 9 pixels down and 9 pixels right during the transition, and the user perceived the foreground objects moving 10 times faster on the screen then the background objects did. This creates the illusion that the background elements are much further away than the foreground elements, adding a great sense of depth to the scene.