프로젝트/프로젝트 A

#011 Minimap

효따 2025. 8. 16. 19:32

안녕하세요.

 

이번에는 미니맵에 대해 작업을 해보려고 합니다.

 

 

처음에는 Unity에서 카메라를 한대를 더 생성해서 RenderTexture로 보여주는 방식을 생각했는데요

 

구현을 해놓고 미니맵 카메라에서만 보이는 레이어가 다른 아이콘 이미지를 생성하려던 도 중 

 

현재 구상 중인 미니맵은 고정된 지형이고, 구조가 바뀌거나 파괴 등 실시간으로 렌더링 할 필요가 없다고 생각되어

 

렌더링 비용도 줄이고자

 

2D 이미지로 대체해 보기로 마음먹었습니다.

 

 

또한 미니언, 플레이어, 포탑, 넥서스 등 

 

필요한 좌표만 구해와서 미니맵에 2D 이미지 아이콘만 해당 좌표에 띄우는 것이 훨씬 효율적이라고 생각했습니다.

 


 

 

MinimapController 스크립트입니다.

using UnityEngine;
using UnityEngine.AI;


public class MinimapController : MonoBehaviour
{
    [SerializeField] private NavMeshAgent playerNav;
    [SerializeField] private Transform playerTransform;
    [SerializeField] private RectTransform minimapRawImage;
    [SerializeField] private RectTransform playerIcon;
    [SerializeField] private RectTransform pointIcon;
    [SerializeField] private GameObject ground;

    private float worldMinX = 0f;
    private float worldMaxX = 0f;
    private float worldMinZ = 0f;
    private float worldMaxZ = 0f;

    void Start()
    {
        // groundSizeX(100) = 10f * 10
        float groundSizeX = 10f * ground.transform.localScale.x;
        float groundSizeZ = 10f * ground.transform.localScale.z;

        // worldMinX(-50) = groundSizeX(100) / 2f
        worldMinX = -groundSizeX / 2f;
        worldMaxX = groundSizeX / 2f;
        worldMinZ = -groundSizeZ / 2f;
        worldMaxZ = groundSizeZ / 2f;
    }
    void Update()
    {
        float xNorm = Mathf.InverseLerp(worldMinX, worldMaxX, playerTransform.position.x);
        float zNorm = Mathf.InverseLerp(worldMinZ, worldMaxZ, playerTransform.position.z);

        float uiX = (xNorm - 0.5f) * minimapRawImage.rect.width;
        float uiZ = (zNorm - 0.5f) * minimapRawImage.rect.height;

        playerIcon.anchoredPosition = new Vector2(uiX, uiZ);

        if (pointIcon.gameObject.activeSelf)
        {
            if (!playerNav.pathPending && playerNav.remainingDistance <= playerNav.stoppingDistance)
            {
                pointIcon.gameObject.SetActive(false);
            }
        }

        if (Input.GetMouseButtonDown(1))
        {
            Vector2 localPoint;
            // 클릭이 미니맵 안인지 체크
            if (RectTransformUtility.ScreenPointToLocalPointInRectangle(minimapRawImage, Input.mousePosition, null, out localPoint))
            {
                // print("11");
                if (localPoint.x >= -minimapRawImage.rect.width / 2f && localPoint.x <= minimapRawImage.rect.width / 2f &&
                    localPoint.y >= -minimapRawImage.rect.height / 2f && localPoint.y <= minimapRawImage.rect.height / 2f)
                {
                    // Debug.Log("22");
                    float clickXNorm = (localPoint.x / minimapRawImage.rect.width) + 0.5f;
                    float clickZNorm = (localPoint.y / minimapRawImage.rect.height) + 0.5f;

                    // 0~1
                    float targetX = Mathf.Lerp(worldMinX, worldMaxX, clickXNorm);
                    float targetZ = Mathf.Lerp(worldMinZ, worldMaxZ, clickZNorm);

                    playerNav.isStopped = true;
                    playerNav.ResetPath();
                    playerNav.SetDestination(new Vector3(targetX, playerTransform.position.y, targetZ));

                    // pointIcon 활성화
                    pointIcon.gameObject.SetActive(true);
                    pointIcon.anchoredPosition = new Vector2(localPoint.x, localPoint.y);
                }
            }
        }
    }
}

 

이번 스크립트는 Unity에서 미니맵 클릭을 통해 플레이어 이동 경로를 표시하고 목표 지점까지 이동시키는 기능을 구현했습니다.

  • 사용자는 마우스로 미니맵을 클릭
  • 클릭된 지점까지 NavMeshAgent가 이동
  • 미니맵 위의 플레이어 아이콘과 목표 아이콘(pointIcon)을 갱신

즉, 플레이어 이동과 미니맵 UI를 실시간으로 연결하는 기능입니다.

 


 

 

1. 필요한 컴포넌트와 변수 선언입니다.

 

 

    [SerializeField] private NavMeshAgent playerNav;
    [SerializeField] private Transform playerTransform;
    [SerializeField] private RectTransform minimapRawImage;
    [SerializeField] private RectTransform playerIcon;
    [SerializeField] private RectTransform pointIcon;
    [SerializeField] private GameObject ground;
  • playerNav : 플레이어의 NavMeshAgent
  • playerTransform : 플레이어의 Transform
  • minimapRawImage : 미니맵 UI의 RectTransform (미니맵 이미지)
  • playerIcon : 미니맵 위 플레이어 위치 아이콘
  • pointIcon : 미니맵 클릭 지점 아이콘
  • ground : 월드 공간에서의 맵 범위 계산용

 


 

 

2. 맵 좌표 범위 계산입니다.

    private float worldMinX = 0f;
    private float worldMaxX = 0f;
    private float worldMinZ = 0f;
    private float worldMaxZ = 0f;

    void Start()
    {
        // groundSizeX(100) = 10f * 10
        float groundSizeX = 10f * ground.transform.localScale.x;
        float groundSizeZ = 10f * ground.transform.localScale.z;

        // worldMinX(-50) = groundSizeX(100) / 2f
        worldMinX = -groundSizeX / 2f;
        worldMaxX = groundSizeX / 2f;
        worldMinZ = -groundSizeZ / 2f;
        worldMaxZ = groundSizeZ / 2f;
    }

 

  • 목적: 월드 좌표를 0~1 범위로 정규화하여 미니맵 좌표로 변환할 때 필요합니다.
  • ground.transform.localScale을 기준으로 월드 크기를 계산
  • -50 ~ 50처럼 중앙을 기준으로 좌표 범위를 잡았습니다.

 


 

 

3. 플레이어 아이콘 위치 갱신

    void Update()
    {
        float xNorm = Mathf.InverseLerp(worldMinX, worldMaxX, playerTransform.position.x);
        float zNorm = Mathf.InverseLerp(worldMinZ, worldMaxZ, playerTransform.position.z);

        float uiX = (xNorm - 0.5f) * minimapRawImage.rect.width;
        float uiZ = (zNorm - 0.5f) * minimapRawImage.rect.height;

        playerIcon.anchoredPosition = new Vector2(uiX, uiZ);

 

  • Mathf.InverseLerp : 월드 좌표 → 0~1 정규화 (예 -50, 50의 플레이어의 현재 x축 좌표 (0) = -50과 50의 중앙값 인 0.5)
  • (xNorm - 0.5f) : 미니맵 중심 기준으로 좌표 변환 (예 0.5 - 0.5 = 0) 
  • 최종적으로 anchoredPosition에 적용 → 미니맵 위 아이콘 위치 갱신

 


 

 

4. 플레이어가 목표 도착 시 아이콘 비활성화

        if (pointIcon.gameObject.activeSelf)
        {
            if (!playerNav.pathPending && playerNav.remainingDistance <= playerNav.stoppingDistance)
            {
                pointIcon.gameObject.SetActive(false);
            }
        }

 

  • 목적: 플레이어가 목표 지점에 도착하면, 미니맵 목표 아이콘(pointIcon)을 숨김
  • pathPending : NavMeshAgent가 목적지 계산 중인지 확인
  • remainingDistance : 목표까지 남은 거리
  • stoppingDistance : 도착 기준 거리

 


 

 

5. 마우스 오른쪽(1) 클릭 처리

        if (Input.GetMouseButtonDown(1))
        {
            Vector2 localPoint;
            // 클릭이 미니맵 안인지 체크
            if (RectTransformUtility.ScreenPointToLocalPointInRectangle(minimapRawImage, Input.mousePosition, null, out localPoint))
            {
                // print("11");
                if (localPoint.x >= -minimapRawImage.rect.width / 2f && localPoint.x <= minimapRawImage.rect.width / 2f &&
                    localPoint.y >= -minimapRawImage.rect.height / 2f && localPoint.y <= minimapRawImage.rect.height / 2f)
                {
                    // Debug.Log("22");
                    float clickXNorm = (localPoint.x / minimapRawImage.rect.width) + 0.5f;
                    float clickZNorm = (localPoint.y / minimapRawImage.rect.height) + 0.5f;

                    // 0~1
                    float targetX = Mathf.Lerp(worldMinX, worldMaxX, clickXNorm);
                    float targetZ = Mathf.Lerp(worldMinZ, worldMaxZ, clickZNorm);

                    playerNav.isStopped = true;
                    playerNav.ResetPath();
                    playerNav.SetDestination(new Vector3(targetX, playerTransform.position.y, targetZ));

                    // pointIcon 활성화
                    pointIcon.gameObject.SetActive(true);
                    pointIcon.anchoredPosition = new Vector2(localPoint.x, localPoint.y);
                }
            }
        }

 

  • 클릭이 미니맵 영역 안인지 체크
  • 클릭 좌표를 0~1 범위로 정규화 → 월드 좌표로 변환
  • NavMeshAgent 목적지로 이동
  • 미니맵 목표 아이콘 갱신
  • 마우스 오른쪽이 클릭되면 진입합니다.
  • 화면 좌표를 미니맵 내부 좌표로 변환해 줍니다. (x (-50, +50), z (-50, +50))

 

 


 

 

흐름 요약

 

 

1. 마우스 오른쪽 클릭 → 플레이어 이동

2. 플레이어 위치 아이콘 실시간 갱신

3. 목표 아이콘(pointIcon) 표시/도착 시 제거

4. 클릭 위치까지 NavMesh 경로 기반 이동

 

 

 


 

 

1. PlayerMovement 스크립트의 플레이어 이동도 마우스 오른쪽(1) 버튼 클릭을 감지하고 있기에

MoveContriller() 함수 내에 UI가 아니면 return을 하게끔 조건을 걸어주었습니다.

    private void MoveController()
    {
        if (playerSkillController.isSkill03 || playerSkillController.isSkill04)
        {
            playerNav.isStopped = true;
            playerNav.ResetPath();
            playerNav.velocity = Vector3.zero;
            return;
        }
        if (EventSystem.current.IsPointerOverGameObject())
            return; // UI 클릭이면 이동 무시
        if (Input.GetMouseButtonDown(1))

 

 

2. 미니맵을 클릭할 때 생기는 이미지에 애니메이션을 주어 스케일을 반복 조정되게 하였습니다.

EventSystem.current.IsPointerOverGameObject()는 현재 마우스가 UI 위에 올라가 있는지 확인하는 함수입니다.

지금 클릭/터치한 위치가 UI 요소 위에 있다 : true

UI 위가 아닌 게임 월드나 빈 화면을 클릭했다 : false

 

 


 

 

 

 


감사합니다. 😌