학습 매체 : 책
책이름 : 레트로의 유니티 게임 프로그래밍 에센스
저자 : 이제민
본 내용은 해당 강의 내용을 공부하면서 정리한 글입니다.
앞 장까지 게임에 필요한 개별 요소인 플레이어, 적, 적 생성기 등을 완성했다.
이 장에서는 UI를 만들고, 게임의 규칙을 관리하고 게임오버 상태를 표현하는 게임 매니저를 만든다.
이 장에서 다루는 내용
1. 게임 매니저와 UI 만들기
2. 씬 관리자로 씬을 로드하는 방법
3. PlayerPrefs를 사용해 데이터를 저장하는 방법
4. 게임을 빌드하는 방법
바닥 회전을 진행하기 전에 프로젝트 창에 폴더를 만들어 에셋과 스크립트를 정돈하자.
Assets 폴더 내부에 또 다른 폴더를 만들고 에셋의 경로를 수정하는 것은 프로젝트와 씬에 영향을 미치지 않는다.
따라서 안심하고 언제든지 개발 도중에 에셋의 경로를 바꾸고 정돈해도 된다.
프로젝트 창에서 + > Folder 클릭 > 폴더명을 Scripts로 변경
프로젝트 창에서 + > Folder 클릭 > 폴더명을 Materials로 변경
프로젝트 창에서 + > Folder 클릭 > 폴더명을 Prefabs로 변경
에셋을 종류별로 폴더에 정리하기
이제 플레이어 아래의 바닥을 일정 속도로 회전시키는 기능을 구현할 것이다. 바닥 회전 기능을 스크립트로 구현하여 Level 게임 오브젝트에 적용해보자.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Rotator : MonoBehaviour
{
public float rotationSpeed = 60f;
void Update()
{
transform.Rotate(0f, rotationSpeed, 0f);
}
}
선언된 변수 rotationSpeed는 게임 오브젝트가 1초에 Y축 기준으로 몇 도 회전할지 타나낸다. 초깃값으로 60을 할당했다.
게임 오브젝트를 회전시키는 데 트랜스폼 컴포넌트에 내장된 Rotate( ) 메서드를 사용했다.
Rotate(float xAngle, float yAngle, float zAngle)
Transform 타입의 Rotate( ) 메서드는 입력값으로 X, Y, Z축에 대한 회전값을 받고, 현재 회전 상태에서 입력된 값만큼 상대적으로 더 회전한다.
transform.Rotate(0f, rotationSpeed, 0f);는 한 번에 Y축을 기준으로 자신의 게임 오브젝트를 rotationSpeed(60도)만큼 회전한다.
이러면 매 프레임마다 60도 회전을 하게 된다.
이것은 '1초에 60도'가 아니라 '한 번에 60도'를 회전하는 문제가 있는 코드이다.
스크립트를 Level에 적용시키고 플레이 버튼을 눌러보자.
비정상적으로 빠른 속도로 Level 게임 오브젝트가 회전한다. 이것은 우리가 의도한 동작이 아니다.
작성한 코드의 문제는 transform.Rotate(0f, rotationSpeed, 0f);가 1초에 60도 회전하는 코드가 아니라 한 번(한 프레임)에 60도 회전한다는 거다.
Update( ) 메서드는 게임 화면(프레임)이 한 번 갱신될 때 1회 실행한다.
따라서 Update( ) 메서드에서 초당 이동 속도나 회전 속도 등 시간과 관련된 수치를 다룰 때는 시간 간격도 고려해야 한다. 그렇지 않으면 컴퓨터 성능에 따라 같은 코드가 다르게 동작할 수 있다.
이 문제와 관련된 흔한 현상으로 PC 게이머들이 '프레임 제한'을 해제했을 때 발생하는 문제가 있다.
엑스박스, 플레이스테이션 같은 콘솔 게임기는 기기의 사양이 정확하게 정해져 있어서 PC보다 초당 프레임을 상대적으로 정확하게 추측할 수 있다.
많은 콘솔 게임이 초당 30프레임이나 초당 60프레임으로 제한된 고정 프레임을 주로 사용한다.
그런데 몇몇 콘솔 게임은 PC로 출시될 때 프레임 제한을 그대로 유지하는 경우가 있다. 그러면 고성능 PC라 해도 프레임 제한 이상으로 초당 프레임을 높일 수 없어 고사양 PC 게이머들은 불만이 생긴다.
몇몇 PC 게이머들은 게임 파일을 수정하거나 해킹 프로그램을 사용하여 프레임 제한을 강제로 해제하기도 한다.
여기서 일부 게임은 프레임 제한을 해제하면 게임 속 물체들의 속도가 몇 배 빨라지거나 물리 상호작용이 비현실적으로 변경되어 게임을 정상적으로 진행하기 힘들어지기도 한다.
이것은 해당 게임이 고정 프레임을 기준으로 만들어졌기 때문이다.
고정 프레임이 60인 게임을 가정해보자.
어떤 게임 오브젝트가 1초 동안 1미터를 이동해야 한다면 Update( ) 메서드에서 한 번에 1/60 미터를 이동하도록 작성한다.
Update( ) 메서드가 1초에 60번 실행된다. 그래서 한 번에 1/60 미터를 이동하는 코드가 1초 동안 60번 누적 실행되면 총 1 미터를 이동한다.
1/60미터 X 60번 = 1미터
1/60미터 X 120번 = 2미터
결론적으로 Update( ) 메서드 안에 작성된 코드는 초당 프레임이 다른 경우를 고려해야 한다.
정답부터 이야기하자면 이 문제를 해결하는 방법은 '시간 간격으로 쪼개서 누적하는 것'이다.
즉, 초당 프레임의 역수를 회전값에 곱하면 된다.
1초에 코드가 실행되는 횟수 60에 역수를 취하면 1/60이다. 이렇게 구한 1/60은 직전 프레임과 현재 프레임 사이의 시간 간격이 된다.
그리고 해당 값을 회전 속도인 60도에 곱한다. 그러면 1초 동안 원래 의도했던 초당 회전값이 나온다.
(60도 회전) X (1/60) X (60번 실행) = 총 60도 회전
결론은 초당 프레임의 역수를 취한 값(프레임의 주기)을 곱하면 '한 번에 x만큼 이동'에서 '1초 동안 x만큼 이동'을 구현할 수 있다.
7장 'Time.deltaTime'에서 이전 프레임과 현재 프레임 사이의 시간 간격은 Time.deltaTime으로 제공된다는 사실을 배웠다.
Time.deltaTime 값은 프레임의 주기이자 초당 프레임에 역수를 취한 값이다. Time.deltaTime을 사용하여 지금까지 설명한 해결 방법을 구현할 수 있다.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Rotator : MonoBehaviour
{
public float rotationSpeed = 60f;
void Update()
{
transform.Rotate(0f, rotationSpeed * Time.deltaTime, 0f);
}
}
다음 강의에서 계속~