- Home /
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 demo link is wrong, here you go: link
Any help will be greatly appreciated. Any miraculous solution will have me start a new religion around you.
Have you tried using coroutines or using the LateUpdated to update your camera? What's the camera configuration? It seems that the next frame is rendering while the previous frame is playing.
On the actual game I'm using LateUpdate to move my camera, so everything else has finished moving before the camera does. On the demo above, however, only the camera moves. So I have the script above coupled to my orthographic camera, and all it's doing is moving horizontally forever. I agree that the next frame seems to be rendering before the previous frame is done, but shouldn't vsync take care of that? I also thought it might be a response time problem, so I tried on a screen with 2 ms response, but the problem is still there.
Really? An exe? You expect everyone to execute some random exe from a stranger on the internet? Provide a unitypackage that shows the problem, if the code above isn't enough.
Your code could start with OpenPandorasBox() for all I know...
That link looks promising @Happy$$anonymous$$oo, I will try that, thank you.
Answer by HappyMoo · Jan 14, 2014 at 02:48 AM
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.
Deadlines started hitting me hard, and this problem got postponed for later on. I'm considering this to be the right answer, not because I confirmed it fixed my problem, but because I believe this is the most probable solution. If anyone runs into the same problem and finds this page, my recommendation is to follow what Happy$$anonymous$$oo suggested.
Thanks to Happy$$anonymous$$oo, and to everyone else who tried to help.
I tried implementing your solution, because I was curious. But it didn't work.
I think the issue is more related to Unity's basic drawing backend. It seems like a refresh-rate issue, or an optical illusion. Not jitters like an execution order issue, nor incorrect speed, like a time-step issue would cause. A render-texture looks fine when attached, as does a paused game-screen in the editor.
I don't honestly know how to fix this, but I'm casting a vote against this being the solution. I've also tried fixing it with execution order changes, render quality changes, etc.
Basically, fast moving things ghost in Unity. Could just be the screen refresh-rate... AFAI$$anonymous$$ there's no way to fix it but to use a motionblur post-effect, or pre-rendered animation.
You implemented fixed timesteps with blending between current and last state based on the remainder frametime? Do you have a generic solution or just for one object. Can we see the code?
So feel free to correct me if I did this wrong. I'd love the input, and love more to truly crack this case. It was kind of a rushed job, frankly... States are just Vector3's for this example.
Here's the OP's class mixed with what you posted.
EDIT: Hmmm I may have glossed over the integration part of the article...
using UnityEngine;
using System.Collections;
public class SmoothScroll : $$anonymous$$onoBehaviour {
[SerializeField]
private double speed = 1.0f;
private float screenRadius;
private double t = 0.0;
private const double dt = 0.01;
private double currentTime = 0.0;
private double accumulator = 0.0;
private Vector3 previousState;
private Vector3 currentState;
private void Awake () {
Application.targetFrameRate = 60;
this.currentState = new Vector3 ();
this.previousState = new Vector3 ();
this.currentTime = 0.0;
this.screenRadius = this.camera.orthographicSize * this.camera.aspect + 500.0f;
}
private Vector3 UpdateCurrentPosition( Vector3 current, double time, double deltaTime )
{
current.x += (float)(this.speed * deltaTime);
if (current.x > this.screenRadius)
current.x = - this.screenRadius;
return current;
}
/*
private void OnGUI () {
GUILayout.Label (this.speed.ToString ());
}
*/
private void LateUpdate ()
{
double newTime = Time.time;
double frameTime = newTime - currentTime;
if ( frameTime > 0.25 )
frameTime = 0.25; // note: max frame time to avoid spiral of death
currentTime = newTime;
accumulator += frameTime;
while ( accumulator >= dt )
{
previousState = currentState;
currentState = UpdateCurrentPosition( currentState, t, dt );
t += dt;
accumulator -= dt;
}
float alpha = (float)(accumulator / dt);
Vector3 position = currentState*alpha + previousState*( 1.0f - alpha );
this.transform.position = position;
}
}
thanks, will take a look later, because I want to take time to look in detail into it and what's happening ti$$anonymous$$g-wise. $$anonymous$$aybe someone with access to the unity profiler who knows how to use it can also have a look.
Answer by giudark · Jan 13, 2014 at 02:06 PM
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).
$$anonymous$$oving the camera on LateUpdate is great if you have other moving objects in the scene, because then the camera only moves when the other objects finished moving. On the demo above, however, only the camera moves (the SmoothScrolling script is attached to the main camera). $$anonymous$$athf.Lerp is a good way to move from A to B smoothly over a consistent amount of time regardless of the distance between A and B, but because on the demo above the camera moves forever, it seems simpler to just add "Time.deltaTime * speed" to the position. I tried $$anonymous$$athf.Lerp anyway, just in case there was something I was missing, but sadly it didn't change anything.
Thank you for the link, but basically all the camera controllers at their core have to change it's position somehow. Regardless of whether they're moving it with Translate, $$anonymous$$athf.Lerp, Smooth.Damp [..], at the end of the day all they do is move a specific amount of distance multiplied by the time that has passed since the last frame (Time.deltaTime). So the code above is, as far as I know, the simplest way a camera can move smoothly, changing the calculation of the movement to a more complex solution won't solve this (though I have tried it over this past year :/ ).
Answer by BenIvory · Oct 23, 2018 at 07:51 PM
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!
Your answer
Follow this Question
Related Questions
smooth follow 2d camera runner ios 0 Answers
The sprite that is being followed by thr camera is very jittery and doesn't look very nice at all. 2 Answers
Problems with 2D Camera Shaking 3 Answers
Unity smooth following 2D camera not working properly on different resolutions 0 Answers
2d camera help! 0 Answers