Questions behind rationale of code,Why does my code work with ScreenToWorldPoint but not ViewportToWorldPoint?

Hi guys,

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 Screen VS Viewport What is the difference - Questions & Answers - Unity Discussions

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?

  1. 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?

  2. 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

To 1:

ScreenToWorldPoint simply projects the point from screen space to worldspace. So the resulting point is given in world space coordinates. So it represents the vector from the world origin (0,0,0) to that point (that’s what a point actually is). However what we want is a relative direction vector that goes from the center of the object to the mouse point. As you may know to get the vector between two points A and B you just subtract them from each other. If you want the vector from A to B you have to subtract A from B (head minus tail).

To 2:

Yes, normalizing in this case makes not too much sense / is unnecessary.

To3:

I think you got something the wrong way. ViewportToWorldPoint takes in a viewport position and outputs a worldspace position. So the input is in the range of 0-1 however the output ist just a world space position which could be anything depending on where the camera is located.

ScreenToWorldPoint also returns a worldspace position but the input is in screen space (0 - width / 0 - height). The mouse position is specified in screen space coordinates. So when you want to get the world space position of a screen space point you would obviously use ScreenToWorldPoint.

Though you can convert a screen space position into viewport space by doing:

float viewportX = Input.mousePosition.x / Screen.width;
float viewportY = Input.mousePosition.y / Screen.height;

So both axis (viewportX an viewportY) are in the range of 0-1 when the mouse is inside the “screen”.

Note that ScreenToWorldPoint as well as ViewportToWorldPoint actually takes in a Vector3. The z component is used to determine the distance from the camera. If you just pass Input.mousePosition z will be 0. So the resulting worldspace position will be on the same plane as the camera.