C++/Game

[게임 프레임워크 개발 일지] #12 코드와의 재회는 최악이었다

Kareus 2023. 5. 26. 02:50

오랜만입니다.

어떻게 이게 2023년 첫 글이 될 수 있으며, 지난 글과 7~8개월 정도 차이가 날 수 있을까요.

 

그동안 어디서 굴러먹다 왔느냐 하면, 음악을 하고 있었습니다.

곡 투고하느라 몇 개월을 고생했고, 그 와중에 2~3주마다 외주를 했습니다.

 

그러고 오랜만에 코드를 살펴보는데, 영 이상한 게 있어서 갈아엎었습니다.

다 갈아엎은 건 아니고, Event 메커니즘만 바꿨습니다.

 

이번 글은 뭘 새로 만들었다 라는 내용보다는, 기존에 있던 걸 새로 만들었다는 내용입니다.

그냥 생존 신고 같은 거죠 뭐

 

이전에는 EventManager과 Event만 있어서, Manager에 Event를 추가하고 Manager에 특정 EventType을 트리거시키면

등록된 Event를 호출하는 방식으로 구현했었습니다.

 

근데 가만 보니 단일 Scene 내에서만 동작하는 이벤트가 있다거나, 특정 Scene/Entity는 이벤트를 호출하면 안 된다거나 그런 경우가 있어서 이게 아니다 싶은 마음이 들었습니다.

구조가 Engine - Scene - Entity 순으로 이어지는데 Engine과 Scene을 연결해주는 이벤트 매개체가 없었습니다.

Scene 내에서부터는 사용자가 뭐든 마음대로 하게끔 람다식 같은 걸 끼워넣어 실행하게끔 할 수 있는데,

Engine는 못 건드리게끔 하려다보니 해결책이 딱히 없더군요.

따라서 Scene 내에서 Event를 호출할 Manager를 만들더라도, Engine에서 따로 호출해주지 않는 한 (Engine 내에서만 발생하는) 시스템 이벤트를 받지 못하는 문제점이 생깁니다.

 

뭐 그런 이유로 Trigger와 Listener를 분리했습니다.

Manager는 이벤트 호출을 요청받으면, 등록된 Listener에게 이벤트를 호출합니다. (Trigger)

Listener는 호출한 이벤트에 대응하여 등록된 Event를 실행합니다.

 

아까 말했듯이, Engine과 Scene을 연결해줘야 했기 때문에 Scene 생성자에서는 EventManager argument를 받아서 해당 Manager에 Scene 내의 Listener를 등록해줍니다.

 

뭐 결국 중간에 EventListener 객체가 추가된 것이지, 코드가 크게 바뀐 건 아닙니다.

 

Button도 조금 수정했습니다.

여러 개 그래픽을 사용하는 경우에는 그래픽이 없는 빈 부분도 boundary 안에 포함되면 클릭이 되는 문제가 있었는데,

클릭한 지점의 pixel 값을 가져와서 alpha threshold를 검사하도록 했습니다.

 

근데 이 pixel을 가져온다는 게 번거롭고 비용이 큽니다. 이미지 포맷을 하나 만들어서 그려야하거든요.

threshold가 0인 경우에는 이미지를 따로 그릴 필요도 없고, 검사를 할 필요도 없으니 스킵하게 하고

검사가 필요한 경우에는 그래픽이 추가/삭제될 때마다 이미지를 그려놓게끔 했습니다.

이러니까 되게 귀찮아집니다.

 

/*member variables
sf::Uint8 threshold;
sf::RenderTexture texture;
sf::Image image;
*/

void Button::updateTexture()
{
    if (threshold == 0) return;
    auto bound = getLocalBounds();
    if (empty() || bound.width == 0 || bound.height == 0) return;

    texture.create(bound.width, bound.height);
    texture.draw(*this);

    image = texture.getTexture().copyToImage();
}

//pixel compare

sf::Vector2f point = ...; //mouse position
bool included = getGlobalBounds().contains(point);

if (included && threshold > 0)
{
    auto p = getInverseTransform().transformPoint(point);
    if (image.getPixel(p.x, p.y).a < threshold) included = false;
}

 

마지막으로, Broadphase입니다.

지난 포스팅에서 만든 걸 가져와서 넣으려고 보니, 상황에 맞게 수정해야 될 게 좀 많았습니다.

 

먼저 Broadphase에서 판정에 사용하는 AABB 객체와 실제 Entity가 연결되어 있어야 한다는 점이었습니다.

EntityManager에서 Entity를 찾기 위한 entity id는 entity마다 고유한 값 (entt 라이브러리에서는 entt::entity 자체가 고유한 id 값입니다)을 지정해주면 됩니다만, broadphase 이후에 충돌 가능성 있는 pair를 다시 entity로 변환해서 반환해줄 필요가 있었습니다.

 

그 외에 AABBTree를 map을 쓰는 방식으로 바꿨습니다.

AABB node 추가/삭제 또한 entity와 연동되어야 하다보니, tree 형식으로 구성하고 있으면 복잡해지더라구요.

그래서 node를 저장하는 map과, entity id와 map id를 매핑하는 map을 만들었습니다.

앞서 사용한 entity id가 map에서도 쓸 수 있다면 (hashable이라면) 굳이 매핑할 필요는 없겠지만,

저는 그런 경우가 아니라서요.

 

class DynamicAABBTree : public BroadPhase
{
    std::unordered_map<size_t, AABBNode*> nodes; //all nodes
    std::unordered_map<entt::entity, size_t> mapper; //entity to leaf nodes
    size_t root;
    
    ...
};

 

ColliderSystem에 관한 내용을 쓰려고 생각했는데, 이건 다음 포스팅으로 미뤄야겠습니다.

얘는 새로 구현해서 넣은 게 있네요.

 

근데 이러고 곧 돌아올 수 있을지 모르겠습니다.

슬슬 또 음악해야 됨;;