RectTransform slowly drifts away from position

I have a code for a dynamic crosshair:


using UnityEngine;

public class Crosshair : MonoBehaviour
{
    [Range(0, 100)]
    public float dilation;
    public float crosshairSpeed;
    public float margin, length;

    public RectTransform top, bottom, left, right, center;
    [SerializeField] WeaponManager weaponManager;

    float marginCached,marginChanged,baseSpreadCached;

    [SerializeField] Canvas canvas;

    void OnGUI()
    {
    
        float topValue, bottomValue, leftValue, rightValue;

        topValue = Mathf.MoveTowards(top.position.y, center.position.y + marginChanged + dilation, crosshairSpeed * Time.deltaTime);
        bottomValue = Mathf.MoveTowards(bottom.position.y, center.position.y - marginChanged - dilation, crosshairSpeed * Time.deltaTime);
        rightValue = Mathf.MoveTowards(right.position.x, center.position.x - marginChanged - dilation, crosshairSpeed * Time.deltaTime);
        leftValue = Mathf.MoveTowards(left.position.x, center.position.x + marginChanged + dilation, crosshairSpeed * Time.deltaTime);

        top.position = new Vector2(top.position.x, topValue);
        bottom.position = new Vector2(bottom.position.x, bottomValue);
        right.position = new Vector2(rightValue, right.position.y);
        left.position = new Vector2(leftValue, left.position.y);

        top.sizeDelta = new Vector2(top.sizeDelta.x, length);
        bottom.sizeDelta = new Vector2(bottom.sizeDelta.x, length);
        right.sizeDelta = new Vector2(length, right.sizeDelta.y);
        left.sizeDelta = new Vector2(length, left.sizeDelta.y);


        if (marginCached != margin || baseSpreadCached != weaponManager.weapon.baseSpread)
        {
            marginChanged = weaponManager.weapon.baseSpread + margin;
            marginCached = margin;
            baseSpreadCached = weaponManager.weapon.baseSpread;
        }

        dilation = weaponManager.spreadAmount;

    }
}

It starts like this:

But as i move, it becomes offset from the center dot:

What’s wrong in this code, and how can i prevent this from happening?

Sounds like you wrote a bug… and that means… time to start debugging!

To my understanding, crosshairs in 3D don’t move: they are placed (usually) dead center and then as the camera goes around, they are automatically in the right place.

If you mean the scale then make some anchors, such as center and “outwards a bit” in all directions you want, then use Lerp() to move between those positions.

NOTE: using OnGUI() may work just fine, but OnGUI() is an extremely old system so you may want to consider redoing it with a modern crosshair setup.

By debugging you can find out exactly what your program is doing so you can fix it.

Use the above techniques to get the information you need in order to reason about what the problem is.

You can also use Debug.Log(...); statements to find out if any of your code is even running. Don’t assume it is.

Once you understand what the problem is, you may begin to reason about a solution to the problem.

Remember with Unity the code is only a tiny fraction of the problem space. Everything asset- and scene- wise must also be set up correctly to match the associated code and its assumptions.

I’ve tried switching from OnGui() to Update() (and later LateUpdate but with the same results), and the whole thing falls apart whenever i move my screen. Except the center dot, which has no movement going on…

Here are my canvas settings:

The crosshair object setup:

]

The crosshair “sticks”:

I really don’t know what’s happening here. The camera rotation script is very simple and it doesn’t interact with the canvas.

Something’s wrong with the rectTransform movement functions on the script.
Specifically, i think it’s going on at these parts:

        topValue = Mathf.MoveTowards(top.position.y, center.position.y + marginChanged + dilation, crosshairSpeed * Time.deltaTime);
        bottomValue = Mathf.MoveTowards(bottom.position.y, center.position.y - marginChanged - dilation, crosshairSpeed * Time.deltaTime);
        rightValue = Mathf.MoveTowards(right.position.x, center.position.x - marginChanged - dilation, crosshairSpeed * Time.deltaTime);
        leftValue = Mathf.MoveTowards(left.position.x, center.position.x + marginChanged + dilation, crosshairSpeed * Time.deltaTime);

        top.position = new Vector2(top.position.x, topValue);
        bottom.position = new Vector2(bottom.position.x, bottomValue);
        right.position = new Vector2(rightValue, right.position.y);
        left.position = new Vector2(leftValue, left.position.y);

I don’t think the issue lies in the sizeDelta part, i commented these blocks out but the issue still persists, which makes me think these lines are the culprit. Changing to Mathf.Lerp didn’t help either. But honestly, i have no clue for a solution. Sweeping google didn’t help me either…

OnGUI() and Update() have absolutely NOTHING to do with each other.

If you would like to keep guessing and trying combinations, that’s great.

But if you want to save some time, see the steps above to debug and find out what piece of data (eg, which variable) is not containing the value you want.

Otherwise, as I already noted, crosshairs are a done thing, well understood, well-covered in thousands of Youtube tutorials, so build on the shoulders of giants and start there.

Two steps to tutorials and / or example code:

  1. do them perfectly, to the letter (zero typos, including punctuation and capitalization)
  2. stop and understand each step to understand what is going on.

If you go past anything that you don’t understand, then you’re just mimicking what you saw without actually learning, essentially wasting your own time. It’s only two steps. Don’t skip either step.

Imphenzia: How Did I Learn To Make Games:

I got rid of the issue!

As it turns out, i was transforming the crosshair dilation in world space coordinates. Not only does this cause the strange sliding issues, but it also makes the dilation fluctuate depending on the screen resolution, which is bad since it would appear larger on smaller resolutions, and smaller on higher resolutions.

I eventually learned that you can transform Canvas objects on anchor space instead, so what i did was changing from this:

 topValue = Mathf.MoveTowards(top.position.y, center.position.y + marginChanged + dilation, crosshairSpeed * Time.deltaTime);
        bottomValue = Mathf.MoveTowards(bottom.position.y, center.position.y - marginChanged - dilation, crosshairSpeed * Time.deltaTime);
        rightValue = Mathf.MoveTowards(right.position.x, center.position.x - marginChanged - dilation, crosshairSpeed * Time.deltaTime);
        leftValue = Mathf.MoveTowards(left.position.x, center.position.x + marginChanged + dilation, crosshairSpeed * Time.deltaTime);

        top.position = new Vector2(top.position.x, topValue);
        bottom.position = new Vector2(bottom.position.x, bottomValue);
        right.position = new Vector2(rightValue, right.position.y);
        left.position = new Vector2(leftValue, left.position.y);

To this:

topValue = Mathf.MoveTowards(top.anchoredPosition.y, center.anchoredPosition.y + marginChanged + dilation, crosshairSpeed * Time.deltaTime);
        bottomValue = Mathf.MoveTowards(bottom.anchoredPosition.y, center.anchoredPosition.y - marginChanged - dilation, crosshairSpeed * Time.deltaTime);
        rightValue = Mathf.MoveTowards(right.anchoredPosition.x, center.anchoredPosition.x - marginChanged - dilation, crosshairSpeed * Time.deltaTime);
        leftValue = Mathf.MoveTowards(left.anchoredPosition.x, center.anchoredPosition.x + marginChanged + dilation, crosshairSpeed * Time.deltaTime);

        top.anchoredPosition = new Vector2(0, topValue);
        bottom.anchoredPosition = new Vector2(0, bottomValue);
        right.anchoredPosition = new Vector2(rightValue, 0);
        left.anchoredPosition = new Vector2(leftValue, 0);

I set the values of the X and Y components of the vertical and horizontal sticks respectively to 0 since the center dot is never going to move away from the center anyway.

Also, this might be specific to my weapon algorithm, but since i changed the dilation transform operations to anchor space, the crosshair appeared more spaced out than usual. So i just had to divide the weapon spread values by two to get it back to it’s accurate size again:

   if (marginCached != margin || baseSpreadCached != weaponManager.weapon.baseSpread)
        {
            marginChanged = weaponManager.weapon.baseSpread/2 + margin;
            marginCached = margin;
            baseSpreadCached = weaponManager.weapon.baseSpread;
        }

        dilation = weaponManager.spreadAmount/2;

Anyway, i hope this helps if anyone’s having a similar issue.