NEW PARALLAX LAYER (Previously ScrollableParallaxBackground)
Due to my current project needing more variance in terms of parallaxing items in the game, I decided to create the parallax layer class. It is a modified class based off Andengine's original parallax background class. This class requires GLES20.
Selling points of this class:
- Non-Background: ParallaxLayer is not a background, it is an entity which is attached to the scene like a sprite or shape. The purpose for this is to allow zooming to affect perspective of the parallax entities and if for some instance you need to apply parallax images to the hud or anywhere else, you can do that simply by attachChild(parallaxLayer).
- Multiple Uses: ParallaxLayer allows for more readily available use of both camera scrolling parallax entities as well as auto scrolling parallax entities. You can easily attach clouds to the parallax layer which will move across the screen continuously and on the same parallax layer, you can attach hills in the background which will only move when the camera moves. My favourite part of this is the simplicity. You can achieve a situation like this with only 4 lines of code with this class.
- Customizable Frequencies: You may notice when creating parallax entities, the original auto parallax background strings the images together end to end, which might not be such a good idea for things like clouds. In order to have 1 cloud scroll the screen at a time, this would require you to have a cloud .png (or whatever file type you're using) the entire width of your camera size (width). With this class you can lower the frequency of the images that are scrolling. This will allow you to reduce the image sizes of things like clouds, and simply reduce the frequency of the parallax entity containing the cloud sprite so that it is not strung end to end. Best of all, this also helps to improve framerate considerably, depending on the size of your parallaxing sprites.
- Customizable Ranges: Since Andengine's parallax background is based off, well... a background, the parallax entities have a range of camera min x to camera max x. This causes parallax entities to revert back to the "starting point" when it exits camera view. Parallax Layer allows you to set the range of the parallax entities (how far they will travel before being reverted back to the other side of the layer). You may be thinking that this is not so important, but here's an example situation.. Half of your level is a happy place, with clouds and blue sky, the other half things start to turn dark and gloomy. You can have the parallax entities from the "happy" place stop at a specific range and allow more gloomy (maybe somebats or dead leaves) sprites to take over when you get to the second, darker half of the level.
- Best of all? No more Issue where a tiny space appears in between parallax entities when moving the camera (or otherwise)
That's it for now, let me know if you find any problems so I can fix them.
Class:
Using java Syntax Highlighting
- public class ParallaxLayer extends Entity {
- // ===========================================================
- // Constants
- // ===========================================================
- // ===========================================================
- // Fields
- // ===========================================================
- private final ArrayList<ParallaxEntity> mParallaxEntities = new ArrayList<ParallaxEntity>();
- private int mParallaxEntityCount;
- protected float mParallaxValue;
- protected float mParallaxScrollValue;
- protected float mParallaxChangePerSecond;
- protected float mParallaxScrollFactor = 0.2f;
- private Camera mCamera;
- private float mCameraPreviousX;
- private float mCameraOffsetX;
- private float mLevelWidth = 0;
- private boolean mIsScrollable = false;
- // ===========================================================
- // Constructors
- // ===========================================================
- public ParallaxLayer() {
- }
- public ParallaxLayer(final Camera camera, final boolean mIsScrollable){
- this.mCamera = camera;
- this.mIsScrollable = mIsScrollable;
- mCameraPreviousX = camera.getCenterX();
- }
- public ParallaxLayer(final Camera camera, final boolean mIsScrollable, final int mLevelWidth){
- this.mCamera = camera;
- this.mIsScrollable = mIsScrollable;
- this.mLevelWidth = mLevelWidth;
- mCameraPreviousX = camera.getCenterX();
- }
- // ===========================================================
- // Getter & Setter
- // ===========================================================
- public void setParallaxValue(final float pParallaxValue) {
- this.mParallaxValue = pParallaxValue;
- }
- public void setParallaxChangePerSecond(final float pParallaxChangePerSecond) {
- this.mParallaxChangePerSecond = pParallaxChangePerSecond;
- }
- public void setParallaxScrollFactor(final float pParallaxScrollFactor){
- this.mParallaxScrollFactor = pParallaxScrollFactor;
- }
- // ===========================================================
- // Methods for/from SuperClass/Interfaces
- // ===========================================================
- @Override
- public void onManagedDraw(GLState pGLState, Camera pCamera) {
- super.preDraw(pGLState, pCamera);
- final float parallaxValue = this.mParallaxValue;
- final float parallaxScrollValue = this.mParallaxScrollValue;
- final ArrayList<ParallaxEntity> parallaxEntities = this.mParallaxEntities;
- for(int i = 0; i < this.mParallaxEntityCount; i++) {
- if(parallaxEntities.get(i).mIsScrollable){
- parallaxEntities.get(i).onDraw(pGLState, pCamera, parallaxScrollValue, mLevelWidth);
- } else {
- parallaxEntities.get(i).onDraw(pGLState, pCamera, parallaxValue, mLevelWidth);
- }
- }
- }
- @Override
- protected void onManagedUpdate(float pSecondsElapsed) {
- if(mIsScrollable && mCameraPreviousX != this.mCamera.getCenterX()){
- mCameraOffsetX = mCameraPreviousX - this.mCamera.getCenterX();
- mCameraPreviousX = this.mCamera.getCenterX();
- this.mParallaxScrollValue += mCameraOffsetX * this.mParallaxScrollFactor;
- mCameraOffsetX = 0;
- }
- this.mParallaxValue += this.mParallaxChangePerSecond * pSecondsElapsed;
- super.onManagedUpdate(pSecondsElapsed);
- }
- // ===========================================================
- // Methods
- // ===========================================================
- public void attachParallaxEntity(final ParallaxEntity parallaxEntity) {
- this.mParallaxEntities.add(parallaxEntity);
- this.mParallaxEntityCount++;
- }
- public boolean detachParallaxEntity(final ParallaxEntity pParallaxEntity) {
- this.mParallaxEntityCount--;
- final boolean success = this.mParallaxEntities.remove(pParallaxEntity);
- if(!success) {
- this.mParallaxEntityCount++;
- }
- return success;
- }
- // ===========================================================
- // Inner and Anonymous Classes
- // ===========================================================
- public static class ParallaxEntity {
- // ===========================================================
- // Constants
- // ===========================================================
- // ===========================================================
- // Fields
- // ===========================================================
- final float mParallaxFactor;
- final IAreaShape mAreaShape;
- final boolean mIsScrollable;
- final float shapeWidthScaled;
- // ===========================================================
- // Constructors
- // ===========================================================
- public ParallaxEntity(final float pParallaxFactor, final IAreaShape pAreaShape) {
- this.mParallaxFactor = pParallaxFactor;
- this.mAreaShape = pAreaShape;
- this.mIsScrollable = false;
- shapeWidthScaled = this.mAreaShape.getWidthScaled();
- }
- public ParallaxEntity(final float pParallaxFactor, final IAreaShape pAreaShape, final boolean mIsScrollable) {
- this.mParallaxFactor = pParallaxFactor;
- this.mAreaShape = pAreaShape;
- this.mIsScrollable = mIsScrollable;
- shapeWidthScaled = this.mAreaShape.getWidthScaled();
- }
- public ParallaxEntity(final float pParallaxFactor, final IAreaShape pAreaShape, final boolean mIsScrollable, final int mReduceFrequency) {
- this.mParallaxFactor = pParallaxFactor;
- this.mAreaShape = pAreaShape;
- this.mIsScrollable = mIsScrollable;
- shapeWidthScaled = this.mAreaShape.getWidthScaled() * mReduceFrequency;
- }
- // ===========================================================
- // Getter & Setter
- // ===========================================================
- // ===========================================================
- // Methods for/from SuperClass/Interfaces
- // ===========================================================
- // ===========================================================
- // Methods
- // ===========================================================
- public void onDraw(final GLState pGLState, final Camera pCamera, final float pParallaxValue, final float mLevelWidth) {
- pGLState.pushModelViewGLMatrix();
- {
- float widthRange;
- if(mLevelWidth != 0){
- widthRange = mLevelWidth;
- } else {
- widthRange = pCamera.getWidth();
- }
- float baseOffset = (pParallaxValue * this.mParallaxFactor) % shapeWidthScaled;
- while(baseOffset > 0) {
- baseOffset -= shapeWidthScaled;
- }
- pGLState.translateModelViewGLMatrixf(baseOffset, 0, 0);
- float currentMaxX = baseOffset;
- do {
- this.mAreaShape.onDraw(pGLState, pCamera);
- pGLState.translateModelViewGLMatrixf(shapeWidthScaled - 1, 0, 0);
- currentMaxX += shapeWidthScaled;
- } while(currentMaxX < widthRange);
- }
- pGLState.popModelViewGLMatrix();
- }
- // ===========================================================
- // Inner and Anonymous Classes
- // ===========================================================
- }
- }
Parsed in 0.054 seconds, using GeSHi 1.0.8.4
and implementation:
1. Create your ParallaxLayer object:
Using java Syntax Highlighting
- ParallaxLayer parallaxLayer = new ParallaxLayer(camera, true, 4000);
Parsed in 0.030 seconds, using GeSHi 1.0.8.4
Parameter 1: Camera, needed for Scrolling entities such as hills.
Parameter 2: allow scrolling? this should be enabled if you plan to use parallax entities which move depending on camera movement.
Parameter 3: this is the range in which parallax layer will allow scrolling. Do not include this parameter if you would simply like the parallax layer to use default camera width. The "4000" here means that the parallax entity would scroll for approximately 4000 pixels before it is reverted back to the "starting" point.
These two methods will adjust the speed in which parallax entities travel. Top for auto parallax entities, bottom for camera based parallax entities.
Using java Syntax Highlighting
- backgroundParallax.setParallaxChangePerSecond(8);
- backgroundParallax.setParallaxScrollFactor(1);
Parsed in 0.033 seconds, using GeSHi 1.0.8.4
Adding ParallaxEntities:
Using java Syntax Highlighting
- // Create your sprites as you normally would
- Sprite mountainsSprite = new Sprite(0, 0, WIDTH, HEIGHT, mountainsTextureRegion, mEngine.getVertexBufferObjectManager());
- Sprite starsSprite = new Sprite(0, 0, WIDTH, HEIGHT, starsTextureRegion, mEngine.getVertexBufferObjectManager());
- backgroundParallax.attachParallaxEntity(new ParallaxEntity(15, starsSprite, false, 1));
- backgroundParallax.attachParallaxEntity(new ParallaxEntity(10, mountainsSprite, true));
Parsed in 0.036 seconds, using GeSHi 1.0.8.4
Parameters 1&2: Nothing new, you've used these before with default ParallaxBackground.
Parameter 3: true for camera parallax, false for continuous(auto) parallax.
Parameter 4: Reduce frequency of the parallax image by a number given. For example, with the first parallaxEntity (starsSprite), the frequency is reduced by 1. This means that for every starsSprite on the parallaxLayer, there will be a space the size of starsSprite.getWidth() between each parallax image shown. This is good for improving framerate and obviously not having to create parallax images with unnecessary transparent pixels.
And of course, a quick video which is very simple. You can see how it works at a very basic level though.


