0
\$\begingroup\$

I'm currently following tutorials on Youtube to learn howto use Unity. My code runs perfectly as per the tutorial, but i just have some questions regarding the code, with regards to screenToWorldPoint & viewportToWorldPoint.

The purpose of the code is for a 2D platformer, where the character's arms will rotate and face where the mouse cursor is.

Code:

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

public class ArmRotation : MonoBehaviour {

    public int rotationOffset = 0;

    // Update is called once per frame
    void Update () {
        Vector3 difference = Camera.main.ScreenToWorldPoint(Input.mousePosition) - transform.position; //Gets the difference between 
        difference.Normalize(); //Normalizing the vector

        float rotateZ = Mathf.Atan2(difference.y, difference.x) * Mathf.Rad2Deg;
        transform.rotation = Quaternion.Euler(0f, 0f, rotateZ + rotationOffset);
    }
}

I've done some googling and am aware of the difference between screenToWorldPoint and viewportToWorldPoint as per the answer here https://answers.unity.com/questions/168156/screen-vs-viewport-what-is-the-difference.html


Where

  1. Screen: from [0,0] to [Screen.width, Screen.height]
  2. Viewport: from [0,0] to [1,1]


Questions:

  1. Why do we have to calculate the difference between the position of the mouse cursor and the position of the character's arm instead of just setting the following: . Vector3 difference = Camera.main.ScreenToWorldPoint(Input.mousePosition);
    I've tried setting it to the position of the mouse cursor instead of using the difference between the arm & the cursor and while the code still works and the arm is still able to rotate, it is a little "off" from the direction of the cursor. E.g A little higher than where the cursor is. Why does using the difference solve this issue?

  2. Why do we call difference.Normalize() ? To my understanding, Normalize returns a vector between 0 & 1.However, Mathf.Atan2 returns the angle in radians whose Tan is calculated by y/x. As such, calling Normalize and returning a vector to a value between 0 & 1 makes no difference because the value calculated will be the same between a un-normalised and normalised vector since the proportions are the same?

  3. When i use ViewportToWorldPoint instead of ScreenToWorldPoint, i do not use Normalize because to my understanding, the Vector returned by ViewportToWorldPoint should already be within 0 & 1, hance we do not have to normalise it. However, when i test the code out in play mode,the arm barely rotates. Why is this so?



I would appreciate any help or pointers in the right directions.

Thanks!

EDIT: I had a look at the manual and realise that Normalize() returns a vector with the same direction, but magnitude 1, but i'm still just as confused

\$\endgroup\$
2
\$\begingroup\$

Because if you don't, it'll be wrong.

Imagine putting the mouse DIRECTLY over the character and then 1 pixel to the right (we want a non-zero vector before we normalize to avoid weirdness with trying to deal with a zero-length vector).

We'll say that this happens at 300 pixels from the left and 120 pixels from the top of the screen. Values aren't important other than to walk through both scenarios and see what happens.

So here's your code:

    Vector3 difference = Camera.main.ScreenToWorldPoint(Input.mousePosition) - transform.position; //Gets the difference between 
    difference.Normalize(); //Normalizing the vector

    float rotateZ = Mathf.Atan2(difference.y, difference.x) * Mathf.Rad2Deg;
    transform.rotation = Quaternion.Euler(0f, 0f, rotateZ + rotationOffset);

We don't need to know what (300,120) works out to after transforming it to a world point, as we said it's "1 pixel to the right" of the character. Subtracting off the character's position leaves us with (x,0) where x is that tiny fraction of a unit that corresponds to 1 pixel. Then difference.Normalize(); is called, giving us (1,0) (because Normalize gives us a unit vector with the same heading, which in our case was +1 to the right, or positive X).

Then we get to calculate rotateZ, which on the vector of (1,0) ends up being 0.099 radians (or 0.0017 degrees).

Quaternion.Euler(0f, 0f, rotateZ + rotationOffset) ends up being some rotation equal to (0,0,0.099) as rotationOffset is 0. Functionally this is pretty much (0,0,0) (rounding errors using le Google calculator) and the arm will end up pointing to the right.

Now what happens if we don't subtract?

Well, we'd end up normalizing (300,120) instead, which is in the neighborhood of (0.928, 0.371). That gets plugged into Atan and we get 0.38 (21 degrees). That's almost a quarter of the way to vertical! Our character would be pointing too high. And this gets even worse if we move our mouse to be 100 pixels to the right (he should point in the same direction, right?)

(400,120) normalizes to (0.958,0.287) which Atans to 0.29 radians. His arm moves down pointing lower. Not a lot lower, but it still moved. At the far right of the screen (1000,120) the rotation is 0.119 radians.

What about on his left?

Oh boy. So if we move 2 pixels to the left of our original position, so our mouse is 1 pixel to the left instead of 1 pixel to the right the arm's rotation (when not subtracting) works out to be...

0.38 radians

Wait what?

Yep. Exactly the same (plus or minus a few thousandths). Now your mouse is to the left and your character is pointing to the right.

Remember how (300,120) normalized to (0.928, 0.371)? Well (298,120) normalizes to pretty much the same value: (0.927, 0.373) and you wanted him to flip around a full 180 degrees!

The only way we can do that is by subtracting off the character's location:

toWorld(298,120) - pos would then result in (-x,0) and normalize to (-1,0) and be 3.14 radians (180 degrees).

| improve this answer | |
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.