이 구성 요소 아키텍처를 올바르게 사용하고 있습니까?


9

최근에는 딥 클래스 계층을 제거하고 구성 가능한 구성 요소로 대체하기 위해 게임 아키텍처를 개선하기로 결정했습니다. 내가 교체하는 첫 번째 계층은 항목 계층이며 올바른 길을 가고 있는지 알고 싶습니다.

이전에는 다음과 같은 계층 구조가있었습니다.

Item -> Equipment -> Weapon
                  -> Armor
                  -> Accessory
     -> SyntehsisItem
     -> BattleUseItem -> HealingItem
                      -> ThrowingItem -> ThrowsAsAttackItem

말할 것도없이 지저분 해지기 시작했고 여러 유형이 필요한 아이템에 대한 쉬운 해결책이 아니 었습니다 (즉, 일부 장비는 아이템 합성에 사용되고 일부 장비는 던질 수 있습니다).

그런 다음 리팩토링하고 기능을 기본 항목 클래스에 배치하려고했습니다. 그러나 나는 그 아이템에 사용되지 않은 / 불필요한 데이터가 많았다는 것에 주목했다. 이제 다른 게임 클래스에 시도하기 전에 적어도 항목에 대해 아키텍처와 같은 구성 요소를 만들려고합니다.

다음은 현재 구성 요소 설정에 대한 생각입니다.

다양한 구성 요소 (예 : 장비 구성 요소 슬롯, 치유 구성 요소 슬롯 등 및 임의 구성 요소에 대한 맵)에 대한 슬롯이있는 기본 항목 클래스가 있습니다.

class Item
{
    //Basic item properties (name, ID, etc.) excluded
    EquipmentComponent* equipmentComponent;
    HealingComponent* healingComponent;
    SynthesisComponent* synthesisComponent;
    ThrowComponent* throwComponent;
    boost::unordered_map<std::string, std::pair<bool, ItemComponent*> > AdditionalComponents;
} 

모든 항목 구성 요소는 기본 ItemComponent 클래스에서 상속되며 각 구성 요소 유형은 엔진에 해당 기능을 구현하는 방법을 알려줍니다. 즉, HealingComponent는 배틀 메카닉에게 아이템을 힐링 아이템으로 소비하는 방법을 알려주는 반면, ThrowComponent는 배틀 엔진에게 아이템을 던질 수있는 아이템으로 취급하는 방법을 알려줍니다.

맵은 핵심 항목 구성 요소가 아닌 임의의 구성 요소를 저장하는 데 사용됩니다. 항목 컨테이너를 ItemComponent를 관리해야하는지 또는 외부 소스에서 관리하고 있는지 표시하기 위해 부울과 페어링하고 있습니다.

내 생각은 내 게임 엔진에서 사용하는 핵심 구성 요소를 미리 정의하고 항목 팩토리가 항목에 실제로있는 구성 요소를 할당한다는 것입니다. 그렇지 않으면 null입니다. 맵에는 일반적으로 스크립팅 파일에 의해 추가 / 소비되는 임의의 구성 요소가 포함됩니다.

내 질문은 이것이 좋은 디자인입니까? 그렇지 않은 경우 어떻게 개선 할 수 있습니까? 모든 구성 요소를지도로 그룹화하는 것을 고려했지만 핵심 항목 구성 요소에는 문자열 인덱싱을 사용하는 것이 불필요 해 보였습니다

답변:


8

매우 합리적인 첫 단계 인 것 같습니다.

일반성 ( "추가 구성 요소"맵)과 조회 성능 (하드 코딩 된 멤버)의 조합을 선택하고 있습니다. 이는 약간 사전 최적화 된 것일 수 있습니다. 문자열 기반의 비 효율성에 관한 요점 조회는 잘 이루어 지지만 해시하기에 더 빠른 방법으로 구성 요소를 색인화하도록 선택하여이를 완화 할 수 있습니다. 한 가지 방법은 각 구성 요소 유형에 고유 한 유형 ID ( 실제로 사용자 정의 RTTI를 구현 하고 있음)를 제공하고이를 기반으로 색인을 작성하는 것입니다.

어쨌든 모든 구성 요소 (하드 코딩 된 구성 요소 및 추가 구성 요소)를 균일 한 방식으로 요청할 수있는 Item 객체에 대한 공개 API를 공개해야합니다. 이렇게하면 모든 항목 구성 요소 클라이언트를 리팩터링하지 않고도 하드 코딩 된 / 하드 코딩되지 않은 구성 요소의 기본 표현 또는 균형을 쉽게 변경할 수 있습니다.

또한 하드 코딩 된 각 구성 요소의 "더미"버전이없는 버전을 제공하고 항상 할당되어 있는지 확인하십시오. 그런 다음 포인터 대신 참조 멤버를 사용할 수 있으며 NULL 포인터를 확인할 필요가 없습니다. 하드 코딩 된 컴포넌트 클래스 중 하나와 상호 작용하기 전에 구성 요소의 멤버와 상호 작용하기 위해 동적 디스패치 비용이 여전히 발생하지만 포인터 멤버에서도 발생합니다. 성능에 미치는 영향은 거의 무시할 수 있기 때문에 이것은 코드 청결 문제입니다.

두 가지 종류의 수명 범위를 갖는 것이 좋은 생각은 아닙니다 (즉, 추가 구성 요소 맵에있는 부울이 좋은 생각이라고 생각하지 않습니다). 그것은 시스템을 복잡하게 만들고 파괴와 자원 공개가 끔찍하게 결정적이지 않다는 것을 암시합니다. 하나의 수명 관리 전략 또는 다른 하나를 선택하면 구성 요소에 대한 API가 훨씬 명확 해집니다. 엔티티가 구성 요소 수명을 관리하거나 구성 요소가 수행하는 하위 시스템을 선호합니다. 다음에 논의 할 접근법).

내가 당신의 접근 방식에서 볼 수있는 큰 단점은 "엔티티"객체에 모든 구성 요소를 함께 모으고 있다는 것입니다. 실제로 항상 최고의 디자인은 아닙니다. 에서 내 관련 답변을 다른 구성 요소 기반의 질문 :

게임 오브젝트에서 큰 컴포넌트 맵과 update () 호출을 사용하는 접근 방식은 매우 차선 적입니다 (그리고 이러한 종류의 시스템을 처음 구축하는 사람들에게는 일반적인 함정). 업데이트하는 동안 캐시 일관성이 매우 떨어지며 동시성 및 대량의 데이터 일괄 처리 또는 동작의 SIMD 스타일 프로세스 경향을 한 번에 활용할 수 없습니다. 게임 개체가 구성 요소를 업데이트하지 않는 대신 디자인 자체를 담당하는 하위 시스템이 한 번에 모두 업데이트하는 디자인을 사용하는 것이 좋습니다.

구성 요소를 항목 엔터티에 저장하여 동일한 접근 방식을 취합니다 (다시 말해서 완전히 수용 가능한 첫 번째 단계). 성능에 대해 우려하는 구성 요소에 대한 대부분의 액세스는 해당 구성 요소를 업데이트하는 것이며 , 구성 요소가 캐시 일관성을 유지하는 구성 요소 구성에 대해보다 아웃 보드 접근 방식 을 사용하기로 선택한 경우 요구 사항을 가장 잘 이해하는 서브 시스템에 의한 효율적이고 (도메인에 대한) 데이터 구조를 통해 훨씬 더 병렬화 가능한 업데이트 성능을 달성 할 수 있습니다.

그러나 나는 이것을 미래의 방향으로 고려해야 할 것으로 만 지적합니다. 당신은 이것을 오버 엔지니어링하기 위해 배 밖으로 가고 싶지 않습니다. 지속적인 리팩토링을 통해 점진적으로 전환하거나 현재 구현이 귀하의 요구를 완벽하게 충족시키고 반복 할 필요가 없음을 발견 할 수 있습니다.


1
Item 객체 제거를 제안하는 +1 더 선행 작업이기는하지만 더 나은 구성 요소 시스템을 생산하게됩니다.
James

몇 가지 추가 질문이 있었으므로 새로운 topiuc을 시작 해야할지 확신 할 수 없으므로 먼저 아래에서 시도하십시오. 항목 클래스의 경우 evert 프레임 (또는 가까운)을 호출 할 방법이 없습니다. 내 그래픽 하위 시스템의 경우 조언을 받아 모든 객체가 시스템에서 업데이트되도록 유지합니다. 내가 가진 또 다른 질문은 구성 요소 검사를 어떻게 처리합니까? 예를 들어, 항목을 X로 사용할 수 있는지 확인하고 싶습니다. 따라서 자연스럽게 항목에 X를 수행하는 데 필요한 구성 요소가 있는지 확인하고 싶습니다. 이것이 올바른 방법입니까? 답변 주셔서 다시 한 번 감사드립니다
user127817
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.