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.
Add the ship and background textures to the project, for the background you need to set Mesh type - Full rect and Wrap mode - Repeat
Create an object structure
Add texture to the ship
Add texture for the background, set Draw mode - Tiled and Order in Layer = -1
Create a UniverseHandler script, add it to the Universe object
usingSystem;usingUnityEngine;publicclassUnevirseHandler:MonoBehaviour{// References to scene objects
[SerializeField]privateCameramainCamera=null; [SerializeField]privateGameObjectship=null; [SerializeField]privateGameObjectspace=null;// The radius of a possible camera view
privatefloat_spaceCircleRadius=0;// Original background object dimensions
privatefloat_backgroundOriginalSizeX=0;privatefloat_backgroundOriginalSizeY=0;// Direction of travel
privateVector3_moveVector;// Turning speed in radians
privatefloat_rotationSpeed=1f;// Helper Variables
privatebool_mousePressed=false;privatefloat_halfScreenWidth=0;voidStart(){// Starting direction
_moveVector=newVector3(0,1.5f,0);// Used to determine the direction of rotation
_halfScreenWidth=Screen.width/2f;// Original Background Sizes
SpriteRenderersr=space.GetComponent<SpriteRenderer>();varoriginalSize=sr.size;_backgroundOriginalSizeX=originalSize.x;_backgroundOriginalSizeY=originalSize.y;// Camera height equal to the orthographic size
floatorthographicSize=mainCamera.orthographicSize;// Camera width equals to orthographic size multiplied by aspect ratio
floatscreenAspect=(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=newVector2(_spaceCircleRadius*2+_backgroundOriginalSizeX*2,_spaceCircleRadius*2+_backgroundOriginalSizeY*2);}voidUpdate(){// 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
introtation=Input.mousePosition.x>=_halfScreenWidth?-1:1;// Calculation of rotation of the direction vector
floatxComp=(float)(_moveVector.x*Math.Cos(_rotationSpeed*rotation*Time.deltaTime)-_moveVector.y*Math.Sin(_rotationSpeed*rotation*Time.deltaTime));floatyComp=(float)(_moveVector.x*Math.Sin(_rotationSpeed*rotation*Time.deltaTime)+_moveVector.y*Math.Cos(_rotationSpeed*rotation*Time.deltaTime));_moveVector=newVector3(xComp,yComp,0);// Rotate the sprite of the ship and camera along the direction vector
floatrotZ=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);}}privatevoidOnDrawGizmos(){// 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);}}
Link Main camera, Ship and Space to Universe object
Enjoy the 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! =)