[Unity][2D-Game] Undead Survivor (10)

suhan0304·2023년 11월 8일
0

유니티 - Undead Survivor

목록 보기
12/21
post-thumbnail

Review

  • 몬스터의 피격, 사망 리액션을 구현했다.
  • 피격 리액션은 넉백과 Hit 애니메이션 재생으로 구현했다.
  • 사망 리액션은 Dead 애니메이션을 재생시킨 후에 여러 컴포넌트의 속성 값들을 수정해주는 로직을 구현했다.
  • 플레이어의 레벨, 킬 수, 경험치 변수와 GetExp 함수 를 만들었다.

강의영상 (10) - HUD 제작하기


개발

UI 캔버스

일반적인 게임 오브젝트는 월드 공간에 배치되지만 유저 인터페이스(GUI)는 우리가 보고 있는 화면, 스크린 공간에 배치된다. 따라서 하이어라키에서 UI > Canvas 메뉴로 캔버스를 생성한다.

  • RectTrabsfirn : 스크린 전용 Transfom 역할 컴포넌트
  • Render Mod : UI를 어디에 얹을것인지를 결정
    - Screen-Camera = 카메라에 그대로 얹는 형태
    - Screen-Overlay = 스크린에 그대로 얹는 형태
  • Screen-Camera : 스크린을 카메라에 맞추는 형태
  • UI Scale Mode :
    - Constant Pixel Size : 고정된 픽셀 사이즈로 Aspeact가 올라갈 수록 점점 작아진다.
    - Scale with Screen Size : 화면의 사이즈에 따라 Canvas의 Scale을 알아서 조정
  • Refernce Resolution : 기존 Main Camera의 Refernce Resolution과 동일하게 설정
  • Match : 가로 세로 중 어떤 것을 기준으로 맞출 것인 지 조정
  • Refernce Pexels Per Unit : 현재 개발 중인 게임은 거의 모든 단위 유닛이 18픽셀로 설정했기 때문에 캔버스도 동일하게 18로 설정


스크립트 준비

미리 UI를 관리하는 스크립트를 HUD(Head-Up DisPlay) 스크립트를 다음과 같이 작성했다. 코드에 관련된 설명은 주석을 참고하자.

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;

public class HUD : MonoBehaviour
{
    //다루게 될 데이터를 열겨형 enum으로 선언
    public enum InfoType { Exp, Level, Kill, Time, Health }

    public InfoType type;

    //UnityEngine.UI 선언후 사용
    Text myText;
    Slider mySlider;

    void Awake()
    {
        myText = GetComponent<Text>();      //텍스트   초기화
        mySlider = GetComponent<Slider>();  //슬라이더 초기화
    }

    void LateUpdate() //연산이 끝나고 갱신되도록 LateUpate 사용
    {
        switch (type)
        {
            case InfoType.Exp:
                break;

            case InfoType.Level:

                break;
            case InfoType.Kill:

                break;
            case InfoType.Time:

                break;
            case InfoType.Health:

                break;
        }
    }
}

캔버스 컴포넌트를 사용하고 싶으면 using UnityEngine.UI; 가 반드시 필요하다. Text만 선언해도 자동으로 선언되지만 Visual Studio의 버전에 따라 다르니 유의해서 사용하자.


경험치 게이지

경험치 게이지를 표현할 슬라이더를 캔버스 안에 UI>Slider를 추가했다.

Rect Transform은 길이와 높이 위치, 앵커로 구성되어있다.
- 앵커 변경 시 + Shift : 기준점 변경
- 앵커 변경 시 + Alt : 위치(크기) 변경

위쪽에 경험치 바가 꽉 차도록 Shift Alt를 누른 후에 앵커를 골라준다. 높이는 7로 설정한다

  • 앵커가 지정된 후의 Left, Right는 좌우 여백을 의미한다.

그 다음 슬라이더 인스펙터에서 다음과 같이 설정해준다.

  • 플레이어가 게이지를 임의로 수정할 수 없도록 INteractible 해제
  • 버튼 클릭 시 색이 변하는 Transition 또한 필요 없으므로 None으로 설정
  • Navigation : UI의 포커싱 순서로 Tap으로 포커싱되지 않도록 None으로 설정
  • Handle Rect 항복을 선택하고 Del 또는 Backspace로 지워준다.
  • Slider의 자식 오브젝트 중 Handle Slider Area를 삭제한다.

Slider의 자식오브젝트인 BackGround와 FillArea의 앵커가 꽉 차도록 설정했다. 이때 각 오브젝트의 여백이 없도록 Left, Top, Right, Bottom을 모두 0으로 지정해주었다.
- Fill 자식 오브젝트의 여백도 0으로 변경해준다. (원래는 Handle이 차지하고 있던 공간)

마지막으로 Background의 Sprite를 Back 0을, Fill Area의 자식 오브젝트인 Fill 의 Sprite를 Fron 0을 드래그 드랍으로 설정한다.

이제 슬라이더가 변경되도록 HUD 스크립트 를 다음과 같이 수정한다. 수정한 HUD 스크립트를 Slider에 추가하고 열거형으로 선언해준 Type을 Exp로 선택해준다.

case InfoType.Exp:
    float curExp = GameManager.Instance.exp; //현재 exp
    float maxExp = GameManager.Instance.nextExp[GameManager.Instance.level]; //레벨업에 필요한 경험치
    mySlider.value = curExp / maxExp;
    break;

슬라이더는 0~1까지의 value를 가지고 해당 값의 비율로 슬라이더의 채워지는 정도가 결정된다. 따라서 value를 수정해 경험치 바 게이지를 조정할 수 있다.


레벨 및 킬수 텍스트

레벨 및 킬수 표시를 위해 텍스트를 두개 만들고 앵커를 적절히 선택한다. 앵커를 설정할 때는 항상 위치하고 중심점을 옮길 것이기 때문에 항상 Shift + Alt를 같이 눌러서 앵커를 설정

레벨 텍스트

  • 이름을 Level로 설정
  • 레벨 텍스트를 오른쪽 상단 앵커로 고정
  • 텍스트 컴포넌트의 ALignment에서 오른쪽 상단 정렬 설정
  • PosX = -1, PosY = -8로 해서 공백을 설정
  • 글꼴을 Fonts 폴더의 neodgm 으로 설정
  • 글자 크기를 6으로 설정

킬수 텍스트

  • UI > Image를 생성후 Icon 0을 넣어준 후 Set Nativce Size를 선택해 원래 위치로 설정
  • 이름을 Kill로 설정
  • 해당 아이콘을 왼쪽 위로 앵커 설정
  • Pox X = 2, Pox Y = -8로 설정
  • 레벨 텍스트를 복사해서 이미지 안에 자식 오브젝트로 붙여넣기
  • 자식 텍스트의 Pos X = 9 나머지는 0
  • 자식 텍스트의 Height = 8로 설정
  • 킬수 UI 오브젝트의 이름을 각각 Kill, KillText로 변경

스크립트를 아래와 같이 수정한 후에 각각의 오브젝트에 컴포넌트로 HUD를 넣어주고 인스펙터에서 각각 Level, Kill을 선택해주었다.

case InfoType.Level:
    myText.text = string.Format("Lv.{0:F0}", GameManager.Instance.level);
    break;
case InfoType.Kill:
    myText.text = string.Format("{0:F0}", GameManager.Instance.kill);
    break;

Format : 각 숫자 인자값을 지정된 형태의 문자열로 만들어주는 함수
- 인자 값의 문자열이 들어갈 자리를 '{순번:형태}'로 작성
- 형태 : F0, F1, F2 소수점의 자릿수 F2 = 소숫점 2자리까지
- 형태 : D0, D1, D2 정수의 자릿수 D2 = 십의 자리까지


타이머 텍스트

생존 시간을 동일하게 텍스트로 표현한다. 따라서 Level 오브젝트를 복사해서 다음과 같이 설정한다.

  • 이름은 Timer로 설정
  • 정 가운데 상단 앵커 위치 + 중심점
  • pos Y = -8로 설정
  • 글자 가운데 정렬
  • 글자 size를 9로, color를 흰색으로 섲렁
  • HUD 스크립트 타입을 Timer로 설정

스크립트를 다음과 같이 수정한다.

case InfoType.Time:
    //총 시간 - 현재 게임 시간 = 남은시간
    float remainTime = GameManager.Instance.maxGameTime - GameManager.Instance.gameTime;
    int min = Mathf.FloorToInt(remainTime / 60); // 분 = 남은 시간 (초) / 60 \ 소숫점은 버리고 int로 변환 
    int sec = Mathf.FloorToInt(remainTime % 60); //분 = 남은 시간 (초) % 60 \ 소숫점은 버리고 int로 변환
    myText.text = string.Format("{0:D2}:{0:D2}",min, sec);
    break;


체력 게이지

게임 매니저에서 체력, 최대체력 변수를 선언해준 후 사용했다. 이 후 캔버스에 Health라는 오브젝트를 새로 생성해준다. Right는 12, Height는 4로 설정해준다.

경험치 게이지를 복사 후 Health 오브젝트에 밀어넣는다.
Health Slide로 이름 변경후 다음과 같이 속성을 설정해주었다.

  • 아래 꽉차게 앵커 설정
  • Pos Y = -12로 설정
  • Height = 4로 설정
  • Background와 Fill Area의 Fill의 스프라이트를 Back1, Front1 스프라이트로 설정

스크립트를 아래와 같이 수정한 후 Health Slider의 HUD 컴포넌트의 Type 값을 Health로 수정해주었다.

case InfoType.Health:
    float curHealth = GameManager.Instance.health; //현재 exp
    float maxHealth = GameManager.Instance.maxHealth; //레벨업에 필요한 경험치
    mySlider.value = curHealth / maxHealth;
    break;

- 플레이어가 체력바를 벗어난다 = 시네머신 카메라로 인해 발생하는 오류
- 체력이 없다고 뜬다 = 체력 초기화 코드가 아직 없음

체력 초기화와 시네머신 카메라로 인해 체력바가 플레이어를 따라가지 못하는 오류를 다음과 같이 수정해서 해결했다.

체력바 오류

게임 매니저에 체력 초기화 함수를 추가로 작성해서 해결했다.

GameManager.cs

private void Start()
{
	health = maxHealth;
}

체력바 위치 오류

Follow 스크립트를 아래와 같이 작성한 후 Health 오브젝트에 컴포넌트로 추가해줬다.

public class Follow : MonoBehaviour
{
    RectTransform rect;

    private void Awake()
    {
        rect = GetComponent<RectTransform>();
    }

    void FixedUpdate() //플레이어가 물리 프레임 단위로 이동 중
    {
        //월드좌표와 스크린 좌표는 서로 다르기 때문에 바꿔준 후에 설정
        rect.position = Camera.main.WorldToScreenPoint(GameManager.Instance.player.transform.position);
    }
}

정말로 중요한 개념이 월드 상의 좌표와 스크린 상의 좌표는 전혀 다르다는 것이다. 따라서 스크린 상의 좌표를 월드 상의 좌표로 사용하거나 그 반대의 경우에도 변환이 반드시 필요하다.

스크린 상의 좌표

월드 상의 좌표

Camera.main.WorldToScreenPoint : 월드 상의 오브젝트 위치를 스크린 좌표로 변환
Camera.main.ScreenToWorldPoint : 스크린 상의 오브젝트 위치를 월드 좌표로 변환


결과물

profile
Be Honest, Be Harder, Be Stronger

0개의 댓글