Unity Camera WorldToScreenPoint κ°€μ΄λ“œ

Camera.WorldToScreenPoint λ©”μ†Œλ“œ κ°œμš”

Camera.WorldToScreenPointλŠ” μ›”λ“œ μ’Œν‘œ(World Position)λ₯Ό 슀크린 μ’Œν‘œ(Screen Position)둜 λ³€ν™˜ν•˜λŠ” μœ λ‹ˆν‹°μ˜ λ©”μ†Œλ“œμž…λ‹ˆλ‹€. μ΄λŠ” 3D μ›”λ“œ 곡간에 μžˆλŠ” νŠΉμ • 지점(예: κ²Œμž„ 였브젝트의 μœ„μΉ˜)을 μΉ΄λ©”λΌμ˜ 뷰포트λ₯Ό κΈ°μ€€μœΌλ‘œ 2D 슀크린 곡간에 λŒ€μ‘ν•˜λŠ” μ’Œν‘œλ‘œ λ³€ν™˜ν•  λ•Œ μ‚¬μš©λ©λ‹ˆλ‹€.

λ©”μ†Œλ“œ μ‹œκ·Έλ‹ˆμ²˜

public Vector3 Camera.WorldToScreenPoint(Vector3 position);
  • λ§€κ°œλ³€μˆ˜ position: λ³€ν™˜ν•  3D μ›”λ“œ μ’Œν‘œ (Vector3 νƒ€μž…)
  • λ°˜ν™˜κ°’: 2D 슀크린 μ’Œν‘œ (Vector3)
    • x, y: 슀크린 μƒμ˜ ν”½μ…€ μ’Œν‘œ
    • z: ν•΄λ‹Ή μ§€μ κΉŒμ§€μ˜ μΉ΄λ©”λΌλ‘œλΆ€ν„° 깊이(거리)

μ’Œν‘œκ³„ μ΄ν•΄ν•˜κΈ°

μ›”λ“œ μ’Œν‘œ(World Coordinates)

  • μœ λ‹ˆν‹°μ˜ μ›”λ“œ 곡간(World Space)μ—μ„œμ˜ μœ„μΉ˜λ₯Ό λ‚˜νƒ€λƒ„
  • 기쀀은 κΈ€λ‘œλ²Œ 원점(0,0,0)이며 λͺ¨λ“  κ²Œμž„ μ˜€λΈŒμ νŠΈλŠ” 이 기쀀을 λ°”νƒ•μœΌλ‘œ μœ„μΉ˜κ°€ 결정됨
  • 예: (5, 10, 0)μ΄λΌλŠ” μ’Œν‘œλŠ” μ›”λ“œ κ³΅κ°„μ—μ„œ (x:5, y:10, z:0) 지점을 λ‚˜νƒ€λƒ„

슀크린 μ’Œν‘œ(Screen Coordinates)

  • κ²Œμž„ ν™”λ©΄ μƒμ˜ ν”½μ…€ μœ„μΉ˜λ₯Ό κΈ°μ€€μœΌλ‘œ μ •μ˜
  • 쒌츑 ν•˜λ‹¨ (0, 0), 우츑 상단은 ν™”λ©΄μ˜ 해상도에 따라 (Screen.width, Screen.height)
  • 예: 해상도가 1920x1080인 ν™”λ©΄μ—μ„œ (960, 540)은 ν™”λ©΄μ˜ 정쀑앙을 의미

κΈ°λ³Έ μ‚¬μš© μ˜ˆμ‹œ

using UnityEngine;

public class WorldToScreenExample : MonoBehaviour
{
    public Camera mainCamera;
    public Transform targetObject;

    void Update()
    {
        // μ›”λ“œ μ’Œν‘œμ—μ„œ 슀크린 μ’Œν‘œλ‘œ λ³€ν™˜
        Vector3 screenPos = mainCamera.WorldToScreenPoint(targetObject.position);

        Debug.Log($"슀크린 μ’Œν‘œ: {screenPos}");
    }
}
  • 이 μŠ€ν¬λ¦½νŠΈλŠ” targetObject의 μ›”λ“œ μ’Œν‘œλ₯Ό μ‹€μ‹œκ°„μœΌλ‘œ 슀크린 μ’Œν‘œλ‘œ λ³€ν™˜ν•˜μ—¬ 좜λ ₯
  • 슀크린 μ’Œν‘œλŠ” 해상도에 따라 λŒ€λž΅ (Screen.width / 2, Screen.height / 2)에 κ°€κΉŒμš΄ κ°’μœΌλ‘œ 좜λ ₯

보좩 μ„€λͺ…

  • λ°˜ν™˜λœ Vector3의 z값은 μΉ΄λ©”λΌλ‘œλΆ€ν„° μ›”λ“œ μ’Œν‘œκΉŒμ§€μ˜ 거리λ₯Ό λ‚˜νƒ€λ‚΄λ©°, 값이 0보닀 μž‘μœΌλ©΄ ν•΄λ‹Ή 지점이 카메라 뒀에 μžˆλ‹€λŠ” 의미
  • UI μš”μ†Œλ₯Ό νŠΉμ • μ›”λ“œ μœ„μΉ˜μ— λ§žμΆ”κ³  싢을 λ•Œ 주둜 μ‚¬μš©λ¨

뷰포트 μ’Œν‘œμ™€ 슀크린 μ’Œν‘œμ˜ 차이

  • 뷰포트 μ’Œν‘œ(Viewport Coordinates): ν™”λ©΄ 전체λ₯Ό (0,0) ~ (1,1) λ²”μœ„λ‘œ ν‘œν˜„ν•œ μ’Œν‘œ
  • 슀크린 μ’Œν‘œ(Screen Coordinates): ν”½μ…€ λ‹¨μœ„λ‘œ ν‘œν˜„ν•œ μ’Œν‘œ

μ’Œν‘œ λ³€ν™˜ λ©”μ†Œλ“œ:

  • Camera.WorldToScreenPoint = μ›”λ“œμ’Œν‘œ β†’ μŠ€ν¬λ¦°μ’Œν‘œλ‘œ λ³€ν™˜
  • Camera.WorldToViewportPoint = μ›”λ“œμ’Œν‘œ β†’ λ·°ν¬νŠΈμ’Œν‘œλ‘œ λ³€ν™˜

μ‹€μ „ ν™œμš© μ˜ˆμ‹œ

1. 적의 체λ ₯ λ°” ν‘œμ‹œν•˜κΈ°

상황

  • 3D μŠˆνŒ… κ²Œμž„μ—μ„œ ν”Œλ ˆμ΄μ–΄κ°€ 적을 μ‘°μ€€ν•  λ•Œ 적의 머리 μœ„μ— 체λ ₯λ°” ν‘œμ‹œ
  • 적은 μ›”λ“œ 곡간에 μ‘΄μž¬ν•˜μ§€λ§Œ 체λ ₯ λ°”λŠ” 2D UI μš”μ†Œμ΄λ―€λ‘œ 슀크린 μ’Œν‘œ κΈ°μ€€μœΌλ‘œ ν‘œμ‹œ

κ΅¬ν˜„ μ½”λ“œ

using UnityEngine;
using UnityEngine.UI;

public class EnemyHealthBar : MonoBehaviour
{
    public Camera mainCamera;
    public Transform enemyTransform;
    public RectTransform healthBarUI;

    void Update()
    {
        // 적의 μ›”λ“œ μ’Œν‘œλ₯Ό 슀크린 μ’Œν‘œλ‘œ λ³€ν™˜
        Vector3 screenPos = mainCamera.WorldToScreenPoint(enemyTransform.position + Vector3.up * 2);

        // 적이 μΉ΄λ©”λΌμ˜ μ•žμ— μžˆλŠ” κ²½μš°μ—λ§Œ 체λ ₯ λ°” ν‘œμ‹œ
        if (screenPos.z > 0)
        {
            healthBarUI.gameObject.SetActive(true);
            healthBarUI.position = screenPos; // 체λ ₯ λ°”λ₯Ό 슀크린 μ’Œν‘œμ— 배치
        }
        else
        {
            healthBarUI.gameObject.SetActive(false); // 적이 카메라 뒀에 μžˆμ„ 경우 μˆ¨κΉ€
        }
    }
}

2. ν€˜μŠ€νŠΈ 마컀 ν‘œμ‹œν•˜κΈ°

상황

  • RPG κ²Œμž„μ—μ„œ ν”Œλ ˆμ΄μ–΄κ°€ 맡을 νƒν—˜ν•  λ•Œ μ€‘μš”ν•œ ν€˜μŠ€νŠΈ λͺ©ν‘œ 지점을 화면에 μ•„μ΄μ½˜μœΌλ‘œ ν‘œμ‹œ
  • ν€˜μŠ€νŠΈ λͺ©ν‘œλŠ” 3D μ›”λ“œ 곡간에 μœ„μΉ˜ν•˜μ§€λ§Œ ν”Œλ ˆμ΄μ–΄λŠ” 항상 2D ν™”λ©΄μƒμ˜ μœ„μΉ˜λ‘œ 확인 κ°€λŠ₯ν•΄μ•Ό 함

κ΅¬ν˜„ μ½”λ“œ

using UnityEngine;
using UnityEngine.UI;

public class QuestMarker : MonoBehaviour
{
    public Camera mainCamera;
    public Transform questTarget;
    public RectTransform questMarkerUI;
    public float screenEdgeBuffer = 50f; // κ°€μž₯μžλ¦¬μ—μ„œ λ–¨μ–΄μ§„ 거리

    void Update()
    {
        // ν€˜μŠ€νŠΈ λͺ©ν‘œμ˜ μ›”λ“œ μ’Œν‘œλ₯Ό 슀크린 μ’Œν‘œλ‘œ λ³€ν™˜
        Vector3 screenPos = mainCamera.WorldToScreenPoint(questTarget.position);

        // λͺ©ν‘œκ°€ 카메라 μ•žμ— μžˆλŠ” 경우
        if (screenPos.z > 0)
        {
            screenPos.x = Mathf.Clamp(screenPos.x, screenEdgeBuffer, Screen.width - screenEdgeBuffer);
            screenPos.y = Mathf.Clamp(screenPos.y, screenEdgeBuffer, Screen.height - screenEdgeBuffer);

            questMarkerUI.gameObject.SetActive(true);
            questMarkerUI.position = screenPos; // μ•„μ΄μ½˜μ„ 슀크린 μ’Œν‘œμ— 배치
        }
        else
        {
            questMarkerUI.gameObject.SetActive(false); // 카메라 뒀에 있으면 μˆ¨κΉ€
        }
    }
}

3. MOBA κ²Œμž„μ—μ„œμ˜ ν™œμš© (리그 였브 λ ˆμ „λ“œ μ˜ˆμ‹œ)

μŠ€ν‚¬ λ²”μœ„ 미리보기 κ΅¬ν˜„

using UnityEngine;

public class SkillRangeIndicator : MonoBehaviour
{
    public Camera mainCamera;
    public Transform skillTarget; // μŠ€ν‚¬ λ²”μœ„μ˜ λͺ©ν‘œ 지점
    public RectTransform skillRangeUI; // μŠ€ν‚¬ λ²”μœ„ 미리보기 UI

    void Update()
    {
        // μŠ€ν‚¬ λͺ©ν‘œ 지점을 μ›”λ“œ μ’Œν‘œμ—μ„œ 슀크린 μ’Œν‘œλ‘œ λ³€ν™˜
        Vector3 screenPos = mainCamera.WorldToScreenPoint(skillTarget.position);

        if (screenPos.z > 0) // 카메라 μ•žμ— μžˆλŠ” κ²½μš°μ—λ§Œ ν‘œμ‹œ
        {
            skillRangeUI.gameObject.SetActive(true);
            skillRangeUI.position = screenPos;
        }
        else
        {
            skillRangeUI.gameObject.SetActive(false);
        }
    }
}

μ‹€μ œ κ΅¬ν˜„ μ˜ˆμ‹œ

private void LateUpdate()
{
    // 화면에 target이 보이지 μ•ŠμœΌλ©΄ UI μ‚­μ œ
    if (targetTransform == null)
    {
        Destroy(gameObject);
        return;
    }

    // 였브젝트의 μ›”λ“œ μ’Œν‘œλ₯Ό κΈ°μ€€μœΌλ‘œ ν™”λ©΄μ—μ„œμ˜ μ’Œν‘œ 값을 ꡬ함
    Vector3 screenPosition = Camera.main.WorldToScreenPoint(targetTransform.position);

    // ν™”λ©΄λ‚΄μ—μ„œ μ’Œν‘œ + distance만큼 λ–¨μ–΄μ§„ μœ„μΉ˜λ₯Ό UI의 μœ„μΉ˜λ‘œ μ„€μ •
    rectTransform.position = screenPosition + distance;
}

μ½”λ“œ μ„€λͺ…

  • LateUpdate μ‚¬μš© 이유: 였브젝트의 μœ„μΉ˜κ°€ Updateμ—μ„œ κ°±μ‹ λœ ν›„ UI의 μœ„μΉ˜λ„ μ΅œμ’…μ μœΌλ‘œ λ§žμΆ”κΈ° μœ„ν•¨
  • screenPosition: λŒ€μƒ μ˜€λΈŒμ νŠΈκ°€ ν™”λ©΄μ—μ„œ μ–΄λŠ μœ„μΉ˜μ— μžˆμ–΄μ•Ό ν•˜λŠ”μ§€ ν”½μ…€ μ’Œν‘œλ‘œ μ €μž₯
  • rectTransform.position: UIλ₯Ό λŒ€μƒ 였브젝트의 슀크린 μ’Œν‘œμ—μ„œ distance만큼 λ–¨μ–΄μ§„ μœ„μΉ˜μ— 배치

κ²°λ‘ 

Camera.WorldToScreenPointλŠ” κ²Œμž„μ—μ„œ 3D μ›”λ“œ κ³΅κ°„μ˜ 였브젝트λ₯Ό 2D ν™”λ©΄ μš”μ†Œ(UI, μ•„μ΄μ½˜ λ“±)와 동기화할 λ•Œ ν•„μˆ˜μ μΈ λ©”μ†Œλ“œμž…λ‹ˆλ‹€. 체λ ₯ λ°”, ν€˜μŠ€νŠΈ 마컀, μ‘°μ€€μ„  λ“± λ§Žμ€ UI μš”μ†Œλ₯Ό μ›”λ“œ μ˜€λΈŒμ νŠΈμ— λ§žμΆ”μ–΄ ν‘œμ‹œν•  λ•Œ μœ μš©ν•˜κ²Œ μ‚¬μš©λ©λ‹ˆλ‹€. 3D μ›”λ“œμ— μ‘΄μž¬ν•˜μ§€λ§Œ 2D둜 λ³΄μ—¬μ€˜μ•Ό ν•˜λŠ” μš”μ†Œκ°€ μžˆλ‹€λ©΄ 이 λ©”μ†Œλ“œλ₯Ό 적극 ν™œμš©ν•˜μ‹œκΈ° λ°”λžλ‹ˆλ‹€.