어떤 게임이든 미니맵이 필요할 때가 있다. 그럴 때마다 아래의 방법으로 미니맵을 구현하였다.
기존 프로젝트에서 사용한 방법
1. 메인카메라 외에 미니맵 구현을 위한 카메라 오브젝트를 생성(이하 미니맵 카메라)한다.
이때 추가한 카메라 오브젝트에 Audio Listener 컴포넌트를 제거한다.
그리고 Culling Mask의 레이어를 미니맵에 표현하기를 원하는 오브젝트의 레이어로 선택한다.
마지막으로 Clear Flags는 Solid Color로 변경한다.
2. 미니맵 카메라의 카메라 컴포넌트의 Projection을 Orthographic(직교)로 변경한다.
Orthograpihc이란 원근감이 없는 평행 투영 방식으로,
모든 객체가 동일한 크기로 투영되어 물체의 거리에 상관없이 일정한 크기로 화면에 나타난다.
3. 미니맵 카메라에 아래 스크립트를 부착한다.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class PositionLocator : MonoBehaviour
{
[SerializeField] private bool x, y, z;
[SerializeField] private Transform target;
// 각각 미니맵에서 어떤 축의 위치를 추적할지를 결정하는 변수이다.
// target은 플레이어의 위치를 추적하기 위한 Transform 컴포넌트를 가리킨다.
void Update()
{
if (!target) return;
transform.position = new Vector3(
(x ? target.position.x : transform.position.x),
(y ? target.position.y : transform.position.y),
(z ? target.position.z : transform.position.z));
}
// 조건 연산자 (?:)를 사용하여 x, y, z 각 축의 값을 결정한다.
// x, y, z가 true일 경우, target의 해당 축의 위치값을 미니맵의 해당 축에 할당한다.
// x, y, z가 false일 경우, 미니맵의 해당 축의 위치를 변화시키지 않고 미니맵 카메라 위치를 유지한다.
// 일반적인 미니맵 구현이라면 y값을 false로 두면 되겠다.
}
4. 미니맵 카메라의 Output Texture를 미니맵 지도로 쓸 텍스쳐파일로 지정해준다.
Output Texture란 카메라가 렌더링한 화면을 어떻게 처리하고 출력할지를 결정한다.
5. RawImage 오브젝트 만들고 RawImage 컴포넌트의 Texture를 미니맵 지도로 할 텍스처파일과 연결한다.
이때 RenderTexture의 FilterMode는 Point로 하여 텍스쳐를 선명하게 한다.
BilinearMode는 텍스쳐를 주변 픽셀의 값을 고려하여 보간하는 모드로,
더 부드러운 텍스쳐 표현을 나타내기 위해 사용된다.
6. 미니맵에 표시할 대상이 된 캐릭터에게 Sprite Renderer컴포넌트가 부착된 자식 오브젝트를 연결한다.
새로 알게 된 방법
아래의 내용은 A Different way to Make a Map를 보고 알게 된 내용이다.
미니맵 구현에 핵심적인 기능만 작성하였고,
미니맵 내에서 아이콘위에 마우스포인트를 올렸을 때의 반응은 작성하지 않았으니
해당 부분은 영상 후반부를 참고하면 된다.
MapBounds 스크립트
: 이 스크립트는 주어진 lower와 upper Transform으로 정의된
바운딩 박스의 내부에 있는 지점의 정규화된(normalized) 위치를 계산하는 기능을 제공한다.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class MapBounds : MonoBehaviour
{
[SerializeField] private Transform lower;
[SerializeField] private Transform upper;
// 이 두 개의 SerializeField로 선언된 변수는 Bounds 스크립트에서 사용할 Transform 컴포넌트를 저장하는 역할을 한다.
// lower는 바운딩 박스의 왼쪽 아래 꼭지점을, upper는 바운딩 박스의 오른쪽 위 꼭지점을 나타낸다.
// 이 두 꼭지점을 이용하여 바운딩 박스의 크기와 위치를 결정한다.
public Vector2 FindNormalizedPosition(Vector3 position)
// FindNormalizedPosition 함수는 바운딩 박스 내부에 있는 지점의 정규화된 위치를 계산하는 함수
// 함수의 파라미터로는 바운딩 박스 내부에 있는지 확인하고자 하는 위치인 position을 받는다.
// 이 위치를 바운딩 박스 내부에 있는 정규화된 위치로 변환하여 Vector2 형태로 반환한다.
{
float xPosition = Mathf.InverseLerp(lower.position.x, upper.position.x, position.x);
float zPosition = Mathf.InverseLerp(lower.position.z, upper.position.z, position.z);
// xPosition과 zPosition을 각각 바운딩 박스의 x축과 z축 방향에서의 정규화된 위치를 계산한다.
return new Vector2(xPosition, zPosition);
}
}
Mathf.InverseLerp(min, max, value) 함수는
주어진 최소값(min)과 최대값(max) 사이에서 value의 정규화된(normalized) 위치를 계산한다.
Map 스크립트
: 지도(Map) 오브젝트를 표현하고, 지도 내에서 캐릭터와 캐릭터 마커 이미지의 위치를 업데이트하는 역할을 한다.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Map : MonoBehaviour
{
[Header("References")]
[SerializeField] private MapBounds bounds;
// 지도의 경계 정보를 제공하는 컴포넌트이다.
// 이 컴포넌트는 Map 오브젝트의 자식 오브젝트로 위치하고 있어서 지도의 최소 경계와 최대 경계를 나타내며, 캐릭터의 위치를 지도 내에서 정규화된(normalized) 좌표로 변환하는 데 사용된다.
[Header("캐릭터와 캐릭터 마커 이미지")]
[SerializeField] private Transform worldTransform;
[SerializeField] private RectTransform imageTransform;
private RectTransform MapTransform => transform as RectTransform;
void Update()
{
imageTransform.anchoredPosition = -FindInterfacePoint();
// 캐릭터 마커 이미지의 위치를 업데이트한다.
// FindInterfacePoint() 함수를 호출하여 캐릭터의 위치를 지도 상의 위치로 변환하고, 해당 위치를 imageTransform의 anchoredPosition으로 설정하여 캐릭터 마커 이미지의 위치를 지도 상에 표시한다.
}
private Vector2 FindInterfacePoint()
{
Vector2 normalizedPosition = bounds.FindNormalizedPosition(worldTransform.position);
// bounds.FindNormalizedPosition(worldTransform.position)를 호출하여 캐릭터의 위치를 지도 내에서 정규화된 좌표로 변환
return Rect.NormalizedToPoint(MapTransform.rect, normalizedPosition);
// Rect.NormalizedToPoint() 함수를 사용하여 해당 정규화된 좌표(두 번째 인자)를 지도의 RectTransform 좌표(첫 번째 인자)로 변환
}
}
private RectTransform MapTransform => transform as RectTransform; 에 대한 보충설명
위 코드는 C# 6.0 이상에서 지원되는 프로퍼티 표현식(property expression)을 사용한 코드이다.
MapTransform은 private로 선언된 프로퍼티(property)로, RectTransform 타입의 값을 반환한다.
이 프로퍼티는 transform 컴포넌트를 RectTransform으로 변환하여 반환한다.
=> 기호를 사용하여 우측에 값을 반환하는 표현식을 지정할 수 있다.
따라서 MapTransform 프로퍼티는 transform as RectTransform 표현식의 결과를 반환하게 된다.
만약 위 프로퍼티 표현식 대신 getter 를 이용해서 표현했다면 아래처럼 했을 것이다.
private RectTransform MapTransform
{
get
{
return transform as RectTransform;
}
}
transform은 Transform 타입이지만, as 키워드를 사용하여 RectTransform 타입으로 타입 캐스팅을 시도하고 있다.
타입 캐스팅은 변수의 타입을 다른 타입으로 변환하는 작업을 의미한다.
하지만, C#에서는 캐스팅이 허용되지 않는 경우에 예외를 발생시킨다.
그래서 as 연산자는 변환할 수 없는 경우에도 예외를 발생시키지 않고, 대신 캐스팅에 실패하면 null을 반환한다.
따라서 transform as RectTransform;는 transform을 RectTransform으로 변환하려고 시도하고,
만약 RectTransform으로 변환이 가능하다면 해당 결과를 반환하고, 불가능하다면 null을 반환한다.
이렇게 RectTransform으로 타입 캐스팅이 성공하면 RectTransform으로 다룰 수 있는 기능을 사용할 수 있다.
'게임 기획과 개발 > 응용 프로젝트' 카테고리의 다른 글
ZEP(젭) + 유니티 엔진을 연계한 콘텐츠 만들기 (0) | 2025.01.18 |
---|---|
유니티에서 3D 오브젝트를 드래그하여 이동시키기 (0) | 2023.10.30 |