Screenspace to Worldspace with Perspective Direction

With Block Fuse, I spent a very long time trying to work out how to launch a projectile exactly where the player clicks. Figuring out the worldspace position from screenspace was as simple as using Camera.ScreenToWorldPoint. The main challenge comes when the direction the projectile should be thrown created by the field of view must be taken into account.

ScreenToWorldPoint is dependent on the distance variable because the point is multiplied by the distance. So that should make you wonder, “How do we find the distance the ball should be spawned when we only know the screenspace of the point?”

The diagram below shows the problem visually. From the point of view (pov), positions p1 will appear to be in the exact screen location as p2; however, p2 will appear relatively smaller. Also, note the distances d1 and d2 vary in length.

Using these two positions sampled from various distances from screenspace to worldspace, we can find the direction. The first step is find the distance between the points, which can be done by subtracting the two points. The next step is to then normalize the distance, and there, we have a vector which will set the projectile in the correct direction.

Vector3 m = Input.mousePosition;
Vector3 point1 = Camera.main.ScreenToWorldPoint(new Vector3(m.x, m.y, 1f));
Vector3 point2 = Camera.main.ScreenToWorldPoint(new Vector3(m.x, m.y, 3f));
Vector3 direction = point2 - point1;
Vector3 normalizedDirection = direction.normalized;

To actually assign this perspective direction correction to the projectile, we can simply assign it as the initial velocity. The projectile can be thrown faster by multiplying the velocity. The exact number would be based around your game's scale. Bear in mind, the velocity is represented internally as metres per second.

End Result

Vector3 m = Input.mousePosition;
// Get two samples of where the mouse is in world space at varying distances
Vector3 point1 = Camera.main.ScreenToWorldPoint(new Vector3(m.x, m.y, 1));
Vector3 point2 = Camera.main.ScreenToWorldPoint(new Vector3(m.x, m.y, 3));
// Spawn a projectile, in this case a ball at point1 with default (zero) rotation
GameObject ball = Instantiate(projectile, p1, Quaternion.identity) as GameObject;
// By subtracting both positions, we get the direction the projectile should travel at
ball.rigidbody.velocity = (point2 - point1).normalized * velocity;
Written on November 21, 2012