12. 들어가기 전에
이버엔 움직이는 배경과 움직이는 발판을 만든다. 플레이어 캐릭터는 계속 뛰는 것처럼 보이지만 캐릭터의 실제 X축 위치는 고정되어 있다. 대신 배경과 발판이 플레이어를 향해 계속 다가온다.
다양한 비율과 크기의 게임 화면에 대응하는 UI 를 만든다. 그리고 게임의 상태와 규칙을 정의하는 게임 매니저를 만든다.
레트로의 유니티 게임 프로그래밍 에센스 | 이제민 - 교보문고
레트로의 유니티 게임 프로그래밍 에센스 | 이제민 - 교보문고
레트로의 유니티 게임 프로그래밍 에센스 | 독자분들로부터 수많은 찬사를 받았던 유니티 대표 도서 『소문난 명강의:레트로의 유니티 게임 프로그래밍 에센스』가 개정판으로 돌아왔습니다!
product.kyobobook.co.kr
12.1 배경 추가하기
먼저 게임에 배경 하늘을 추가한다.
1. 배경 추가
- Sprites 폴더의 Sky 스프라이트를 하이어라키 창으로 드래그&드롭
- Sky 게임 오브젝트의 위치를 (0, 0, 0) 으로 변경

2. 카메라의 배경색 변경
- 하이어라키 창에서 Main Camera 게임 오브젝트 선택
- Camera 컴포넌트의 Clear Flags 를 Solid Color 로 변경
- Background 컬러 필드 클릭 > 컬러를 (163, 185, 194) 로 변경

12.1.1 정렬 레이어
몇몇 사람들은 Sky 스프라이트를 배경으로 추가하면 플레이어 캐릭터와 시작 지점의 발판이 Sky 스프라이트에 의해 가려진다. 이런 경우 Sky 게임 오브젝트가 Player 게임 오브젝트와 Start Platform 게임 오브젝트 뒤에 그려지도록 수정해야 한다.
스프라이트 렌더러 컴포넌트가 그리는 그래픽의 앞뒤 정렬은 트랜스폼 컴포넌트의 위치값과 상관없다. 2D 게임 오브젝트가 그려지는 순서는 스프라이트 렌더러의 정렬 레이어가 결정한다.
1. 정렬 레이어 추가하기
- Sprite Renderer 컴포넌트의 Sorting Layer 의 Default 클릭 > Add Sorting Layer... 클릭
- Tags&Layers 창에서 Sorting Layers 리스트의 + 버튼 클릭
- 생성된 Layer 이름을 Background 로 변경
- + 버튼 클릭 > 생성된 Layer 이름을 Middleground 로 변경
- + 버튼 클릭 > 생성된 Layer 이름을 Foreground 로 변경

뒤쪽에 그려질 배경 게임 오브젝트에 위쪽 정렬 레이어를 할당하고, 앞쪽에 그려질 플레이어 캐릭터에 아래쪽 정렬 레이어를 할당한다.
2. 정렬 레이어 할당하기
- 하이어라키 창에서 Sky 게임 오브젝트 선택 > Sprite Renderer 컴포넌트의 Sorting Layer 를 Background 로 변경
- Player 게임 오브젝트 선택 > Sprite Renderer 컴포넌트의 Sorting Layer 를 Foreground 로 변경
- Start Platform 게임 오브젝트 선택 > Sprite Renderer 컴포넌트의 Sorting Layer 를 Foreground 로 변경

12.2 움직이는 배경과 발판
움직이는 배경과 발판을 만든다. ScrollingObject 스크립트를 완성하고 게임 오브젝트에 추가하여 해당 게임 오브젝트가 일정 속도로 움직이게 한다.
12.2.1 ScrollingObject
ScrollingObject 스크립트를 배경 Sky 게임 오브젝트와 발판 Start Platform 게임 오브젝트에 추가하고 스크립트를 완성한다.
1. ScrollingObject 스크립트 추가하기
- 프로젝트 창에서 Scripts 폴더로 이동 > ScrollingObject 스크립트를 하이어라키 창의 Start Platform 과 Sky 로 드래그&드롭
- 프로젝트 창에서 ScrollingObject 스크립트를 더블 클릭하여 열기

2. ScrollingObject 스크립트 완성하기
아래 코드로 게임 오브젝트를 초당 (-speed, 0, 0) 만큼 이동시킨다. 트랜스폼 컴포넌트의 Translate() 메서드는 10장에서 본 평행이동 메서드이다. Translate() 메서드는 이동할 거리를 Vector3 타입으로 받는다.
Vector3.left 의 값은 (-1, 0, 0) 이다. 따라서 Vector3.left * speed 의 값은 (-speed, 0, 0) 이 된다.
using UnityEngine;
// 게임 오브젝트를 계속 왼쪽으로 움직이는 스크립트
public class ScrollingObject : MonoBehaviour {
public float speed = 10f; // 이동 속도
private void Update() {
// 게임 오브젝트를 왼쪽으로 일정 속도로 평행 이동하는 처리
// 초당 speed 의 속도로 왼쪽으로 평행이동
transform.Translate(Vector3.left * speed * Time.deltaTime);
}
}
12.2.2 반복되는 배경 만들기
배경은 끊임없이 반복 스크롤링되야 한다. 2D 게임에서 반복되는 배경을 만드는 방법은 크랭키 박스와 비슷하다.
우리는 배경인 Sky 게임 오브젝트를 두 개 이어붙여 길게 만들 것이다. 각 배경 게임 오브젝트는 왼쪽으로 계속 움직이다가 일정 거리 이상 움직이면 오른쪽 화면 끝으로 순간 이동한다.
좀 더 구체적으로 보면, 다음 그림에서 width 는 배경 하나의 가로 길이이다. 게임이 시작되면 각각의 배경이 계속해서 왼쪽으로 이동한다.
BackgroundLoop 스크립트는 게임 오브젝트의 X축 위치(transform.position.x) 를 계속 검사한다. 만약 게임 오브젝트의 X축 위치값이 -width 이하라면 너무 많이 왼쪽으로 이동했다고 판단하고 현재 위치에 2*width 를 더한다.
결론적으로 게임 오브젝트의 X축 위치가 -width 가 되는 순간 +width 로 변경되어(오른쪽으로 순간이동) 배경 스크롤링이 무한 반복된다.
Sky 게임 오브젝트의 가로 길이 width 는 Sky 에 박스 콜라이더 2D 컴포넌트를 추가해 구할 수 있다. 박스 콜라이더 2D 컴포넌트는 추가될 때 2D 게임 오브젝트의 스프라이트에 맞춰서 크기가 자동 설정된다. 따라서 박스 콜라이더 2D 컴포넌트의 size 필드의 x 값을 게임 오브젝트의 가로 길이로 볼 수 있다.
1. 박스 콜라이더 2D 와 BackgroundLoop 준비하기
- Sky 게임 오브젝트 선택
- Box Colider 2D 컴포넌트 추가
- Box Collider 2D 컴포넌트의 Is Trigger 체크(sky 게임 오브젝트가 다른 게임 오브젝트를 물리적으로 밀어내면 안 되므로..)
- BackgroundLoop 스크립트를 Sky 게임 오브젝트로 드래그&드롭

2. BackgroundLoop 스크립트의 Awake() 메서드 완성하기
Awake() 메서드는 Start() 메서드처럼 초기 1회 자동 실행되는 유니티 이벤트 메서드이고, Start() 메서드보다 실행 시점이 한 프레임 더 빠르다.
private void Awake() {
// 가로 길이를 측정하는 처리
// BoxCollider2D 컴포넌트의 Size 필드의 x 값을 가로 길이로 사용
BoxCollider2D backgroundCollider = GetComponent<BoxCollider2D>();
width = backgroundCollider.size.x;
}
3. BackgroundLoop 스크립트의 Update() 메서드 완성하기
Update() 메서드에서는 게임 오브젝트의 x축 위치(transform.position.x) 를 매 프레임마다 검사한다. 현재 위치의 x 값이 -width 보다 작거나 같다면 왼쪽으로 너무 많이 이동했다는 의미이다. 이때는 Reposition() 매서드를 실행하여 게임 오브젝트를 오른쪽으로 밀어낸다.
private void Update() {
// 현재 위치가 원점에서 왼쪽으로 width 이상 이동했을때 위치를 리셋
// 현재 위치가 원점에서 왼쪽으로 width 이상 이동했을 때 위치를 재배치
if (transform.position.x <= -width)
{
Reposition();
}
}
4. BackgroundLoop 스크립의 Reposition() 메서드 완성하기
// 위치를 리셋하는 메서드
private void Reposition()
{
// 현재 위치에서 오른쪽으로 가로 길이 * 2 만큼 이동
Vector2 offset = new Vector2(width * 2f, 0f);
transform.position = (Vector2)transform.position + offset; // trnasform.position 은 3차원 벡터니까 형변환하기
}
5. 전체 BackgroundLoop 스크립트
using TreeEditor;
using UnityEngine;
// 왼쪽 끝으로 이동한 배경을 오른쪽 끝으로 재배치하는 스크립트
public class BackgroundLoop : MonoBehaviour {
private float width; // 배경의 가로 길이
private void Awake() {
// 가로 길이를 측정하는 처리
// BoxCollider2D 컴포넌트의 Size 필드의 x 값을 가로 길이로 사용
BoxCollider2D backgroundCollider = GetComponent<BoxCollider2D>();
width = backgroundCollider.size.x;
}
private void Update() {
// 현재 위치가 원점에서 왼쪽으로 width 이상 이동했을때 위치를 리셋
// 현재 위치가 원점에서 왼쪽으로 width 이상 이동했을 때 위치를 재배치
if (transform.position.x <= -width)
{
Reposition();
}
}
// 위치를 리셋하는 메서드
private void Reposition()
{
// 현재 위치에서 오른쪽으로 가로 길이 * 2 만큼 이동
Vector2 offset = new Vector2(width * 2f, 0f);
transform.position = (Vector2)transform.position + offset;
}
}
6. Sky 추가 배치
- Sky 게임 오브젝트 선택 > Ctrl+D 로 복제
- 복제된 게임 오브젝트의 위치를 (20.48, 0, 0) 으로 변경

7. 배경 오브젝트 정리하기
- 빈 게임 오브젝트 생성(+ > Create Empty)
- 생성된 게임 오브젝트의 이름을 Background 로 변경, 위치를 (0, 0, 0) 으로 변경
- Sky 와 Sky(1) 을 Background 로 드래그&드롭하여 Background 의 자식으로 만들기

12.3 게임 UI 만들기
점수와 게임오버 메시지를 표시하는 UI 를 만든다. UI 를 구성하기 위해서는 먼저 캔버스 게임 오브젝트부터 만든다. 캔버스는 모든 UI 요소를 잡아주는 루트 게임 오브젝트이기 때문이다.
12.3.1 고정 픽셀 크기
캔버스는 UI 를 잡아두는 틀이기 때문에 캔버스의 크기나 가로세로 비율이 달라지면 캔버스에 배치된 UI 의 모습도 다르게 보인다.
캔버스의 크기는 게임을 실행 중인 화면의 해상도로 결정된다. 그런데 캔버스 컴포넌트의 UI 스케일 모드(UI Scale Mode) 의 기본 설정인 고정 픽셀 크기(Constant Pixel Size) 는 캔버스 크기가 변해도 배치된 UI 요소의 크기를 변경하지 않는다. 이 경우 화면 크기가 달라지면 UI 요소의 크기나 UI 요소 사이의 간격이 의도와 다르게 크거나 작아지는 문제가 생긴다.
12.3.2 화면 크기에 따라 스케일
다양한 크기와 비율의 화면에서도 유니런 UI의 크기와 배치를 일정하게 유지하고 싶다.
이때는 기준 화면 크기를 정하고, 실행 화면이 기준 화면보다 크거나 작을 때는 자동으로 확대/축소(스케일업/스케일다운) 하는 화면 크기에 따라 스케일 모드를 UI 스케일 모드로 사용할 수 있다.
예를들어 640x360 화면에서의 UI 배치 모습을 다른 크기의 화면에서도 유지한다.
방향매치
화면 크기에 따라 스케일 모드는 실제 화면과 기준 해상도 사이의 화면 비율이 다른 경우 캔버스 스케일러 컴포넌트의 일치(Match) 필드 값이 높은 방향의 길이를 유지하고 다른 방향의 길이를 조절한다.
12.3.3 캔버스 스케일러 설정
화면 크기에 따라 스케일 모드를 사용해 640x360 을 기준 해상도로 사용하여 UI 를 배치한다. 캔버스 스케일 모드는 캔버스 스케일러 컴포넌트에서 설정한다.
1. 캔버스를 만들고 기준 해상도 사용
- Canvas 게임 오브젝트 생성
- Canvas Scaler 컴포넌트의 UI Scale Mode 를 Scale With Screen Size 로 변경
- Reference Resolution 을 (640, 360) 으로 변경

이제 실제 화면 크기와 상관없이 640x360 해상도에서의 UI 배치만 신경 쓰면 된다.
12.3.4 점수 UI 텍스트 만들기
점수를 표시하기 위한 UI 텍스트를 만든다. 그리고 텍스트 내용과 폰트를 변경하고 그림자 효과를 준다.
1. 점수 UI 텍스트 만들기
- 새로운 Text 게임 오브젝트 생성
- Text 게임 오브젝트의 이름을 Score Text 로 변경
- Rect Transform 컴포넌트의 Width 를 300, Height 를 80으로 변경
- Anchor Preset 클릭 > Alt+Shift 를 누른 채로 bottom-center 클릭

Score Text 텍스트가 캔버스 하단 중앙에 정렬된다.
2. Score Text 의 텍스트 컴포넌트 설정
- Text 컴포넌트의 Text 필드를 Score : 0 으로 변경
- Font 필드 옆의 선택 버튼 클릭 > 선택 창에서 Kenny Mini Square 폰트 더블 클릭
- Font Size 를 50으로 변경
- Color 필드 클릭 > 폰트 컬러를 (255, 255, 255) 로 변경

3. Score Text 에 그림자 추가
- Score Text 게임 오브젝트에 Shadow 컴포넌트 추가

12.3.5 게임오버 텍스트 만들기
이번에는 게임오버 메시지를 표시할 Gameover Text 게임 오브젝트를 만든다. 빠르게 만들기 위해 Score Text 게임 오브젝트를 복제한다.
1. 게임오버 텍스트 만들기
- Score Text 선택 > Ctrl+D 로 복제
- Score Text(1) 의 이름을 Gameover Text 로 변경
- Anchor Preset 클릭 > Alt+Shift 를 누른 채 top-center 클릭

2. 게임오버 텍스트의 Text 컴포넌트 설정하기
- Text 컴포넌트의 Text 필드 값을 Gameover! 로 변경
- Color 필드 클릭 > 폰트 컬러를 (255, 66, 85) 로 변경

3. 게임 재시작 안내 텍스트 만들기
- Gameover Text 클릭 > Ctrl+D 로 복제
- Gameover Text (1) 의 이름을 Restart Text 로 변경
- Rect Transform 컴포넌트의 Pos Y 값을 -40 으로 변경
- Text 컴포넌트의 Text 필드를 Jump To Restart 로 변경
- Font Size 를 33으로 변경
- Restart Text 를 Gameover Text 의 자식으로 만들기(드래그&드롭)

4. 게임오버 텍스트 비활성화하기
Gameover Text 는 평소에 비활성화해 두었다가 게임오버가 된 순간 활성화한다. 따라서 미리 비활성화해 둔다.
- Gameover Text 선택
- 인스펙터 창에서 활성화 체크 해제

12.4 게임 매니저 만들기
이제 UI 는 물론 플레이어의 상태에 따라 게임의 전반적인 상태를 관리하는 게임 매니저를 만들어야 한다.
게임 매니저의 역할
- 점수 저장
- 게임 오버 상태 표현
- 플레이어의 사망을 감지해 게임오버 처리 실행
- 점수에 따라 점수 UI 텍스트 갱신
- 게임오버되었을 때 게임오버 UI 활성화
12.4.1 싱글턴 패턴의 필요성
게임 매니저처럼 관리자 역할을 하는 오브젝트는 일반적으로 프로그램에 단 하나만 존재해야 한다. 그리고 언제 어디서든 즉시 접근 가능해야 한다.
이런 조건에서는 주로 싱글턴이라는 디자인 패턴을 사용한다. 이를 사용하면 게임 매니저가 씬에 단 하나만 있게 하고, 어느곳에서도 게임 매니저에 즉시 접근할 수 있게 만들 수 있다.
싱글턴을 사용하는 이유
게임 매니저는 아래 두 조건을 만족해야 한다.
- 게임 매니저 오브젝트는 단 하나만 존재
- 어떤 곳에서도 손쉽게 게임 매니저 오브젝트에 접근 가능
12.4.2 정적
싱글턴 패턴을 구현할 때는 정적(static) 변수의 특징을 사용한다.
클래스에 선언된 변수들은 해당 클래스 타입의 오브젝트가 생성되면 함께 생성된다. 이것은 메모리 상에 멤버 변수가 오브젝트 수만큼 존재한다는 의미이다.
다음과같은 Dog 클래스가 있다고 가정한다.
public class Dog : MonoBehaviour {
public string dogName; // 개 이름
public int count = 0; // 프로그램에 존재하는 개의 총 수
void Awake() {
count++;
}
}
위 코드에서 Dog 오브젝트는 자신이 생성되고 활성화될 때 Awake() 메서드에 의해 count 값을 1 증가시킨다.
Dog 클래스로부터 Dog 타입의 오브젝트 A, B, C 를 순서대로 생성했다고 가정한다. 이때 count 값의 변화는 다음과 같다.

Dog 오브젝트가 2개 있기 때문에 변수 count 도 3개 존재한다. count 는 Dog 의 총 수를 나타내야 한다. 의도대로라면 count 의 값은 3이 되어야 하지만 현재 각각의 Dog 오브젝트의 count 값은 1이다.
위 구현의 문제점은 각 개체의 count 라는 것. count 가 Dog 오브젝트의 총 수를 나타내기 위해서는 count 가 Dog 오브젝트마다 하나씩 존재하면 안 된다. 다음 그림과 같이 count 는 단 하나만 존재하고 모든 Dog 오브젝트가 하나의 count 를 공유해야 한다.

그림에서 보듯이 Dog 오브젝트가 3개지만 이들이 단 하나의 count 를 공유하면 어떤 Dog 오브젝트가 count 값을 변경했을 때 다른 Dog 오브젝트에서 사용하는 count 값도 같이 변경된다. 3개의 Dog 오브젝트가 각자 count 값을 1씩 증가시켰지만 사실 하나의 count 를 공유하고 있으므로 count 값이 0 -> 1 -> 2 -> 3 으로 증가하면서 count 는 3이 된다.
어떤 변수를 static 으로 선언하면 여러 오브젝트가 해당 변수 하나를 공유한다. 정적 선언된 변수는 해당 타입의 오브젝트를 몇 개를 생성하든 그 수에 상관없이 메모리에 하나만 존재한다.
정적 선언된 변수는 특정 오브젝트에 속하지 않고 같은 타입의 모든 오브젝트가 공유한다. 따라서 Dog 클래스 외부에서는 count 변수에 접근할 때 Dog.count 로 접근한다.
static 의 이러한 특징은 싱글턴을 구현하는 데도 활용된다.
12.4.3 GameManager 스크립트 준비
정적 변수로 싱글턴을 구현하는 방법을 살펴본 다음 GameManager 스크립트를 완성한다.
1. 게임 매니저 준비
- 빈 게임 오브젝트 생성(+ > Create Empty)
- 생성된 게임 오브젝트의 이름을 Game Manager 로 변경
- Scripts 폴더의 GameManager 스크립트를 Game Manager 게임 오브젝트로 드래그&드롭
- GameManager 스크립트를 더블 클릭하여 열기

12.4.4 GameManager 싱글턴 구현
GameManager 에서 싱글턴을 구현한 부분을 먼저 살펴본다.
using UnityEngine;
using UnityEngine.SceneManagement;
using UnityEngine.UI;
// 게임 오버 상태를 표현하고, 게임 점수와 UI를 관리하는 게임 매니저
// 씬에는 단 하나의 게임 매니저만 존재할 수 있다.
public class GameManager : MonoBehaviour {
public static GameManager instance; // 싱글톤을 할당할 전역 변수
// 게임 시작과 동시에 싱글톤을 구성
void Awake() {
// 싱글톤 변수 instance가 비어있는가?
if (instance == null)
{
// instance가 비어있다면(null) 그곳에 자기 자신을 할당
instance = this;
}
else
{
// instance에 이미 다른 GameManager 오브젝트가 할당되어 있는 경우
// 씬에 두개 이상의 GameManager 오브젝트가 존재한다는 의미.
// 싱글톤 오브젝트는 하나만 존재해야 하므로 자신의 게임 오브젝트를 파괴
Debug.LogWarning("씬에 두개 이상의 게임 매니저가 존재합니다!");
Destroy(gameObject);
}
}
}
GameManager 스크립트의 최상단에는 싱글턴 오브젝트를 할당하기 위한 static 선언된 GameManager 타입의 변수 instance 가 있다. Awake() 메서드에서는 현재 오브젝트를 싱글턴 오브젝트로 만들고 instance 에 할당하는 작업을 실행한다. instance 는 싱글턴이 될 GameManager 오브젝트가 저장될 변수이다.
static 으로 선언된 변수는 모든 오브젝트가 공유하는 단 하나의 변수가 된다.
instance 에는 GameManager 타입 오브젝트의 참조를 할당할 수 있다. 그런데 씬에 GameMAnager 타입의 오브젝트가 100개 존재해도 instance 는 메모리에 단 하나만 존재하므로 instance 에 할당될 수 있는 GameMAnager 오브젝트도 단 하나뿐이다.
싱글턴이 될 GameManager 오브젝트는 스스로를 instance 에 할당한다. Awake 의 if 문 블록에서 instance 가 비어있다면 그곳에 자기 자신을 할당한다.
만약 instance 의 값이 null 이라면 instance 에 아직 어떠한 GameMAnager 타입의 오브젝트도 할당되지 않은 상태이다. 따라서 Awake() 메서드를 실행하고 있는 GameManager 오브젝트가 this 를 instance 에 할당하여 스스로를 instance 에 할당한다. this 는 오브젝트가 자기 자신을 가리키는 키워드로서 스스로에 대한 참조값이 출력된다.
이렇게 instance 에 할당된 오브젝트는 GameManager.instance 로 즉시 접근할 수 있다.
GameManager 타입의 오브젝트는 씬에 단 하나만 존재해야 한다. 어떠한 이유로 GameManager 오브젝트가 둘 이상 존재하는 경우 싱글턴이 된 GameMAnager 오브젝트 하나만 남기고 나머지는 모두 파괴해야 한다.
이어지는 else 문 블록은 instance 가 null 이 아닌 경우에 실행된다. 즉, 자신이 아닌 다른 GameManager 오브젝트가 instance 에 이미 할당된 경우이다.
12.4.5 GameManager 의 필드
싱글턴 구현 부분을 살펴봤으니 이제 GameManger 의 다른 변수들을 살펴보고 나머지 메서드를 완성한다.
싱글턴을 위한 변수 instance 이외의 다른 변수들은 다음과 같다.
public bool isGameover = false; // 게임 오버 상태
public Text scoreText; // 점수를 출력할 UI 텍스트
public GameObject gameoverUI; // 게임 오버시 활성화 할 UI 게임 오브젝트
private int score = 0; // 게임 점수
12.4.6 GameManager 의 Update() 메서드
이제 GameManager 스크립트의 Update() 메서드에서 게임오버 상태일 때 게임을 재시작하는 기능을 만든다. 게임오버 상태는 isGameover 의 값으로 알 수 있다.
게임 재시작은 현재 활성화된 씬을 다시 로드하는 방식으로 구현할 수 있다. 따라서 씬 관리자 SceneManager 가 필요하므로 스크립트 상단에 다음과 같은 선언을 미리 추가해두었다.
using UnityEngine.SceneManagement;
1. Update() 메서드 완성하기
게임오버 상태에서 마우스 왼쪽 버튼을 누르면 현재 활성화된 씬을 다시 로드하여 게임을 재시작한다. GetActiveScene() 은 현재 활성화된 씬의 정보를 Scene 타입의 오브젝트로 가져온다.
void Update() {
// 게임 오버 상태에서 게임을 재시작할 수 있게 하는 처리
if (isGameover && Input.GetMouseButtonDown(0)) {
// 게임오버 상태에서 마우스 왼쪽 버튼을 클리갛면 현재 씬 재시작
SceneManager.LoadScene(SceneManager.GetActiveScene().name);
}
}
12.4.7 GameManager 의 AddScore() 메서드
점수를 추가하는 AddScore() 메서드를 구현한다. AddScore() 는 입력으로 추가할 점수를 받아 입력된 값만큼 점수를 증가시키고, 점수 UI 텍스트에 출력되는 점수를 갱신한다.
1. AddScore() 메서드 완성하기
게임오버가 아닌 상태에서만 점수를 추가하도록 했다.
scoreText.text = "Score : " + score; 에서는 Score Text 게임 오브젝트의 Text 컴포넌트의 Text 필드인 scoreText.text 의 값을 "Score : " + score 로 변경한다. 즉, 점수가 추가될 때마다 Score Text 게임 오브젝트의 Text 컴포넌트는 갱신된 점수를 출력한다.
// 점수를 증가시키는 메서드
public void AddScore(int newScore) {
// 게임오버가 아니라면
if (!isGameover)
{
// 점수를 증가
score += newScore;
scoreText.text = "Score : " + score;
}
}
12.4.8 GameManager 의 OnPlayerDead() 메서드
플레이어가 사망했을 때 실행할 처리를 구현하는 OnPlayDead() 메서드를 완성한다. 플레이어가 사망하면 게임오버 상태가 되며 게임오버 UI 가 활성화되어야 한다.
1. OnPlayerDead() 메서드 완성하기
// 플레이어 캐릭터가 사망시 게임 오버를 실행하는 메서드
public void OnPlayerDead() {
isGameover = true;
gameoverUI.SetActive(true);
}
12.4.9 GameManager 스크립트 전체 코드
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;
scoreText.text = "Score : " + score;
}
}
// 플레이어 캐릭터가 사망시 게임 오버를 실행하는 메서드
public void OnPlayerDead() {
isGameover = true;
gameoverUI.SetActive(true);
}
}
12.4.10 PlayerController 스크립트 수정
GameManager 의 게임오버 기능인 OnPlayerDead() 를 알맞은 시점에 실행하려면 PlayerController 스크립트에 수정이 필요하다.
OnPlayerDead() 메서드는 플레이어가 사망할 때 실행되므로 PlayerController 스크립트의Die() 메서드에서 실행되어야 한다.
1. PlayerController 스크립트의 Die() 메서드 수정하기
씬의 GameManager 오브젝트에 접근해 OnPlayerDead() 를 사용한다. 이제 플레이어가 죽는 순간 게임오버 처리가 실행된다.
씬에 유일하게 존재하는 GameManager 오브젝트로 접근하는 데 싱글턴 변수 GameManager.instance 를 사용했음에 주목한다.
private void Die() {
// 사망 처리
// 애니메이터의 Die 트리거 파라미터를 셋
animator.SetTrigger("Die");
// 오디오 소스에 할당된 오디오 클립을 deathClip 으로 변경
playerAudio.clip = deathClip;
// 사망 효과음 재생
playerAudio.Play();
// 속도를 제로로 변경
playerRigidbody.velocity = Vector2.zero;
// 사망 상태를 true 로 변경
isDead = true;
// 게임 매니저의 게임오버 처리 실행
GameManager.instance.OnPlayerDead();
}
12.4.11 ScrollingObject 스크립트 수정
게임오버인 상태에서는 움직이던 발판과 배경이 멈춰야 한다. GameManager 의 변수 isGameover 가 게임오버 상태를 나타내고 있으므로 ScrollingObject 스크립트에서 GameManager 의 isGameover 를 검사하여 움직임 여부를 결정하면 된다.
1. ScrollingObject 스크립트 수정하기
using UnityEngine;
// 게임 오브젝트를 계속 왼쪽으로 움직이는 스크립트
public class ScrollingObject : MonoBehaviour {
public float speed = 10f; // 이동 속도
private void Update() {
// 게임 오브젝트를 왼쪽으로 일정 속도로 평행 이동하는 처리
// 게임 오버가 아니라면
if (!GameManager.instance.isGameover)
{
// 초당 speed 의 속도로 왼쪽으로 평행이동
transform.Translate(Vector3.left * speed * Time.deltaTime);
}
}
}
12.4.12 GameManager 컴포넌트 설정하기
Game Manager 게임 오브젝트의 GameManager 컴포넌트의 필드를 설정한다.
1. GameManager 컴포넌트 설정하기
- 하이어라키 창에서 GameManager 게임 오브젝트 선택
- 하이어라키 창의 Score Text 게임 오브젝트를 GameManager 컴포넌트의 Score Text 필드로 드래그&드롭
- 하이어라키 창의 Gameover Text 게임 오브젝트를 GameManager 컴포넌트의 Gameover UI 필드로 드래그&드롭

12.5 마치며
이 장에서는 배경과 발판을 지속적으로 움직이고, 배경을 반복하는 방법을 배웠다. 게임 UI 를 구성하면서 다양한 크기와 비율의 화면에서 의도한 UI 의 모습을 유지하는 방법도 배웠다. 그리고 싱글턴을 사용하여 게임 매니저를 구현했다.
다음 장에서는 발판 프리팹과 장애물을 구성하고, 발판을 무한 반복 배치하는 기능을 만들어 게임을 완성한다.