Parallax Scrolling in Unity 2D

It’s been a busy week both with work and with trying to get the groundwork done for my PhD (more to come on that later). But I did manage to get my parallax background code working for a Unity game I am working on.

Parallax backgrounds are made up of multiple layers each one has a different movement offset to give it the appearance of depth. The further the background image is the slower it moves.

The simplest way to do this is to make your 2D game in a 3D environment, then all your layers are basically offset via the z axis.  However, if you are creating a 2D game in a 2D environment things are a little more complex (But not very much).

To keep this as simple as possible I created a simple data structure that contains a few essential things and a couple of items that I added to reduce the amount of indirection, and to make working with it that bit easier.

    public class BackgroundImage
    {
        public GameObject backDrop;
        
        [HideInInspector]
        public Transform backDropTransform;

        public float offsetX;
        public float offsetY;
        public int numberToMake;

        // Used to make the is offscreen calcualtions faster.
        private Transform _backgroundEdgeMarker;
        private GameObject _sprite;
        private int _layer;
        public int LayerNo
        {
            get { return _layer; }
        }

        private float _width;
        public float Width
        {
            get { return _width; }
        }

        public Vector3 EdgePosition
        {
            get { return _backgroundEdgeMarker.position; }
        }

        // Cache the important bits so we can get access to them easier later. 
        public void Init()
        {
            _backgroundEdgeMarker = backDrop.GetComponent<BackgroundObjects>().edgeMarker.transform;
            _sprite = backDrop.GetComponent<BackgroundObjects>().sprite;

            _width = _sprite.GetComponent<SpriteRenderer>().bounds.extents.x;
            _layer = _sprite.GetComponent<SpriteRenderer>().sortingOrder;

            backDropTransform = backDrop.transform;
        }
    }

The main bits here are the background object (backDrop, our image) and x and y offset and a marker so we know when the background object as disappeared of the screen.

Each backDrop within the class is a prefab. These prefabs consist of three objects, the main root object, the sprite object and an object that is used to quickly get the left or right edge of the backdrop. The sprite object contains a sprite component, it is here that we set the layer order.

The order is as follows

  1. 0 layer, is our sky base colour
  2. 1 layer, is the landscape with the pylons. This will have a movement offset
  3. 3 third layer, this is the foreground image, grass soil etc. This has no offsets

A base array of these BackgroundImages is used that is presented to the inspector. Each prefab is added to this array. When the game starts I create a pool of backdrop images. This is so we do not have to keep on allocating and releasing memory for each image. Multiple copy’s of each prefab is created these are then positioned next to each other. The number of copy’s is determined via the numberToMake attribute. There needs to be enough so we don’t see any seams when an image is moved from the left side of the camera rect to the right.

After this its just applying the offsets when the camera moves.

    public void ApplyOffsets( float scale )
    {
        for (int i = 0; i != _backgroundPool.Length; ++i )
        {
            Vector3 position = _backgroundPool[i].backDropTransform.position;
            position.x += _backgroundPool[i].offsetX * scale;

            _backgroundPool[i].backDropTransform.position = position;
        }
    }

Whenever one image leaves the screen, we move that image off screen to either the left or right (depending on its edge marker position right now its only left edge). Make sure the image is snapped to the last one that is off screen.

For this to work I keep a local array each element stores the last position used for each layer. I just update this after a backdrop as been moved to its next position.

    void Update()
    {
        if ( Camera.current == null )
            return;

        for (int i = 0; i != _backgroundPool.Length; ++i )
        {
            if (UsefulScripts.IsOutsideLeftOfScreen(Camera.current, _backgroundPool[i].EdgePosition))
            {
                int layer = _backgroundPool[i].LayerNo;
                _backgroundPool[i].backDropTransform.position = _lastPositionInLayer[layer];

                _lastPositionInLayer[layer].x += _backgroundPool[i].Width;
            }
        }
    }

And that’s pretty much it. Next I hope to go over some of my PhD ideas and how I am starting the code side of the project. I also want to revisit unreal engine again soon as I really enjoy working with it. Oh there is also a blog on the use of narrative in the new Starwars game to come soon as well, this will be part of my PhD process.

Leave a comment

Your email address will not be published. Required fields are marked *