안녕하세요.
이번에는 미니맵에 대해 작업을 해보려고 합니다.
처음에는 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
감사합니다. 😌