이제 최종완성할 차례다. 추가구현할 내용은 아래와 같다.
- UI버튼
- 게임오버상태, 점수를 관리하는 GameManager
- 적 생성기
- 아이템 생성기
- 포스트프로세싱을 통한 비주얼개선
HUD Canvas
주어진 Prefab을 그대로 사용한다.
UIManager.cs
using UnityEngine;
using UnityEngine.SceneManagement; // 씬 관리자 관련 코드
using UnityEngine.UI; // UI 관련 코드
// 필요한 UI에 즉시 접근하고 변경할 수 있도록 허용하는 UI 매니저
public class UIManager : MonoBehaviour {
// 싱글톤 접근용 프로퍼티
public static UIManager instance
{
get
{
if (m_instance == null)
{
m_instance = FindObjectOfType<UIManager>();
}
return m_instance;
}
}
private static UIManager m_instance; // 싱글톤이 할당될 변수
public Text ammoText; // 탄약 표시용 텍스트
public Text scoreText; // 점수 표시용 텍스트
public Text waveText; // 적 웨이브 표시용 텍스트
public GameObject gameoverUI; // 게임 오버시 활성화할 UI
// 탄약 텍스트 갱신
public void UpdateAmmoText(int magAmmo, int remainAmmo) {
ammoText.text = magAmmo + "/" + remainAmmo;
}
// 점수 텍스트 갱신
public void UpdateScoreText(int newScore) {
scoreText.text = "Score : " + newScore;
}
// 적 웨이브 텍스트 갱신
public void UpdateWaveText(int waves, int count) {
waveText.text = "Wave : " + waves + "\nEnemy Left : " + count;
}
// 게임 오버 UI 활성화
public void SetActiveGameoverUI(bool active) {
gameoverUI.SetActive(active);
}
// 게임 재시작
public void GameRestart() {
SceneManager.LoadScene(SceneManager.GetActiveScene().name);
}
}
UI와 로직은 분리해서 구현해야 한다.
HUD Canvas 게임오브젝트는 여러 UI 요소를 자식으로 가지고 있는데, HUD Canvas의 UI 요소들을 사용하려는 Script(=로직)이, UI요소를 직접 접근한다면 유지보수가 힘들다.
UI코드가 변경되었을 때 로직이 변경되는 일을 최소화하고 싶다.
UIManager라는 UI 관리용 스크립트에 UI관리코드를 몰아넣자.
일단 Manager는 싱글턴 프로퍼티로 선언한다.
public class UIManager : MonoBehaviour {
// 싱글톤 접근용 프로퍼티
public static UIManager instance
{
get
{
if (m_instance == null)
{
m_instance = FindObjectOfType<UIManager>();
}
return m_instance;
}
}
UIManager 타입 오브젝트는 m_instance에 할당되고, null일 경우에만 get이 초기화작업을 수행한다. 그 외에는 m_instance를 그대로 리턴한다.
아주 명료한 싱글턴이다.
그리고 각 public Text 변수들을 초기화하는 Update 함수를 만들자.
탄알갱신 이후, 로직이 Text를 수정하는게 아니라, 로직은 단지 UIManager의 UpdateXXXXX 함수를 호출하여 해당 변수를 수정하게끔 하는 것이 목적이다.
쓰기는 내부에서만, 읽기는 외부에서도 가능하게 하는 법
나는 ammoText 변수는 외부에서는 읽기만 가능하고, 쓰기는 내부의 public 함수인 UpdateAmmoText를 통해서만 가능하게 하고싶었다. 이 경우 아래와 같이 private를 두고, public 변수에는 get만 선언하고 set은 안두는 방식으로 구현가능하다.
public class UIManager : MonoBehaviour {
[SerializeField] // [SerializeField]를 사용하면 private 필드도 Unity 인스펙터 창에 노출되어, Unity 에디터에서 해당 필드의 값을 설정할 수 있습니다. 이를 통해 코드의 캡슐화를 유지하면서도 유연하게 값을 설정할 수 있습니다.
private Text ammoText; // 탄약 표시용 텍스트
public Text AmmoText {
get { return ammoText; }
}
// 탄약 텍스트 갱신
public void UpdateAmmoText(int magAmmo, int remainAmmo) {
ammoText.text = magAmmo + "/" + remainAmmo;
}
아무튼 게임 재시작 메서드를 구현하자.
게임 재시작은 현재 씬을 다시 로드하는 방식으로 구현한다.
public void GameRestart() {
SceneManager.LoadScene(SceneManager.GetActiveScene().name);
}
}
이 GameRestart 함수가 Restart Button 이 눌렀을 때 호출되도록 해야한다.
Restart 버튼의 OnClick에, HUDCanvas의 UIManager 스크립트의 GameRestart() 함수를 넣으면
Restart 버튼 클릭시 GameRestart() 함수가 실행된다.
이게 무엇을 한거냐면,
Restart Button의 OnClick 이벤트를 구독할 이벤트리스너 슬롯에, UIManager.GameRestart() 메서드를 등록한 것이다.
게임시작하자마자 GameOver 창을 띄울수는 없으니, GameOver UI는 인스펙터창에서 활성화체크를 해제하자.
GameOver를 제외한 UI들이 나오는것을 볼 수 있다.
GameManager.cs 구현
당연히 싱글턴으로 구현한다.
public class GameManager : MonoBehaviour {
// 싱글톤 접근용 프로퍼티
public static GameManager instance
{
get
{
// 만약 싱글톤 변수에 아직 오브젝트가 할당되지 않았다면
if (m_instance == null)
{
// 씬에서 GameManager 오브젝트를 찾아 할당
m_instance = FindObjectOfType<GameManager>();
}
// 싱글톤 오브젝트를 반환
return m_instance;
}
}
두 가지 변수를 관리한다.
private int score = 0; // 현재 게임 점수
public bool isGameover { get; private set; } // 게임 오버 상태
isGameover는 외부에서 참조가능하지만 변경은 내부에서만 가능하다.(앞선 포스트에서 불필요하게 복잡하게 구현된 방법을 제안했었는데, 이런 방법이 있었다는걸 까먹었고있었다;;;;)
Awake() 에서는, 이미 다른 GameManager가 싱글턴으로 존재하고 있다면(그럴리는 없겠지만), 자신의 게임오브젝트를 파괴한다.
더 튼튼하게 오로지 1개만 존재함을 보장한다.
private void Awake() {
// 씬에 싱글톤 오브젝트가 된 다른 GameManager 오브젝트가 있다면
if (instance != this)
{
// 자신을 파괴
Destroy(gameObject);
}
}
이제 Start() 함수를 보자. Awake 직후 실행되는 메서드이다.
씬에서 PlayerHealth 타입의 오브젝트를 찾고, 해당 오브젝트의 EndGame 메서드를 실행할수있게 append해놓는다.
private void Start() {
// 플레이어 캐릭터의 사망 이벤트 발생시 게임 오버
FindObjectOfType<PlayerHealth>().onDeath += EndGame;
}
엄밀히 말하면, onDeath 이벤트를 구독하는 처리이다.
onDeath 이벤트가 발동될 때, EndGame() 메서드가 실행될 수 있도록 한다.
AddScore과 EndGame 함수는 별게 없어서, 설명은 생략한다.
// 점수를 추가하고 UI 갱신
public void AddScore(int newScore) {
// 게임 오버가 아닌 상태에서만 점수 증가 가능
if (!isGameover)
{
// 점수 추가
score += newScore;
// 점수 UI 텍스트 갱신
UIManager.instance.UpdateScoreText(score);
}
}
// 게임 오버 처리
public void EndGame() {
// 게임 오버 상태를 참으로 변경
isGameover = true;
// 게임 오버 UI를 활성화
UIManager.instance.SetActiveGameoverUI(true);
}
완성된 GameManager.cs
using UnityEngine;
// 점수와 게임 오버 여부를 관리하는 게임 매니저
public class GameManager : MonoBehaviour {
// 싱글톤 접근용 프로퍼티
public static GameManager instance
{
get
{
// 만약 싱글톤 변수에 아직 오브젝트가 할당되지 않았다면
if (m_instance == null)
{
// 씬에서 GameManager 오브젝트를 찾아 할당
m_instance = FindObjectOfType<GameManager>();
}
// 싱글톤 오브젝트를 반환
return m_instance;
}
}
private static GameManager m_instance; // 싱글톤이 할당될 static 변수
private int score = 0; // 현재 게임 점수
public bool isGameover { get; private set; } // 게임 오버 상태
private void Awake() {
// 씬에 싱글톤 오브젝트가 된 다른 GameManager 오브젝트가 있다면
if (instance != this)
{
// 자신을 파괴
Destroy(gameObject);
}
}
private void Start() {
// 플레이어 캐릭터의 사망 이벤트 발생시 게임 오버
FindObjectOfType<PlayerHealth>().onDeath += EndGame;
}
// 점수를 추가하고 UI 갱신
public void AddScore(int newScore) {
// 게임 오버가 아닌 상태에서만 점수 증가 가능
if (!isGameover)
{
// 점수 추가
score += newScore;
// 점수 UI 텍스트 갱신
UIManager.instance.UpdateScoreText(score);
}
}
// 게임 오버 처리
public void EndGame() {
// 게임 오버 상태를 참으로 변경
isGameover = true;
// 게임 오버 UI를 활성화
UIManager.instance.SetActiveGameoverUI(true);
}
}
'Development > Unity Engine' 카테고리의 다른 글
[Retro유니티] 아이템 생성기 및 Item 구현 (1) | 2024.12.29 |
---|---|
[Retro유니티] 적 생성기 (0) | 2024.12.29 |
[Retro유니티] NavMesh를 통한 길찾기 구현 및 Enemy.cs 완성 (0) | 2024.12.28 |
[Retro유니티] PlayerHealth.cs 완성 (0) | 2024.12.25 |
[Unity] 기본 Editor 변경: Visual Studio -> VSCode (0) | 2024.12.24 |