A Multi-layer Starfield

Everyone wants to grow up to be an astronaut [citation needed]. Sadly, I don’t think it is in the cards for me, so instead I am working on a little game where I can fly my own spaceship.

There was interest in how to make a parallax style starfield on the slick forums, so I figured I’d share some of my code.

package tspace.gfx;

import org.newdawn.slick.Graphics;
import org.newdawn.slick.Image;
import org.newdawn.slick.SlickException;
import org.newdawn.slick.util.Log;

/**
 * This creates a starfield effect where different layers of stars scroll by at
 * different speeds as the player moves.
 * @author twood
 *
 */
public class SimpleStars {

	private Image img;
	private int TILESIZE = 512;
	private float x, y;
	private int width, height;
	private float[] depths;

	/**
	 * Creates a new starfield
	 * @param w - width of the canvas to draw to
	 * @param h - height of canvas
	 * @param depths - an array of floats specifying the depths of the star layers.
	 */
	public SimpleStars(int w, int h, float[] depths){
		loadImage();
		width = w;
		height = h;
		this.depths = depths;
	}

	/** Load the images used for different layers of the star field.
	 *
	 * This uses the same image for all layers, but can be extended to use
	 * different ones at different depths.
	 *
	 * @param i the layer to be loaded
	 */
	public Image loadImage()
	{
		if(img == null)
		{
			try
			{
				img = new Image("res/stars.png");
			}
			catch (SlickException e) {
				Log.error("ERR loading star image", e);
			}
		}
		return img;
	}

	/**
	 * Render each layer of the starfied. Works by determining a different offset for
	 * each layer to give the effect that some are moving faster than others.
	 *
	 * @param g the graphics context to draw to.
	 */
	public void render(Graphics g)
	{
		g.setDrawMode(Graphics.MODE_ADD);
		for(int d=0; d < depths.length;d++) {

			float scale = depths[d];
			float cornerX = x/scale-width/2/scale;
			float cornerY = y/scale-height/2/scale;
			float startX = (float) (Math.floor(cornerX/TILESIZE)*TILESIZE);
			float startY = (float) (Math.floor(cornerY/TILESIZE)*TILESIZE);
			float offsetX = -cornerX;
			float offsetY = -cornerY;

			for(int row = (int) startY; row < startY+2*height; row+=TILESIZE)
			{
				for(int col = (int) startX; col < startX+2*width; col+=TILESIZE)
				{
					g.drawImage(img, col+offsetX, row+offsetY);
				}

			}
		}
		g.setDrawMode(Graphics.MODE_NORMAL);
	}

	public void setPos(float x, float y) {
		this.x = x;
		this.y = y;
	}

}

This class can be used to generate the starfield. In an init() function, you might call sometin like this to create a starfield with 5 layers:

float[] depths = {12f, 8f, 5f, 3.5f, 1f};
stars = new Starfield(container.getWidth(), container.getHeight(), depths);

Then in the update() function of your game you must call setPos(x,y) to specify the current position of the camera. Finally, you must call render(g) to actually display the graphics.

You can use this star image, or you can create your own.

How it works: The basic idea is that you have multiple star images which get layered on top of each other with different offsets.  As you move around, the front layer’s offset moves quickly, while the back layers will move more slowly since their offset is adjusted in a different ratio. This ratio is determined by the depths array. Larger numbers are used for further back layers of stars.

Note: This code assumes you are using the Slick library – to learn more about slick, go here.

Comments (2)