본문 바로가기

Unity/▶ Game Development: 2D Game

[2D Game] #7. 게임 메니저 만들기

728x90
반응형

게임 매니저 만들기

플레이어의 상태에 따라 게임의 전반적인 상태를 관리하는 게임 매니저를 만들어보자. 게임 매니저의 역할은 다음과 같다.

  • 점수 저장
  • 게임오버 상태 표현
  • 플레이어의 사망을 감지해 게임 오버 처리 실행
  • 점수에 따라 점수 UI 텍스트 갱신
  • 게임 오버되었을 시 게임 오버 UI 활성화

싱글턴 패턴

게임 매니저처럼 관리자 역할을 하는 오브젝트는 일반적으로 프로그램에 단 하나만 존재한다. 그리고 언제 어디서든 즉시 접근 가능해야 한다. 따라서 해당 게임 매니저 또한 이 두 가지 조건을 동시에 만족해야 한다.

 

[단일 오브젝트]

 

점수를 관리하는 점수 매니저가 두 개 이상인 경우, 두 파일 매니저가 하나의 파일에 접근하고 수정하는 과정에서 에러가 발생할 경우가 있다. 따라서 파일 매니저는 프로그램에 단 하나만 있는 것이 좋으며, 점수와 UI, 게임 상태를 저장하는 게임 매니저도 게임에 단 하나만 존재해야 한다.

 

[손쉬운 접근]

 

매니저라고 불리는 오브젝트는 보통 프로그램의 특정 영역이 아닌 어느 곳에서도 사용할 수 있는 편의 기능을 제공한다. 따라서 매니저 오브젝트는 코드 어느 곳에서도 쉽게 접근할 수 있는 편의 기능을 제공한다.

 

[싱글턴을 사용하는 이유]

 

앞선 내용을 정리하면, 게임 매니저는 다음 두 조건을 만족해야 한다.

 

  1. 게임 매니저는 단 하나만 존재
  2. 어떤 곳에서 손쉽게 게임 매니저에 접근이 가능

이런 조건에서는 주로 싱글턴이라는 디자인 패턴을 사용한다.

디자인 패턴: 어떤 문제를 해결하는 데 좋다고 알려진 구조. 프로그래머 사이에서 경험적으로 어떤 문제에는 어떤 구조가 좋다고 공유하는 방법

 

싱글턴 패턴은 '어떤 오브젝트가 프로그램에 단 하나만 존재해야 하며, 어느 곳에서도 쉽게 접근 가능해야 할 때' 사용된다. 싱글턴 패턴을 사용하면 게임 매니저가 씬에 단 하나만 있게 하고, 어느 곳에도 게임 매니저에 즉시 접근하게 만든다.

정적(Static)

싱글턴 패턴을 구현할 때는 정적 변수의 특징을 활용한다.

 

클래스에 선언된 변수들은 해당 클래스 타입의 오브젝트가 생성되면 함께 생성된다. 이것은 메모리 상에 멤버 변수가  오브젝트 수만큼 존재한다는 의미이다.

 

다시 말해, 클래스에 의해 생성된 각각의 오브젝트는 각 개체로서 존재하며 해당 개체의 변수를 통해 오브젝트의 총개수를 판단할 수 없다는 의미이다. 개체의 변수로서 오브젝트의 개수를 파악하기 위해서는 각각의 오브젝트마다 해당 개체의 변수가 존재하는 것이 아닌 하나의 변수를 공유해야 한다.

 

어떤 변수를 정적 타입으로 선언하면 해당 타입의 오브젝트를 몇 개를 생성하든 그 수에 상관없이 메모리에 하나만 존재한다.

 

이때 Static 변수는 해당 클래스와 관련 있지만 개별 오브젝트마다 하나씩 가지고 있는 것보다는 모든 오브젝트가 공유하는 것이 더 나은 값을 표현할 때 사용한다. 그러한 값의 예로는 오브젝트의 개수가 있다.

 

Static 변수의 특징은 다음과 같다.

  • 메모리에 단 하나만 존재하고 모든 오브젝트가 공유한다.
  • 클래스 이름과 점(.) 연산자를 이용해 접근이 가능하다.

Static의 이러한 특징은 싱글턴을 구현하는데도 사용된다.

GamaManager 스크립트 생성

GamaManager 스크립트를 먼저 다음과 같이 생성한다.

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

// 게임 오버 상태를 표현하고, 게임 점수와 UI를 관리하는 게임 매니저
// 씬에는 단 하나의 게임 매니저만 존재할 수 있다.
public class GameManager : MonoBehaviour {
    public static GameManager instance; // 싱글톤을 할당할 전역 변수

    public bool isGameover = false; // 게임 오버 상태
    public Text scoreText; // 점수를 출력할 UI 텍스트
    public GameObject gameoverUI; // 게임 오버시 활성화 할 UI 게임 오브젝트

    private int score = 0; // 게임 점수

    // 게임 시작과 동시에 싱글톤을 구성
    void Awake() {
        // 싱글톤 변수 instance가 비어있는가?
        if (instance == null){
            // instance가 비어있다면(null) 그곳에 자기 자신을 할당
            instance = this;
        }
        else{
            // instance에 이미 다른 GameManager 오브젝트가 할당되어 있는 경우
            // 씬에 두개 이상의 GameManager 오브젝트가 존재한다는 의미.
            // 싱글톤 오브젝트는 하나만 존재해야 하므로 자신의 게임 오브젝트를 파괴
            Debug.LogWarning("씬에 두개 이상의 게임 매니저가 존재합니다!");
            Destroy(gameObject);
        }
    }
    void Update() {
        // 게임 오버 상태에서 게임을 재시작할 수 있게 하는 처리
        if(isGameover && Input.GetMouseButtonDown(0)){
            SceneManager.LoadScene(SceneManager.GetActiveScene().name);
        }
    }
    // 점수를 증가시키는 메서드
    public void AddScore(int newScore) {
        if(isGameover){
            score += newScore;
            score.text = "Score" + score;
        }
    }
    // 플레이어 캐릭터가 사망시 게임 오버를 실행하는 메서드
    public void OnPlayerDead() {
        isGameover = true;
        gameoverUI.SetActive(true);
    }
}

1. GamaManager 싱글턴 구현

GamaManager에서 싱글턴을 구현한 부분을 먼저 살펴보고 다른 부분을 완성하도록 한다.

 void Awake() {
    if (instance == null) instance = this;
    else{
        Debug.LogWarning("씬에 두개 이상의 게임 매니저가 존재합니다!");
        Destroy(gameObject);
    }
}

GamaManager 스크립트 최상단에는 싱글턴 오브젝트를 할당하기 위한 static 선언된 GamaManager 타입의 변수 instance가 있다.

public static GameManager instance; // 싱글톤을 할당할 전역 변수

Awake 메서드에는 현재 오브젝트를 싱글턴 오브젝트로 만들고 instance에 할당하는 작업을 실행한다. instance는 싱글턴이 될 GamaManager 오브젝트가 저장될 변수이다.

 

static으로 선언된 변수는 모든 오브젝트가 공유하는 하나의 변수가 된다. 이러한 instance에는 GamaManager 타입 오브젝트의 참조를 할당할 수 있다. 그런데 씬에 GamaManager 타입의 오브젝트가 100개 존재해도 instance는 메모리에 단 하나만 존재하므로 instance에 할당될 수 있는 GamaManager 오브젝트도 단 하나이다.

 

즉, 싱글턴이 될 GamaManager 오브젝트는 스스로를 instance에 할당한다.

 

Awake() 메서드의 if 문 블록에는 instance가 비어있다면 그곳에 자기 자신을 할당한다.

if (instance == null) instance = this;

이렇게 instance에 할당된 GamaManager 오브젝트는 GamaManager.instance로 즉시 접근할 수 있다.

 

단, GamaManager 타입의 오브젝트는 씬에 단 하나만 존재해야 한다. 어떠한 이유로 GamaManager 오브젝트가 둘 이상 존재하는 경우 싱글턴이 된 GamaManager 오브젝트 하나만 남기고 나머지 GamaManager 오브젝트는 파괴해야 한다. 따라서 자신이 아닌 다른 GamaManager 오브젝트가 instance에 이미 할당된 경우 else문을 통해 제어한다.

else{
    Debug.LogWarning("씬에 두개 이상의 게임 매니저가 존재합니다!");
    Destroy(gameObject);
}

2. GamaManager의 필드

GamaManager의 다른 변수를 살펴보고 나머지 메서드를 작성해보자.

public bool isGameover = false; // 게임 오버 상태
public Text scoreText; // 점수를 출력할 UI 텍스트
public GameObject gameoverUI; // 게임 오버시 활성화 할 UI 게임 오브젝트

private int score = 0; // 게임 점수

3. GamaManager의 Update() 메서드

void Update() {
    // 게임 오버 상태에서 게임을 재시작할 수 있게 하는 처리
    if(isGameover && Input.GetMouseButtonDown(0)){
        SceneManager.LoadScene(SceneManager.GetActiveScene().name);
    }
}

먼저 if 문으로 isGameover가 true인지 검사함과 동시에 좌클릭을 눌렀는지 확인한다. 게임 오버 상태에서 좌클릭을 누르면 현재 활성화된 씬을 재 로딩하여 재시작한다.

GetActiveScene() 메서드

SceneManager.LoadScene(SceneManager.GetActiveScene().name);

SceneManager.GetActiveScene() 메서드는 현재 활성화된 씬의 정보를 Scene 타입으로 오브젝트로 가져오는 메서드이다. 그리고 Scene 타입의 오브젝트는 씬의  이름을 변수 name으로 제공한다.

 

즉, SceneManager.GetActiveScene().name현재 씬의 이름을 가져온 것이다.

4. GamaManager의 AddScore() 메서드

public void AddScore(int newScore) {
    if(isGameover){
        score += newScore;
        scoreText.text = "Score" + score;
    }
}

게임 오버가 아닌 경우 점수를 추가하며 스코어를 업데이트할 때 score += newScore; 을 이용하여 newScore만큼 증가한다. 이후 스코어 텍스트 오브젝트의 텍스트를 업데이트한다.

5. GamaManager의 OnPlayerDead() 메서드

public void OnPlayerDead() {
    isGameover = true;
    gameoverUI.SetActive(true);
}

OnPlayerDead() 메서드가 실행될 때, isGameover는 true가 되며 gameoverUI에 할당된 Gameover Text 게임 오브젝트가 실행되게 된다.

PlayerController 스크립트 수정

GamaManager의 게임오버 기능인 OnPlayerDead()를 알맞은 시점에 실행하려면 PlayerController 스크립트의 수정이 필요하다.

 

OnPlayerDead() 메서드는 플레이어가 사망할 때 실행되므로 PlayerController 스크립트의 Die() 메서드에서 실행되어야 한다. 따라서 PlayerController의 스크립트를 다음과 같이 수정한다.

private void Die() {
    // 애니메이터의 Die 트리거 파라미터를 셋
    animator.SetTrigger("Die");

    // 오디오 소스에 할당된 오디오 클립을 deathClip으로 변경
    playerAudio.clip = deathClip;
    // 사망 효과음 재생
    playerAudio.Play();

    // 속도를 제로(0, 0)로 변경
    playerRigidbody.velocity = Vector2.zero;
    // 사망 상태를 true로 변경
    isDead = true;

    GameManager.instance.OnPlayerDead();
}

ScrollingObject 스크립트 수정

게임 오버 상태에서는 움직이던 발판과 배경이 멈춰야 하므로 GamaManager의 변수 isGameover를 검사하여 움직임 여부를 결정한다.

private void Update() {
    // 게임 오브젝트를 왼쪽으로 일정 속도로 평행 이동하는 처리
    if(!GameManager.instance.isGameover)
        transform.Translate(Vector3.left*speed *Time.deltaTime);
}

GamaManager 컴포넌트 설정 및 테스트

컴포넌트 설정

게임 플레이 테스트

 

728x90
반응형
댓글