Ace Combat Zero: 유니티로 구현하기 #8 : UI (3) - 미니맵 + 1인칭 시점

Lunetis·2021년 4월 17일
2

Ace Combat Zero

목록 보기
9/27
post-thumbnail





역시 UI는 할 게 무지하게 많습니다.

할 게 산더미인데 벌써 3편이고, 이거로 끝나는 것도 아니라니요.

전체 화면:
미니맵
아군/적군 UI
게임 내 대사
조준점
기총 UI
경고 UI
색상 변경
화면에 안 보이는 목표물의 위치를 가리키는 화살표

미니맵을 제외한 부분도 해결해야 하고,

1인칭 시점 UI는 1인칭에서만 보이도록 수정

이것도 해결해야 합니다.
우선 지난 포스트에서 해야했던 1인칭 시점 UI 컨트롤부터 하죠.


1인칭 시점 UI 컨트롤

이 스크린샷은 3인칭 시점,

이 스크린샷은 1인칭 시점입니다.

1인칭 시점에서만 보이고 3인칭 시점에서 보이지 않는 UI는,

  • 속도계 (숫자 제외)
  • 고도계 (숫자 제외)
  • 방향계
  • 자세계
  • 스로틀

이렇게 5개입니다.

여기서 속도/고도 UI는 3인칭 뷰, 1인칭 뷰 모두 보이게 되는데요,

속도/고도 UI는 3인칭 뷰에서는 고개를 돌려도 위치가 고정되지만,
1인칭 뷰에서 고개를 돌릴 때 위치가 바뀌어야 합니다.

구현 방식을 생각해보면, 속도/고도 UI를 똑 떼어놓은 다음
카메라에 따라 3인칭 뷰, 1인칭 뷰에 붙여넣으면 되겠죠.

그리고 현재 각도에 따라서 위치를 조정해주면 될 것입니다.



1인칭 뷰 표시/비표시

먼저 활성화/비활성화될 1인칭 뷰는 한 곳에 모두 몰아넣어있는지 확인합니다.
(애초에 캔버스랑 카메라가 3인칭(공통) 뷰와는 분리해서 만들었죠?)

3인칭 뷰일 때 1인칭 UI 카메라를 비활성화시키느냐, 1인칭 UI 오브젝트나 캔버스를 비활성화시키느냐는 구현하는 사람 마음입니다.

저는 1인칭 UI 오브젝트를 비활성화하는 방향 으로 가겠습니다.

UIController에서 UI 오브젝트를 이미 변수로 얻어올텐데 또 카메라 변수를 얻어오기가 좀 귀찮아서요.



UIController.cs

[Header("1st-3rd View Control")]
public RectTransform commonCenterUI;
public RectTransform firstCenterViewTransform;
public RectTransform thirdCenterViewTransform;

public void SwitchUI(CameraController.CameraIndex index)
{
    bool isFirstView = (index == CameraController.CameraIndex.FirstView || 
                        index == CameraController.CameraIndex.FirstViewWithCockpit);

    firstCenterViewTransform.gameObject.SetActive(isFirstView);

    RectTransform parentTransform = (isFirstView) ? firstCenterViewTransform : thirdCenterViewTransform;
    commonCenterUI.SetParent(parentTransform);
}

CameraController.cs

void SetCamera()
{
    ...
    GameManager.Instance.uiController.SwitchUI((CameraIndex)cameraViewIndex);
}

UIControllerRectTransform.parent를 바꾸는 코드를 추가했습니다.

CameraController에 현재 활성화된 카메라 정보인 CameraIndex가 있고,
이 값을 넘겨줘서 1인칭 뷰/3인칭 뷰 UI에 붙여놓는 작업을 하게 됩니다.

1인칭 UI, 그 UI의 부모가 될 객체를 모두 끌어다 붙입니다.

카메라에 따라 UI를 표시/비표시하는 기능은 만들어졌고,

이제 1인칭 기준으로는 카메라를 돌릴 때 1인칭 UI 위치가 바뀌는 기능을 만들어야 합니다.


시선에 따른 UI 위치 이동

주위를 돌아볼 때의 카메라 시선 정보 (회전값) 역시 CameraController에 있습니다.
회전값을 잘 가공해서 위치로 변환해주면 될 거에요.

UIController.cs

[Header("1st-3rd View Control")]
public RectTransform commonCenterUI;
public RectTransform firstCenterViewTransform;
public RectTransform thirdCenterViewTransform;

public Canvas firstViewCanvas;
public Vector2 firstViewAdjustAngle;

void Start()
{
    firstViewAdjustAngle = new Vector2(1 / firstViewAdjustAngle.x, 1 / firstViewAdjustAngle.y);
    ...
}

public void AdjustFirstViewUI(Vector3 cameraRotation)
{
    Vector2 canvasResolution = new Vector2(firstViewCanvas.pixelRect.width,
                                           firstViewCanvas.pixelRect.height);
    Vector2 convertedRotation = new Vector2(cameraRotation.y * firstViewAdjustAngle.x,
                                            cameraRotation.x * firstViewAdjustAngle.y);

    firstCenterViewTransform.anchoredPosition = convertedRotation * canvasResolution;
}

CameraController.cs

void Rotate1stViewCamera()
{
    ...
    uiController.AdjustFirstViewUI(rotateValue);
}

void Rotate1stViewWithCockpitCamera()
{
    ...
    uiController.AdjustFirstViewUI(rotateValue);
}

AdjustFirstViewUI(Vector3 cameraRotation)은 카메라 회전값을 좌표로 변환해주는 기능을 수행합니다.

여기서 Vector2 firstViewAdjustAngle이라는 변수가 있는데,
시선을 돌릴 때 가로/세로 축으로 각각 몇 도를 넘어가면 화면의 가장자리에 UI를 놓을지 결정하는 값입니다.

그리고 현재 캔버스 크기를 구한 다음, 변환된 각도 값을 서로 곱해서 1인칭 UI의 위치를 확정합니다.

그리고 CameraController에서 시선의 회전값을 가지고 있으므로, 이 값을 UIController에 넘겨주게 됩니다.

UIController에 값을 설정하고 실행합니다.
X축은 방향이 반전되어야 해서 음수로 설정합니다.

모니터 해상도마다 다를 수 있지만 일단 제 기준으로는 이 값이 어느정도 적당하게 움직이는 것 같습니다.



적절한 위치에 놓인 것 같네요.

  • 에이스 컴뱃 7에서도 1인칭 UI가 정확히 콕핏에 맞춰지진 않습니다. 대강 맞췄어요.


여기서 주의할 부분이 있는데,

1인칭 뷰 카메라Viewport Rect 값은 (0, 0, 1, 1)이어야 합니다.

만약 3인칭 뷰 카메라처럼 이렇게 약간의 공백이 있게 되면,

이렇게 UI가 잘리게 됩니다.

UI가 잘리지 않게 하기 위해서, 1인칭 뷰 카메라는 공백이 없도록 설정합시다.



전체 화면:
미니맵
아군/적군 UI
게임 내 대사
조준점
기총 UI
경고 UI
색상 변경
화면에 안 보이는 목표물의 위치를 가리키는 화살표

이제 전체 화면에서의 묵은 이슈들을 해결할 시간입니다.

하나씩 차례대로 하죠.


미니맵

표시할 미니맵은 3종류가 있습니다.

  1. 내가 조종하는 비행기를 기준으로 주변 상황 보기

  1. 조금 더 넓은 범위로 보기

  1. 전체 맵 보기

보시다시피 원래는 미니맵에 표현해줄 오브젝트가 되게 많습니다.
아군 비행기, 적 비행기, 적 타겟 비행기, 일반 목표물, 타겟 목표물 등이 있죠.

(뒤에 깔리는 지형 등은 둘째치고요)

근데 제가 만들려고 하는 스테이지는 1:1 보스전입니다.







게임 내내 미니맵에 빨간색 화살표 한 개만 있을 거라는 뜻입니다.



이렇게 일감을 성공적으로 줄이는군요.


1단계 미니맵

유니티에서 미니맵을 만드는 고전적인 방식이 있습니다.

  1. 미니맵 오브젝트만 보일 레이어를 하나 만듭니다.
    UI처럼 미니맵 아이콘만 보여줄 레이어요.

  1. 화면에 띄우려는 오브젝트에게 Sprite Renderer를 줍니다.
    그리고 Layer를 아까 만든 레이어로 설정합니다.

  1. Orthographic 카메라를 하나 만들고, 위에서 아래를 보게 만듭니다.
    (Rotation.x 를 90으로 주면 됩니다.)
    Clear Flags = Solid Color, Projection = Orthographic 으로 설정합니다.

  1. 카메라의 Culling Mask를 미니맵 레이어만 선택되도록 설정합니다.
    여기까지는 UI랑 비슷하죠.

  1. Render Texture 파일을 하나 생성합니다.

  1. 생성한 Render Texture의 가로 세로 Size를 조절합니다.
    미니맵이 정사각형 형태이므로 값을 동일하게 맞춰줍니다.
    (298이라는 값은 미니맵 스프라이트 사이즈와 동일하게 맞추려는 용도였는데, 사실 상관없습니다.)

  1. 미니맵을 비추는 카메라의 Target Texture에 아까 만든 Render Texture를 넣어줍니다.
    이제 카메라 부분을 보시면 방금 추가한 아이콘만 보이게 됩니다.

  1. 미니맵 UI를 만들어줍니다.

참고로 저는 이미지를 2개 사용해서 Map이 맨 뒤에, Map_center가 맨 앞에 오고,
화면에 띄울 오브젝트는 그 사이에 표시하게끔 만들려 합니다.

  1. UI Raw Image를 만든 다음, Texture에 이전에 만든 Render Texture를 넣어줍니다.

그러면 Scene 화면에서 이렇게 미니맵이 만들어지게 됩니다.

움직이지 않지만 말이죠.

이제 스크립트로 카메라를 제어해야 합니다.



다시 원본 미니맵을 봅시다.
귀찮게도 플레이어가 미니맵의 중앙에 있는 게 아니라 약간 아래에 있습니다.

이 사실을 감안하면서 카메라 위치를 조정해줘야 해요.



MinimapCamera.cs

public class MinimapCamera : MonoBehaviour
{
    public Transform target;
    public float offsetRatio;

    Camera cam;

    void Start()
    {
        cam = GetComponent<Camera>();
    }

    void Update()
    {
        Vector3 targetForwardVector = target.forward;
        targetForwardVector.y = 0;
        targetForwardVector.Normalize();

        Vector3 position = new Vector3(target.transform.position.x, 1, target.transform.position.z)
                           + targetForwardVector * offsetRatio * cam.orthographicSize;
        transform.position = position;
        transform.eulerAngles = new Vector3(90, 0, -target.eulerAngles.y);
    }
}

target은 카메라가 따라갈 대상입니다. 플레이어의 비행기를 여기다 할당하면 됩니다.
offsetRatio이미지 하단으로부터 몇 % 지점에 플레이어가 있는지 에 대한 값입니다. 이 값을 이용해서 카메라의 위치를 UI에 맞게 보정해줍니다.

미니맵 사진을 보면 대략 33% 쯤 되어보이네요.

간략하게 설명하면, UI에 맞도록 비행기가 향하는 방향(Y축 무시)의 일정 거리 앞에다가 카메라를 놓고, (Vector3 position = ...)

비행기의 방향에 따라 카메라도 같이 회전하도록 구현했습니다. (transform.eulerAngles = ...)


여기서 transform.positiony 값을 1로 고정하는데, 모든 미니맵 아이콘의 y 값은 0으로 둘 예정이기 때문입니다.
Clipping Planes 값을 최대한 낮추기 위해, 카메라와 미니맵 아이콘의 거리를 최소화하려고 합니다.



MinimapSprite.cs

[RequireComponent(typeof(SpriteRenderer))]
public class MinimapSprite : MonoBehaviour
{
    // Update is called once per frame
    void Update()
    {
        transform.rotation = Quaternion.Euler(90, transform.parent.eulerAngles.y, 0);
        transform.position = new Vector3(transform.parent.position.x, 0, transform.parent.position.z);
    }
}

미니맵 아이콘 스크립트입니다. 아직은 세상 간단하죠.

우선 미니맵 스프라이트는 본체의 각도가 어떻게 되었든 간에 항상 똑바로 보여야 합니다. 사진처럼 같이 돌아가면 곤란해져요.

그러므로 transform.rotation 값에서 x = 90을 줘서 항상 눕혀주고,
비행기가 바라보는 각도를 표현하기 위해 y = transform.parent.eulerAngles.y 로 설정합니다.

위치는 부모를 따라가도록 position.xz는 부모의 transform.position으로 설정합니다.
아까 카메라 설명할 때 미니맵 아이콘의 y값 (글로벌 좌표)을 모두 0으로 한다고 했었기 때문에, y 값은 0으로 고정시킵니다.

이제 적 비행기에 추가했던 미니맵 아이콘 스프라이트에 MinimapSprite를 추가하고,

미니맵 카메라에는 MinimapCamera를 추가한 다음,
target과 offsetRatio를 할당하고 실행시켜보죠.



타겟 비행기 근처를 비행하면서 미니맵에 제대로 보이는지 확인해봅니다.



미니맵 밖에 있는 오브젝트 표시하기

사진의 왼쪽 하단을 보면, <처럼 된 화살표가 보이죠?
미니맵 범위에 표시되지 않는 오브젝트들도 저렇게 표시해줘야 합니다.

...말하기는 쉽죠. 로직을 어떻게 짜야 할까요?


  1. 오브젝트가 화면에 안 잡히는 범위에 있는지 확인
  2. 그 때 미니맵 가장자리에 맞춰서 화살표 표시
  3. 1번 미니맵의 경우에는 플레이어의 회전 상태도 고려해서 화살표를 표시해야 함
  4. 근데 이 화살표를 띄우는 주체가 누구지?
    오브젝트가 아이콘 스프라이트를 바꾸나? 아니면 미니맵이 화살표 오브젝트를 추가하나?

생각보다 신경쓸 게 많습니다.


우선 프로토타이핑만 해서 실증이 가능한지 정도만 다뤄보겠습니다.
카메라 하위에 화살표를 하나 만들어놓고, 그 화살표 하나만 움직여보죠.

(어차피 1:1 보스전이니까 화살표는 하나면 되잖아요)

미리 말씀드리자면, 제가 만든 화살표 표시 스크립트는 "오브젝트가 미니맵에 화살표 추가를 요청" 하는 방식입니다. 화살표를 띄우는 주체는 미니맵입니다.


MinimapCamera.cs


Camera cam;
Vector2 size;
public Transform indicator;

void Start()
{
    ...
    size = new Vector2(cam.orthographicSize, cam.orthographicSize * cam.aspect);
}

// Update is called once per frame
void Update()
{
    ...
}

public void ShowBorderIndicator(Vector3 position)
{
    float reciprocal;
    float rotation;
    Vector2 distance = new Vector3(transform.position.x - position.x, transform.position.z - position.z);
    
    distance = Quaternion.Euler(0, 0, target.eulerAngles.y) * distance;
    
    // X axis
    if(Mathf.Abs(distance.x) > Mathf.Abs(distance.y))
    {
        reciprocal = Mathf.Abs(size.x / distance.x);
        rotation = (distance.x > 0) ? 90 : -90;
    }
    // Y axis
    else
    {
        reciprocal = Mathf.Abs(size.y / distance.y);
        rotation = (distance.y > 0) ? 180 : 0;
    }

    indicator.localPosition = new Vector3(distance.x * -reciprocal, distance.y * -reciprocal, 1);
    indicator.localEulerAngles = new Vector3(0, 0, rotation);
}

public void HideBorderIncitator()
{
    indicator.gameObject.SetActive(false);
}

지금 작성한 코드는 미니맵 가장자리에 맞춰서 화살표 표시를 구현한 부분입니다.

현재 카메라와 오브젝트 사이의 거리를 계산하고, (Vector2 distance = ...)
카메라의 회전을 고려해서 거리 벡터를 회전시킵니다. (distance = ...)
(3. 1번 미니맵의 경우 회전 상태를 고려를 적용했습니다. 다른 미니맵의 경우 이 코드를 실행하지 않게끔 구현하면 되겠네요.)

이렇게 계산한 거리는 카메라의 가장자리에 위치할 수 있도록 값을 조절합니다.

이렇게 오브젝트들이 배치되었을 때를 가정해봅시다.

두 빨간색 오브젝트가 X, Y 값이 모두 미니맵 범위를 넘어섰다고 해서 두 오브젝트 모두 이렇게 미니맵 코너에 화살표를 넣으면 안 됩니다.
대부분의 오브젝트가 코너에 몰릴 가능성이 있기 때문입니다.

이런식으로 거리 벡터를 재조정해서 두 빨간색 점에 위치하도록 조정해줘야 합니다.

if문에서 이 거리를 재조정하는 계산이 들어가 있고, distance의 부호에 따라서 화살표의 회전값도 조정하고 있습니다. (rotation = ...)

화살표를 띄우는 위치와 회전값 계산이 끝나면 localPosition, localEulerAngles에 값을 대입합니다.



MinimapSprite.cs

[RequireComponent(typeof(SpriteRenderer))]
public class MinimapSprite : MonoBehaviour
{
    SpriteRenderer spriteRenderer;
    public MinimapCamera minimapCamera;

    void Start()
    {
        spriteRenderer = GetComponent<SpriteRenderer>();
    }

    // Update is called once per frame
    void Update()
    {
        ...

        if(spriteRenderer.isVisible == false)
        {
            minimapCamera.ShowBorderIndicator(transform.position);
        }
        else
        {
            minimapCamera.HideBorderIncitator();
        }
    }
}

그 다음, 화면에 오브젝트가 잡히지 않는지 확인하는 방법으로
미니맵 아이콘이 그려지고 있는가? 를 사용하겠습니다.

SpriteRenderer.isVisible()

이 함수를 사용하면 아이콘이 미니맵에 그려지고 있는지 확인할 수 있습니다.

isVisible() == false일 때 위에서 만든 미니맵 카메라의 화살표 표시 함수를 호출하도록 구현했습니다. (minimapCamera.ShowBorderIndicator(transform.position);)

다시 보여지게 되면 minimapCamera.HideBorderIncitator()를 호출해서 화살표를 숨깁니다.


지금 구현한 MinimapSprite는 미니맵 카메라를 필요로 합니다.
등록한 후 실행해보죠.

(프로토타이핑하느라 일단 구조는 신경쓰지 않고 화살표 출력이 잘 되는지 봅시다.)

화살표는 잘 나오는 것 같습니다.


2단계 미니맵, 3단계 미니맵

2단계 미니맵의 특징을 보면,

  • 1단계보다 넓음
  • 카메라가 회전하지 않음
  • 플레이어 아이콘이 바뀜

카메라의 Size를 조정해주고,
Render Texture의 UI 크기도 조정해주고,
회전 안 시키고,
플레이어 아이콘도 보여주게 하면 될 것 같습니다.

3단계 미니맵은 2단계에서 한 가지만 바뀝니다.

  • 맵 전체를 보여줌

그러니까 한 번에 구현해보죠.



MinimapCamera.cs

내용이 너무 쩔어줘서 보실 분만 보세요.

public class MinimapCamera : MonoBehaviour
{
  public enum MinimapIndex
  {
      Small,
      Large,
      All
  }

  public Transform target;
  public GameObject playerIcon;
  public float offsetRatio;

  public float smallViewSize;
  public float largeViewSize;
  public float allViewSize;

  Camera cam;
  Vector2 size;
  public Transform indicator;
  public float indicatorSize;
  float sizeReciprocal;
  int minimapIndex;

  public void ChangeMinimapView(InputAction.CallbackContext context)
  {
      if(context.action.phase == InputActionPhase.Performed)
      {
          minimapIndex = (++minimapIndex) % 3;
          SetCamera();
      }
  }

  public void SetCamera()
  {
      switch((MinimapIndex)minimapIndex)
      {
          case MinimapIndex.Small:
              cam.orthographicSize = smallViewSize;
              cam.cullingMask &= (1 << LayerMask.NameToLayer("Minimap"));
              break;
              
          case MinimapIndex.Large:
              cam.orthographicSize = largeViewSize;
              cam.cullingMask |= (1 << LayerMask.NameToLayer("Minimap (Player)"));
              break;
              
          case MinimapIndex.All:
              cam.orthographicSize = allViewSize;
              break;
      }

      size = new Vector2(cam.orthographicSize, cam.orthographicSize * cam.aspect);
  }

  public void ShowBorderIndicator(Vector3 position)
  {
      float reciprocal;
      float rotation;
      Vector2 distance = new Vector3(transform.position.x - position.x, transform.position.z - position.z);

      // When the x, z positions are same
      if(distance.x == 0 || distance.y == 0)
          return;

      if((MinimapIndex)minimapIndex == MinimapIndex.Small)
      {
          distance = Quaternion.Euler(0, 0, target.eulerAngles.y) * distance;
      }
      
      // X axis
      if(Mathf.Abs(distance.x) > Mathf.Abs(distance.y))
      {
          reciprocal = -Mathf.Abs(size.x / distance.x);
          rotation = (distance.x > 0) ? 90 : -90;
      }
      // Y axis
      else
      {
          reciprocal = -Mathf.Abs(size.y / distance.y);
          rotation = (distance.y > 0) ? 180 : 0;
      }
      
      float scale = sizeReciprocal * GetCameraViewSize();

      indicator.localScale = new Vector3(scale, scale, scale);
      indicator.localPosition = new Vector3(distance.x * reciprocal, distance.y * reciprocal, 1);
      indicator.localEulerAngles = new Vector3(0, 0, rotation);
      
      if(indicator.gameObject.activeInHierarchy == false)
      {
          indicator.gameObject.SetActive(true);
      }
  }

  public void HideBorderIncitator()
  {
      indicator.gameObject.SetActive(false);
  }

  public float GetCameraViewSize()
  {
      return cam.orthographicSize;
  }

  void Awake()
  {
      minimapIndex = (int)MinimapIndex.Small;
      cam = GetComponent<Camera>();
      SetCamera();
      sizeReciprocal = indicatorSize / GetCameraViewSize();
  }
  
  // Update is called once per frame
  void Update()
  {
      Vector3 targetForwardVector = target.forward;
      targetForwardVector.y = 0;
      targetForwardVector.Normalize();

      Vector3 position;
      float cameraRotation;

      if(minimapIndex == (int)MinimapIndex.Small)
      {
          position = new Vector3(target.transform.position.x, 1, target.transform.position.z)
                         + targetForwardVector * offsetRatio * cam.orthographicSize;
          cameraRotation =  -target.eulerAngles.y;
      }
      else
      {
          if(minimapIndex == (int)MinimapIndex.Large)
          {
              position = new Vector3(target.transform.position.x, 1, target.transform.position.z);
          }
          else
          {
              position = new Vector3(0, 1, 0);
          }
          cameraRotation = 0;
      }

      transform.position = position;
      transform.eulerAngles = new Vector3(90, 0, cameraRotation);
  }
}


2, 3번 미니맵에서도 사용할 수 있도록 미니맵 카메라의 기능을 그냥 많이 추가했습니다.

  • Input Event에 따라서 모드 변경
  • 모드에 따라 orthographicSize (카메라가 비추는 범위) 변경
  • 1번 미니맵이 아닐 때는 카메라 회전 및 오프셋 미사용
  • 카메라와 오브젝트가 겹치는 상황에 ShowBorderIndicator가 호출될 경우 에러 발생 수정
  • 플레이어 아이콘 표시/미표시 : Culling Mask 값 변경
  • 미니맵 밖의 오브젝트를 표시하는 화살표도 Size에 맞게 크기 변경

AircraftController에 연결되어 있던 Input Event를 MinimapCamera에 연결시킵니다.



MinimapSprite.cs

public float iconSize;
public float depth;
float sizeReciprocal;

void Start()
{
    spriteRenderer = GetComponent<SpriteRenderer>();
    sizeReciprocal = iconSize / minimapCamera.GetCameraViewSize();
    depth *= 0.01f;
}

// Update is called once per frame
void Update()
{
    ...

    float scale = sizeReciprocal * minimapCamera.GetCameraViewSize();
    transform.localScale = new Vector3(scale, scale, scale);
}

미니맵 스프라이트에는 현재 카메라 사이즈에 맞도록 크기를 조절하는 기능을 추가합니다.
카메라의 orthographicSize가 커지면 더 넓은 범위를 비추게 되고, 미니맵 아이콘의 크기는 작게 보이게 됩니다.

크기에 맞게 스케일을 조정해서 어느 미니맵을 보든 적당한 크기로 보이도록 만들어줍니다.

*이 기능은 미니맵 밖에 있는 오브젝트를 보여주는 화살표도 적용되어야 합니다.

그리고 depth 기능도 추가했는데, 미니맵 아이콘이 겹쳐버릴 때는 아이콘들이 깜빡일 수 있기 때문에 꼭 구분되어야 하는 아이콘은 depth를 바꿔줍니다.

플레이어의 미니맵 아이콘은 항상 위에 있어야 하므로, depth를 1 올립니다.

(실제 y 좌표는 depth * 0.01만큼 올라갑니다.)

값을 모두 할당한 다음 실행합니다.



1단계 미니맵에서 벗어날 때까지 유지하다가, 2단계 - 3단계 순으로 넘어가는 모습입니다.

그러고보니 미니맵 배경을 안 바꿔줬네요.

빠르게 배경 이미지를 만들어서 넣어주고,

RenderTexture 크기도 키워줍니다.

음... 2단계와 3단계 미니맵은 아이콘 크기를 재설정해줘야 할 것 같습니다.
아니면 카메라를 좀 손보거나요.


MinimapCamera.cs

public GameObject[] minimaps = new GameObject[3];

public void SetCamera()
{
    ...
    
    for(int i = 0; i < minimaps.Length; i++)
    {
        minimaps[i].gameObject.SetActive(i == minimapIndex);
    }
}

CameraController에서 카메라 전환할 때 카메라를 활성화/비활성화해줬던 것처럼,
여기서도 미니맵 3개를 활성화/비활성화해주는 코드를 넣습니다.

이제 타겟 근처를 지나가면서 아이콘들이 잘 보이는지 확인하면 됩니다.

코드의 구조가 좀 이상하고 아이콘 크기가 너무 커보이긴 하지만,
목표는 달성한 것 같습니다.



이렇게 1인칭 시점과 미니맵 부분을 끝냈는데요,

전체 화면:
미니맵
아군/적군 UI
게임 내 대사
조준점
기총 UI
경고 UI
색상 변경
화면에 안 보이는 목표물의 위치를 가리키는 화살표

이 남은 것들을 4편에 다 끝낼 수 있으면 좋겠군요.
생각보다 미니맵 만들기가 시간을 많이 잡아먹었습니다.



이 프로젝트의 작업 결과물은 Github에 업로드되고 있습니다.
https://github.com/lunetis/OperationZERO

1개의 댓글

comment-user-thumbnail
2023년 1월 17일

도움이 정말 많이 되었습니다. 감사합니다 센세!

답글 달기