parallax scroll sample

Hello.

Has anyone already done a 2D parallax scrolling example? I’m looking something like this one, but this is in flash. http://active.tutsplus.com/tutorials/games/add-depth-to-your-game-with-parallax-scrolling/

Or is there already an engine that I can use to create exactly the same effect as the flash one?

Thanks.
Luiz

well its not brain surgery
have a background, scroll it at speed X
have another layer behind it and scroll it slower, for example at speed X/2

okay but how can I draw them? I have the following layers for my parallax

  1. Sky
  2. Far mountain
  3. Near mountain
  4. Road
  5. Tree

Then I have the sprite car that should be placed over the road.

But first I’m drawing the all layers in the order above and then the sprite and obviously it’s not working. How could I do that? Add a sprite object to the layer?

not sure what you mean - backgrounds are different than sprites of course.
you render the tilemap layers of you level in order, with scroll factors and then the sprites.

The book “Developing Games in Java” explains stuff you would need to know in chapter 5, “Creating a 2D Platform Game”. Highly recommended.


g.drawImage(sky, (int)(scrollX * 0.2f), (int)scrollY, width, height);
g.drawImage(farMountain, (int)(scrollX * 0.4f), (int)scrollY, width, height);
g.drawImage(nearMountain, (int)(scrollX * 0.6f), (int)scrollY, width, height);
g.drawImage(road, (int)(scrollX * 0.8f), (int)scrollY, width, height);
g.drawImage(tree, (int)(scrollX * 1.0f), (int)scrollY, width, height);

Yes that works ok. But I don’t want to have the tree being displayed at the same ratio as the others. See the link in my first post. In that example the tree appears and then there’s a gap until it appears again. I’m having some trouble to find a way to code this gap. Any help.


int xTree = (int)(scrollX * 1.0f);
xTree %= (canvasWidth*n);
xTree += (canvasWidth*n);
xTree %= (canvasWidth*n);
xTree -= width/2;
g.drawImage(tree, xTree, (int)scrollY, width, height);

Edit: the tutorial shows how to do it in step 28…

could someone put a working example of it? I’m not able to get the same effect… ??? It seems to be simple but I have stopped programming for a while but now I’m back. I just a need a working code to get started. Really appreciate if someone could post it.

That’s not how it works. Spoon feeding has never be associated with significantly gaining skills.

If you struggle with the tutorial you mentioned, pick another (simpler) tutorial.

ok here is the code I did. It runs at 60FPS but it’s not smooth. How can I make the movement smooth??


public class ParallaxStandAlone extends JFrame {

	/** Parallax Engine. */
	private ParallaxEngine parallaxEngine;

	public ParallaxStandAlone() throws Exception {

		setSize(640, 400);
		setResizable(false);
		show();

		// Load image with a sprite sheet.
		Image mount = ImageLoader.loadImage(this, "mount1.png");
		Image mount2 = ImageLoader.loadImage(this, "mount2.png");
		Image road = ImageLoader.loadImage(this, "road2.png");
		Image tree = ImageLoader.loadImage(this, "tree.png");

		// Set horizontal speed for each layer.
		double[] vx = { -0.15, -0.25, -0.4, -0.5 };

		// Set y position for each layer.
		int[] y = { 0, 50, 280, 0 };

		// Gap for each layer.
		int[] gap = { 0, 0, 0, 1000 };

		// Break main image in several pieces.
		Image layers[] = new Image[4];
		// layers[0] = tree;
		layers[0] = mount2;
		layers[1] = mount;
		layers[2] = road;
		layers[3] = tree;

		// Create a parallax engine.
		parallaxEngine = new ParallaxEngine(640, (int) (32 * 0.25), layers, vx,
				y, gap);

		createBufferStrategy(2);
		BufferStrategy strategy = getBufferStrategy();

		int fps = 0;
		long lastLoopTime = System.currentTimeMillis();

		long nextFrameStart = System.nanoTime();
		long FRAME_PERIOD = 1000000000L / 60;
		while (true) {
			if (System.currentTimeMillis() - lastLoopTime > 1000) {
				lastLoopTime += 1000;
				super.setTitle("FPS=" + fps);
				fps = 0;
			}

			do {
				// Tell the engine to move layers to the right.
				parallaxEngine.setMoveRight();
				// Actually performs the movement.
				parallaxEngine.move();
				nextFrameStart += FRAME_PERIOD;
			} while (nextFrameStart < System.nanoTime());

			Graphics2D g = (Graphics2D) strategy.getDrawGraphics();
			g.setColor(Color.blue);
			g.fillRect(0, 0, 640, 400);
			parallaxEngine.render(g);
			strategy.show();

			fps++;
			long remaining = nextFrameStart - System.nanoTime();
			if (remaining > 0) {
				try {
					Thread.sleep(remaining / 1000000);
				} catch (Throwable t) {
				}
			}
		}

	}

	public static void main(String[] args) throws Exception {
		new ParallaxStandAlone();
	}
}


public class ParallaxEngine {

	/** Layers used to execute parallax scrolling. */
	private Layer[] layers;

	/** Number of layers. */
	private int numLayers;

	/**
	 * Creates a new parallax engine.
	 * 
	 * @param pScreenWidth
	 *            Screen width in pixels.
	 * @param pDx
	 *            Movement factor in pixels.
	 * @param pImages
	 *            Images used to compose parallax layers.
	 * @param pVx
	 *            Velocity for layers movement.
	 */
	public ParallaxEngine(int pScreenWidth, int pDx, Image[] pImages,
			double[] pVx, int[] gap) {
		this(pScreenWidth, pDx, pImages, pVx, new int[pImages.length], gap);
	}

	/**
	 * Creates a new parallax scrolling engine.
	 * 
	 * @param pScreenWidth
	 *            Screen width in pixels.
	 * @param pDx
	 *            Movement factor in pixels.
	 * @param pImages
	 *            Images used to compose parallax layers.
	 * @param pVx
	 *            Velocity for layers movement.
	 * @param pY
	 *            Y position to render the image layers.
	 */
	public ParallaxEngine(int pScreenWidth, int pDx, Image[] pImages,
			double[] pVx, int[] pY, int[] gap) {
		this.numLayers = pImages.length;
		this.layers = new Layer[this.numLayers];
		for (int i = 0; i < this.numLayers; i++) {
			this.layers[i] = new Layer(pScreenWidth, 0, pY[i], pImages[i],
					(int) (pVx[i] * pDx), gap[i]);
		}
	}

	/**
	 * Move layers to the right.
	 */
	public void setMoveRight() {
		for (int i = 0; i < this.numLayers; i++) {
			this.layers[i].setRightDirection();
		}
	}

	/**
	 * Move layers to the left.
	 */
	public void setMoveLeft() {
		for (int i = 0; i < this.numLayers; i++) {
			this.layers[i].setLeftDirection();
		}
	}

	/**
	 * Stop layers movement.
	 */
	public void stop() {
		for (int i = 0; i < this.numLayers; i++) {
			this.layers[i].stop();
		}
	}

	/**
	 * Move layers.
	 */
	public void move() {
		for (int i = 0; i < this.numLayers; i++) {
			this.layers[i].move();
		}
	}

	/**
	 * The display order is important. Display layers from the back to the front
	 * of the scene.
	 * 
	 * @param g
	 *            Graphics context.
	 */
	public void render(Graphics2D g) {
		for (int i = 0; i < this.numLayers; i++) {
			this.layers[i].render(g);
		}
	}

	/**
	 * Represents an horizontal parallax layer.
	 * 
	 */
	private class Layer {

		/** Image for the layer. */
		private Image image;

		/** Width of the image in pixels. */
		private int width;

		/** Height of the image in pixels. */
		private int height;

		/** Width of the screen in pixels. */
		private int screenWidth;

		/**
		 * X position in screen where the left side of the image should be
		 * drawn.
		 */
		private int x;

		/**
		 * Y position in screen where the left side of the image should be
		 * drawn.
		 */
		private int y;

		/** Number of pixels the layer will move. */
		private int dx;

		/** Gap between layers. */
		private int gap;

		/** Flag indicating if the layer is moving to the right. */
		private boolean isMovingRight;

		/** Flag indicating if the layer is moving to the left. */
		private boolean isMovingLeft;

		/**
		 * Creates a new layer at origin (0,0).
		 * 
		 * @param pScreenWidth
		 *            Screen width in pixels.
		 * @param pImage
		 *            Image for the layer.
		 * @param pDx
		 *            Movement factor in pixels.
		 */
		private Layer(int pScreenWidth, Image pImage, int pDx) {
			this(pScreenWidth, 0, 0, pImage, pDx, 0);
		}

		/**
		 * Creates a new layer in a specific (x,y) position.
		 * 
		 * @param pScreenWidth
		 *            Screen width in pixels.
		 * @param pX
		 *            The x position in the screen where the start of the image
		 *            should be drawn. It can range between -width to width-1
		 *            ,so can have a value beyond the confines of the screen
		 *            (0-pScreenWidth). 

		 *            As x varies, the on-screen layer will usually be a
		 *            combination of its tail followed by its head.
		 * @param pY
		 *            The y position in the screen where the start of the image
		 *            should be drawn.
		 * @param pImage
		 *            Image for the layer.
		 * @param pDx
		 *            Movement factor in pixels.
		 */
		private Layer(int pScreenWidth, int pX, int pY, Image pImage, int pDx,
				int gap) {
			this.screenWidth = pScreenWidth;
			this.image = pImage;
			this.width = pImage.getWidth(null);
			this.height = pImage.getHeight(null);
			this.dx = pDx;
			this.isMovingRight = false;
			this.isMovingLeft = false;
			this.x = pX;
			this.y = pY;
			this.gap = gap;
		}

		/**
		 * Make the layer move to the right.
		 */
		private void setRightDirection() {
			this.isMovingRight = true;
			this.isMovingLeft = false;
		}

		/**
		 * Make the layer move to the left.
		 */
		private void setLeftDirection() {
			this.isMovingRight = false;
			this.isMovingLeft = true;
		}

		/**
		 * Stop layer movement.
		 */
		private void stop() {
			this.isMovingRight = false;
			this.isMovingLeft = false;
		}

		/**
		 * Increment the x value depending on the movement flags. It can range
		 * between -width to width-1, which is the width of the image.
		 */
		private void move() {
			if (this.isMovingRight) {
				this.x = (this.x + this.dx) % (this.width + gap);
			} else if (isMovingLeft) {
				this.x = (this.x - this.dx) % this.width;
			}
		}

		/**
		 * Performs the layer rendering.
		 * 
		 * @param g
		 *            Graphics context.
		 */
		private void render(Graphics2D g) {
			if (x == 0) {
				// Draws image head at (0,0).
				draw(g, 0, screenWidth, 0, screenWidth);
			} else if ((x > 0) && (x < screenWidth)) {
				// Draws image tail at (0,0) and image head at (x,0).
				// Tail.
				draw(g, 0, x, width - x, width);
				// Head.
				draw(g, x, screenWidth, 0, screenWidth - x);
			} else if (x >= screenWidth) {
				// Draws only image tail at (0,0).
				draw(g, 0, screenWidth, width - x, width - x + screenWidth);
			} else if ((x < 0) && (x >= screenWidth - width)) {
				// Draws only image body.
				draw(g, 0, screenWidth, -x, screenWidth - x);
			} else if (x < screenWidth - width) {
				// Draws image tail at (0,0) and image head at (width+x,0)
				// Tail.
				draw(g, 0, width + x, -x, width);
				// Head.
				draw(g, gap + width + x, gap + screenWidth, 0, screenWidth
						- width - x);
			}
		}

		/**
		 * Actually draws the layer.
		 * 
		 * @param g
		 *            Graphics context.
		 * @param destX1
		 *            Destination X1 position in the graphics context.
		 * @param destX2
		 *            Destination X2 position in the graphics context.
		 * @param imageX1
		 *            Source X1 position of image's layer.
		 * @param imageX2
		 *            Source X2 position of image's layer.
		 */
		private void draw(Graphics2D g, int destX1, int destX2, int imageX1,
				int imageX2) {
			g.drawImage(image, destX1, y, destX2, y + height, imageX1, 0,
					imageX2, height, null);
		}
	}
}


The JAR file with full source and images is here if anyone can help:

https://docs.google.com/open?id=0B-2oyJZgQ-7IZGgyU2lBRElxMEU

Thanks!

Store positions as floats and then cast to int only when drawing, as I did in my short code snippet example.