UI lags behind position using WorldToScreenPoint (w/ Build, LateUpdate, and a lower execution order)

Hey everyone!

I am currently stuck with an issue. I am using WorldToScreenPoint to move waypoints, healthbars, and timers to match that of a real world position. However, there seems to be a serious issue with this method. If I turn my camera, the UI will not follow quickly enough to maintain the proper position of the UI elements. You can see it here in this GIF:

gloriousleafycaterpillar

Things I have tried:

I am using LateUpdate, I have tried resetting my camera matrix before moving the UI, I’ve lowered my script that controls the UI to execute below the script that moves my camera, I have tried using both SetPositionAndRotation as well as just setting the position manually, AND I have tried forcing the UI to update after rendering. Nothing works.

6656689--761086--upload_2020-12-24_13-36-57.png
(Just to show what I have, the ac_CharacterController script moves my camera, and the CrosshairScript moves the UI)

Things I’d like to avoid:

If possible, I’d like to avoid using multiple canvases per object. I use a separate canvas for the waypoints, healthbars, and timers anyway since updating my canvas with more stuff on it would be useless for my HUD. However, I don’t want to have to use multiple worldspace canvases since I want these UI objects to show through walls (and I think having 300 canvases would probably be less performant than just having one canvas that pools the same UI when necessary).

Some details on the bug:

  • The closer I get to the object the UI is meant to position on, the worse the lag becomes

  • The lower the framerate, the worse the lag becomes

  • It is not as prominent in Build, but it is still very much there (the GIF I recorded was from Build)

  • No other scripts are interacting with the UI or position of the object, it is static (unstatic doesn’t change results)

  • No other scripts are interacting with the canvas

The code:
----------------------------------------

Vector3 Position = MainCamera.WorldToScreenPoint(TargetPosition.transform.position);
Timer.rectTransform.SetPositionAndRotation(Position, Quaternion.identity);

----------------------------------------

I am really REALLY stuck here, and I would greatly appreciate someone working with me to find a solution to this. I have to push an update to my game here soon, and I really don’t want to update it with something this glaring in the game.

Thanks!

2 Likes

After doing all this, have you tried putting Debug.Log() statements in to prove to yourself that script execution order really is what you think it is? One handy way to timestamp stuff is to print the Time.time out along with the information, as all uses of Time.time will be the same in a given frame, so you can see that part X is before part Y, for instance.

When I update stuff positionally (vs transform-wise), and expect something else to chase it (without being parented to it), I like to explicitly call a MyUpdate() method on the other thing, to ensure that order of operation is preserved.

I also do not like using script execution order. The reason is that it lives outside of your code, and relying on it can result in you copying code from this project to another project (where script execution has NOT been set) and now the new project behaves incorrectly.

2 Likes

Just did it, and the CharacterController is executing before Crosshair. Still, the problem persists. I don’t think I’ll ever be reusing this code since I enjoy starting projects from scratch (and I have improved drastically so I’d like to make a more modular system in my next project).

Any other ideas?

I’m not sure what you’re doing differently, but here is a fully-operational non-laggy example.

Pause or single-step the video to see the black ticker always right on the world object.

Full package attached below the script below.

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

// demoing following a world object with a UI "ticker marker"
// @kurtdekker
// video of this running: https://youtu.be/OkpvdbqDVZI

public class UpdateRectTransforms : MonoBehaviour
{
    // just something to spin the target object
    public Transform Carousel;
    // the target we're watching
    public Transform Target;
    // the UI marker that will track it
    public RectTransform Ticker;

    const float CarouselRate = 500;

    Camera cam;

    void Start()
    {
        cam = Camera.main;
    }

    void UpdateCarousel()
    {
        Carousel.localRotation = Quaternion.Euler (0, Time.time * CarouselRate, 0);
    }

    void UpdateLook()
    {
        var dx = Input.GetAxis( "Mouse X"); 

        dx = (dx * 1500.0f) / Screen.width;

        cam.transform.Rotate( Vector3.up * dx);
    }

    void UpdateTicker()
    {
        var pos = cam.WorldToScreenPoint( Target.position);
        Ticker.SetPositionAndRotation( pos, Quaternion.Euler( 0, 0, 45));
    }

    void Update ()
    {
        // order is important!
        UpdateCarousel();

        UpdateLook();

        UpdateTicker();
    }
}

6656968–761116–UpdateRectTransforms.unitypackage (5.78 KB)

1 Like

I’ve read that disabling VSync can help with this, but then you have VSync disabled, which isn’t ideal.

I also found Reddit - Dive into anything,
with the relevant quote being:

“The way I do it (might not be the best way, but it works without lag for me) is: I draw all stuff like that on single canvas attachad to the camera. Every Canvas object (images) start as a child of the gameObject it references, but all of them have little script that changes their parent to the single camera canvas. This way every image keeps reference to it’s gameObject for tracking their position and status (health in my case for healthbars), but is drawn on a single camera canvas with WorldToScreenPoint. No lag experienced even on mobile.”

Okay, SO I imported this and it worked fine, but once I locked a frame rate at a lower frame rate (in this case 30) it started to kind of lag behind a bit. I literally can’t imagine why. However, even at 15 FPS in build, your scene seemed to work perfectly. Maybe in this case that frame dependence can be chalked up to an editor issue?

My game is usually running at about 90 - 120 FPS, but maybe something with having so much more going on on screen could lower the latency enough to exaggerate the issue.

The good news? I’ve managed to reduce the issue significantly.

I set the separated canvas on a higher layer than the main HUD and unparented it from my game’s base (the base doesn’t actually move, but it followed my player’s z coordinate from the original canvas, so that would explain why it’d get worse if I was closer), had the update where I did my camera movement call my crosshair script to update the UI, and adjusted some of the UI elements a bit to have less objects (from 7 to 4) and it has actually helped a significant amount.

I also did a secondary check in LateUpdate to re-adjust the position of the UI element if there was too much of a difference in world space (since the world space coordinates seem to be correct). I had that print a debug log whenever it’d have to compensate, and after doing the above mentioned things I actually have yet to get a debug log from it (so I think that means it’s about as close to correct as I’m gonna be able to get it).

Thanks for all your help!

1 Like

Similar issue with AR rendering and solution here for anyone interested:

1 Like

I know this thread is old, but I wanted to chime in because I was having the same issue and was going out of my mind because none of the solutions I found online worked. Using LateUpdate, parenting the canvas to the camera, etc. Nothing worked.

I narrowed down that the issue is specifically related to framerate. At high framerates (300+ or so) there was no lag, but at 60fps, lots of lag.

I managed to fix the issue by manually putting the script that updates the UI position at the very bottom of my script execution order in project settings. After I did that: zero lag regardless of framerate.

Thanks @Kurt-Dekker for pointing me in the right direction above explaining that the execution order matters!

6 Likes

it is weird that one has to do that… i was doing all the transforms manipulations on Update and the UI rendering in LateUpdate… one would think that would produce the same result… right?

Thank you – just want to confirm that THIS WORKED FOR ME too.

My script that updates the UI position from world position is done in LateUpdate and I put that script at the bottom of my Script Execution Order and now it is smooth and works perfectly.

9090949--1259116--upload_2023-6-20_10-14-49.png

BEFORE vs AFTER comparison
https://imgur.com/a/Sou6MUJ

5 Likes

Yes that worked for me too!

I was going to change script execution order but wanted to have look what people say and yeah the only solution is to change order.

script execution order really is what you think it is?

This really helped me. Thank you.
In my case, I just simply write in LateUpdate and it works.