C ++ Entity-Component-Systems의 구성 요소에 올바르게 액세스하려면 어떻게해야합니까?


18

(내가 설명하는 것은이 디자인을 기반으로합니다 : 엔티티 시스템 프레임 워크는 무엇입니까? , 아래로 스크롤하면 찾을 수 있습니다)

C ++에서 엔터티 구성 요소 시스템을 만드는 데 문제가 있습니다. 컴포넌트 클래스가 있습니다 :

class Component { /* ... */ };

실제로 다른 구성 요소를 작성하기위한 인터페이스입니다. 따라서 커스텀 컴포넌트를 만들려면 인터페이스를 구현하고 게임 내에서 사용될 데이터를 추가하기 만하면됩니다.

class SampleComponent : public Component { int foo, float bar ... };

이러한 구성 요소는 Entity 클래스 안에 저장되어 Entity의 각 인스턴스에 고유 한 ID를 제공합니다.

class Entity {
     int ID;
     std::unordered_map<string, Component*> components;
     string getName();
     /* ... */
};

구성 요소 이름을 해시하여 구성 요소를 엔터티에 추가합니다 (아마도 좋은 생각은 아닙니다). 사용자 컴포넌트를 추가하면 컴포넌트 유형 (기본 클래스)으로 저장됩니다.

반면에, 내부에 노드 인터페이스를 사용하는 시스템 인터페이스가 있습니다. Node 클래스는 단일 엔터티의 일부 구성 요소를 저장하는 데 사용됩니다 (시스템이 모든 엔터티의 구성 요소 사용에 관심이 없기 때문에). 시스템이 update()해야 할 경우, 다른 엔티티에서 생성 한 저장된 노드를 통해서만 반복하면됩니다. 그래서:

/* System and Node implementations: (not the interfaces!) */

class SampleSystem : public System {
        std::list<SampleNode> nodes; //uses SampleNode, not Node
        void update();
        /* ... */
};

class SampleNode : public Node {
        /* Here I define which components SampleNode (and SampleSystem) "needs" */
        SampleComponent* sc;
        PhysicsComponent* pc;
        /* ... more components could go here */
};

이제 문제 : 엔티티를 SampleSystem에 전달하여 SampleNode를 빌드한다고 가정 해 봅시다. 그런 다음 SampleNode는 엔티티에 SampleSystem에서 사용할 필수 구성 요소가 있는지 "확인"합니다. Entity 내부의 원하는 구성 요소에 액세스해야 할 때 문제가 나타납니다. 구성 요소가 Component(기본 클래스) 컬렉션에 저장되어 있으므로 구성 요소에 액세스하여 새 노드에 복사 할 수 없습니다. Component파생 형식으로 다운 캐스팅하여 문제를 일시적으로 해결 했지만 더 좋은 방법이 있는지 알고 싶었습니다. 이것이 내가 이미 가지고있는 것을 다시 디자인하는 것을 의미하는지 이해합니다. 감사.

답변:


23

당신이 저장되어야하기 위하여려고하는 경우에 Component모두 함께 컬렉션들 다음 컬렉션에 저장되어있는 유형으로 공통 기본 클래스를 사용해야하며, 사용자가 액세스하려고하면 따라서 올바른 형식으로 캐스팅해야한다 Component컬렉션에들. typeid그러나 템플릿과 함수 를 영리하게 사용하면 잘못된 파생 클래스로 캐스팅하려는 문제를 해결할 수 있습니다 .

이렇게 선언 된지도 :

std::unordered_map<const std::type_info* , Component *> components;

다음과 같은 addComponent 함수

components[&typeid(*component)] = component;

그리고 getComponent :

template <typename T>
T* getComponent()
{
    if(components.count(&typeid(T)) != 0)
    {
        return static_cast<T*>(components[&typeid(T)]);
    }
    else 
    {
        return NullComponent;
    }
}

당신은 잘못 전송되지 않습니다. typeid구성 요소의 런타임 유형 (가장 파생 된 유형)의 유형 정보에 대한 포인터를 리턴 하기 때문 입니다. 구성 요소는 키로 해당 유형 정보와 함께 저장되므로 유형이 일치하지 않아 캐스트로 인해 문제가 발생할 수 없습니다. 또한 Component에서 파생 된 형식이거나 static_cast<T*>형식이 일치하지 않으므로 템플릿 형식에서 컴파일 시간 형식 검사를 수행 합니다 unordered_map.

그러나 다른 유형의 구성 요소를 공통 콜렉션에 저장할 필요는 없습니다. Entity포함 하는 아이디어를 버리고 Component대신 각 Component저장소에 Entity(실제로 정수 ID 일 것입니다) 각 저장소를 갖는 경우 각 파생 구성 요소 유형을 원래 형식 대신 자체 파생 형식 컬렉션에 저장할 수 있습니다 공통 기본 유형을 찾고 해당 ID Component를 "포함"하는 것을 찾습니다 Entity.

이 두 번째 구현은 첫 번째보다 생각하기에 다소 직관적이지 않지만 인터페이스 뒤에 구현 세부 정보로 숨겨져 시스템 사용자가 신경 쓸 필요가 없습니다. 나는 실제로 두 번째를 사용하지 않았으므로 어느 것이 더 낫다고 언급하지는 않지만 static_cast를 첫 번째 구현이 제공하는 유형에 대한 강력한 보증 문제로 보지 않습니다. 플랫폼 및 / 또는 철학적 신념에 따라 문제가 될 수도 있고 아닐 수도있는 RTTI가 필요합니다.


3
저는 거의 6 년 동안 C ++을 사용해 왔지만 매주 새로운 요령을 배웁니다.
knight666

대답 해줘서 고마워. 첫 번째 방법을 먼저 사용 해보고 나중에 나중에 다른 방법을 사용하는 방법을 생각할 것입니다. 그러나 addComponent()메소드가 템플릿 메소드 일 필요는 없습니까? 를 정의하면 addComponent(Component* c)추가 한 하위 구성 요소가 Component포인터에 저장되고 typeid항상 Component기본 클래스를 참조합니다 .
Federico

2
Typeid는 포인터가 기본 클래스 인 경우에도 가리키는 객체의 실제 유형을 제공합니다
Chewy Gumball

나는 chewy의 답변을 정말로 좋아했기 때문에 mingw32에서 구현을 시도했습니다. typeid가 구성 요소를 모든 유형으로 반환하기 때문에 addComponent ()가 모든 것을 구성 요소로 저장하는 fede rico가 언급 한 문제에 부딪 쳤습니다. 여기 누군가 포인터가 기본 클래스를 가리키는 경우에도 typeid가 가리키는 객체의 실제 유형을 제공해야한다고 언급했지만 컴파일러 등에 따라 다를 수 있다고 생각합니다. 다른 사람이 이것을 확인할 수 있습니까? Windows 7에서 g ++ std = c ++ 11 mingw32를 사용하고있었습니다. 템플릿으로 getComponent ()를 수정 한 다음 그 형식을 th로 저장했습니다.
shwoseph

이것은 컴파일러마다 다릅니다. 아마도 typeid 함수에 대한 인수로 올바른 표현이 없었을 것입니다.
질긴 검볼

17

Chewy가 맞지만 C ++ 11을 사용하는 경우 사용할 수있는 새로운 유형이 있습니다.

const std::type_info*지도에서 키로 사용하는 대신 std::type_index( cppreference.com 참조 )를 사용할 수 있습니다 std::type_info. 왜 사용하겠습니까? 는 std::type_index사실과의 관계 저장 std::type_info포인터 등을,하지만 걱정에 당신을 위해 하나의 포인터 덜합니다.

실제로 C ++ 11을 사용하는 경우 Component스마트 포인터 안에 참조를 저장하는 것이 좋습니다 . 따라서지도는 다음과 같습니다.

std::map<std::type_index, std::shared_ptr<Component> > components

다음과 같이 새 항목을 추가 할 수 있습니다.

components[std::type_index(typeid(*component))] = component

여기서 componenttype은입니다 std::shared_ptr<Component>. 지정된 유형에 대한 참조를 검색하는 방법 Component은 다음과 같습니다.

template <typename T>
std::shared_ptr<T> getComponent()
{
    std::type_index index(typeid(T));
    if(components.count(std::type_index(typeid(T)) != 0)
    {
        return static_pointer_cast<T>(components[index]);
    }
    else
    {
        return NullComponent
    }
}

static_pointer_cast대신 사용하십시오 static_cast.


1
나는 실제로 내 자신의 프로젝트에서 이런 종류의 접근 방식을 사용하고 있습니다.
vijoc

C ++ 11 표준을 참조로 사용하여 C ++을 배우고 있기 때문에 이것은 실제로 매우 편리합니다. 내가 알아 차린 한 가지는 웹에서 찾은 모든 엔티티 구성 요소 시스템이 일종의을 사용한다는 것입니다 cast. 캐스트없이 이와 비슷한 시스템 디자인을 구현하는 것이 불가능할 것이라고 생각하기 시작했습니다.
Federico

@Fede Component단일 컨테이너에 포인터를 저장 하려면 반드시 파생 된 유형으로 캐스팅해야합니다. 그러나 Chewy가 지적했듯이 캐스팅이 필요없는 다른 옵션이 있습니다. 나는 상대적으로 안전하기 때문에 디자인에 이러한 유형의 캐스트를 갖는 데 "나쁜"것은 없다고 생각합니다.
vijoc 2016 년

@vijoc 그들은 종종 그들이 도입 할 수있는 메모리 일관성 문제로 인해 나쁜 것으로 간주된다.
akaltar
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.