scene에서 GameObject를,
GameObject에서 Component를 저장하는 컨테이너로 채택된 건 std::unordered_multimap 이었다. 이유는 중복된 값을 저장할 수 있기 때문이다. 아직 이게 성능상으로 어떤 문제가 있을 수 있을 지는 모르겠다. 그냥 unordered_map과는 차이점이 좀 있는데, 연산자 []를 제공하지 않는다. 어찌보면 당연한 얘기다. 중복 값이 들어갈 수 있으므로 그중에 어떤 값을 가져와야 되는지 모르니까, std::list로 값들의 집합을 리턴하고 있다. unordered_multimap에 대한 내용을 따로 작성하고 아래 링크를 남기고자 한다.
2020/01/15 - [Programming] - [C++] std::unordered_map // std::unordered_multimap
어쨌든, 테스트해본 결과 괜찮게 동작했다.
며칠 간의 고생이 드디어 보상을 받은 느낌이다. Scene의 생성자에서 GameObject와 Component를 추가해주면 update()에서 알아서 컴포넌트가 자기 할 일을 알아서 하게 되었다.
Mesh Renderer의 구현을 어떻게 하였는가 하면,
Scene의 static 변수로 Renderer와 Input의 포인터를 생성자에서 받은 후 저장한다.
Scene의 friend로 MonoBehavior 클래스를 지정해준다. 그리고 MonoBehavior의 생성자에서 Scene의 스태틱 변수를 받아와서 레퍼런스로 저장해준다. 그러면 이후 이를 상속하는 Component와 그 하위 클래스들은 모두 기본적으로 Renderer와 Input 변수를 사용할 수 있게 된다.
그러면 Renderer는 어떤 식으로 구성이 되어있는 가 하면,
#include "Graphics.h"
#include "Mesh.h"
#include "Shader.h"
#include "ConstantBuffer.h"
class Renderer {
public:
struct MeshData
{
Mesh mesh;
CB_Transformation transform;
std::string vertexShader;
std::string pixelShader;
};
public:
Renderer(Graphics& gfx);
void OnCall(std::function<void(void)> drawcall);
void Render();
Shader::Vertex* GetVS(std::string id);
Shader::Pixel* GetPS(std::string id);
CBuffer<CB_Transformation>& GetCB();
private:
std::queue<std::function<void(void)>> drawCalls;
std::unordered_map<std::string, Shader::Vertex*> vertexShaders;
std::unordered_map<std::string, Shader::Pixel*> pixelShaders;
CBuffer<CB_Transformation> cb;
Graphics& renderer;
DEFINEPROPERTY:
READONLY_PROPERTY(Graphics&, gfx);
GET(gfx)
{
return renderer;
}
};
이런 식이다. Renderer에서 모든 셰이더와 상수버퍼를 관리한다. MeshRenderer는 단지 어떤 셰이더를 가지고 렌더링을 할 것인지만 알려주는 것이다. 그리고 그려달라고 하는 명령이 std::function 객체로 Renderer의 큐로 전달되고, 매 업데이트마다 큐에서 명령을 꺼내서 시행하는 방식이다.
아직 완벽한 방식이 아니라서 MeshRenderer의 Update() 함수는 이런 식으로 구성되어 있다.
void MeshRenderer::Update()
{
renderer.OnCall(
[this]()
{
//CONSOLE("MESHRENDERER : DRAW START");
mesh->Bind();
CB_Transformation tr;
//tr.transform = gameObject->transform.transformMatrix;
tr.transform = dx::XMMatrixIdentity();
renderer.GetCB().data = tr;
renderer.GetCB().SetBuffer(0);
renderer.GetVS(vs)->Bind();
renderer.GetPS(ps)->Bind();
renderer.gfx.context.Draw(3, 0);
}
);
}
일단 삼각형이 잘 그려지나 테스트 해보려고 만든 거라 완벽하지는 않다. 그리고 이상하게도 transform이 먹히지 않는다. 이도 앞으로 해결해야 할 사안이다.
그러니까 이 기능을 사용하기 위해 std::function의 기능, 람다 함수의 구현, 그중에서도 멤버 함수를 전달하기 위해서는 어떻게 해야하는 지 공부해야만 했다.
이걸 만들면서 맹점이 뭐였는가 하면, std::function 객체를 대체 어떻게 만들어야 하는 가, 특히 멤버 함수를 전달하기 위해서는 어떻게 해야하는 가 였었다. 멤버함수를 넘기는 건 람다함수의 []에 해당 인스턴스의 포인터를 넘겨주면 되는 문제였고, std::function 객체는 직접적으로 MeshRenderer의 함수를 전달하는 것에 실패하였기 때문에 람다함수로 전달해 주었다. 후에 함수의 인스턴스를 직접 전달하는 방법이 필요할 것 같다.
자, 그러면 다시 AddComponent와 GetComponent 기능을 만들어 보도록 하자.
드디어 템플릿의 엄청난 기능을 한가지 익혔으므로 다음 기능을 활용해보고자 한다.
2020/01/15 - [Programming] - [C++] 임의의 파라미터를 받는 Template 만들기(feat.std::make_unique)
std::make_unique가 어떻게 만들어졌나 궁금해서 살펴보다가 깨달음을 얻었다.
그리고 엄청난 기능을 또 한 가지 발견했다.
https://stackoverflow.com/questions/1024648/retrieving-a-c-class-name-programmatically
클래스의 자료형을 string으로 리턴해주는 기능이었다. 이것만 있으면 유니티같은 AddComponent와 GetComponent를 구현 할 수 있을 것 같다!
좋아. 성공했다!
gameObjects.insert({ "Triangle", std::make_shared<GameObject>("Triangle") });
auto go = gameObjects.find("Triangle")->second;
std::vector<Vertex> t;
t.push_back(Vertex{ -0.5f, 0.0f, 0.0f,1.0f, 0.0f, 0.0f });
t.push_back(Vertex{ 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 1.0f });
t.push_back(Vertex{ 0.5f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f });
//std::make_unique<MeshRenderer>(t);
go->AddComponent<MeshRenderer>().mesh = new Mesh(renderer.gfx, t);
또는
go->AddComponent<MeshRenderer>(new Mesh(renderer.gfx, t));
로 생성자에 직접 넣어서 생성도 가능!
이제 이런식으로 코드를 작성할 수 있게 되었다!
어떤 식으로 만들었는가 하면,
일단 Key는 std::string으로 저장하고, 템플릿에서 아까 언급한 클래스타입을 문자열로 바꿔주는 헤더를 활용해 스트링을 전달하여 인스턴스를 얻는 방식이다.
코드는 이렇다.
template<class T, class... rValue>
T& AddComponent(rValue&&... arg)
{
std::string type = typeid(T).name();
components.insert(
{type, std::unique_ptr<T>(new T(_STD forward<rValue>(arg)...)) }
);
auto pointer = components.find(type)->second.get();
pointer->gameObject = this;
return *(static_cast<T*>(pointer));
}
template<class T>
T& GetComponent()
{
std::string type = typeid(T).name();
return *(static_cast<T*>(components.find(type)->second.get()));
}
'진행과정 기록 > GameEngine' 카테고리의 다른 글
20200121 enum value를 template parameter로 사용하려면...? (0) | 2020.01.21 |
---|---|
20200117 (0) | 2020.01.17 |
20200114 (0) | 2020.01.14 |
20200110 (0) | 2020.01.10 |
20200109 (0) | 2020.01.09 |