Unity ScreenToWorldPoint: click lệch world khi đổi độ phân giải【studio VN】
Bug tọa độ Unity gần như không phải “toán khó” — mà là đang đứng ở không gian nào, và pixel này thuộc camera nào. ScreenToWorldPoint cần X/Y pixel + Z = depth từ camera; z = 0 là lỗi kinh điển. Canvas Overlay ≠ Camera. Click mặt đất 3D nên ưu tiên ray + plane. Khi Editor và build mobile lệch, đối chiếu FOV / resolution / camera bằng công cụ Screen ↔ World rồi mới sửa script.
Bạn gắn drag handler: đọc Input.mousePosition, gọi Camera.main.ScreenToWorldPoint, spawn prefab. Trong Game view mặc định ổn. Build dọc trên điện thoại — prefab bay lên trời. Studio outsourcing và freelance Unity ở Việt Nam gặp case này hàng tuần: client giao build portrait, máy QA dùng tablet landscape, lead chỉ xem Editor 16:9.
Bài này tập trung Screen ↔ World cho workflow studio / indie VN: đặt tên không gian, chọn API đúng theo canvas, pattern ground click, checklist resolution, và cách sanity-check nhanh trên trình duyệt mà không cần mở lại scene mỗi lần nghi ngờ số.
Bài viết này giúp bạn
- Phân biệt Screen / Viewport / World / Canvas local
- Hiểu vì sao Z depth quyết định
ScreenToWorldPoint - Chọn giữa ScreenToWorldPoint và ScreenPointToRay
- Tránh nhầm Overlay / Camera / World Space canvas
- Checklist khi Editor đúng, device sai
- Đối chiếu số với công cụ chuyển đổi tọa độ Unity
Bốn không gian phải gọi tên to
Trước khi sửa code, team nên trả lời cùng một câu: pixel này thuộc không gian nào?
| Không gian | Gốc ý niệm | API thường dùng |
|---|---|---|
| Screen | Pixel; Input gốc góc dưới trái | Input.mousePosition, touch position |
| Viewport | 0–1 theo camera | ViewportToWorldPoint |
| World | Đơn vị scene | Transform.position |
| Canvas / UI local | RectTransform | RectTransformUtility |
ScreenToWorldPoint nhận Vector3: X/Y là pixel màn hình, Z là khoảng cách từ camera (world unit) với camera perspective. Truyền z = 0 → điểm nằm sát mặt phẳng camera, không khớp ground bạn tưởng đã click.
Orthographic (chiến thuật top-down) thường chịu Z cố định tốt hơn FPS perspective — nhưng vẫn nên đối chiếu raycast khi có collider.
Canvas render mode đổi toàn bộ câu chuyện
Screen Space — Overlay
Vẽ trên scene, không gắn world camera cho layout theo cách Camera mode. Click nút UI → EventSystem / GraphicRaycaster. Đừng lấy screen rect của button rồi ScreenToWorldPoint rồi mong character gameplay đứng đúng chỗ HUD, trừ khi feature cố ý map HUD → world.
Screen Space — Camera
Canvas gắn một camera và plane distance. Mix Camera.main với UI camera không phải main → offset trông như “lệch ngẫu nhiên”. Log tên camera của canvas.
World Space
Canvas là transform trong scene — xử lý như object khác; raycast có chủ đích.
Quy tắc nhanh cho sprint review: tên camera + tên plane + tên canvas mode. Hầu hết thread “ScreenToWorldPoint bị hỏng” thiếu một trong ba tên đó.
Pattern click mặt đất 3D đáng tin
Đoán Z cho ScreenToWorldPoint dễ sai khi camera nghiêng hoặc ground không vuông góc view. Pattern ổn định:
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);
}
Khi vẫn dùng ScreenToWorldPoint, đặt Z = depth của mặt phẳng mục tiêu tính từ camera (không phải Z world tuyệt đối của object). Orthographic khác perspective — đọc đúng projection camera đang dùng.
Touch trên mobile (thị trường VN hay ship Android)
Mỗi touch.fingerId convert riêng. Đừng tái dùng mousePosition cũ trên build mobile. Multi-touch + safe area + notch = ba nguồn lệch độc lập — checklist QA nên tách ba mục.
Resolution, aspect và safe area
Notch và letterbox đổi vị trí “tâm màn hình” so với gameplay. Hỗ trợ portrait + landscape → verify conversion trên từng aspect. Game view Editor ≠ aspect thiết bị.
Checklist trước merge interaction:
- Log
(0,0),(Screen.width, Screen.height), tâm — map có khớp frustum mong đợi? - Đổi aspect 9:16 và 16:9 trong Game view
- So sánh
camera.pixelWidth/Heightvới resolution tool giả lập - Nếu có safe area: convert sau khi đã tính inset, không hard-code 100px từ máy lead
Khi số trong log “trông lạ”, mở công cụ Screen ↔ World trên trình duyệt: nhập FOV vertical, resolution, vị trí / pitch / yaw camera, rồi Screen→World với depth bạn đang truyền trong script. Nếu góc màn hình không khớp cạnh frustum kỳ vọng → sai camera reference hoặc sai projection, chưa phải bug prefab.
Công cụ chạy local trên trình duyệt — phù hợp khi pair remote với freelancer: gửi bộ số FOV + res + cam, đối phương đối chiếu không cần clone repo.
Case study 1 — Overlay HUD spawn VFX vào world
Triệu chứng: Bấm icon skill trên Overlay canvas, VFX xuất hiện sau lưng camera hoặc lệch nửa màn hình trên build 1080×2340.
Nguyên nhân thường gặp: Lấy rectTransform.position (không gian canvas) nhầm thành screen pixel, hoặc ScreenToWorldPoint với Camera.main trong khi gameplay dùng camera khác.
Hướng xử lý:
- UI feedback thuần HUD → giữ screen space (follow bằng world→screen một chiều)
- Cần điểm world thật →
RectTransformUtility.ScreenPointToWorldPointInRectanglehoặc ray từ camera gameplay, không từ UI camera Overlay - Một feature một không gian authoritative; round-trip screen→world→screen tích lũy sai khi camera move / canvas scale
Case study 2 — Studio VN ship portrait, Editor 16:9
Triệu chứng: Đặt building bằng drag trên ground — Editor ổn, APK portrait lệch theo trục dọc.
Nguyên nhân: Depth Z lấy từ khoảng cách camera–ground đo trên scene 16:9; aspect đổi làm ray cắt plane khác vị trí XZ dù cùng pixel tương đối.
Hướng xử lý: Đổi sang ray–plane; thêm test scene aspect 9:16 trong CI checklist thủ công; ghi FOV + camera height vào design doc để artist / programmer cùng một số.
World → Screen cho damage number
Spawn số damage bay trên đầu unit khác đặt building trên đất. Thường convert world → screen rồi animate trên HUD. Prefer một chiều: world là source of truth, screen chỉ để vẽ.
World→Screen trên công cụ Kawa giúp ước lượng nhanh pixel trước khi viết coroutine; nếu điểm báo “(sau camera)” thì object đang sau mặt phẳng gần — đúng với log Unity khi WorldToScreenPoint cho depth âm / sau camera.
Thói quen debug trong Editor
- Vẽ gizmo tại điểm đã convert
- Log cạnh tọa độ:
camera.name,orthographic, canvasrenderMode - Hai camera active (minimap + main): đừng giả định
Camera.mainlà camera render click - Tag và inject camera trong interaction service — tránh
FindObjectOfTypelúc runtime khi scene load additive
Cách dùng nhanh trên Kawa Dev Tools
- Mở Chuyển đổi tọa độ Unity
- Điền FOV, Width/Height, vị trí và góc camera giống scene
- Screen → World: nhập pixel + depth → đối chiếu với
camera.ScreenToWorldPoint - World → Screen: nhập vị trí world → xem pixel / depth; điểm sau camera sẽ được báo rõ
- Đổi resolution sang kích thước device QA rồi lặp lại góc và tâm
Không thay Unity Debugger — nhưng cắt được vòng “đoán Z → Play → dừng → sửa” khi chỉ cần xác nhận công thức.
FAQ nhanh
ScreenToWorldPoint bỏ qua mouse X/Y?
Thường Z = 0. Truyền khoảng cách camera → plane gameplay.
Overlay và Camera canvas dùng chung code?
Không. Match API với render mode.
Editor đúng, máy thật sai?
Aspect / safe area / camera reference — test nhiều độ phân giải.