Unity ScreenToWorldPoint: Overlay Canvas Clicks Land in the Wrong Place
You wire a drag handler: read Input.mousePosition, call Camera.main.ScreenToWorldPoint, drop a prefab. In the Editor’s default view it looks fine. On a vertical phone build the prefab appears in the sky. Coordinate bugs in Unity are rarely “math is hard” — they are which space am I in, and which camera owns this pixel?
Spaces you must name out loud
| Space | Origin idea | Common APIs |
|---|---|---|
| Screen | Pixels; bottom-left in Input | Input.mousePosition, touch position |
| Viewport | 0–1 on the camera | ViewportToWorldPoint |
| World | Scene units | Transform positions |
| Canvas / local UI | RectTransform | RectTransformUtility |
ScreenToWorldPoint expects a Vector3 where X/Y are screen pixels and Z is distance from the camera in world units for perspective cameras. Passing z = 0 is the classic footgun: you get a point on the camera’s near-ish plane that does not match the ground you think you clicked.
Canvas render modes change the story
Screen Space — Overlay draws on top of the scene without a world camera binding for layout. Clicking a UI button should use the EventSystem / GraphicRaycaster path, not a naïve world conversion of the button’s screen rect into gameplay coordinates unless you intentionally map HUD to world.
Screen Space — Camera ties the canvas to a camera and plane distance. Mixing Camera.main with a UI camera that is not main produces offsets that look like “random” error.
World Space canvases are transforms in the scene — treat them like other objects; raycast into them deliberately.
A reliable 3D ground click pattern
Ray ray = camera.ScreenPointToRay(screenPos);
Plane ground = new Plane(Vector3.up, Vector3.zero);
if (ground.Raycast(ray, out float enter)) {
Vector3 hit = ray.GetPoint(enter);
}
This avoids guessing Z for ScreenToWorldPoint. When you do use ScreenToWorldPoint, set Z to the depth of the target plane relative to the camera (for orthographic cameras the Z handling differs — read the camera projection you actually use).
Resolution and safe area
Notches and letterboxing change where “center of screen” sits relative to gameplay. If you support both portrait and landscape, verify conversion on each. Editor Game view aspect ≠ device aspect.
Calculate and sanity-check
For quick mental math while debugging, a screen ↔ world helper is useful alongside logging camera.ScreenToWorldPoint outputs at known corners (0,0), (Screen.width, Screen.height), and the center. If corners do not map to expected frustum edges, you have the wrong camera reference or an unexpected projection.
UI-to-world for gameplay feedback
Spawning a floating damage number at a unit’s head is a different problem from placing a building on the ground. Often you convert world → screen for HUD, then keep the follow-up in screen space. Round-tripping screen → world → screen accumulates error if cameras move or canvases scale. Prefer one authoritative space per feature.
Orthographic strategy games can use ScreenToWorldPoint with a fixed Z more safely than perspective FPSs; still verify against raycasts when colliders exist. Touch input multiplies fingers — convert each touch id independently and never reuse a stale mousePosition on mobile builds.
Editor debugging habits
Draw gizmos at converted points. Log camera name, projection, and canvas mode beside the coordinates. If two cameras are active (minimap + main), Camera.main may not be the camera that rendered the click. Tag and reference cameras explicitly in your interaction service.
Name the camera, name the plane, name the canvas mode. Most “ScreenToWorldPoint is broken” threads are missing one of those three names.