주제
- 본 강의의 핵심 주제는 GameObject를 구성하는 다양한 기능들을 Component(컴포넌트) 라는 독립된 부품 단위로 설계하고 구현하는 것이다.
- 유니티(Unity)의 GameObject + Component 구조를 모방하여, 조합 기반(Composition-based) 구조를 갖는 확장성 높은 게임 오브젝트 시스템을 구축한다.
개념
1. Component란?
- Component는 GameObject에 부착되어 기능을 제공하는 부품 단위 클래스이다.
- 예를 들어 위치를 담당하는
Transform, 렌더링을 담당하는 MeshRenderer, 시점을 담당하는 Camera, 사용자 정의 로직을 담당하는 Script가 있다.
- 상속 기반의 Unreal 방식과 달리 Unity 방식은 빈 GameObject에 필요한 기능을 조립해서 완성하는 구조이며, 이는 유연성과 재사용성이 뛰어나다.
2. 구조 설계 원칙
- 고정형 컴포넌트(Fixed): Transform, MeshRenderer, Camera, Animator는 하나만 존재 가능.
- 동적 컴포넌트(Script 계열): Script(MonoBehaviour)는 여러 개 부착 가능.
- Component는 Awake, Start, Update, LateUpdate, FixedUpdate 같은 생명 주기 함수를 통해 시점 기반 동작을 처리한다.
용어정리
| 용어 | 설명 |
|---|
| GameObject | 게임 월드 내 존재하는 모든 객체 |
| Component | GameObject에 부착되어 기능을 수행하는 부품 |
| MonoBehaviour | 사용자 정의 Script 컴포넌트의 기본 클래스 |
| ComponentType | enum으로 컴포넌트 종류를 구분 |
| Awake | 오브젝트 생성 시점에 초기화하는 함수 |
| Start | 게임 실행 직후 한 번만 호출되는 초기 함수 |
| Update | 매 프레임마다 호출되는 일반 갱신 함수 |
| LateUpdate | Update 이후 호출되는 후처리용 함수 |
| FixedUpdate | 일정 간격으로 호출되는 물리 갱신용 함수 |
| shared_from_this | 클래스 내부에서 자신을 shared_ptr로 안전하게 반환 |
| weak_ptr | 참조용 스마트 포인터, 소유권 없음 |
코드 분석
1. ComponentType 정의 및 고정형 컴포넌트 갯수
enum class ComponentType : uint8
{
Transform,
MeshRenderer,
Camera,
Animator,
Script,
End
};
enum
{
FIXED_COMPONENT_COUNT = static_cast<uint8>(ComponentType::End) - 1
};
- 고정형 컴포넌트는 총 4개이며, Script는 유일하게 여러 개 부착 가능한 컴포넌트로
FIXED_COMPONENT_COUNT 이후부터 관리된다.
2. Component 클래스 설계
class Component
{
public:
Component(ComponentType type);
virtual ~Component() {}
virtual void Awake() {}
virtual void Start() {}
virtual void Update() {}
virtual void LateUpdate() {}
virtual void FixedUpdate() {}
ComponentType GetType() { return _type; }
void SetGameObject(shared_ptr<GameObject> gameObject) { _gameObject = gameObject; }
shared_ptr<GameObject> GetGameObject();
shared_ptr<Transform> GetTransform();
protected:
ComponentType _type;
weak_ptr<GameObject> _gameObject;
};
Awake~FixedUpdate: 생명 주기 메서드. 게임 흐름에 따라 자동 호출됨.
SetGameObject: 소유 GameObject를 설정한다.
GetTransform: 연결된 GameObject의 Transform을 간접적으로 반환한다.
3. MonoBehaviour 클래스 구현
class MonoBehaviour : public Component
{
public:
MonoBehaviour() : Component(ComponentType::Script) {}
virtual void Awake() override {}
virtual void Update() override {}
};
- Script 전용 컴포넌트로, 사용자 정의 스크립트 로직을 구현할 기본 클래스이다.
- 직접 GameObject에 붙여서 사용할 수 있으며,
Update() 등을 재정의하여 동작시킨다.
4. GameObject 클래스와 컴포넌트 배열
class GameObject : public enable_shared_from_this<GameObject>
{
protected:
std::array<shared_ptr<Component>, FIXED_COMPONENT_COUNT> _components;
std::vector<shared_ptr<MonoBehaviour>> _scripts;
public:
void AddComponent(shared_ptr<Component> component);
shared_ptr<Component> GetFixedComponent(ComponentType type);
shared_ptr<Transform> GetTransform();
shared_ptr<Transform> GetOrAddTransform();
void Awake();
void Start();
void Update();
void LateUpdate();
void FixedUpdate();
};
_components: 고정형 컴포넌트들 저장 (Transform, Camera 등)
_scripts: MonoBehaviour를 여러 개 저장하는 리스트
5. AddComponent 구현
void GameObject::AddComponent(shared_ptr<Component> component)
{
component->SetGameObject(shared_from_this());
uint8 index = static_cast<uint8>(component->GetType());
if (index < FIXED_COMPONENT_COUNT)
_components[index] = component;
else
_scripts.push_back(dynamic_pointer_cast<MonoBehaviour>(component));
}
- 반드시
enable_shared_from_this를 상속해야 shared_from_this() 사용 가능
- ComponentType에 따라
_components 혹은 _scripts에 분류하여 삽입
shared_ptr<Transform> GameObject::GetTransform()
{
return static_pointer_cast<Transform>(GetFixedComponent(ComponentType::Transform));
}
shared_ptr<Transform> GameObject::GetOrAddTransform()
{
if (!GetTransform())
AddComponent(make_shared<Transform>());
return GetTransform();
}
- Transform은 GameObject에 반드시 존재해야 함
- 없을 경우 자동으로 생성해서 부착
7. 컴포넌트 생명 주기 실행
void GameObject::Update()
{
for (auto& comp : _components)
if (comp) comp->Update();
for (auto& script : _scripts)
script->Update();
_transformData.matWorld = GetOrAddTransform()->GetWorldMatrix();
_constantBuffer->CopyData(_transformData);
}
- 모든 컴포넌트들을 순회하면서 생명 주기 메서드를 호출한다.
Awake, Start, LateUpdate, FixedUpdate 역시 동일한 방식으로 구현
class Transform : public Component
{
public:
Transform() : Component(ComponentType::Transform) {}
Vec3 GetRight() { return _matWorld.Right(); }
Vec3 GetUp() { return _matWorld.Up(); }
Vec3 GetLook() { return _matWorld.Backward(); }
};
_right, _up, _look은 캐싱하지 않고 행렬에서 실시간 계산
- 좌표계 방향에 따라
Right, Up, Backward를 선택함
| 함수 | 설명 |
|---|
| TransformNormal | 방향 벡터 변환 (w=0), 위치 무시 |
| TransformCoord | 좌표 변환 (w=1), 위치까지 포함 |
- 방향(look, up, right) 계산 시에는 반드시
TransformNormal을 사용해야 한다.
핵심
- Component는 GameObject의 기능을 수행하는 독립된 부품 클래스이다.
- GameObject는 실제 게임 엔티티이며, 모든 기능은 Component 조합을 통해 구현된다.
- 고정형 컴포넌트와 동적 컴포넌트로 구조를 나누어 효율적 관리가 가능하다.
- Unity 스타일의 생명주기 함수(Awake, Start, Update 등)를 지원하여 시간 기반의 흐름을 통합할 수 있다.
shared_from_this()와 weak_ptr 기반 연결로 스마트 포인터의 안전성 확보.
- Transform, Camera, MonoBehaviour 등 모든 기능은 Component로 확장 가능하며, 재사용성과 유지보수성을 극대화할 수 있다.