Note: The background sprites in this blog post were downloaded from opengameart: Forest Background by ansimuz
In the game that I’m currently working on I need an infinite scrolling parallax background behind the player. Because the camera needs to be orthagraphic but support parallax scrolling, I decided to make the world move while the player stands still. Everything was going great until I noticed this:
I had a damn gap appearing between my tiles – and it wasn’t all the tiles, in fact my foreground seemed to work ok. It seemed to happen more often with very slow moving sprites. After much research I realized that the problem was occurring because of float precision errors in Unity. Basically this coordinate:
would move a few frames and then explode into something like this:
The tile at the front of the queue that’s moving ends up with .4599998, but it still has to render to a single pixel on the screen. The camera has to take a guess how to render this and either rounds the position up or down, placing the sprite one pixel too far or short, creating a gap between the tiles. In many 2D only game frameworks you don’t notice this issue because the world unit to pixel ratio is already 1:1, so it’s easier to move around consistently.
My first solution was to round all coordinates down to 3 decimal places – it seemed like an easy fix. However, the gap started to appear again and I realized that I was just now doing in code what the camera was doing when it rendered anyway – taking a precision error and then trying to guess if it should be rounded up or down.
After 3 days of faffing about with different techniques I came up with this solution:
My orthographic camera has a ratio of 1 Unity world unit to 100 pixels (note: You can find more about getting your orthographic scale here). What I realized was that for a sprite to move a single pixel on the screen the absolute minimum distance it can travel in Unity is 0.01 world units. As long as I constrained my background movement to move only in multiples of 0.01 I would avoid any rounding or float precision errors. After all, if something lands in 10.672, how does the camera reliably render that at .672 of a pixel?
I changed my movement code to simply use 0.01 as the minimum distance a sprite can travel, which seemed to work great except for one tiny issue:
It’s way too fast! He’s supposed to be running but not that quick! The problem now is that 0.01 as a minimum distance isn’t small enough. If the backgrounds move that 1 pixel every frame, that’s still 60 pixels being covered every second. The solution was to take the movement code and place it into a Coroutine that runs at different intervals for different layers. For the background, they only shift one pixel every 0.05 seconds, while the foreground moves every 0.03 seconds.
public float MovementSpeed;
public float MovementDelay;
void Start ()
IEnumerator WaitAndMove(float waitTime)
yield return new WaitForSeconds(waitTime);
transform.position = new Vector3(transform.position.x + (MovementSpeed), transform.position.y, transform.position.z);
Very simple but very effective. One drawback with this technique (which is less to do with the technique and just pixel based movement) is that you can’t move the sprites with anything that uses floats as its values – this includes transform.Translate, moving using a velocity or multiplying movement by delta time.
If you need pixel based movement in Unity, make sure you are only moving in whole pixel amounts based on your orthographic camera size.
Some final notes
If you find that the pixel based movement for very slow backgrounds is too jerky, you can get sub-pixel movement by setting the move distance to half your orthographic camera size (eg. 0.005 instead of 0.01) and then enable bilinear filtering on your sprites. You will lose the pixel-perfect outline of your sprites, but if your using this on far away objects (eg. clouds) the player probably wont even notice.
Also, pixel-based movement will always be jerkier than sub-pixel world based movement (which can lerp between pixels without just jumping from point A to point B in a single frame). It’s not a silver bullet and it sometimes takes some experimentation to see if the effect is worth it. If you are using pixel based movement however, turning on vsync under the quality settings will also help prevent too much sprite-jumping, owing to the fact that you can’t use time.deltatime to smooth our your frame-to-frame movement.