3
\$\begingroup\$

I have a 3D plane with "enemies", each one has it's canvas with a health bar, that follows the enemy position. When the enemy rotates, the health bar position gets laggy and jittery (you see no fluently, like a jump)

The code that I'm using is:

 private Camera _camera;
 private RectTransform _healthBarReact;

 void Start {
     _camera = GameObject.Find("Camera").GetComponent<Camera>();
     _healthBarReact = transform.FindChild("HealthBarImage").GetComponent<RectTransform>();
 }

 void Update {
       Vector3 screenPoint = _camera.WorldToScreenPoint(transform.parent.position); // the parent is the enemy, I attached the script to the child canvas "HealthBar"
     _healthBarReact.position = screenPoint;
 }

How can I fix that "lag" issue? apparently, the issue only occurs when the enemy is rotating... when it follows you in a straight line, the healthbar looks well.

\$\endgroup\$
2
  • 1
    \$\begingroup\$ couldn't you just stick the canvas to the enemy gameobject and set its rotation to identity on the LateUpdate()? \$\endgroup\$
    – Leggy7
    Commented Aug 27, 2016 at 8:50
  • \$\begingroup\$ As @Leggy7 mentioned - just put the canvas GameObject inside enemy GameObject and rotate the canvas. But you can also avoid all of this by putting both Canvas and Enemy under 1 parent GameObject. Then you can move the parent GameObject via Enemy and do the rotation of the enemy on enemy GameObject. This way canvas will move with enemy but the rotation will be applied to enemy only. \$\endgroup\$ Commented May 6, 2018 at 12:22

2 Answers 2

2
\$\begingroup\$

All UI follow functionality should be implemented in LateUpdate() method. See manual.

Example using your code:

void LateUpdate {
    Vector3 screenPoint = _camera.WorldToScreenPoint(transform.parent.position); // the parent is the enemy, I attached the script to the child canvas "HealthBar"
    _healthBarReact.position = screenPoint;
}
\$\endgroup\$
1
  • 2
    \$\begingroup\$ To give a bit more context for why this change is useful: LateUpdate runs after all the other Update scripts, and any animations that might change the positions/orientations of things in the world. So it helps avoid lag problems where your following object is constantly chasing where its lead object was instead of tracking where it is in the latest frame. \$\endgroup\$
    – DMGregory
    Commented Aug 27, 2016 at 11:29
0
\$\begingroup\$

Camera matrix update is delayed until very late in frame (probably updates just before rendering). WorldToScreenPoint relies on matrix to calculate screen position.

To make it work correctly you have to do two things:

  1. Ensure HealthBar update is called AFTER input handling (which moves camera)
  2. Force Camera matrix to be updated

The code:

using UnityEngine;

public class HealthBarPositionUpdate : MonoBehaviour
{
    public GameObject Enemy;
    public RectTransform HealthBarElement;

    // LateUpdate ensures that camera position was already updated
    private void LateUpdate()
    {
        Camera.main.ResetWorldToCameraMatrix(); // Force camera matrix to be updated
        var targetPos = Camera.main.WorldToScreenPoint(Enemy.transform.position);
        if (targetPos.z > 0)
        {
            var pos = targetPos.Round(); // Pixel snapping
            pos.z = 0;

            HealthBarElement.SetPositionAndRotation(pos, Quaternion.identity);
        }
    }
}
\$\endgroup\$

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .