일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | |||||
3 | 4 | 5 | 6 | 7 | 8 | 9 |
10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 | 18 | 19 | 20 | 21 | 22 | 23 |
24 | 25 | 26 | 27 | 28 | 29 | 30 |
- Multithread
- material
- Unreal
- thread
- 게임공학과
- 프레임워크
- 디자인패턴
- multi-core
- C
- observer pattern
- Atomic
- 옵저버 패턴
- vector
- 멀티쓰레드
- 유니크포인터
- 쓰레드
- 멀티코어
- sequential
- 메모리관리
- random access
- MultiCore
- c++
- EFFECTIVE C++
- Design Pattern
- 복사생성자
- 한국산업기술대학교
- stl
- 스마트포인터
- 멀티코어 프로그래밍
- 옵저버
- Today
- Total
태크놀로지
컴포넌트 패턴이란 본문
컴포넌트 패턴이란?
한 개체가 여러 분야를 서로 커플링 없이 다룰 수 있게 한다. 라고 책에 적혀있다. 내가 이해한 방식으로 쉽게 풀어서 말해보자면 탈부착이 가능한 클래스라고 설명하고 싶다. 유니티를 사용한 분이 계신다면 컴포넌트에 개념을 더 쉽게 이해할수 있을거다.
컴포넌트 패턴을 왜 사용하는가?
게임을 개발하다보면 물리코드 렌더링코드, AI코드 등등을 작성하게되는데, 만약 한 클래스안에 모든 코드들이 섞여있다면 정말 끔찍할것이다. 또한 협업에 있어서 개발속도가 늦춰지고 디버깅에 많은 어려움을 겪게된다. 이러한 상황을 피하기 위해 분야별로 담당하는 파트를 나눠 설계해야 할 필요가 있다.
컴포넌트 패턴의 특성1 - 분야 나누기
게임 캐릭터는 조작을 담당하는 파트, 애니메이션을 담당하는 상태머신 컨트롤러, 캐릭터의 월드행렬을 갖고 있는 물리파트등이 존재한다. 위와 같이 구현되는 게임의 분야를 나눠보자.
컴포넌트 패턴의 특성2 - 각각 독자적인 컴포넌트들
각 컴포넌트 클래스들은 서로에 대해 알지 못하는것이 중요하다. 애니메이션을 담당하는 상태머신 컨트롤러에서 캐릭터의 위치를 바꿔버리는 상황은 매우 끔찍하다.
하지만 현실적으로 컴포넌트들끼리 서로의 정보를 알아야할상황이 많이 발생한다. A.I 컴포넌트에서는 물리컴포넌트의 월드행렬에 대해 알아야한다. 이때 다른 대상을 통해 그 정보를 알수있는 방법도 있고 이를 해결하는 방법은 매우 다양하다.
컴포넌트 패턴 언제 쓸것인가?
- 한 클래스에서 여러분야를 건드리고 있어, 이들을 서로 디커플링하고 싶다.
- 클래스가 거대해져서 작업하기 어렵다.
- 여러 다른 기능을 공유하는 다양한 객체를 정의하고 싶다. 단 상속으로는 딱 원하는 부분만 골라서 재사용할수가 없다.
주의사항
사실 컴포넌트 패턴에서 가장 어려운 부분은 컴포넌트끼리 통신하는 부분이라 생각한다. 또한 컴포넌트들을 어디에 두어 관리할것인지도 복잡하다.
[실습] 게임오브젝트와 컴포넌트 인터페이스를 설계하고, 서로 통신하는 원리를 이해하자.
컴포넌트 인터페이스 설계
class GameObject;
class Component
{
protected:
string m_Name;
GameObject* m_Owner;
public:
void SetOwner(GameObject* obj);
GameObject* GetOwner() const;
string GetName() const;
public:
virtual void Awake() {};
virtual void Update() {};
public:
Component(string compName);
virtual ~Component(void);
};
추상클래스 Awake, Update 함수가 존재하고, 자신이 누구한테 소유되어있는지를 알수있는 컴포넌트 소유자의 포인터를 저장합니다.
게임오브젝트 인터페이스 설계
class Component;
class GameObject
{
protected:
string m_Name;
map<string, Component*> m_Components;
public:
void AddComponent(Component* pComponent);
template<class tComp>
tComp* GetComponent(const string componentName)
{
if (!m_Components.count(componentName)) return nullptr;
return dynamic_cast<tComp*>(m_Components.at(componentName));
}
public:
virtual void Awake() {};
virtual void Update() {};
virtual void Render() {};
public:
GameObject(string name);
virtual ~GameObject();
};
void GameObject::AddComponent(Component* pComponent)
{
if (m_Components.count(pComponent->GetName())) return;
pComponent->SetOwner(this);
m_Components.emplace(pComponent->GetName(), pComponent);
}
게임오브젝트가 컴포넌트를 추가하면 컴포넌트의 이름과, 주소를 저장합니다. 객체를 통해 컴포넌트를 찾기 위해 검색이 빠른 map을 사용하였습니다. 컴포넌트를 추가할때 게임오브젝트 자신의 주소를 컴포넌트에 지정해줍니다. (SetOwner)
오브젝트와 컴포넌트를 생성하주는 메서드 기능 제공
namespace ObjectFactory
{
template <class tGameObject>
tGameObject* CreateObject(string objName)
{
tGameObject* obj = new tGameObject(objName);
obj->Awake();
return obj;
}
template <class tComponent>
tComponent* CreateComponent(string compName)
{
tComponent* comp = new tComponent(compName);
comp->Awake();
return comp;
}
}
게임오브젝트나 컴포넌트가 맨처음 생성될때 Awake함수를 한번 호출합니다. 이외에 상속을 받는 자식객체들을 생성해주는 유틸함수를 제공해주어야하기때문에 namespace로 분류하였습니다. 혹은 가변템플릿인자로도 구현이 가능해보입니다.
제작된 컴포넌트 사용방법
vector<GameObject*> m_GameObjects;
vector<Component*> m_Components;
class Camera : public Component
{
public:
virtual void Awake()
{
cout << "Awake Camera" << endl;
};
virtual void Update()
{
cout << "Update Camera" << endl;
//m_Owner->GetComponent ,,, 객체내의 다른 컴포넌트와 통신
};
public:
Camera(string name) : Component(name) {}
};
int main(void)
{
// Init
GameObject* player1 = ObjectFactory::CreateObject<GameObject>("player1");
m_GameObjects.push_back(player1);
Camera* comp1 = ObjectFactory::CreateComponent<Camera>("camera1");
m_Components.push_back(comp1);
// Running
while(true)
{
player1->AddComponent(comp1);
Camera* player1Camera = player1->GetComponent<Camera>("camera1");
for (auto& p : m_GameObjects) {
p->Update();
p->Render();
}
}
return 0;
}
카메라를 컴포넌트로 만들어서 플레이어1에 추가해보았습니다. ObjectFactory를 사용하여 컴포넌트를 생성하고 객체를 통해 컴포넌트를 얻을 수 있습니다.
※ 컴포넌트가 자신을 소유한 객체의 주소를 알고있기 때문에 객체의 다른 컴포넌트와도 통신이 가능합니다.
컴포넌트 패턴을 구현할때 주의해야할점
필자는 모든객체와 컴포넌트들을 vector로 따로 한번더 관리하고 있다. 만약 게임오브젝트가 사라지면 게임오브젝트가 소유하고 있는 컴포넌트들에 접근할 방법이 사라진다. 이는 게임오브젝트가 사라질때 소유한 컴포넌트들을 모두 삭제하는 방법도 존재한다. 이부분은 컴포넌트 패턴을 사용하며 가장 어려움을 겪었던 부분이다.
객체의 생성과 해제를 관리하는 오브젝트풀에 대해선 많은 고민과 디자인패턴들을 연구하고 고민해보아야한다.
출처
'디자인 패턴' 카테고리의 다른 글
단점을 보안한 옵저버 패턴 (0) | 2020.09.15 |
---|---|
옵저버 패턴이란? (0) | 2020.09.13 |
커맨드 패턴이란? (0) | 2020.09.11 |
인덱스를 사용한 씬관리 방법 (0) | 2020.09.03 |
싱글턴 패턴이란 (0) | 2020.09.02 |