16 October 2012
Tutorial: Parallax Simplified
In this week’s Tuesday Tutorial, Corona Ambassador Brent Sorrentino explores the basics of parallax scrolling and walks you through a demo project to implement a customizable touch-and-drag parallax view in your app.
We Live in a Parallax World
Parallax is a broad term with numerous definitions in its application to astronomy, photography, and optics. In a 2-dimensional scrolling simulation, which especially concerns mobile game developers, parallax is used to provide a sense of “distance” between the camera and moving objects that recede back into the simulated space. This method has been used since the 16-bit Nintendo days and it continues to be used in modern apps like “Angry Birds” and “Squids”.
Corona’s usage of display groups as specifically-ordered layers makes parallax scrolling simple to implement, at least in theory — but many developers get confused in the coding stage. This tutorial and demo project aims to address that!
1. Getting Started
The first thing we’ll do is configure our parallax “layers” as display groups. Open a blank project in Corona and copy the following lines into “main.lua”:
Notice that each parallax layer has a specified “distanceRatio” parameter — this is very important! We’ll adjust these values to determine the scrolling speed of each layer in relation to 1:1 scrolling (1.0) of the foreground. If a specific layer should be exempted from parallax scrolling, simply leave this parameter undeclared. For example, in the overall hierarchy of your display group setup, you’ll likely have one or two layers which do not scroll — a UI/button/widget layer for example. Only layers with the “distanceRatio” parameter will scroll within the parallax behavior!
Additional note: the “distanceRatio” parameter can be greater than 1.0 if you wish. Using a value such as 1.4 will provide a layer which moves faster than the foreground layer, for example, a layer of opaque mist which resides between the camera and the foreground.
2. Setting Scroll Limits
In almost every scenario, we’ll need to constrain the boundaries of the “world” — that is, limit how far the user can drag the world in any direction. This is accomplished with the following four variables, each of which we’ll declare as parameters of the stage for easy access later in the project. Add the following line to your own project now:
These four parameters simply limit the world’s boundaries. xMin will typically be a negative value, defining how far to the left the world can scroll. xMax limits the movement to the right, and yMin and yMax limit upward and downward scrolling respectively.
Note that all limits are in pixels, not percentage, and they constrain the foreground 1:1 layer’s movement. Layers behind or in front will not be constrained by these limits.
3. Add Layers to a Containing Table
We’ll now add our parallax layers to a containing table named paraGroups, and that table will be set as a sub-table of the stage. Copy the following lines into your project:
In this code block, we first declare the containing table. Then we loop through all display groups, each of which is a child of the core stage. However, only the layers with a “distanceRatio” parameter are added — all other layers, such as those you wish to exempt from scrolling, will be ignored.
4. Populate the World
In the following code, we’ll set a gradient background and add some sample objects to our world. I won’t go through this line-by-line since your own implementation of a parallax world will vary — just note that we’re adding one object to each parallax layer, but you’ll want to populate your world with many objects and images.
5. Implementing Touch and Drag
A scrollable world isn’t much good if you can’t actually move it! The following function handles the screen touch “began” and “moved” phases and manipulates all groups in the paraGroups table as you drag around the screen. Please copy this function and touch listener into your project:
Again, I won’t walk through this function line-by-line, but I’ll summarize its behavior. In the began phase, we loop through the paraGroups table and set the proper “offset” X/Y values in relation to the foreground reference group. This is necessary to place all layers in a positional reference based on the position of the core 1:1 group.
The moved phase is where the real action occurs. First, we determine the xDif and yDif “delta” values in which to move the reference group, based on where the user started the touch and how far the touch has moved since the previous phase check.
Next, we check if either the resulting X or Y position will be outside the world boundaries declared in Step #2 above. If any limit has been exceeded, we pre-adjust the xDif or yDif value so that the world will never move beyond its limits. As noted in the code, you may comment out these lines if you don’t wish to (or don’t need to) impose any scrolling limits.
Finally, we loop through the paraGroups table and adjust the position of each parallax layer in relation to the distanceRatio value you specified for the group. The foreground group, with a distance ratio of 1.0, will follow/track the user’s touch exactly. Each layer behind or in front will move at the distance ratio that you specified. Perhaps the best part is that you can tweak and adjust your ratios in one place (where you declare the groups) and see the result reflected immediately in your app. After all, setting the proper ratios is based more on “feel” than an exact mathematical calculation, and you’ll need to determine which ratio feels best for each layer depending on the images/object within it and the “implied distance” from the camera.
It’s that simple! We now have a flexible method to implement a scrolling touch-and-drag parallax world with no practical limitation on the number of layers. Adjusting the movement ratio of each layer is equally simple and can be set dynamically from a database or a text file if you wish.
6. Cleaning Up
Don’t forget to clean up your layer references, especially if you’re using a scene manager like Storyboard or Director. In addition to using standard cleanup practices for each respective scene manager, we’ll need to clear out the paraGroups table that we created since it contains references to your parallax display groups, all of which will likely be removed when you change scenes. Likewise, don’t forget to remove the touch listener, unless you’re implementing this entire method at a global level and intend to use it from scene to scene.
In a future Tuesday Tutorial, we’ll build upon this method and introduce a convenient zoom feature to dynamically zoom your parallax layers in/out at the proper dimensional ratios. Until then, please test out and tweak this demo project to your liking, and post your questions and suggestions below.