일반적인 게임 오브젝트는 월드 공간에 배치되지만 유저 인터페이스(GUI)는 우리가 보고 있는 화면, 스크린 공간에 배치된다. 따라서 하이어라키에서 UI > Canvas 메뉴로 캔버스를 생성한다.
미리 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로 설정한다
그 다음 슬라이더 인스펙터에서 다음과 같이 설정해준다.
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를 같이 눌러서 앵커를 설정
스크립트를 아래와 같이 수정한 후에 각각의 오브젝트에 컴포넌트로 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 오브젝트를 복사해서 다음과 같이 설정한다.
스크립트를 다음과 같이 수정한다.
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로 이름 변경후 다음과 같이 속성을 설정해주었다.
스크립트를 아래와 같이 수정한 후 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 : 스크린 상의 오브젝트 위치를 월드 좌표로 변환