How to smoothly move a 2D camera without ghosting

Hello,

I’ve struggled with this problem for over a year, and now, nearing the end of the production cycle of our commercial game, I’m out of time and ideas.

For the life of me, I cannot smoothly move a 2D camera from one end of the screen to the other without ghosting.

Here’s a simple code sample that shows the problem:

using UnityEngine;
using System.Collections;

public class SmoothScroll : MonoBehaviour {

    [SerializeField]
    private float speed = 800.0f;

    private Vector3 position;
    private float screenRadius;

    private void Awake () {
        Application.targetFrameRate = 60;

        this.position = new Vector3 ();
        this.screenRadius = this.camera.orthographicSize * this.camera.aspect;
    }

    private void Update () {
        this.position = this.transform.position;

        this.position.x += this.speed * Time.deltaTime;

        if (this.position.x > this.screenRadius)
            this.position.x = - this.screenRadius;

        this.transform.position = this.position;
    }
}

The target platform is iOS, but the problem remains the same in standalone MacOS and Windows. Here’s a list of things I’ve tried:

  • Could be a response time issue, tried multiple screens.
  • Could be a vsync issue, tried turning it on, turning it off, setting it for every second blank.
  • Changed Application.targetFrameRate to 60, 30 and disabled.
  • Tried different types of movement, setting position directly, using translate, changing it on update, fixed update, late update, using Time.deltaTime, using Time.smoothDeltaTime, ignoring deltaTime completely (obviously, that last one was the worst)
  • Tried without a rigidbody, rigidbody with interpolation, and rigidbody with extrapolation
  • Tried moving the camera while the objects are still, tried moving the objects while the camera is still
  • Tried all settings of anti-alias
  • Could be distortion from stretching, so I tried having a pixel-perfect orthographic camera
  • Could be a floating point precision problem, so I tried using only rounded integer positions on a pixel-perfect camera (grasping at straws here)
  • Tried multiple combinations of the previous points

Nothing seems to work. I’ve been all over the web this past year searching for solutions for this problem, and I’ve come up empty.

Here’s a demo, with the same code as above, that shows the problem (MacOS and Windows standalone builds).

The problem is, there will always be interferences between the updatespeed and framerate - even with deltaTime, this is actually the Time from last Frame and with FixedUpdate, this is worse.

And some setups show the problem more than others.

What you actually would need to do, is Interpolated Fixed Timestep like it is explained in the article we all know and love: FIX YOUR TIMESTEP!.

But Unity chose to not implement this from the start and now it’s hard to implement it yourself because of how the GameObjects include the data for simulation and for the gfx effects.
One could try to copy all the positions around and implement the needed interpolation between OnPreRender and OnPostRender(or end of OnRenderImage), but this would probably mess up phyics.

Or we could try to separate invisible simulation GOs and simple visible ones that just get the transformation copied over as a first step, but then interpolates between the current and the last saved state, but that’s of course working very hard to circumvent the way Unity is designed to work… You know what, I might actually give this a try one day, just to see if it can be done.

I am at the beginning so don’t think I can help a lot, but your problem sounds very strange to me … have you tried to use Mathf.Lerp in combination with LateUpdate and Time.deltaTime?

private float currentXPosition;
//other and initialization ...

private void LateUpdate () {
 this.position = this.transform.position;
 if (this.position.x > this.screenRadius)
  this.position.x = - this.screenRadius;
 else{
  float desiredXPosition = this.screenRadius; //or other ...
  currentXPosition = Mathf.Lerp(currentXPosition , desiredXPosition, Time.deltaTime * this.speed);
 this.position.x = currentXPosition;
 }
 
 this.transform.position = this.position;
}

Or you can find many camera script controller here (3d but not is important i suppose).

Me and my friend had this issue and our case was fixed by putting both the camera and the target in FixedUpdate using .FixeddeltaTime, wish you the best of luck!