Simple and flexible scrolling behavior


One of the staples of 2D games pioneered in the old school days is seamless scrolling. More specifically, having an endless scrolling behavior that seems infinite. This is usually applied to background layers, and when you combine different layers with varying speeds, you can achieve a very nice parallax effect that sells depth on a flat screen. I'll be honest – I'm a sucker for anything with sensible parallax. To me, the effect never gets old and instantly makes smaller worlds feel expansive.

The technique is very, very simple. Perhaps it's one of the first effects that many beginner game programmers are introduced to. It's also probably one of the most satisfying results you'll get in a very short amount of time.

In essence, if you create 2 copies of your scrolling layer and place them side by side, you can have them both scrolling in the intended direction, and when one of them is completely off screen, simply move it to the opposite edge. You can see the illusion illustrated here. If you pay attention to the center camera and the two hills, you can see that they appear to be seamless.

In Unity, it's simple enough to create a small behavior script that gives the game object a speed to be scrolling at, and to snap back to the other side when off screen. If you apply this script to two identical side-by-side game objects, you can get this seamless behavior. The nice thing about the approach in Unity is that you just need to translate the objects in your Update() method, but you don't need the offscreen-check performed every update. You can use the MonoBehaviour callback OnBecameInvisible() to handle this:

using UnityEngine;
using System.Collections;

public class Scrolling : MonoBehaviour
{
	public float xSpeed;
	public float ySpeed;

	void Update()
	{
		transform.Translate(new Vector3(xSpeed, ySpeed, 0f));
	}

	void OnBecameInvisible()
	{
		Vector3 newPosition = transform.localPosition;
		Vector3 viewportPoint = Camera.main.WorldToViewportPoint(transform.position);
		
		// move to opposite edge of screen (horizontally)
		if (xSpeed != 0 && (viewportPoint.x < 0f || viewportPoint.x > 1f))
		{
			newPosition.x = -transform.localPosition.x;
		}

		// move to opposite edge of screen (vertically)
		if (ySpeed != 0 && (viewportPoint.y < 0f || viewportPoint.y > 1f))
		{
			newPosition.y = -transform.localPosition.y;
		}

		// move to the new position
		transform.localPosition = newPosition;
	}
}