엔터티 시스템에 대해 끊임없이 칭찬받는 두 가지 주요 이점은 1) 복잡한 상속 계층 구조와 얽 히지 않아도되므로 새로운 종류의 엔터티를 쉽게 구성 할 수 있으며 2) 캐시 효율성입니다.
(1)은 ES / ECS뿐만 아니라 구성 요소 기반 설계 의 이점입니다 . "시스템"부분이없는 여러 가지 방법으로 구성 요소를 사용할 수 있으며 제대로 작동합니다 (인디 및 AAA 게임 모두 그러한 아키텍처를 사용합니다).
표준 Unity 오브젝트 모델 (사용 GameObject
및 MonoBehaviour
오브젝트)은 ECS가 아니라 컴포넌트 기반 설계입니다. 최신 Unity ECS 기능은 물론 실제 ECS입니다.
시스템은 둘 이상의 구성 요소를 사용할 수 있어야합니다. 즉 렌더링 및 물리 시스템 모두 변환 구성 요소에 액세스해야합니다.
일부 ECS는 구성 요소 컨테이너를 엔티티 ID별로 정렬합니다. 즉, 각 그룹의 해당 구성 요소는 동일한 순서를 갖습니다.
당신이 선형 적 요소 그래픽을 통해 반복하는 경우 당신이 있다는 것을이 수단 또한 선형 적으로 대응하는 변환 구성 요소를 통해 반복. 렌더링하지 않는 물리 트리거 볼륨을 가질 수 있기 때문에 일부 변환을 건너 뛸 수 있지만 항상 메모리에서 건너 뛰기 때문에 (특히 일반적으로 큰 거리는 아니지만) 여전히 진행 중입니다. 효율성 향상.
이는 HPC에 권장되는 접근 방식 (SOA)을 사용하는 방법과 유사합니다. CPU와 캐시는 단일 선형 배열을 처리 할 수있을뿐만 아니라 여러 선형 배열을 처리 할 수 있으며 임의 메모리 액세스를 처리 할 수있는 것보다 훨씬 우수합니다.
Unity ECS를 포함하여 일부 ECS 구현에 사용되는 또 다른 전략은 해당 엔티티의 아키타 입을 기반으로 컴포넌트를 할당하는 것입니다. 즉, 구성 요소를 정확하게 설정 모든 엔티티 ( PhysicsBody
, Transform
) 다른 구성 요소와 엔티티 별도로 할당됩니다 (예를 들어 PhysicsBody
, Transform
, 및 Renderable
).
이러한 설계의 시스템은 먼저 요구 사항과 일치하는 모든 아키 타입 (필요한 컴포넌트 세트가 있음)을 찾고 해당 아키 타입 목록을 반복하며 각 일치하는 아키 타입에 저장된 컴포넌트를 반복하여 작동합니다. 이를 통해 아키 타입 내에서 완전히 선형적이고 진정한 O (1) 컴포넌트 액세스가 가능하며 시스템은 잠재적으로 수십만 엔터티를 검색하는 대신 작은 아키 타입 목록을 검색하여 매우 낮은 오버 헤드로 호환 가능한 엔터티를 찾을 수 있습니다.
컴포넌트가 다른 컴포넌트에 대한 포인터를 저장하거나 컴포넌트에 대한 포인터를 저장하는 엔티티에 대한 포인터를 저장할 수 있습니다.
동일한 엔터티에서 다른 구성 요소를 참조하는 구성 요소는 아무것도 저장할 필요가 없습니다. 다른 엔티티에서 컴포넌트를 참조하려면 엔티티 ID를 저장하십시오.
단일 엔터티에 대해 구성 요소가 두 번 이상 존재할 수 있고 특정 인스턴스를 참조해야하는 경우 다른 엔터티의 ID와 해당 엔터티의 구성 요소 인덱스를 저장하십시오. 그러나 많은 ECS 구현에서는 이러한 경우를 허용하지 않습니다. 특히 이러한 작업의 효율성이 떨어지기 때문입니다.
모든 구성 요소 배열이 'n'이 큰지 확인할 수 있습니다. 여기서 'n'은 시스템에 존재하는 엔티티 수입니다.
포인터가 아닌 핸들 (예 : 인덱스 + 생성 마커)을 사용하면 객체 참조가 손상 될 염려없이 배열의 크기를 조정할 수 있습니다.
std::deque
어떤 이유로 포인터를 허용하거나 문제를 측정 한 경우 많은 일반적인 구현 과 비슷한 "청크 배열"접근법 (배열 배열) 을 사용할 수 있습니다 어레이 크기 조정 성능.
둘째, 이것은 모든 엔티티가 프레임 / 틱마다 목록에서 선형으로 처리된다고 가정하지만 실제로는 그렇지 않습니다.
엔티티에 따라 다릅니다. 예, 많은 유스 케이스의 경우 사실이 아닙니다. 실제로 이것이 컴포넌트 기반 설계 (양호)와 엔티티 시스템 (특정 형태의 CBD) 의 차이를 강조하는 이유 입니다.
일부 구성 요소는 확실히 선형으로 처리하기 쉽습니다. 일반적으로 "트리 헤비 (tree heavy)"사용 사례에서도 엄격하게 패킹 된 어레이 (일반적으로 게임의 AI 에이전트와 같이 N이 최대 수백 개의 N을 포함하는 경우)를 사용하여 성능이 확실히 향상되었습니다.
일부 개발자는 또한 데이터 지향 선형 할당 데이터 구조를 사용하는 경우의 성능 이점이 "스마트 한"트리 기반 구조를 사용하는 경우의 성능 이점보다 높다는 것을 알게되었습니다. 물론 게임과 특정 사용 사례에 따라 다릅니다.
섹터 / 포털 렌더러 또는 octree를 사용하여 오 클루 전 컬링을 수행한다고 가정합니다. 섹터 / 노드 내에 엔터티를 연속적으로 저장할 수는 있지만 원하는지 여부에 관계없이 점프 할 것입니다.
배열이 여전히 얼마나 도움이되는지 놀랄 것입니다. "어디서나"보다 훨씬 작은 메모리 영역에서 뛰어 다니고 있으며 모든 점프에서도 캐시에 무언가가 생길 가능성이 훨씬 큽니다. 특정 크기 이하의 트리를 사용하면 전체 항목을 캐시로 미리 가져올 수 있으며 해당 트리에서 캐시 누락이 발생하지 않을 수도 있습니다.
단단히 포장 된 배열에 살도록 구축 된 트리 구조도 있습니다. 예를 들어, octree를 사용하면 힙 같은 구조 (자녀 앞에서 부모, 서로 형제)를 사용하여 나무를 "드릴 다운"할 때도 항상 배열에서 반복되는 것을 보장 할 수 있습니다. CPU는 메모리 액세스 / 캐시 조회를 최적화합니다.
중요한 점입니다. x86 CPU는 복잡한 짐승입니다. CPU는 머신 코드에서 마이크로 코드 옵티 마이저를 효과적으로 실행하여 작은 마이크로 코드 및 명령 순서 변경, 메모리 액세스 패턴 예측 등을 수행합니다. CPU 또는 캐시 작동 방식
그런 다음 다른 순서로 저장된 엔티티를 선호하는 다른 시스템이 있습니다.
여러 번 저장할 수 있습니다. 배열을 최소한의 세부 사항까지 제거하면이 방법으로 실제로 메모리를 절약 할 수 있습니다 (64 비트 포인터를 제거하고 더 작은 인덱스를 사용할 수 있기 때문에).
별도의 배열을 유지하는 대신 엔티티 배열을 인터리브 할 수는 있지만 여전히 메모리를 낭비하고 있습니다.
이것은 좋은 캐시 사용과는 반대입니다. 변환 및 그래픽 데이터 만 신경 쓰는 경우 물리 및 AI 및 입력 및 디버그 등에 대한 다른 모든 데이터를 머신이 가져 오는 데 시간을 허비하는 이유는 무엇입니까?
그것은 일반적으로 ECS vs 모 놀리 식 게임 객체를 선호하는 요점입니다 (다른 구성 요소 기반 아키텍처와 비교할 때 실제로 적용 할 수는 없지만).
가치가있는 것은 인터리브 스토리지를 사용하는 것으로 알고있는 대부분의 "프로덕션 급"ECS 구현입니다. 앞에서 언급 한 (예 : Unity ECS에서 사용 된) 널리 사용되는 아키 타입 접근 방식은 아키 타입과 관련된 컴포넌트에 인터리브 된 스토리지를 사용하도록 매우 명시 적으로 구축되었습니다.
AI가 엔티티 렌더링에 사용 된 변환 또는 애니메이션 상태에 영향을 줄 수 없다면 AI는 의미가 없습니다.
AI가 변환 데이터에 선형으로 효율적으로 액세스 할 수 없다고해서 다른 시스템에서 해당 데이터 레이아웃 최적화를 효과적으로 사용할 수있는 것은 아닙니다. 게임 로직 시스템이 일반적으로 수행하는 임시 방식의 게임 로직 시스템을 중단하지 않고도 데이터 변환에 팩형 배열을 사용할 수 있습니다.
또한 코드 캐시를 잊어 버렸습니다 . 순진한 구성 요소 아키텍처와 달리 ECS의 시스템 접근 방식을 사용하면 동일한 작은 코드 루프를 실행하고 가상 함수 테이블을 통해 무작위로 Update
분산 된 임의의 함수로 앞뒤로 건너 뛰지 않습니다. 이진. 따라서 AI의 경우, 모든 다른 AI 구성 요소를 (물론 동작을 구성 할 수 있도록 둘 이상의 구성 요소가 있기 때문에) 실제로 별도의 버킷에 유지하고 최상의 코드 캐시 사용을 얻기 위해 각 목록을 개별적으로 처리하려고합니다.
지연된 이벤트 큐 (시스템이 이벤트 목록을 생성하지만 시스템이 모든 엔티티 처리를 완료 할 때까지 전달하지 않는 경우)를 사용하면 이벤트를 유지하면서 코드 캐시가 잘 사용되도록 할 수 있습니다.
각 시스템이 프레임에 대해 읽을 이벤트 큐를 알고있는 접근 방식을 사용하면 이벤트를 빠르게 읽을 수도 있습니다. 또는 적어도없는 것보다 빠릅니다.
성능은 절대가 아닙니다. 우수한 데이터 지향 설계의 성능 이점을보기 위해 마지막 단일 캐시 누락을 모두 제거 할 필요는 없습니다.
ECS 아키텍처 및 데이터 지향 디자인 패턴으로 많은 게임 시스템이 더 잘 작동하도록하는 연구가 여전히 진행 중입니다. 최근 몇 년 동안 SIMD로 수행 한 놀라운 일들 (예 : JSON 파서)과 마찬가지로, ECS 아키텍처를 사용하여 고전적인 게임 아키텍처에는 직관적이지 않지만 여러 가지를 제공하는 점점 더 많은 일들을보고 있습니다 이점 (속도, 멀티 스레딩, 테스트 가능성 등).
또는 모두가 사용하고 있지만 아무도 이야기하지 않는 하이브리드 접근법이있을 수 있습니다.
이것은 특히 ECS 아키텍처에 회의적 인 사람들을 위해 과거에 옹호 한 것입니다. 성능이 중요한 구성 요소에 대한 우수한 데이터 지향 접근 방식을 사용하십시오. 단순성이 개발 시간을 향상시키는 간단한 아키텍처를 사용하십시오. ECS가 제안한 것과 같이 모든 단일 구성 요소를 엄격한 구성 요소 정의로 엄격히 분류하지 마십시오. 이해하기 쉬운 ECS 유사 접근 방식을 쉽게 사용할 수 있고 ECS 유사 접근 방식이 이해가 안되는 단순한 구성 요소 구조 (또는 트리 구조보다 덜 이해되는 방식)를 사용하는 방식으로 컴포넌트 아키텍처를 개발하십시오. .
저는 개인적으로 ECS의 진정한 힘으로 비교적 최근에 개종했습니다. 저에게있어 결정 요소는 ECS에 대해 거의 언급되지 않았습니다. 과거에 작업했던 밀접하게 결합 된 로직 탑재 컴포넌트 기반 디자인에 비해 게임 시스템 및 로직에 대한 필기 테스트를 거의 사소하게 만듭니다. ECS 아키텍처는 구성 요소 만 소비하고 구성 요소 업데이트를 생성하는 모든 논리를 시스템에 배치하므로 시스템 동작을 테스트하기위한 "모의"구성 요소 집합을 작성하는 것은 매우 쉽습니다. 대부분의 게임 로직은 시스템 내부에만 존재하기 때문에 모든 시스템을 테스트하면 게임 로직의 코드 범위가 상당히 높아집니다. 시스템은 복잡성이나 성능에 미치는 영향이 훨씬 적은 테스트에 모의 종속성 (예 : GPU 인터페이스)을 사용할 수 있습니다.
옆으로, 많은 사람들이 ECS가 무엇인지 실제로 이해하지 못하고 ECS에 대해 이야기한다는 것을 알 수 있습니다. 나는 고전 Unity가 주파수를 낮추면서 ECS라고 불렀는데, 너무 많은 게임 개발자들이 "ECS"를 "Components"와 동일시하고 "Entity System"부분을 완전히 무시한다는 것을 보여줍니다. 많은 사람들이 실제로 실제 ECS가 아닌 구성 요소 기반 설계를 옹호 할 때 인터넷의 ECS에 많은 사랑이 쌓이는 것을 볼 수 있습니다. 이 시점에서 논쟁하는 것은 거의 의미가 없습니다. ECS는 원래의 의미에서 일반적인 용어로 손상되었으며 "ECS"가 "데이터 지향 ECS"와 같은 의미가 아님을 인정할 수도 있습니다. : /