Endless 2D background in Unity

Tulenber 3 April, 2020 ⸱ Intermediate ⸱ 7 min ⸱ 2019.3.7f1 ⸱

In this post, we will look at how you can apply the Dark Matter Engine approach to create an infinite 2D background.

If you used public transport, you might have noticed that one of the most popular mobile game genres between passengers is the Endless runners, for example, Subway Surfers. The main feature of this genre is an infinitely moving element (character or another object), which usually independently moves forward towards obstacles, and control is given only to avoid these obstacles. Infinite movement implies infinite space available for the player’s movement, the creation of which is not an easy task, as it might seem at first glance. With this post, we open a cycle in which we consider the elements that will help create such infinite worlds.

If you search the Internet for information on endless spaces for Unity, then one of the most popular tutorials will be about the creation of infinite background objects for sidescrollers. In our case, we will complicate the task a bit and try to make an endless background for the top-down camera. As an example, let’s implement a model of a spaceship that moves across an infinite outer space background.

Issue

The first thing that comes to mind when someone asks you to make a ship that moves in space is to add a ship model, set a speed vector for it, attach a camera, put a background, and start moving the ship along the vector multiplied by Time.deltaTime. You can throw away obvious things with limited background sizes, which in any case, have to be solved. The main problem of infinite movement will be the Unity coordinate space, which tied to variables of the float type, and accuracy will decrease with increasing coordinates. For a more accurate description of the problem, you can watch this video "64 Bit In Kerbal Space Program.“

Dark Matter Engine

To solve this issue, you can use the approach by which operates the Dark Matter Engine from Futurama. Briefly describe the principle of its action, the Engine does not move the ship, but the space around the ship. In our case, instead of moving ship along the velocity vector, we will move all objects around it in the opposite direction. So, if our camera attached to the spaceship, then we will always remain at the origin, and our accuracy will not fall due to the increase in distance. Also, in case of infinite backgrounds, choose our position as origin looks like a more elegant solution than snap to any other point in space.

Objective

Make the spaceship move along an endless 2D background. The ship can rotate around the Z-axis by clicking on the right/left side of the screen. The camera always aligned with the ship’s motion vector.

Solution

I will not distribute the assets used in this example. Finding the right resources for you is also a lot of work, and I suggest you practice this, if you don’t know where to start, you can visit the free itch.io or search the Internet for "Free game assets.“

We need a texture for the ship and texture for the background. The main requirement for the background texture is that it is repeatable in both vertical and horizontal directions.

  1. Add the ship and background textures to the project, for the background you need to set Mesh type - Full rect and Wrap mode - Repeat
    Add sprites
  2. Create an object structure
    Object structure
  3. Add texture to the ship
    Spaceship
  4. Add texture for the background, set Draw mode - Tiled and Order in Layer = -1
    Add scenes
  5. Create a UniverseHandler script, add it to the Universe object
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
using System;
using UnityEngine;

public class UnevirseHandler : MonoBehaviour
{
    // References to scene objects
    [SerializeField] private Camera mainCamera = null;
    [SerializeField] private GameObject ship = null;
    [SerializeField] private GameObject space = null;
    
    // The radius of a possible camera view
    private float _spaceCircleRadius = 0;

    // Original background object dimensions
    private float _backgroundOriginalSizeX = 0;
    private float _backgroundOriginalSizeY = 0;

    // Direction of travel
    private Vector3 _moveVector;
    // Turning speed in radians
    private float _rotationSpeed = 1f;

    // Helper Variables
    private bool _mousePressed = false;
    private float _halfScreenWidth = 0;
    
    void Start()
    {
        // Starting direction
        _moveVector = new Vector3(0, 1.5f, 0);
        // Used to determine the direction of rotation
        _halfScreenWidth = Screen.width / 2f;
        
        // Original Background Sizes
        SpriteRenderer sr = space.GetComponent<SpriteRenderer>();
        var originalSize = sr.size;
        _backgroundOriginalSizeX = originalSize.x;
        _backgroundOriginalSizeY = originalSize.y;

        // Camera height equal to the orthographic size
        float orthographicSize = mainCamera.orthographicSize;
        // Camera width equals to orthographic size multiplied by aspect ratio
        float screenAspect = (float)Screen.width / (float)Screen.height;
        // The radius of the circle describing the camera
        _spaceCircleRadius = Mathf.Sqrt(orthographicSize * screenAspect * orthographicSize * screenAspect + orthographicSize * orthographicSize);

        // The final background sprite size should allow you to move by one basic background texture size in any direction + overlap the radius of the camera also in all directions
        sr.size = new Vector2(_spaceCircleRadius * 2 + _backgroundOriginalSizeX * 2, _spaceCircleRadius * 2 + _backgroundOriginalSizeY * 2);
    }

    void Update()
    {
        // Changing the direction of movement by clicking the mouse button
        if (Input.GetMouseButtonDown (0)) {
            _mousePressed = true;
        }

        if (Input.GetMouseButtonUp(0))
        {
            _mousePressed = false;
        }
        
        if (_mousePressed)
        {
            // The direction of rotation is determined depending on the side of the screen on which the click occurred
            int rotation = Input.mousePosition.x >= _halfScreenWidth ? -1 : 1;

            // Calculation of rotation of the direction vector
            float xComp = (float)(_moveVector.x * Math.Cos(_rotationSpeed * rotation * Time.deltaTime) - _moveVector.y * Math.Sin(_rotationSpeed * rotation * Time.deltaTime));
            float yComp = (float) (_moveVector.x * Math.Sin(_rotationSpeed * rotation * Time.deltaTime) + _moveVector.y * Math.Cos(_rotationSpeed * rotation * Time.deltaTime));
            _moveVector = new Vector3(xComp, yComp,0);

            // Rotate the sprite of the ship and camera along the direction vector
            float rotZ = Mathf.Atan2(_moveVector.y, _moveVector.x) * Mathf.Rad2Deg;
            ship.transform.rotation = Quaternion.Euler(0f, 0f, rotZ - 90);
            mainCamera.transform.rotation = Quaternion.Euler(0f, 0f, rotZ - 90);
        }

        // Move the background in the opposite direction
        space.transform.Translate(-_moveVector.x * Time.deltaTime, -_moveVector.y * Time.deltaTime, 0);

        // When the background reaches a shift equal to the original size in any direction, we return it to the origin exactly in this direction
        if (space.transform.position.x >= _backgroundOriginalSizeX)
        {
            space.transform.Translate(-_backgroundOriginalSizeX, 0, 0);
        }
        if (space.transform.position.x <= -_backgroundOriginalSizeX)
        {
            space.transform.Translate(_backgroundOriginalSizeX, 0, 0);
        }
        if (space.transform.position.y >= _backgroundOriginalSizeY)
        {
            space.transform.Translate(0, -_backgroundOriginalSizeY, 0);
        }
        if (space.transform.position.y <= -_backgroundOriginalSizeY)
        {
            space.transform.Translate(0, _backgroundOriginalSizeY, 0);
        }
    }
    
    private void OnDrawGizmos()
    {
        // Circle describing the camera
        UnityEditor.Handles.color = Color.yellow;
        UnityEditor.Handles.DrawWireDisc(Vector3.zero , Vector3.back, _spaceCircleRadius);

        // Direction of travel
        UnityEditor.Handles.color = Color.green;
        UnityEditor.Handles.DrawLine(Vector3.zero, _moveVector);
    }
}
  1. Link Main camera, Ship and Space to Universe object
    Univerce handler
  2. Enjoy the result
    Result

Result

As you can see in the resulting GIF, the background object returns to origin by axis in which happen shift by the initial size, so we achieve a seamless movement.

Alternatives

There are no ideal solutions in the programming area. To solve any task programmer must choose between a relatively large selection of methods. In our case, you also can solve the task in several ways. For example, instead of a sprite, you can make an object of type quad and set up a material that also uses a tile structure to control its offset, which will save us from moving the object at all. If we return to the more classical version, then we can combine the usual movement of the object and the shift of the center of coordinates for the entire scene upon reaching some limits.

Conclusion

When we think about creating infinite spaces, the Dark Matter Engine approach is the leading way to circumvent precision constraints for Unity. The shift of the center of coordinates combined with ordinary movements can also belong to the same group. The above example is perfect for creating backgrounds and the parallax effect. However, the development of a real unlimited space requires working with more complex objects and mechanics. We will consider these techniques in the following articles of the cycle of endless worlds. See you next time! =)



Privacy policyCookie policyTerms of service
Tulenber 2020