출처 : 『레트로의 유니티 게임 프로그래밍 에센스』 , 이제민
1. 상속과 재사용
게임 엔진은 이미 완성된 기반 코드를 제공하고, 개발자는 게임 엔진의 코드를 재사용한다. 코드를 재사용하는 방법인 '상속'을 통해 유니티 엔진을 다룰 수 있다.
상속으로 몬스터 만들기
클래스 | class Monster | class Orc : Monster | class OrcChieftan : Orc |
기능 | + 인공지능 기능 + 애니메이션 기능 + 공격과 방어 기능 + 물리 기능 + 기타 필수 기능 |
+ 초록색 피부 + 오크의 애니메이션 + 오크의 스킬 + 그 외 오크의 고유 기능 |
+ 대장 모자 + 새로운 무기와 강력한 스킬 + 그 외 오크 대장의 고유 기능 |
특징 | - 제대로 된 외형 없음 - 여러 종류의 몬스터 클래스를 구현할 때 부모 클래스로 사용하기 위함 |
- 'Monster'클래스의 모든 기능을 상속받음. - 따라서 인공지능, 애니메이션, 공격과 방어, 물리 기능을 다시 구현 할 필요가 없음 |
- 몬스터와 오크에 이미 구현되어 있는 기능은 다시 구현 할 필요가 없음 |
상속의 한계
플레이어와 NPC, 몬스터의 기반이 될 Human 클래스를 만들었다고 가정해보자.
클래스 | class Human | class Player : Human | class NPC : Human | class Monster : Human |
기능 | + 모습을 그려주는 렌더 기능 + 물리 기능 + 애니메이션 기능 + 체력 기능 + 기타 필수 기능 |
+ 조작 기능 + 공격 기능 + 직업 기능 + 그 외 필수 기능 |
- 물리 기능 삭제 - 체력 기능 삭제 + 대화 기능 추가 + 거래 기능 추가 |
→ 기존 애니메이션을 몬스터 애니메이션으로 수정 → 기존 물리를 몬스터 물리로 수정 - 몬스터에 어울리지 않는 Human 기능 삭제 + 인공지능 기능 추가 + 공격 기능 추가 |
특징 | - 사람 형태를 가진 오브젝트에 필요한 기능을 미리 예상해 Human 클래스에 추가 | - Human 클래스에 Player 클래스의 기능이 추가됨 | - NPC가 죽으면 문제가 생기기 때문에 Human의 체력 기능 필요 없음 - NPC는 한 곳에 머무르며 플레이어와 상호작용 하기 때문에 물리기능 필요 없음 - NPC에게 필요한 기능 추가 |
- Human의 애니메이션은 사람의 뼈대를 기준으로 만들어졌지만, 몬스터는 사람과 다른 뼈대를 가진 경우가 많음 - Human의 물리 기능은 비현실적인 움직임을 보여주는 몬스터와 맞지 않음 - 몬스터에게 필요한 기능 추가 |
위 예시에서 Human 클래스를 상속받음으로써 NPC 클래스와 Monster 클래스에서는 필요 없는 기능을 삭제해야하는 추가 작업이 생겼다.
상속으로 인해 오히려 기존 코드를 재사용하기 어려운 경우가 생길 수 있다.
부모 클래스에는 자식 클래스에 공통적으로 필요한 기능을 구현한다. 하지만 나중에 구현할 자식 클래스에 무엇이 필요한지 처음부터 정확하게 추측하기 어렵다. 부모 클래스의 기능이 나중에 구현한 자식 클래스의 기능과 충돌할 수도 있다.
상속에만 의존하면 기획자가 새로운 오브젝트를 만들 때 매번 프로그래머에게 부탁해야 하는 문제도 생긴다.
2. 컴포넌트 패턴 : 조립하는 게임 세상
유니티는 게임 오브젝트를 컴포넌트 패턴을 사용해 만든다.
컴포넌트 패턴 혹은 컴포지션 패턴이란 미리 만들어진 부품을 조립하여 완성된 오브젝트를 만드는 방식이다. 여기서 미리 만들어진 부품을 컴포넌트라 부른다.
컴포넌트로 게임 속 동물을 만든다고 가정해보자.
과정 1. 컴포넌트를 미리 여러 개 만들기
게임 콘텐츠는 계속 달라지고 추가되기 때문에 사전에 모든 동물을 기획할 수는 없다. 따라서 동물을 만드는 것보다 동물에 사용할 부품들을 미리 만들어 놓는 것이 현실적이다.
프로그래머는 기획자가 요청한 부품들을 구현해 부품 주머니에 넣는다. 이 부품들을 컴포넌트라고 부르며, 컴포넌트마다 한 가지 능력을 가진다. 예시로 '날개'컴포넌트는 하늘을 나는 능력을 제공한다.
컴포넌트 주머니 |
▶ 폐 ▶ 아가미 ▶ 탯줄 ▶ 뿔 ▶ 식사 ▶ 지느러미 ▶ 잠자기 ▶ 다리 ▶ 날개 ▶ 알 낳기 |
프로그래머가 위와 같은 10개의 컴포넌트를 미리 만들어 둠 (스크립트 = 클래스) |
과정2. 빈 게임 오브젝트 생성하기
먼저 코뿔소라는 게임 오브젝트를 생성한다. 이 게임 오브젝트는 내부가 비어있고, 컴포넌트를 붙일 수 있는 뼈대나 홀더 역할을 한다.
코뿔소 |
(비어 있음) |
과정3. 코뿔소 게임 오브젝트 완성하기
원하는 기능을 제공하는 컴포넌트를 찾아 게임 오브젝트에 붙여서 실질적인 기능을 부여한다. 코뿔소에 필요한 모든 컴포넌트를 찾아 코뿔소 게임 오브젝트에 추가하면 온전한 기능을 가진 코뿔소 게임 오브젝트가 완성된다.
빈 오브젝트에 컴포넌트를 추가함으로써 다양한 동물 오브젝트를 만들 수 있다.
코뿔소 | 상어 | 독수리 |
▶ 폐 ▶ 다리 ▶ 식사 ▶ 뿔 ▶ 탯줄 ▶ 잠자기 |
▶ 아가미 ▶ 지느러미 ▶ 잠자기 ▶ 식사 ▶ 알 낳기 |
▶ 폐 ▶ 날개 ▶ 다리 ▶ 잠자기 ▶ 식사 ▶ 알 낳기 |
게임 오브젝트와 컴포넌트의 특징
- 유연한 재사용이 가능하다.
- 상속만을 사용한 예제에서는 부모 클래스의 불필요한 기능까지 모두 가져오기 때문에 코드 재사용이 힘든 경우가 있다. 컴포넌트 방식에서는 원하는 기능을 가진 컴포넌트만 선택적으로 골라 쓸 수 있다.
- 기획자의 프로그래머 의존도가 낮아진다.
- 기획자는 미리 만들어진 컴포넌트를 조립하여 게임 오브젝트를 만들 수 있다.
- 독립성 덕분에 기능 추가와 삭제가 쉽다.
- 컴포넌트 방식에서는 어떤 기능을 추가하거나 삭제할 때 다른 기능이 망가지지 않기 때문에 기능 수정이 쉽다.
컴포넌트의 독립성
위와 같은 컴포넌트 패턴의 장점은 두 가지 특징에서 파생된다.
- 게임 오브젝트는 단순한 빈 껍데기
- 몇 가지 식별 기능과 자신에게 어떠한 컴포넌트가 조립되어 있는지 알 수 있는 기능을 제외하면 특별한 기능은 없다.
- 컴포넌트는 스스로 동작하는 독립적인 부품
- 컴포넌트는 자신과 같은 게임 오브젝트에 추가된 다른 컴포넌트에 관심이 없다. 컴포넌트의 기능은 컴포넌트 내부에 완성(연결)되어 있기 때문이다. 그러므로 컴포넌트는 다른 컴포넌트에 의존하지 않는다.
결론적으로 컴포넌트 방식에서는 새로운 기능을 추가하거나 삭제할 때 기존 기능이 망가질까봐 걱정할 필요 없다.
3. 메시지와 브로드캐스팅
컴포넌트 구조에서는 '전체 방송'을 이용해 컴포넌트의 특정 기능을 간접적으로 실행할 수 있다. 이러한 전체 방송을 '브로드캐스팅'이라고 한다.
MonoBehaviour
유니티의 모든 컴포넌트는 MonoBehaviour 클래스를 상속한다. MonoBehaviour 클래스는 유니티에서 미리 만들어 제공하는 클래스이며, 컴포넌트에 필요한 기본 기능을 제공한다. 따라서 MonoBehaviour를 상속한 클래스는 게임 오브젝트에 컴포넌트로서 추가될 수 있다.
메시지 기반 방식 (여기부터 수정하기)
컴포넌트 패턴에서 각 컴포넌트들은 독립적이다. 어떤 컴포넌트는 같은 게임 오브젝트에 추가된 다른 컴포넌트가 '일부러 찾아내기' 전에는 알 수 없다.
유니티 엔진 또한 게임 오브젝트에 어떤 컴포넌트가 추가되었는지 모두 파악하지 않는다. 따라서 유니티는 컴포넌트의 어떤 기능을 실행시키고 싶을 때 '당사자를 직접 찾아가는' 방식 대신에 메시지를 날리는 방식을 사용한다.
유니티는 발동시키고 싶은 기능의 이름을 게임 세상에 메시지로 뿌린다. 게임 세상에 오브젝트가 100개가 있다면 100개 오브젝트가 모두 메시지를 받는다. (제한 없이 모든 오브젝트에 메시지를 전파하면 성능에 좋지 않으므로 실제로는 브로드캐스팅에 적절한 필터링과 제약을 건다.)
메시지를 받은 오브젝트가 메시지에 명시된 기능을 가지고 있다면 해당 기능을 실행한다.
메시지 방식은 다음과 같은 특징이 있다.
- 메시지를 보내는 쪽은 누가 받게 될지 신경쓰지 않는다.
- 메시지를 받은 쪽은 누가 보냈는지 신경쓰지 않는다.
- 메시지를 받았을 때 메시지에 명시된 기능을 가지고 있다면 실행, 없다면 무시한다.
메시지 기반 방식은 누가 메시지를 보냈는지, 누가 받게 될지 신경쓰지 않기 때문에 컴포넌트의 독립성을 유지할 수 있다. 이런 식으로 메시지를 무차별적으로 여러 오브젝트에 동시에 뿌리는 방법을 브로드캐스팅이라고 한다.
브로드캐스팅
브로드캐스팅은 특정 오브젝트를 직접 카리키지 않고 원하는 기능을 수행하게 한다. 브로드 캐스팅을 유니티에서 어떤 컴포넌트가 다른 컴포넌트에 의존하지 않고 독립적으로 동작할 수 있는 구조를 가질 수 있는 이유이다.
유니티의 이벤트 메서드
메시지와 브로드캐스팅은 Start(), Update(), OnTriggerEnter() 등의 '유니티 이벤트 메서드'가 동작하는 원리이다.
예를 들어 Start() 메서드는 게임 오브젝트가 처음 활성화될 때 자동으로 한 번 실행된다. 게임 오브젝트가 활성화될 때 유니티가 해당 게임 오브젝트에 Start라고 적힌 메시지를 브로드캐스팅 하기 때문이다. 따라서 Start() 메서드를 수동으로 실행할 필요가 없다.
유니티에는 이런 식으로 이름 철자만 똑같이 구현해두면 메시지에 의해 자동으로 실행되는 메서드들이 있는데, 이러한 메서드들을 유니티 이벤트 함수 또는 유니티 이벤트 메서드라고 한다.