이 글은 '레트로의 유니티 게임 프로그래밍 에센스' 책을 보고 진행한 것에 대해 공부하여 정리한 글입니다.
MonoBehaviour
MonoBehaviour는 유니티 게임 엔진의 핵심 기능들과 상호작용할 수 있게 해주는 기본 스크립트 클래스입니다.
MonoBehavior를 상속받은 스크립트는 반드시 유니티 씬(Scene)에 존재하는 게임 오브젝트에 컴포넌트 형태로 부착되어야만 실행될 수 있습니다.
스크립트가 게임 오브젝트에 부착되면, 해당 스크립트는 게임 오브젝트의 일부가 되어 게임 오브젝트의 행동을 정의하고 제어하게 됩니다.
주요 특징 및 기능
- 컴포넌트 역할
- MonoBehaviour 스크립트 자체가 하나의 컴포넌트처럼 작동합니다.
- 여러 개의 MonoBehaviour 스크립트를 하나의 게임 오브젝트에 붙여 다양한 기능을 조합할 수 있습니다.
- 내장 속성
- MonoBehaviour를 상속받으면 자동으로 다음과 같은 속성들을 사용할 수 있습니다.
- Transform : 스크립트가 붙어 있는 게임 오브젝트의 위치, 회전, 크기 정보에 접근할 수 있습니다.
- GameObject : 스크립트가 붙어 있는 게임 오브젝트 자체에 접근할 수 있습니다.
- this : 현재 스크립트 인스턴스 자체를 참조합니다.
- Enabled : 스크립트의 활성화 / 비활성화 상태를 제어합니다.
- 유니티 에디터 통합
- public 또는 [SerializeField]로 선언된 변수들은 유니티 에디터의 인스펙터 창에 노출되어 값을 쉽게 설정하고 변경할 수 있습니다. 이를 통해 코드 수정 없이 게임의 다양한 설정을 조절할 수 있습니다.
- [SerializeField]
- C#의 어트리뷰트 중 하나로, private이나 protected로 선언한 변수를 유니티 에디터의 인스펙터 창에서 노출시켜 값을 설정하거나 확인할 수 있습니다.
- 주로 데이터 캡슐화를 유지하면서도 유니티 에디터에서 디자이너나 레벨 디자이너가 변수 값을 쉽게 조정할 수 있도록 하기 위해 사용됩니다.
- [SerilizeField]를 사용하면 내부적으로는 private으로 보호하면서 에디터에서만 편리하게 값을 설정할 수 있어 코드의 안정성을 높이는 데 도움이 됩니다.
- 코루틴(Coroutine)
- 비동기 처리를 위한 코루틴 기능을 사용할 수 있습니다.
- 코루틴은 유니티에서 메서드의 실행을 도중에 멈추었다가 원하는 시점에 다시 이어서 실행할 수 있게 해주는 기능입니다.
일반적인 메서드는 호출되면 코드가 끝까지 실행되거나 반환될 때까지 기다려야 하지만, 코루틴은 여러 프레임에 걸쳐 실행될 수 있습니다. - 작동 방식
- 코루틴은 IEnumerator 타입을 반환하는 메서드로 정의되며, 메서드 내부에서 yield return 키워드를 사용하여 실행을 잠시 중지하고 유니티 엔진에 제어권을 넘겨줍니다. yield return 뒤에 오는 값에 따라 언제 다시 코루틴의 실행을 재개할지가 결정됩니다.
- 장점
- 시간 지연 처리 : 특정 시간 후에 어떤 동작을 수행하거나, 일정 시간 간격으로 반복되는 동작을 쉽게 구현할 수 있습니다.
- 순차적 실행 : 여러 단계를 거치는 복잡한 동작을 순서대로 실행할 수 있습니다. (예 : 애니메이션, 이벤트 시퀀스 등)
- 복잡성 감소 : 콜백 함수나 상태 머신 없이 비동기적인 코드를 비교적 직관적으로 작성할 수 있습니다.
라이프사이클 메서드 (이벤트 함수)
MonoBehaviour의 가장 중요한 특징 중 하나는 유니티 엔진에 의해 특정 시점에 자동으로 출되는 "라이프사이클 메서드" 또는 "이벤트 함수"들을 제공한다는 것입니다.
이 함수들은 게임 오브젝트의 생성, 활성화, 업데이트, 비활성화, 파괴 등 특정 이벤트가 발생할 때 호출됩니다.
[자주 사용되는 라이프사이클 메서드]
| 메서드 | 호출 시점 | 용도 | 비고 |
| Awake() | 게임 오브젝트가 생성된 직후, 씬의 모든 오브젝트가 초기화된 후에 호출됩니다. | 초기 설정, 다른 컴포넌트 참조를 가져올 때 사용됩니다. (GetComponent) | 씬 로딩 시 한 번만 호출됩니다. 호출은 스크립트 활성화 여부와 상관없습니다. |
| OnEnable() | 스크립트가 활성화될 때마다 호출됩니다. 처음 시작할 때 또는 비활성화되었다가 다시 활성화 될 때에 해당합니다. |
활성화 시 필요한 초기화, 이벤트 구독 등에 사용됩니다. | 오브젝트가 비활성화 상태에서 활성화될 때마다 호출됩니다. |
| Start() | 스크립트 인스턴스가 생성되고 활성화된 후, 첫 번째 Update 호출 직전에 한 번 호출됩니다. | 게임 로직 초기화, 변수 초기화 설정 등에 사용됩니다. 다른 오브젝트가 초기화된 후 작업합니다. |
스크립트가 활성화 상태일 때만 호출합니다. |
| Update() | 매 프레임마다 호출됩니다. 프레임 속도에 따라 호출 주기가 달라집니다. | 사용자 입력 처리, 오브젝트 상태 변경 등에 사용됩니다. | 프레임 드롭 시 호출 빈도 감소가 가능합니다. |
| FixedUpdate() | 일정한 시간 간격으로 호출됩니다. 이 간격은 유니티의 물리 설정에서 조절할 수 있습니다. (기본 값은 0.02초 입니다.) |
물리 연산(Rigidbody에 힘을 가하는 AddForce, 속도 설정을 위한 velocity, MovePosition 등)에 사용됩니다. | 물리 엔진과 동기화되며, 물리 관련 로직에 필수적입니다. |
| LateUpdate() | 매 프레임마다 호출되며, 해당 프레임의 모든 Update 함수들이 실행된 후에 호출됩니다. | 카메라 따라가기, 오브젝트의 최종 위치 업데이트 등에 사용됩니다. | 다른 오브젝트의 업데이트 결과 반영이 필요할 때 사용됩니다. |
| OnDisable() | 스크립트가 비활성화될 때마다 호출됩니다. | 비활성화 시 필요한 정리 작업, 이벤트 구독 해제 등에 사용됩니다. | 오브젝트가 활성화 상태에서 비활성화될 때마다 호출합니다. |
| OnDestroy() | 게임 오브젝트가 파괴될 때 호출됩니다. | 오브젝트 파괴 시 필요한 정리 작업, 리소스 해제 등에 사용됩니다. | 오브젝트 풀링은 오브젝트를 파괴가 아닌 비활성화를 하는 것이기에, 주의가 필요합니다. |
MonoBehaviour에서의 new 키워드
MonoBehaviour를 상속한 클래스는 new 키워드를 통해 오브젝트로 생성할 수 없습니다.
MonoBehaviour를 상속한 클래스는 스크립트를 게임 오브젝트에 컴포넌트로 추가하는 방법으로만 오브젝트로 만들 수 있습니다.
MonoBehaviour를 상속한 클래스는 컴포넌트로 동작하며, 컴포넌트는 게임 오브젝트의 부품으로만 존재할 수 있습니다.
또한 컴포넌트는 게임 오브젝트에 추가될 때 컴포넌트로서 필요한 초기화 과정을 거칩니다.
new 연산자로 MonoBehaviour를 상속한 클래스를 오브젝트로 생성하면 필요한 초기화 과정과 게임 오브젝트에 추과된느 과정을 전부 생략하고 즉시 오브젝트가 생성됩니다. 따라서 생성된 오브젝트가 정상적으로 동작하지 않습니다.
참조 타입
유니티의 모든 컴포넌트 및 MonoBehaviour를 상속받는 클래스, class 타입은 모두 찹조 타입입니다.
프리팹 (Prefab)
프리팹은 언제든지 재사용할 수 있는 미리 만들어진 게임 오브젝트 에셋(파일)입니다.
해당 게임 오브젝트의 모든 정보(Transform, Mesh Renderer, Collidier, 스크립트 등 모든 컴포넌트와 그 설정값, 자식 오브젝트 구조까지)가 파일 형태로 저장되는 것입니다.
프리팹을 사용하는 이유
- 재사용성
- 똑같은 오브젝트를 여러 개 만들어야 할 때, 프리팹 하나만 만들어두면 씬에 원하는 만큼 쉽게 복제하여 배치할 수 있습니다.
- 일관성 유지
- 프리팹으로 만든 모든 오브젝트는 동일한 템플릿을 따릅니다. 따라서 모든 인스턴스가 일관된 속성과 동작을 가집니다.
- 유지보수 및 업데이트
- 오버라이드가 되지 않은 경우에 프리팹의 원본 파일 설정을 변경하면, 해당 프리팹으로 생성된 모든 인스턴스에 자동으로 변경 사항이 반영됩니다.
- 오버라이드 관리
- 씬에 배치된 특정 프리팹 인스턴스만 일부 설정을 다르게 할 수 있습니다. 그리고 언제든지 이 변경사항을 원본 프리팹에 적용하거나 원본 프리팹 설정으로 되돌릴 수 있습니다.
- 런타임 생성
- 코드를 통해 게임 실행 중에 동적으로 게임 오브젝트를 생성할 때 프리팹을 Instantiate 메서드의 인자로 사용하여 쉽게 인스턴스화 하여 생성할 수 있습니다.
Rigidibody의 AddForce( )와 velocity의 차이
Rigidibody 컴포넌트는 유니티의 물리 엔진을 사용하여 게임 오브젝트에 물리적인 속성(질량, 마찰, 중력 등)과 움직임(속도, 회전)을 부여합니다.
Rigidibody의 AddForce( )와 velocity는 움직임에 영향을 주는 것은 동일하지만, 그 방식이 다릅니다.
Rigidbody.AddForce(Vector3 force, ForceMode mode = ForceMode.Force)
- 역할
- Rigidbody에 힘을 가합니다.
- 작동 방식
- AddForce는 Rigidbody의 물리 시뮬레이션에 영향을 주어 가속도를 발생시키고, 이 가속도가 시간에 따라 누적되어 속도를 변화시킵니다.
- 즉, 순간적으로 속도를 설정하는 것이 아니라, 물리적인 힘이 작용하는 것처럼 속도가 점진적으로 변하게 합니다.
- 사용 용도
- 물리적인 움직임(밀기, 당기기, 폭발 등)을 구현할 때 사용합니다. 중력처럼 지속적으로 힘을 가하거나, 순간적인 충격을 주거나 할 때 적합합니다.
- FoceMode
- 힘을 가하는 방식을 지정할 수 있습니다.
- ForceMode.Force
- 연속적인 힘을 질량에 따라 적용합니다. (기본값)
- FoceMode.Acceleration
- 질량과 상관없이 가속도를 직접 적용합니다.
- ForceMode.Impulse
- 순간적인 힘을 질량에 따라 적용합니다. (충돌, 점프 등)
- ForceMode.VelocityChange
- 질량과 상관없이 순간적인 속도 변화를 적용합니다.
- veloctiy를 직접 변경하는 것과 유사하지만 물리 시스템에 통합되어 있다는 점이 다릅니다.
- 주의 사항
- 유니티의 물리 엔진은 렌더링 주기(프레임)와는 별개로 고정된 시간 간격(Fixed Timestep)으로 업데이트 됩니다.
Update( )는 매 프레임마다 호출되며, 호출 주기는 컴퓨터 성능, 게임 복잡성 등에 따라 가변적입니다.
FixedUpdate( )는 물리 엔진 업데이트 주기와 동기화되어 호출됩니다. - AddForce는 Rigidbody에 힘을 가하여 가속도를 발생시키고, 이 가속도가 시간에 따라 누적되어 속도를 변화시키는 방식입니다. 물리엔진은 매 FixedUpdate 스텝마다 현재 오브젝트에 가해진 힘과 속도 등을 바탕으로 다음 물리 상태(위치, 속도 등)를 계산합니다.
- 따라서 물리적인 힘을 가하여 오브젝트를 움직일 때는 물리 주기에 맞춰 FixedUpdate에서 AddForce를 사용하는 것이 권장됩니다.
- 유니티의 물리 엔진은 렌더링 주기(프레임)와는 별개로 고정된 시간 간격(Fixed Timestep)으로 업데이트 됩니다.
Rigidbody.velocity (Vector3)
- 역할
- Rigidbody의 현재 선형 속도를 나타냅니다. 값을 읽거나 직접 설정할 수 있습니다.
- 작동 방식
- velocity에 직접 값을 설정하면 Rigidbody의 현재 속도가 즉시 해당 값으로 변경됩니다.
- AddForce처럼 가속도를 통해 서서히 속도가 바뀌는 것이 아니라, 순간적으로 원하는 속도로 만듭니다.
- 사용 용도
- 캐릭터 컨트롤러처럼 정확한 속도 제어가 필요하거나, 마찰이나 다른 힘의 영향을 무시하고 특정 속도를 움직이게 하고 싶을 때 사용할 수 있습니다.
- 물리 엔진의 계산을 건너뛰고 속도를 강제로 설정하기 때문에, 충돌이나 다른 물리적 상호작용에서 예상치 못한 결과를 초래할 수 있습니다.
- 이 때문에 물리 기반 이동에서는 AddForce나 MovePosition이 더 권장됩니다.
- 주의 사항
- velocity에 직접 갓을 설정하는 것은 물리 엔진의 가속 계산 과정을 건너뛰고 Rigidbody의 속도를 즉시 원하는 값으로 강제 변경하는 것입니다. 이 방식은 물리 엔진의 제어 흐름을 직접 조작하는 것과 같습니다.
- FixedUpdate는 물리 계산이 수행되기 직전에 호출됩니다. 이때 velocity를 설정하면, 바로 이어지는 물리스텝에서 물리 엔진이 이 설정된 velocity 값을 사용하여 나머지 계산(충돌 감지, 마찰 적용 등)을 수행하게 됩니다.
속도 설정 시점과 물리 엔진이 그 속도를 사용하는 시점이 일치하므로, 충돌과 같은 물리적 상호작용이 비교적 안정적으로 처리됩니다. - 특히 빠른 움직임에서 오브젝트가 벽을 뚫고 지나가는 현상(터널링)을 방지하는 데 도움이 됩니다.
FindObjectOfType( ) 메서드와 FindObjectsOfType( ) 메서드
FindObjectOfType( ) 메서드
FindObjectOfType( )은 유니티 엔진에서 제공하는 메서드로, 현재 활성화된 씬에 존재하는 특정 컴포넌트 타입의 첫 번째(하나의) 게임 오브젝트를 찾아 반환합니다.
FindObjectOfType<T>()
작동 방식
- 현재 씬에 로드된 모든 활성화된 게임 오브젝트를 순회하면서, 지정된 <T> 타입의 컴포넌트를 가지고 있는 오브젝트를 찾습니다. 조건을 만족하는 첫 번째 오브젝트를 찾으면 그 오브젝트에 붙어 있는 <T> 타입의 컴포넌트를 반환하고 검색을 멈춥니다.
- 만약 해당하는 오브젝트를 찾지 못하면 null을 반환합니다.
주의사항
- 성능 문제
- 씬의 모든 활성화된 게임 오브젝트를 순회해야 하기 때문에 씬에 오브젝트가 많을 경우 성능 부하가 발생할 수 있습니다.
특히 Update()와 같이 자주 호출되는 함수에서 사용하면 게임 성능에 큰 영향을 줄 수 있습니다. - 따라서 처리 비용이 큰 FindObjectOfType( ) 메서드는 Start( ) 메서드처럼 초기에 한 두번 실행되는 메서드에서만 사용하는 것이 좋습니다. 다만, Start( )에서 다른 오브젝트를 찾으려고 할 때, 찾으려는 오브젝트가 아직 생성되거나 활성화되지 않았다면 찾이 못할 수도 있습니다.
- 씬의 모든 활성화된 게임 오브젝트를 순회해야 하기 때문에 씬에 오브젝트가 많을 경우 성능 부하가 발생할 수 있습니다.
- 하나의 컴포넌트만 반환
- 동일한 타입의 컴포넌트를 가진 오브젝트가 여러 개 있을 경우, 가장 먼저 발견된 하나만 반환합니다.
- 특정 이름이나 조건을 만족하는 오브젝트를 찾을 때는 적합하지 않습니다.
- 비활성화 오브젝트는 찾지 못함
- 비활성화된 게임 오브젝트나 비활성화된 컴포넌트는 찾을 수 없습니다.
대안
- 변수 할당 (인스펙터 연결)
- public 변수나 [SerializeField] 변수로 선언한 뒤, 유니티 에디터의 인스펙터 창에서 해당 오브젝트를 직접 드래그 앤 드롭하여 연결합니다.
- 코드가 실행되기 전에 참조가 설정되므로 빠르고 안전합니다.
- GetComponent( )
- 동일한 게임 오브젝트에 붙어 있는 다른 컴포넌트를 찾을 때 사용합니다.
- 이 방법은 해당 게임 오브젝트 내에서만 검색하므로 매우 빠릅니다.
- 싱글턴 패턴
- 게임 매니저와 같이 씬에 하나만 존재하는 중요한 오브젝트는 싱글턴 패턴으로 만들어 어디서든 쉽게 접근할 수 있도록 할 수 있습니다.
FindObjectSOfType( ) 메서드
FindObjectsType( ) 메서드는 해당 타입의 오브젝트를 모두 찾아 배열로 반환합니다.
PlayerPrefs
PlayerPrefs는유니티에서 작은 양의 데이터를 저장하고 불러오기 위한 간단한 방법을 제공하는 클래스입니다.
주로 플레이어의 설정, 점수, 진행 상황 등과 같이 게임을 종료했다가 다시 실행해도 유지되어야 하는 데이터를 저장할 때 사용됩니다.
PlayerPrefs는 키 - 값(Key - Value) 쌍의 형태로 데이터를 저장합니다.
여기서 키는 저장하려는 데이터의 이름을 나타내는 string이고, 값은 실제 데이터입니다.
PlayerPrefs는 int, float, string의 기본 데이터 타입만 저장할 수 있습니다.
데이터는 사용자 컴퓨터의 특정 위치에 파일 형태로 저장됩니다. 운영체제마다 저장 위치는 다르지만, 개발자가 직접 파일 경로를 신경 쓸 필요 없이 PlayerPrefs 메서드를 통해 접근할 수 있습니다.
PlayerPrefs 클래스의 정적(static) 메서드
PlayerPrefs 클래스는 정적 메서드로 구성되어 있어, 별도의 인스턴스 생성 없이 클래스 이름으로 바로 호출하여 사용합니다.
- Set 메서드
- 데이터를 저장하기 위해 사용되는 메서드입니다.
- Set 메서드를 호출한다고 해서 데이터가 즉시 파일에 저장되지는 않습니다.
데이터는 메모리에 임시로 저장되며, 실제 파일 저장은 나중에 이루어집니다.
// 정수 저장
PlayerPrefs.SetInt("SavedCoin", 100);
// 부동 소수점 수 저장
PlayerPrefs.SetFloat("MasterVolume", 0.75f);
// 문자열 저장
PlayerPrefs.SetString("PlayerName", "Dobby");
- Get 메서드
- 데이터를 불러오기 위해 사용되는 메서드입니다.
- Get 메서드는 두 번째 인자로 해당 키에 해당하는 데이터가 없을 경우 반호나할 기본값을 지정할 수 있습니다.
기본값을 지정하지 않으면 GetInt는 0, SetFloat는 0f, GetString은 null 또는 빈 문자열을 반환합니다.
// 정수 불러오기, "SavedCoin"이라는 키가 없으면 기본값 0 반환
int currentCoin = PlayerPrefs.GetInt("SavedCoin", 0);
// 부동 소수점 수 불러오기, "MasterVolume"이라는 키가 없으면 기본값 1.0f 반환
float volume = PlayerPrefs.SetFloat("MasterVolume", 1.0f);
// 문자열 불러오기, "PlayerName"이라는 키가 없으면 기본값 "" 반환
string playerName = PlayerPrefs.GetSTring("PlayerName", "");
- HasKey 메서드
- 특정 키에 해당하는 데이터가 저장되어 있는지 확인합니다.
if (PlayerPrefs.HasKey("PlayerName"))
{
// ...
}
- Delete 메서드
- 특정 키 또는 모든 데이터를 삭제합니다.
// 특정 키의 데이터 삭제
PlayerPrefs.DeleteKey("PlayerName");
// 모든 데이터 삭제
PlayerPrefs.DeleteAll();
- Save 메서드
- Set메서드 호출 후, 변경된 내용을 실제 파일에 즉시 저장하고 싶을 때 사용합니다.
- PlayerPrefs.Save()는 게임이 정상적으로 종료될 때 자동으로 호출됩니다.
하지만, 게임이 갑자기 종료되거나 크래시될 경우 저장되지 않은 데이터가 손실될 수 있습니다.
따라서 중요한 데이터를 변경한 후에는 명시적으로 PlayerPrefs.Save()를 호출하는 것이 안전할 수 있습니다.
다만 너무 빈번하게 호출하면 성능에 영향을 줄 수 있으니 주의해야 합니다.
장점
- 코드가 간단하고 직관적이여서, 사용하기 매우 간편합니다.
- 적은 양의 데이터를 저장하는 데 빠르고 효율적입니다.
- 개발자가 파일 경로를 직접 관리할 필요가 없습니다.
단점
- 보안에 취약
- 저장되는 데이터가 암호화되지 않으며, 파일 경로를 알면 사용자가 쉽게 열어서 수정할 수 있습니다.
중요한 게임 데이터(예 : 유료 아이템 개수)를 저장하는 데는 적합하지 않습니다.
- 저장되는 데이터가 암호화되지 않으며, 파일 경로를 알면 사용자가 쉽게 열어서 수정할 수 있습니다.
- 제한적인 데이터 타입
- int, float, string 외의 복잡한 데이터 타입(클래스, 리스트 등)은 직접 저장할 수 없습니다.
- 클래스, 리스트와 같은 데이터를 저장하려면 JSON 등으로 직렬화하여 string으로 저장하고, 불러올 때 다시 역직렬화해야 합니다.
- 많은 양의 데이터 저장 부적합
- 대량의 데이터를 저장하는 데 비효율적이며 성능 문제가 발생할 수 있습니다.
- 플랫폼 간 호환성 문제
- 저장 파일의 위치나 포맷이 플랫폼마다 다를 수 있어, 여러 플랫폼에서 같은 저장 파일을 공유하는 데 문제가 있을 수 있습니다.
'Unity' 카테고리의 다른 글
| [Unity] 디자인 패턴 : SOLID 원칙 (1) | 2025.06.09 |
|---|---|
| [Unity] Unity 2D 게임 개발 정리 (4) (0) | 2025.06.04 |
| [Unity] Unity 2D 게임 개발 정리 (3) (0) | 2025.06.03 |
| [Unity] Unity 2D 게임 개발 정리 (2) (0) | 2025.05.29 |
| [Unity] Unity 2D 게임 개발 정리 (1) (1) | 2025.05.28 |