Unity ScreenToWorldPoint: click lệch world khi đổi độ phân giải【studio VN】

(Cập nhật: 19 tháng 7, 2026 ) Unity ScreenToWorldPoint tọa độ game dev Canvas mobile
Kết luận

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 ScreenToWorldPointScreenPointToRay
  • 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 tọa độ Unity
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:

  1. Log (0,0), (Screen.width, Screen.height), tâm — map có khớp frustum mong đợi?
  2. Đổi aspect 9:16 và 16:9 trong Game view
  3. So sánh camera.pixelWidth/Height với resolution tool giả lập
  4. 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.ScreenPointToWorldPointInRectangle hoặ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, canvas renderMode
  • Hai camera active (minimap + main): đừng giả định Camera.main là camera render click
  • Tag và inject camera trong interaction service — tránh FindObjectOfType lúc runtime khi scene load additive

Cách dùng nhanh trên Kawa Dev Tools

  1. Mở Chuyển đổi tọa độ Unity
  2. Điền FOV, Width/Height, vị trí và góc camera giống scene
  3. Screen → World: nhập pixel + depth → đối chiếu với camera.ScreenToWorldPoint
  4. World → Screen: nhập vị trí world → xem pixel / depth; điểm sau camera sẽ được báo rõ
  5. Đổ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.


Liên kết liên quan