In this tutorial, we are going to continue our understanding of lighting by working with shadow affects. To add more depth to our environment, we are going to also look at layering with parallax effects and end with parallax using camera panning.
Complete source code for all tutorials can be downloaded from here.
Shadows
Much like lighting, in order for shadow to be rendered, we have to register each renderable to actively draw shadows. A renderable can be a shadow caster, a shadow receiver or both. We can use a LightRenderable or an IllumRenderable object as a source of shadow. To do this, we will need to use a ShadowReceiver object. The ShadowReceiver encapsulates a GameObject to provide shadow functionality. We then use this object for drawing.
The general process is as follows...
We create LightRenderable or IllumRenderable objects for the caster and receiver. But also define a Z position for the caster to indicate a depth offset. This value is used to calculate shadows. As usual, we encapsulate the Renderable into GameObjects.
We create our light source making sure to activate the calculation of shadows with the source using the setLightCastShadowTo() function. We apply the light source to the caster and receiver renderables.
We initialize a new ShadowReceiver object with the receiver's GameObject.
For every GameObject that we wish to be able to cast shadows onto the receiver, we call the receiver's addShadowCaster() function with a references to the caster GameObject.
During the draw phase, we draw with the ShadowReceiver object. It will calculate all shadows for its renderable during drawing. We need to make sure that we draw the ShadowReceivers before other GameObjects.
We can view our next example here. The WASD keys control the position of the point light. Though we do not define direction for a point light, the light is emanating from a single point 10 units up the Z-axis. The virtual radial "light rays" provide direction that causes the shadow to move opposing the light source.
Declare variables and load resources.
In the initialization function, we create a background renderable. We use an IllumRenderable as we have a normal map for this background. It is going to be encapsulated in a GameObject for shadows so we have it as a local variable.
We create our light source as normal making sure to enable the calculation of shadows.
Next, we add the light source to the renderables. We make sure to apply a Z position to the GameObject that will be casting a shadow. Then we create the ShadowReceiver object sending in the background GameObject. We call addShadowCaster() with the caster's GameObject.
Our draw and update functions are the same as previous examples except, for those GameObjects that must render shadows, we draw using the ShadowReceiver objects.
Parallax
Parallax is an animation technique used to create an illusion of depth in a 2D environment. This is particularly common in side-scrolling games and is accomplished by making objects in the background scroll at a slower speed than objects in the foreground. For example, you might have cloud in the sky "in the distance" move very slowly while your characters move very quickly.
In our next example, we implement parallax as shown here. This image is created with eight layers. We will create a LightRenderable for each layer and encapsulate in a ParallaxGameObject. We set the layer number motion direction and speed. If the speed were set the same for all layers, the parallax object will automatically adjust the speed based on the layer. We update the objects and draw in order from farthest to nearest.
The images composing the layers will be automatically tiled making it appear to be ongoing (if you watch the example long enough, the sun will disappear off the left edge of the screen and eventually reappear on the right).
First we declare all variables and constants. We load the resources.
Now we initialize.
Now we draw and update. We draw the layers with furthest (and slowest) first. Calling update() on each layer handles the animation.
Camera Panning with Parallax
Scrolling layers with parallax gives a sense of depth but we can take this further by making the effect more interactive. If we remove the calls to setCurrentFrontDir() and setSpeed() for all of the layers in the initialize() function, the layers will not move. We can then have our parallax affect work with the movement of our GameObjects. We can pan the camera within the update() function. Using the camera object's panWith() function will move the camera with the GameObject. We pass in a "subject" GameObject for the camera to focus on and a tolerance. A tolerance of 0.9 will start the pan affect when the subject gets within 90% of the view bounds. The parallax affect will work as expected during the panning.
Use 'WASD' to move the minion. When the minion gets near the edges of the view, the camera will pan to follow the minion. The background will tile with parallax. See here for a live view.
In the sample , the panning call is found in the last lines of the update() function. We also use a directional light and completely disable the ambient light to enhance the images using normal maps for added depth. All other functionality has been covered in the tutorials and should be familiar. We show the full code below.
Conclusion
Over the course of this tutorial, we have taken a peek at many aspects of the GTCS Game Engine. For a more in-depth look at the commands, be sure to reference the API documentation.