Grahpics
[Graphics] DirectX12 - Component
송만덕
2022. 1. 31. 01:35
Component
- Component는 Object를 생성하기 위해 필요한 여러가지(Transform, Mesh, Collider, Light...등등)를 부품(Component)처럼 필요한 것을 Object에 추가하고 제거하는 방식으로 사용하는 것을 의미한다.
- 유니티
- 유니티의 경우 빈 깡통에서 부품을 추가하는 방식을 사용한다.
- Scripts를 통해 MonoBehaviour을 상속한 Component를 만들어 다양한 기능을 만들 수 있다.
- 상대적으로 자유도가 높고 간단하다.
- 언리얼
- 언리얼의 경우 유니티와는 달리 Object종류에 따라 미리 규격화하여 제공한다.
- 틀이 강력하게 잡혀있다.
- 따라서 상대적으로 자유도가 낮고 복잡하지만 같은 유형의 object를 개발할 때 빠르게 진행할 수 있다.
<Game.cpp>
더보기
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
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
|
#include "pch.h"
#include "Game.h"
#include "Engine.h"
#include "Material.h"
#include "GameObject.h"
#include "MeshRenderer.h"
shared_ptr<GameObject> gameObject = make_shared<GameObject>();
void Game::Init(const WindowInfo& info)
{
GEngine->Init(info);
vector<Vertex> vec(4);
vec[0].pos = Vec3(-0.5f, 0.5f, 0.5f);
vec[0].color = Vec4(1.f, 0.f, 0.f, 1.f);
vec[0].uv = Vec2(0.f, 0.f);
vec[1].pos = Vec3(0.5f, 0.5f, 0.5f);
vec[1].color = Vec4(0.f, 1.f, 0.f, 1.f);
vec[1].uv = Vec2(1.f, 0.f);
vec[2].pos = Vec3(0.5f, -0.5f, 0.5f);
vec[2].color = Vec4(0.f, 0.f, 1.f, 1.f);
vec[2].uv = Vec2(1.f, 1.f);
vec[3].pos = Vec3(-0.5f, -0.5f, 0.5f);
vec[3].color = Vec4(0.f, 1.f, 0.f, 1.f);
vec[3].uv = Vec2(0.f, 1.f);
vector<uint32> indexVec;
{
indexVec.push_back(0);
indexVec.push_back(1);
indexVec.push_back(2);
}
{
indexVec.push_back(0);
indexVec.push_back(2);
indexVec.push_back(3);
}
// 오늘 테스트
gameObject->Init(); // Transform
shared_ptr<MeshRenderer> meshRenderer = make_shared<MeshRenderer>();
{
shared_ptr<Mesh> mesh = make_shared<Mesh>();
mesh->Init(vec, indexVec);
meshRenderer->SetMesh(mesh);
}
{
shared_ptr<Shader> shader = make_shared<Shader>();
shared_ptr<Texture> texture = make_shared<Texture>();
shader->Init(L"..\\Resources\\Shader\\default.hlsli");
texture->Init(L"..\\Resources\\Texture\\Manduk.png");
shared_ptr<Material> material = make_shared<Material>();
material->SetShader(shader);
material->SetFloat(0, 0.3f);
material->SetFloat(1, 0.4f);
material->SetFloat(2, 0.3f);
material->SetTexture(0, texture);
meshRenderer->SetMaterial(material);
}
gameObject->AddComponent(meshRenderer);
GEngine->GetCmdQueue()->WaitSync();
}
void Game::Update()
{
GEngine->Update();
GEngine->RenderBegin();
gameObject->Update();
GEngine->RenderEnd();
}
|
cs |
- 우선 mesh와 material의 요소들을 생성하여 meshRenderer에 넣어줌으로써 같이 관리를 하게끔 세팅이 되며
- 이후 gameObject에 meshRenderer를 AddComponent 하면서 Component로써 부착을 시켜주게 된다.
- 마지막으로 gameObject를 Update 해주면 각 Component와 Script들을 업데이트 해주어 이전과 동일하게 동작하게 되는 것이다.
<MeshRenderer.h>
더보기
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
|
#pragma once
#include "Component.h"
class Mesh;
class Material;
class MeshRenderer : public Component
{
public:
MeshRenderer();
virtual ~MeshRenderer();
void SetMesh(shared_ptr<Mesh> mesh) { _mesh = mesh; }
void SetMaterial(shared_ptr<Material> material) { _material = material; }
virtual void Update() override { Render(); }
void Render();
private:
shared_ptr<Mesh> _mesh;
shared_ptr<Material> _material;
};
|
cs |
- 이전에 material과 비슷하지만, mesh까지 통합하여 같이 관리가 됨을 알 수 있고
- Component의 Update를 override 함으로써 gameObject->Update()가 실행되었을 때 Render()를 실행하게 된다.
- Render() 함수는 mesh의 Render와 material의 Update를 실행시켜주는 함수이다.
<Component.h>
더보기
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
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
|
#pragma once
enum class COMPONENT_TYPE : uint8
{
TRANSFORM,
MESH_RENDERER,
// ...
MONO_BEHAVIOUR,
END,
};
enum
{
FIXED_COMPONENT_COUNT = static_cast<uint8>(COMPONENT_TYPE::END) - 1
};
class GameObject;
class Transform;
class Component
{
public:
Component(COMPONENT_TYPE type);
virtual ~Component();
public:
virtual void Awake() { }
virtual void Start() { }
virtual void Update() { }
virtual void LateUpdate() { }
public:
COMPONENT_TYPE GetType() { return _type; }
bool IsValid() { return _gameObject.expired() == false; }
shared_ptr<GameObject> GetGameObject();
shared_ptr<Transform> GetTransform();
private:
friend class GameObject;
void SetGameObject(shared_ptr<GameObject> gameObject) { _gameObject = gameObject; }
protected:
COMPONENT_TYPE _type;
weak_ptr<GameObject> _gameObject;
};
|
cs |
- 상속을 위한 Component Class 이다.
- Component Type을 지님으로써 어떤 Type의 Component 구분이 가능하게 해준다.
- 추후에 다른 Component Type을 추가할 경우 꼭 enum class 의 MonoBehviour보다 앞 순서에 배치해야한다. 그 이유는 MonoBehaviour은 Scripts이기 때문에 여러개의 Component가 존재할 수 있지만, 다른 Component의 경우 GameObject당 하나의 Component만 가질 수 있기 때문이다.
- FIXED_COMPONENT_COUNT에서 END-1을 해주는 이유는 Component와 Script를 분리해주기 위함이다.
- GameObject의 자식이면서도 GameObject를 지니고 있는 이유는 자신의 부모가 누군이 알 수 있게끔 하기 위해서 이다.
<GameObject.h>
더보기
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
|
#pragma once
#include "Component.h"
class Transform;
class MeshRenderer;
class MonoBehaviour;
class GameObject : public enable_shared_from_this<GameObject>
{
public:
GameObject();
virtual ~GameObject();
void Init();
void Awake();
void Start();
void Update();
void LateUpdate();
shared_ptr<Transform> GetTransform();
void AddComponent(shared_ptr<Component> component);
private:
array<shared_ptr<Component>, FIXED_COMPONENT_COUNT> _components;
vector<shared_ptr<MonoBehaviour>> _scripts;
};
|
cs |
- GameObject를 관리하기 위한 class로, Component로는 현재까진 Transform, MeshRenderer, MonoBehaviour를 지니고 있다.
- 우선 특징적인 부분은 enable_shared_from_this를 상속받고 있는데, 그 이유는 다음과 같다.
- enable_shared_from_this => GameObject.cpp의 AddComponent 부분에서 component->SetGameObject를 할 때 부모가 자신임을 알리기 위해서 인자를 넘겨야 하는데, 이 때 스마트 포인터 (shared_ptr)를 넘기기 위함
<GameObject.cpp>
더보기
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
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
|
#include "pch.h"
#include "GameObject.h"
#include "Transform.h"
#include "MeshRenderer.h"
#include "MonoBehaviour.h"
GameObject::GameObject()
{
}
GameObject::~GameObject()
{
}
void GameObject::Init()
{
AddComponent(make_shared<Transform>());
}
void GameObject::Awake()
{
for (shared_ptr<Component>& component : _components)
{
if (component)
component->Awake();
}
for (shared_ptr<MonoBehaviour>& script : _scripts)
{
script->Awake();
}
}
void GameObject::Start()
{
for (shared_ptr<Component>& component : _components)
{
if (component)
component->Start();
}
for (shared_ptr<MonoBehaviour>& script : _scripts)
{
script->Start();
}
}
void GameObject::Update()
{
for (shared_ptr<Component>& component : _components)
{
if (component)
component->Update();
}
for (shared_ptr<MonoBehaviour>& script : _scripts)
{
script->Update();
}
}
void GameObject::LateUpdate()
{
for (shared_ptr<Component>& component : _components)
{
if (component)
component->LateUpdate();
}
for (shared_ptr<MonoBehaviour>& script : _scripts)
{
script->LateUpdate();
}
}
shared_ptr<Transform> GameObject::GetTransform()
{
uint8 index = static_cast<uint8>(COMPONENT_TYPE::TRANSFORM);
return static_pointer_cast<Transform>(_components[index]);
}
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));
}
}
|
cs |
- 우선 특징적은 부분은 Init에서 Transform을 Add 해주고 있는데, Game.cpp에서 굳이 해주지 않은 이유는 모든 GameObject는 Transform을 공통적으로 지니고있기 때문에 따로 생성하지 않고 고정적으로 부착해주는 것이다.
- 이후 GetTransform() 이전의 함수들은 단순하게 자식들이 Component인지, Script인지 구별한 뒤 자식들이 상속받은 함수를 실행시켜주는 부분이다.
- GetTransform() 함수는 Array의 index를 통해 Transform에 접근하여 반환해주는 함수이다.
- 마지막인 AddComponent() 함수는 GameObject.h 부분에서 설명한대로 부착되는 Component에게 부모가 자신임을 알린 뒤 Type의 value를 통해 Component Array에 배정해주거나, Script Vector에 추가해주는 방식이다.