RawImage 기반 미니맵에서 NPC 정보 Hover 처리
1. 시스템 요구 사항
미니맵이 플레이어 위치와 시야 범위를 제공하는 수준에 머문다면, 이는 단순한 보조 UI에 불과하다.
하지만 탐색 중심의 게임에서는 미니맵이 월드 정보를 확장해 전달하는 창구가 되어야 한다.
특히 NPC와 같은 주요 상호작용 대상은 직접 이동해 접근하기 전에, 미니맵 단계에서 어디에 있고, 어떤 역할을 하는지를 미리 파악할 수 있을 필요가 있었다.
문제는 미니맵이 RawImage + RenderTexture 기반으로 구현되어 있었다는 점이다.
RawImage는 단순히 이미지를 출력하는 UI 요소이기 때문에, 마우스가 가리키는 픽셀이 실제 월드의 어떤 오브젝트를 의미하는지 직접적으로 알 수 없다.
즉, UI 상에서는 Hover가 감지되지만 그 Hover가 어떤 NPC를 가리키고 있는지를 판단할 수 있는 정보가 존재하지 않았다.
이 문제를 해결하지 않으면, 미니맵 위에서 NPC 정보를 표시하는 기능 자체가 구조적으로 불가능했다.
2. 설계 목표
- RawImage 기반 미니맵에서도 NPC Hover 정보를 제공할 것
- UI 좌표와 미니맵 카메라의 월드 좌표를 정확히 연결할 것
- 미니맵 위에서의 마우스 입력만 처리하도록 범위를 제한할 것
- NPC 정보 표시 로직을 UI, 감지, 데이터로 명확히 분리할 것
3. 흐름도

이 흐름의 핵심은 UI 공간에서 얻은 좌표를 직접 월드로 쓰지 않고, 반드시 미니맵 카메라의 뷰포트 좌표로 변환한다는 점이다.
4. 구현
미니맵을 RawImage 기반으로 구현하면서, UI 위에서 마우스 Hover만으로는 NPC 정보를 가져올 수 없는 문제가 발생했다.
RawImage는 RenderTexture를 단순히 출력하는 UI 요소이기 때문에, 화면상의 특정 픽셀이 실제 게임 월드의 어떤 오브젝트를 의미하는지 알 수 없었고, UI 좌표와 미니맵 카메라의 월드 좌표 사이에는 직접적인 연결 고리가 존재하지 않았다.
이로 인해 미니맵 위에서 NPC를 가리키고 있음에도, 해당 위치에 어떤 NPC가 있는지를 판별할 수 없는 구조적 한계가 드러났다.
이 문제의 원인을 UI 좌표계와 미니맵 카메라의 뷰포트/월드 좌표계가 완전히 분리되어 있다는 점으로 보았다.
RawImage 자체를 기준으로 문제를 해결하려 하면 한계가 명확하다고 판단했고, 대신 미니맵 카메라를 좌표 변환의 기준점으로 삼는 방식으로 접근했다.
구체적으로는 UI 상의 마우스 위치를 RawImage의 로컬 좌표로 변환한 뒤, 이를 0~1 범위의 정규화된 뷰포트 좌표로 환산하고, 해당 좌표를 기준으로 미니맵 카메라에서 Raycast를 발사하도록 설계했다.
이 방식으로 UI 공간과 월드 공간을 카메라 뷰포트를 매개로 연결할 수 있었고, RawImage 기반 미니맵에서도 실제 NPC 오브젝트를 정확하게 탐지할 수 있게 되었다.
4.1. NPC 정보 데이터 분리 (MiniMapPointInfo)
public class MiniMapPointInfo : MonoBehaviour
{
[SerializeField] private string placeName;
[SerializeField] private string description;
public void ShowInfo()
{
MiniMapInfoUI.Instance.Show(placeName, description);
}
public void HideInfo()
{
MiniMapInfoUI.Instance.Hide();
}
}
NPC 정보는 NPC 오브젝트 자체가 직접 UI를 제어하지 않도록, 전용 데이터 컴포넌트로 분리했다.
MiniMapPointInfo는 NPC의 이름과 설명 같은 순수 데이터만 보유하고, UI에 어떻게 표시되는지는 전혀 알지 못한다.
이를 통해 NPC는 미니맵 UI의 존재를 모른 채, 내가 어떤 정보인지만 제공하는 역할에 집중한다.
이 분리는 이후 NPC 정보 UI가 바뀌거나, 표시 항목이 늘어나더라도 NPC 오브젝트를 수정하지 않아도 되는 구조를 만든다.
4.2. NPC 정보 UI 전담 클래스 (MiniMapInfoUI)
public class MiniMapInfoUI : MonoBehaviour
{
public static MiniMapInfoUI Instance;
[SerializeField] private GameObject panel;
[SerializeField] private TextMeshProUGUI nameText;
[SerializeField] private TextMeshProUGUI infoText;
void Awake()
{
Instance = this;
}
public void Show(string _name, string _info)
{
nameText.text = _name;
infoText.text = _info;
panel.SetActive(true);
}
public void Hide()
{
if (panel.activeSelf)
panel.SetActive(false);
}
}
MiniMapInfoUI는 오직 화면에 정보를 표시하는 책임만 가진다.
NPC가 누구인지, 어디에 있는지는 전혀 알지 못하며 전달받은 문자열을 UI에 출력하는 역할에만 집중한다.
싱글톤 형태로 접근하도록 구성한 이유는, 미니맵 Hover 처리 로직이 매 프레임 실행되기 때문에 UI 참조를 반복해서 찾는 비용과 구조적 복잡도를 줄이기 위함이다.
4.3. RawImage → 뷰포트 → 월드 변환 핵심 로직
private void Update()
{
if (!pointerOver || miniMapCam == null || miniMapRect == null)
{
ClearHit();
return;
}
if (!RectTransformUtility.ScreenPointToLocalPointInRectangle(
miniMapRect,
Input.mousePosition,
null,
out Vector2 localPoint))
{
ClearHit();
return;
}
Vector2 size = miniMapRect.rect.size;
Vector2 normalized = (localPoint / size) + new Vector2(0.5f, 0.5f);
Ray ray = miniMapCam.ViewportPointToRay(
new Vector3(normalized.x, normalized.y, 0f));
if (Physics.Raycast(ray, out RaycastHit hit, 1000f, npcLayerMask))
{
var info = hit.collider.GetComponent<MiniMapPointInfo>();
if (info != null)
{
if (info != currentInfo)
{
if (currentInfo != null)
currentInfo.HideInfo();
currentInfo = info;
currentInfo.ShowInfo();
}
return;
}
}
ClearHit();
}
이 로직의 핵심은 좌표 변환의 단계적 분리다.
먼저 RectTransformUtility.ScreenPointToLocalPointInRectangle를 사용해 스크린 좌표의 마우스 위치를 RawImage 내부의 로컬 좌표로 변환한다.
이는 UI 좌표계에서 현재 마우스가 미니맵 이미지의 어느 지점을 가리키는지를 알아내기 위한 단계다.
그 다음, 이 로컬 좌표를 (-width/2 ~ width/2) 범위에서 (0 ~ 1) 범위의 정규화된 뷰포트 좌표로 변환한다.
이 값은 미니맵 카메라가 이해할 수 있는 좌표 체계다.
이렇게 얻은 뷰포트 좌표를 사용해 ViewportPointToRay로 Ray를 발사하면, UI 위의 특정 지점이 실제 월드의 어느 방향을 가리키는지를 정확히 알 수 있다.
즉, RawImage 자체는 월드를 모르지만, 미니맵 카메라를 중간 매개로 사용함으로써 UI 공간과 월드 공간을 연결한 것이다.
private void ClearHit()
{
if (currentInfo != null)
{
currentInfo.HideInfo();
currentInfo = null;
}
}
ClearHit는 이전 NPC 정보를 해제하는 함수이다.
4.4. Hover 상태 관리 및 정보 전환
public void OnPointerEnter(PointerEventData eventData)
{
pointerOver = true;
}
public void OnPointerExit(PointerEventData eventData)
{
pointerOver = false;
ClearHit();
}
미니맵 Hover 처리는 UI 이벤트 시스템을 사용해 마우스가 미니맵 영역 안에 있을 때만 활성화되도록 제한했다.
이를 통해 불필요한 Raycast 연산을 줄이고, 미니맵 외부에서 발생하는 마우스 이동이 NPC 정보 UI에 영향을 주지 않도록 했다.
5. 개발 의도
이 게시글에서 보여주고자 한 핵심은 RawImage는 월드를 모르지만, 카메라는 알고 있다는 관점 전환이다.
문제의 원인은 UI 좌표와 월드 좌표를 직접 연결하려 했다는 데 있었다.
해결은 미니맵 카메라의 뷰포트라는 중간 좌표계를 도입해 'UI → 카메라 → 월드' 라는 명확한 변환 경로를 만드는 것이었다.
이 구조를 통해 미니맵은 단순한 위치 표시 UI를 넘어 NPC, 오브젝트, 지형 정보를 자연스럽게 연결하는 탐색 보조 시스템으로 확장될 수 있었다.
