Unity Chapter 11-11. 좀비 TPS 게임 만들기 : UI 매니저와 크로스헤어

Date:     Updated:

카테고리:

태그:

인프런에 있는 이제민님의 레트로의 유니티 C# 게임 프로그래밍 에센스 강의를 듣고 정리한 필기입니다. 😀
🌜 [레트로의 유니티 C# 게임 프로그래밍 에센스] 강의 들으러 가기!


Chapter 11. 좀비 TPS 게임 만들기

HUD

플레이어의 상태를 표시하는 전체 UI 캔버스를 HUD라고 부른다.

  • 📂Prefabs 의 🟦HUD Canvas 을 Hierarchy에 추가해준다.
  • UI - EventSystem 을 Hierarchy에 추가해준다.
  • UI 요소들을 다루기 쉽도록 Scene 창을 2D 로 변경한다.
  • image


구성 UI 요소들

  • Ammo DisPlay
    • 화면 오른쪽 아래에 남은 탄약을 나타내는 부분. 이미지가 붙어있음.
    • Ammo Text은 그의 자식으로서 남은 탄약을 텍스트로 표시하는 텍스트 UI 컴포넌트
  • Score Text
    • 화면 정가운데 위쪽에 점수를 나타내는 텍스트 컴포넌트
  • Enemy Wave Text
    • 화면 왼쪽에 나타나는 현재 웨이브와 남은 적의 수를 표시하는 텍스트 컴포넌트
  • GameOver UI
    • 자식 Canvas 다.
    • image
    • GameOver를 나타내는 텍스트 컴포넌트 (“You Die”)
    • 게임을 다시 시작할 수 있는 버튼 컴포넌트
      • 누르면 게임을 재시작하는 📜UIManager.cs 의 GameRestart 함수를 실행함
    • 평소엔 비활성화되다가 플레이어가 죽었을 때만 활성화 된다.
  • Crosshair
    • 크로스 헤어의 UI를 표시하는 렌더러가 붙어있다.
    • 2 개의 크로스 헤어의 UI를 갱신하고 제어하는 📜Crosshair.cs 가 붙어 있다.
    • 크로스헤어가 2 개
      • image
      • image
      • Aim Point
        • 언제나 화면 중앙에 위치한다.
      • Hit Point
        • 총알이 실제로 맞게되는 위치에 표시
          • 보통은 얘도 화면 중앙에 있지만 플레이어 바로 앞에 장애물이 있거나 하면 화면 중앙 조준점과 다를 때가 있다.
        • Aim Point에 비해 조금 투명하게 보인다!
  • Health Display
    • 화면 왼쪽 아래에 현재 HP를 나타내는 부분. 이미지가 붙어있음
      • Health Text는 그의 자식으로서 남은 체력을 텍스트로 표시하는 텍스트 UI 컴포넌트
  • Life Text
    • 화면 왼쪽 위에 남은 생명 수를 표시하는 부분


📜UIManager.cs

HUD Canvas 에 붙게 된다.

  • 📜UIManager.cs 을 거쳐서 UI 요소들에 접근하도록 할 것. UI 통제, 제어 기능을 이 스크립트에 몰아 넣음!
  • 여러 UI 요소들에 바로 즉시 접근하여 표시 상태를 변경할 수 있는 통로를 다른 스크립트, 오브젝트들에게 제공한다. 따라서 싱글톤으로 구현될 것.
  • 📜UIManager.cs (싱글톤)를 사용하는 이유
    • HUD를 구성하는 여러 UI 요소들을 일일이 검색해서 찾아서 각각의 다른 스크립트에서 필드 값을 수정하면 서로가 서로를 참조하게 되므로 혼란을 빚을 수 있기 때문. 다른 스크립트 내에서 UI 요소를 변경하려면 각각 또 GetComponent 해서 일일이 UI 요소들을 가져와야 하고 등등.. 복잡하다.
      • 따라서 하나의 인스턴스인 싱글톤으로 사용

image

싱글톤으로 구현 됨. Instance 프로퍼티를 통해 리턴 받음.

using UnityEngine;
using UnityEngine.SceneManagement;
using UnityEngine.UI;

public class UIManager : MonoBehaviour
{
    /* ✨싱글톤 */
    private static UIManager instance; // 싱글톤. private.
    
    public static UIManager Instance  // 이 프로퍼티를 통해서 UIManager의 싱글톤 private 인스턴스를 리턴 받음.
    {
        get
        {
            if (instance == null) instance = FindObjectOfType<UIManager>();  // 인스턴스가 이미 존재할 땐 받지 않는다. 즉, null 일때만 받음.

            return instance;
        }
    }

    // UHD Canvas 의 구성 UI 요소들.
    [SerializeField] private GameObject gameoverUI;
    [SerializeField] private Crosshair crosshair;
    [SerializeField] private Text healthText;
    [SerializeField] private Text lifeText;
    [SerializeField] private Text scoreText;
    [SerializeField] private Text ammoText;
    [SerializeField] private Text waveText;

    public void UpdateAmmoText(int magAmmo, int remainAmmo)  // 'ammoText' 남은 탄창 UI 갱신
    {
        ammoText.text = magAmmo + "/" + remainAmmo;
    }

    public void UpdateScoreText(int newScore)  // 'scoreText' 점수 UI 갱신
    {
        scoreText.text = "Score : " + newScore;
    }
    
    public void UpdateWaveText(int waves, int count)  // 'waveText', 남은 적의 수 UI 갱신
    {
        waveText.text = "Wave : " + waves + "\nEnemy Left : " + count;
    }

    public void UpdateLifeText(int count)  // 'lifeText' 남은 생명 수 UI 갱신
    {
        lifeText.text = "Life : " + count;
    }

    public void UpdateHealthText(float health) // 'healthText' 남은 HP UI 갱신
    {
        healthText.text = Mathf.Floor(health).ToString(); // 체력의 소숫점을 내림한 후 문자열로 바꿈
    }

    public void SetActiveGameoverUI(bool active) // 게임 오버시 'GameOver' UI 활성화
    {
        gameoverUI.SetActive(active);
    }

    public void UpdateCrossHairPosition(Vector3 worldPosition) // 해당 위치에 크로스 헤어 UI를 표시
    {
        crosshair.UpdatePosition(worldPosition);
    }
    
    public void SetActiveCrosshair(bool active) // 크로스 헤어 UI 활성화
    {
        crosshair.SetActiveCrosshair(active);
    }
    
    public void GameRestart()  // 게임 Over 상태에서 Restart 버튼을 눌렀을 때 실행시킬 함수. 게임 재 시작
    {
        SceneManager.LoadScene(SceneManager.GetActiveScene().name);  // 현재 씬의 이름을 넘겨 다시 로드
    }
}

싱글톤 구현

  • private한 instance를 리턴해주는 Instance 프로퍼티를 통하여 기존의 📜UIManager 인스턴스가 없을 때만, 즉 instace == null일때만 📜UIManager 인스턴스인 instance를 리턴 받는다.
    • 📜UIManager 인스턴스가 오직 하나만 존재하도록 관리해주는 것!

UHD Canvas를 구성하는 자식들인 UI 요소들

  • private 하지만 [SerializeField]로 선언이 되어 있어서 유니티 엔진 상에서 수정할 수 있다.
    • private 하지만 사진 상에서 슬롯이 열려있는 것을 볼 수 있다.

멤버 함수

  • 각각의 UI 요소들을 갱신하는 멤버 함수들
    • public으로 선언이 되어 있다.
  • 크로스 헤어 갱신
    • UpdateCrossHairPosition 👉 월드 좌표계 위치를 넘겨 해당 위치에 크로스 헤어를 표시
    • SetActiveCrosshair 👉 크로스 헤어 활성화
  • 게임 재시작
    • using UnityEngine.SceneManagement;
      • 현재 활성화된 씬을 다시 로드
    • GameRestart() 👉 Restart 버튼의 OnClick 이벤트 함수 목록에 넣어둠
      • image


📜Crosshair.cs

using UnityEngine;
using UnityEngine.UI;

public class Crosshair : MonoBehaviour
{
    public Image aimPointReticle; 
    public Image hitPointReticle; 

    public float smoothTime = 0.2f;  
    private Vector2 currentHitPointVelocity;
    
    private Camera screenCamera;  
    private RectTransform crossHairRectTransform;
    
    private Vector2 targetPoint;

    private void Awake()
    {
        screenCamera = Camera.main;
        crossHairRectTransform = hitPointReticle.GetComponent<RectTransform>();
    }

    public void SetActiveCrosshair(bool active)
    {
        hitPointReticle.enabled = active;
        aimPointReticle.enabled = active;
    }

    public void UpdatePosition(Vector3 worldPoint)
    {
        targetPoint = screenCamera.WorldToScreenPoint(worldPoint);
    }

    private void Update()
    {
        if (!hitPointReticle.enabled) return;

        crossHairRectTransform.position = Vector2.SmoothDamp(crossHairRectTransform.position, targetPoint,
            ref currentHitPointVelocity, smoothTime * Time.deltaTime);
    }
}

멤버 변수

    public Image aimPointReticle; 
    public Image hitPointReticle; 

    public float smoothTime = 0.2f;  
    private Vector2 currentHitPointVelocity;
    
    private Camera screenCamera;  
    private RectTransform crossHairRectTransform;
    
    private Vector2 targetPoint;
  • 크로스헤어 이미지
    • aimPointReticle
      • 화면상 크로스헤어를 나타내는 이미지
    • hitPointReticle
      • 실제로 맞게되는 크로스헤어를 나타내는 이미지
  • 스무딩
    • smoothTime
      • 실제로 맞게되는 크로스헤어는 화면상에서 임의적으로 smooth 하게 움직이게 할건데 그때의 지연시간
      • 0.2 초의 지연시간
    • currentHitPointVelocity
      • smoothing에 사용할 값의 변화량
        • 화면상의 위치라 Vector2인 targetPoint로 스무스하게 변화하는 그 변화량을 나타내기 때문에 얘도 Vector2다.
  • 화면상 크로스 헤어의 위치
    • screenCamera
      • 크로스헤어를 알맞는 위치에 그리려면 실제로 맞게되는 월드 좌표계 위치가 카메라 화면상에서 어디에 위치하는지를 알아야 한다.
        • 그러려면 카메라 정보가 필요함
      • 메인 카메라가 할당 될 것
    • targetPoint
      • 월드 좌표계 위치를 화면상 위치로 변환한 좌표가 될 것.
        • 따라서 Vector2이다.
    • crossHairRectTransform
      • hitPointReticle 이미지의 RectTransform.
      • 이를 총알이 실제로 맞게되는 위치로 옮겨줄 것이다.

멤버 함수

private void Awake()

    private void Awake()
    {
        screenCamera = Camera.main;
        crossHairRectTransform = hitPointReticle.GetComponent<RectTransform>();
    }
  • screenCamera 메인 카메라 가져와서 할당
  • crossHairRectTransform
    • hitPointReticle 이미지의 RectTransform 컴포넌트 할당


SetActiveCrosshair(bool active)

    public void SetActiveCrosshair(bool active)
    {
        hitPointReticle.enabled = active;
        aimPointReticle.enabled = active;
    }
  • active 값에 따라 두 크로스 헤어를 모두 활성화 or 비활성화


public void UpdatePosition(Vector3 worldPoint)

    public void UpdatePosition(Vector3 worldPoint)
    {
        targetPoint = screenCamera.WorldToScreenPoint(worldPoint);
    }
  • 월드 좌표계 위치를 입력으로 받아서 화면상 좌표계로 변환하여 targetPoint에 대입


private void Update()

매 프레임마다 hitPointReticle 이미지의 위치(crossHairRectTransform)를 실제로 총이 맞는 위치에 그려주는 역할을 한다.

  • 계속 위치가 바뀔텐데 그럴때마다 스무스하게 바꿔준다.
    private void Update()
    {
        if (!hitPointReticle.enabled) return;

        crossHairRectTransform.position = Vector2.SmoothDamp(crossHairRectTransform.position, targetPoint,
            ref currentHitPointVelocity, smoothTime * Time.deltaTime);
    }
  • 실제로 맞는 크로스 헤어인 hitPointReticle 가 현재 비활성화 되어있다면 실행하지 않고 그냥 return
  • hitPointReticle 가 활성화 되있을 때만
    • 기존의 크로스 헤어 위치(UI니까 Vector2다)를 crossHairRectTransform.position 👉 targetPoint(화면상 위치)로 스무스하게 갱신
      • Vector2 의 SmoothDamp 호출


🌜 개인 공부 기록용 블로그입니다. 오류나 틀린 부분이 있을 경우 
언제든지 댓글 혹은 메일로 지적해주시면 감사하겠습니다! 😄

맨 위로 이동하기


Unity Lesson 1 카테고리 내 다른 글 보러가기

댓글 남기기