Problem with camera movement

Hi, I’m making my first videogame using Unity.

I was recently scripting the camera movement for my 2D topdown shooter game. The camera needs to make a smooth movement towards the cursor, but the players always needs to be on the screen as well and there is also a maximum look distance. So this is what I came up with:

            switch (playerInput.currentControlScheme)
            {
                case "KeyboardAndMouse":
                   
                    Vector2 mousePos = Mouse.current.position.ReadValue();
                    mousePos.x = Mathf.Clamp(mousePos.x / Screen.width, 0f, 1f) - 0.5f;
                    mousePos.y = Mathf.Clamp(mousePos.y / Screen.height, 0f, 1f) - 0.5f;

                    pointerPos = transform.position + (Vector3)(mousePos * ((isLooking) ?
                                                                             lookDistance :
                                                                            MOUSE_IDLE_LOOK_DISTANCE));

                    break;

                case "Gamepad":

                    Vector2 rightStick = Gamepad.current.rightStick.ReadValue();
                    Vector3 lookPos = Vector3.zero;
                   
                    if (isLooking)
                        lookPos = rightStick * lookDistance;
                    else
                        lookPos = ((rightStick * 10).normalized * GAMEPAD_IDLE_LOOK_DISTANCE); // Only get stick direction

                    pointerPos = transform.position + lookPos;

                    if (crosshair != null)
                    {
                        if (lookPos.magnitude > 0f)
                        {
                            if (!crosshair.gameObject.activeInHierarchy) crosshair.gameObject.SetActive(true);
                            crosshair.anchoredPosition = (Vector2)Camera.main.WorldToScreenPoint(pointerPos) - (crosshair.sizeDelta / 2);
                        }
                        else
                            if (crosshair.gameObject.activeInHierarchy) crosshair.gameObject.SetActive(false);
                    }

                    break;
            }

There must be, of course, two different systems, one for PC and one for gamepad. The gamepad part looks perfect, although I’m having a problem with the KeyboardAndMouse part.

Essentially, I get some kind of normalized 2D vector of the cursor position (for example, the top left corner of the screen is -0.5, 0.5) so that different sized resolutions don’t cause any problems with look distance. The components range from -0.5 to 0.5 instead from -1 to 1 because the player is supposed to be in the center of the screen.

Then, I add that “normalized” vector (multiplied by the look distance) to the player transform position.

Finally, in the FixedUpdate loop, the player character constantly rotates towards the pointer.

    protected override void FixedUpdate()
    {
        base.FixedUpdate();

        //Debug.Log($"{(pointerPos - Camera.main.ScreenToWorldPoint(Mouse.current.position.ReadValue())).magnitude}");

        Vector2 lookDir = pointerPos - transform.position;
        float angle = Mathf.Atan2(lookDir.y, lookDir.x) * Mathf.Rad2Deg;
        rb.rotation = angle;
    }

This works, although it has a pretty severe imprecision, and I have no idea what is causing it. It’s causing the player character to aim incorrectly. Look at the following image: the red dot is the calculated pointerPos, the green dot is the actual mouse position. They are unaligned.

After some time I managed to figure out what’s causing the problem, but I have no idea how to solve it. It’s the aspect ratio: setting the player window to 1:1 fixes the issue. Having it in rectangular ratios like 16:9 (mine) causes the aiming to be imprecise. But of course, the game needs to work on all aspect ratios.

Any suggestions?

Always my standby when I see people reinventing the camera control wheel!

Specifically:

Camera stuff is pretty tricky… you may wish to consider using Cinemachine from the Unity Package Manager.

I wanted to program the camera movement myself so that I have complete control over it. It’s perfect except for this tiny little problem.

I think it’s because at your normalization step you are dividing each axis by the respective screen dimension.

This won’t make for a normalized vector but instead an elliptical pseudo-normal.

Instead try dividing both X and Y inputs by the same amount, perhaps the larger of Screen.width or Screen.height

There may be implications to this change that affect your “max distance” decision.

Nope, doesn’t work. Completely messes up stuff. Any other ideas?

My observation is still valid:

If your mouse is right-100px and up-100px and the normalized values are NOT 0.5f / 0.5f, you’re not going to get 45 degrees out of Atan2().

IOW, 100 / Screen.width will not equal 100 / Screen.height anywhere except on a square screen.

So perhaps you have more than one bug.

I would start by feeding the very innermost parts with known hard-coded offsets such as 100 / 100 and get that working first. At least that way you can step-step-step find where the math does go wrong, such as if you get any other value than 45 degrees out, etc.

How would I put hard-coded offsets? What should I write?

Do you agree that 45 degrees SHOULD be returned when the mouse is x=100 and y=100 from center??

If so then the first thing would be mousePos = new Vector3( centerx + 100, centery + 100); to wipe out the actual live value.

Do you get 45 degrees out at the end?

Fix all the intermediate offsets and computations and fiddles and diddles until that occurs.

I’m not even sure what you consider the (0,0) of the mouse. Is it transform.position?? Unclear.

  1. I don’t know…

7771101--979713--upload_2021-12-30_22-58-4.png

You say 100 pixels right and 100 up, so about point E. I don’t really know what angle that is. If it was a square, it would be 45 degrees for sure, but with a rectangle?

  1. Yes, mousePos should be (0, 0) if the mouse is at the center. (-0.5, 0.5) is top left corner, (0.5, -0.5) is bottom right etc.

The inverse function of y = tan(x) should give you the angle.

Imagine you have a new point (K) that is aligned with both E and H.

Distance from K to H: 8
Distance from K to E: 10

8/10 = 0.8

arctan 0.8 = 38 degrees.

That’s my point.

Atan2() presumes uniform scaling on each axis such that feeding an input ratio of 1 will give you 45 degrees (eg, pi / 4).

Your mapping of mouse to these internal normalized conditions is not uniform scaling.

Therefore isn’t what I’m trying to do impossible to achieve with a rectangular screen?

So lets back up to first principles here. Why are you taking this normalized thingy anyway? That’s the actual problem.

To my view, and correct me if I’m wrong, you have the following:

  • the player’s world position
  • the mouse cursor’s world position (easy enough to get with casting or what ever)
  • a delta between those (Vector)
  • a notion of the direction the player faces (based on the delta)
  • a decision if to show it based on range (based on magnitude of delta)
  • a notion of where to draw the cursor

Where in there do you need this pre-normalized mousex/mousey ?

I need to determine the pointer position (where the character aims AND where the camera looks at) based on the cursor position. I already tried using Camera.main.ScreenToWorldPoint(Mouse.current.position.ReadValue()), but it causes a glitch where the camera continuously goes in one direction. Probably because we get the mouse world position, then the camera moves towards that direction, then we get the mouse world position again, and the camera moves again etc.
My workaround for that issue was getting that normalized cursor thingy and setting the pointerPos to the player position + the normalized cursor vector to avoid using ScreenToWorldPoint(), which caused the issue.