오늘은 뜬금없이 Node Tree 구조에 대해 알아보도록 하겠습니다.
순서가 엉망인거 같은데... 뭐 제 맘대로 연재하는거니까 그냥 마음대로 주제를 정하겠습니다.


일단 밑의 그림을 한번 보시죠~


위의 구조는 현재 제가 참여하고 있는 K게임의 SceneNode 구조입니다.
최상위에는 항상 Root Node가 있고, 그 자식들도 Node들이 속해 있게 됩니다. 그리고 그 자식에 또 다른 Node가 속해지구요.

그러면 이런식으로 하면 어떻게 동작 하는가?
앞서 Scene 구조를 잠깐 봤는데요. 그것을 적용해보시면 됩니다. 일단 Root에 Scene이 Child로 붙습니다. 그리고 그 Scene에 Stage가 붙구요. 그 Stage에 Character와 MapObject가 붙게 됩니다.

그러면 이제 Root에서 OnFrame이 돌게 되면 Child의 OnFrame 함수를 순차적으로 돌게 되는데요. 일단 Scene의 OnFrame 함수가 호출되면서 Scene에서 처리해야할 작업을 하고 Scene의 Child인 Stage의 OnFrame이 불리겠지요? 그 다음 Stage에서 처리 해야 할 작업. Character의 충돌 체크라든가 Stage의 트리거 작동이라든가 처리가 되겠지요. 그 다음 Stage의 Child인 Character와 MapObject의 OnFrame 함수가 불리면서 Character의 이동이라든가 공격 처리 작업등이 수행 될 것입니다.

이런식으로 게임이 돌아가게 되는 겁니다.
참 쉽죠~?


거기 졸지 마세요~~~


일단 몇가지 소스를 살펴 봅시다요~
// 자식 노드를 추가
CNode* AddChild(CNode* pNode)
{
                if(pNode)
                {
                        m_childrenLIST.insert( std::make_pair( pNode->GetName(), pNode ) );
                        return pNode;
                }
    return NULL;
}

// 자식 노드를 삭제
void RemoveChild(const std::string& strName, bool bRelease)
{
                CNode* pNode = NULL;
                CHILDRENLIST_ITERATOR it = m_childrenLIST.find( strName );
                if( it != m_childrenLIST.end() )
                {
                                pNode = it->second;
                                pNode->SetParent(NULL);

                                m_childrenLIST.erase(it);

                                if(bRelease)
                                {
                                                pNode->RemoveAllChild(true);
                                                SafeRelease(pNode);
                                }
                }
}

Child Node를 추가하는 함수와 제거 하는 함수입니다.
직관적인 만큼 이해하기 어렵지 않을 겁니다.

여기서 한 가지 생각해볼 문제는 Child 관리를 무엇으로 하냐입니다.


위의 예제에서는 hash_map을 사용해봤습니다. 이유는 속도와 Child Node를 얻어올 때의 속도 비용때문입니다.

예전에는 List를 사용해봤었는데, List의 문제점은 문자열로 Child를 구하려고할 때, 문자열 비교 속도와 만약 원하는 Child가 List의 뒤쪽에 있다면, 그만큼 Child를 얻어오는 속도가 느려지기 때문입니다. 그렇게 되면 그만큼 Frame Rate가 떨어지겠죠. 그에 반해 hash_map은 리스트 관리를 이진 트리로 관리하기때문에 해당 Child를 얻어오는 것이 빠르고, 순차 탐색도 빠른 편이죠.

하지만 hash_map의 문제점도 있습니다. 바로 Child List의 순서죠. hash_map에 Child를 추가하게 되면, 순서가 어디로 튈지 모른다는게 가장 큰 문제입니다. 만약 OnFrame을 돌아야 할 순서가 정해져 있어야 한다면, hash_map은 문제가 될수가 있죠. 일단, 저는 두 개의 컨테이너를 모두 사용하는 방법을 택했습니다만, 여러분들 중에 좋은 아이디어가 있으시다면 덧글 좀 달아주시기 바랍니다.


다음은 OnFrame 함수를 한번 보죠.
// 프레임 처리기
void OnFrameListener(float fElapsedTime)
{
    OnFrame(fElapsedTime);

    CHILDRENLIST_ITERATOR IterNode = m_childrenLIST.begin();
    while(IterNode != m_childrenLIST.end())
    { 
        CNode* pNode = (CNode*)IterNode->second;
        pNode->OnFrameListener(fElapsedTime);
        ++IterNode;
    }
}

Root에서 OnFrameListener함수가 호출 되면, 그 밑의 Child Node들의 OnFrameListener 함수들이 연속적으로 호출 하게 되죠. 안에 있는 OnFrame 함수는 가상 함수입니다. CNode 함수에서는 그저 빈껍데기 일분이죠. 이 CNode를 상속 받아 작성 될 클래스, 예로 Character 클래스라 치고, OnFrame 함수 안에 처리 해야 할 작업들을 정의하면, 매 프레임 마다 OnFrame 함수 호출을 통해 작업을 처리 할 수 있겠죠.

+ Recent posts