Unity Chapter 11-11. 좀비 TPS 게임 만들기 : UI 매니저와 크로스헤어
카테고리: Unity Lesson 1
태그: Unity Game Engine
인프런에 있는 이제민님의 레트로의 유니티 C# 게임 프로그래밍 에센스 강의를 듣고 정리한 필기입니다. 😀
🌜 [레트로의 유니티 C# 게임 프로그래밍 에센스] 강의 들으러 가기!
Chapter 11. 좀비 TPS 게임 만들기
HUD
플레이어의 상태를 표시하는 전체 UI 캔버스를
HUD
라고 부른다.
- 📂Prefabs 의 🟦HUD Canvas 을 Hierarchy에 추가해준다.
- UI - EventSystem 을 Hierarchy에 추가해준다.
- UI 요소들을 다루기 쉽도록 Scene 창을 2D 로 변경한다.
구성 UI 요소들
Ammo DisPlay
- 화면 오른쪽 아래에 남은 탄약을 나타내는 부분. 이미지가 붙어있음.
Ammo Text
은 그의 자식으로서 남은 탄약을 텍스트로 표시하는 텍스트 UI 컴포넌트
Score Text
- 화면 정가운데 위쪽에 점수를 나타내는 텍스트 컴포넌트
Enemy Wave Text
- 화면 왼쪽에 나타나는 현재 웨이브와 남은 적의 수를 표시하는 텍스트 컴포넌트
GameOver UI
- 자식 Canvas 다.
- GameOver를 나타내는 텍스트 컴포넌트 (“You Die”)
- 게임을 다시 시작할 수 있는 버튼 컴포넌트
- 누르면 게임을 재시작하는 📜UIManager.cs 의 GameRestart 함수를 실행함
- 평소엔 비활성화되다가 플레이어가 죽었을 때만 활성화 된다.
Crosshair
- 크로스 헤어의 UI를 표시하는 렌더러가 붙어있다.
- 2 개의 크로스 헤어의 UI를 갱신하고 제어하는 📜Crosshair.cs 가 붙어 있다.
- 크로스헤어가 2 개
Aim Point
- 언제나 화면 중앙에 위치한다.
Hit Point
- 총알이 실제로 맞게되는 위치에 표시
- 보통은 얘도 화면 중앙에 있지만 플레이어 바로 앞에 장애물이 있거나 하면 화면 중앙 조준점과 다를 때가 있다.
- 왜 그런지는 이전 포스트의 TPS 의 특징 참고
- 보통은 얘도 화면 중앙에 있지만 플레이어 바로 앞에 장애물이 있거나 하면 화면 중앙 조준점과 다를 때가 있다.
Aim Point
에 비해 조금 투명하게 보인다!
- 총알이 실제로 맞게되는 위치에 표시
Health Display
- 화면 왼쪽 아래에 현재 HP를 나타내는 부분. 이미지가 붙어있음
Health Text
는 그의 자식으로서 남은 체력을 텍스트로 표시하는 텍스트 UI 컴포넌트
- 화면 왼쪽 아래에 현재 HP를 나타내는 부분. 이미지가 붙어있음
Life Text
- 화면 왼쪽 위에 남은 생명 수를 표시하는 부분
📜UIManager.cs
HUD Canvas
에 붙게 된다.
- 📜UIManager.cs 을 거쳐서 UI 요소들에 접근하도록 할 것. UI 통제, 제어 기능을 이 스크립트에 몰아 넣음!
- 여러 UI 요소들에 바로 즉시 접근하여 표시 상태를 변경할 수 있는 통로를 다른 스크립트, 오브젝트들에게 제공한다. 따라서 싱글톤으로 구현될 것.
- 📜UIManager.cs (싱글톤)를 사용하는 이유
- HUD를 구성하는 여러 UI 요소들을 일일이 검색해서 찾아서 각각의 다른 스크립트에서 필드 값을 수정하면 서로가 서로를 참조하게 되므로 혼란을 빚을 수 있기 때문. 다른 스크립트 내에서 UI 요소를 변경하려면 각각 또 GetComponent 해서 일일이 UI 요소들을 가져와야 하고 등등.. 복잡하다.
- 따라서 하나의 인스턴스인 싱글톤으로 사용
- HUD를 구성하는 여러 UI 요소들을 일일이 검색해서 찾아서 각각의 다른 스크립트에서 필드 값을 수정하면 서로가 서로를 참조하게 되므로 혼란을 빚을 수 있기 때문. 다른 스크립트 내에서 UI 요소를 변경하려면 각각 또 GetComponent 해서 일일이 UI 요소들을 가져와야 하고 등등.. 복잡하다.
싱글톤으로 구현 됨.
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 이벤트 함수 목록에 넣어둠
- using UnityEngine.SceneManagement;
📜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다.
- 화면상의 위치라 Vector2인
- smoothing에 사용할 값의 변화량
- 화면상 크로스 헤어의 위치
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 호출
- 기존의 크로스 헤어 위치(UI니까 Vector2다)를
🌜 개인 공부 기록용 블로그입니다. 오류나 틀린 부분이 있을 경우
언제든지 댓글 혹은 메일로 지적해주시면 감사하겠습니다! 😄
댓글 남기기