OOP ECS 및 순수 ECS


11

먼저,이 질문이 게임 개발 주제와 관련이 있다는 것을 알고 있지만 더 일반적인 소프트웨어 생성 문제로 인해 여기에서 질문하기로 결정했습니다.

지난 한 달 동안 Entity-Component-Systems에 대해 많이 읽었으며 이제는 개념에 매우 익숙합니다. 그러나 명확한 '정의'가 누락 된 것으로 보이는 한 가지 측면이 있으며 다른 기사에서 근본적으로 다른 솔루션을 제안했습니다.

이것은 ECS가 캡슐화를 중단해야하는지의 문제입니다. 다시 말해, OOP 스타일 ECS (구성 요소는 특정 데이터를 캡슐화하는 상태와 동작이 모두 포함 된 개체) 대 순수 ECS (구성 요소는 공용 데이터와 시스템 만 기능을 제공하는 c 스타일 구조체입니다)입니다.

Framework / API / Engine을 개발 중입니다. 따라서 목표는 사용하는 사람이 쉽게 확장 할 수 있다는 것입니다. 여기에는 새로운 유형의 렌더링 또는 충돌 구성 요소 추가와 같은 항목이 포함됩니다.

OOP 접근 방식의 문제점

  • 컴포넌트는 다른 컴포넌트의 데이터에 액세스해야합니다. 예를 들어 렌더 구성 요소의 draw 메서드는 변환 구성 요소의 위치에 액세스해야합니다. 이것은 코드에 의존성을 만듭니다.

  • 구성 요소는 다형성 일 수 있으며, 이는 약간의 복잡성을 추가로 야기시킨다. 예를 들어 렌더링 구성 요소의 가상 그리기 방법을 재정의하는 스프라이트 렌더링 구성 요소가있을 수 있습니다.

순수한 접근 방식의 문제

  • 다형성 거동 (예를 들어, 렌더링을 위해)이 어딘가에 구현되어야하므로 시스템으로 아웃소싱됩니다. (예 : 스프라이트 렌더 시스템은 렌더 노드를 상속하고 렌더 엔진에 추가하는 스프라이트 렌더 노드를 만듭니다)

  • 시스템 간의 통신을 피하기 어려울 수 있습니다. 예를 들어 충돌 시스템에는 콘크리트 렌더 구성 요소가있는 곳에서 계산되는 경계 상자가 필요할 수 있습니다. 이를 통해 데이터를 통해 통신 할 수 있습니다. 그러나 렌더 시스템이 경계 상자 구성 요소를 업데이트하고 충돌 시스템이이를 사용하기 때문에 즉석 업데이트가 제거됩니다. 시스템의 업데이트 기능을 호출하는 순서가 정의되어 있지 않으면 사전 문제가 발생할 수 있습니다. 시스템이 다른 시스템이 핸들러를 등록 할 수있는 이벤트를 발생시킬 수있는 이벤트 시스템이 있습니다. 그러나 이것은 시스템에게 void 기능을 수행 할 것을 지시하는 경우에만 작동합니다.

  • 추가 플래그가 필요합니다. 예를 들어 타일 맵 구성 요소를 생각해보십시오. 크기, 타일 크기 및 색인 목록 필드가 있습니다. 타일 ​​맵 시스템은 각 정점 배열을 처리하고 구성 요소의 데이터를 기반으로 텍스처 좌표를 할당합니다. 그러나 매 프레임마다 전체 타일 맵을 다시 계산하는 것은 비용이 많이 듭니다. 따라서 시스템에서 변경하기 위해 수행 된 모든 변경 사항을 추적하려면 목록이 필요합니다. OOP 방식으로 이것은 타일 맵 컴포넌트에 의해 캡슐화 될 수 있습니다. 예를 들어 SetTile () 메서드는 호출 될 때마다 꼭짓점 배열을 업데이트합니다.

순수한 접근 방식의 아름다움을 보았지만 더 전통적인 OOP에 비해 어떤 구체적인 이점이 있는지 이해하지 못합니다. 시스템에서 숨겨져 있지만 구성 요소 간의 종속성이 여전히 존재합니다. 또한 같은 목표를 달성하기 위해 더 많은 수업이 필요합니다. 이것은 결코 좋은 일이 아닌 다소 과도하게 설계된 솔루션처럼 보입니다.

또한 성능에 중점을 두지 않으므로 데이터 지향 디자인 및 현금 누락에 대한이 모든 아이디어는 실제로 중요하지 않습니다. 나는 단지 좋은 건축을 원한다 ^^

아직도, 내가 읽은 대부분의 기사와 토론은 두 번째 접근법을 제안합니다. 왜?

생기

마지막으로 순수한 ECS에서 애니메이션을 처리하는 방법에 대한 질문을하고 싶습니다. 현재 애니메이션을 0과 1 사이의 진행 상황을 기반으로 엔티티를 조작하는 펑터로 정의했습니다. 애니메이션 구성 요소에는 애니메이션 목록이있는 애니메이터 목록이 있습니다. 업데이트 기능에서 현재 엔티티에 활성화 된 모든 애니메이션을 적용합니다.

노트 :

방금이 게시물을 읽었습니다 . 엔터티 구성 요소 시스템 아키텍처 개체가 정의 중심입니까? 내가하는 것보다 문제를 조금 더 잘 설명합니다. 기본적으로 동일한 주제에 있지만 순수한 데이터 접근 방식이 더 나은 이유에 대해서는 아직 답변하지 않습니다 .


1
아마도 간단하지만 진지한 질문 일 것입니다 : ECS의 장점 / 단점을 알고 있습니까? 그것은 대부분 '왜'를 설명합니다.
Caramiriel

글쎄, 나는 다중 상속 요법을 통해 죽음의 다이아몬드를 피하기 위해 상속보다는 구성 요소, 즉 상속보다는 구성 요소를 사용하는 이점을 이해합니다. 컴포넌트를 사용하면 런타임에 동작을 조작 할 수 있습니다. 그리고 그들은 모듈 식입니다. 내가 이해하지 못하는 것은 왜 데이터와 기능을 나누고 싶어하는지입니다. 내 현재 구현은 github에 있습니다. github.com/AdrianKoch3010/MarsBaseProject
Adrian Koch

ECS에 대한 충분한 답변을 얻을 수있는 충분한 경험이 없습니다. 그러나 구성은 DoD를 피하기 위해 사용되는 것이 아닙니다. OO 접근 방식을 사용하여 생성하기 어려운 런타임에 (고유 한) 엔티티를 생성 할 수도 있습니다. 즉, 데이터 / 프로 시저를 분할하면 데이터를보다 쉽게 ​​추론 할 수 있습니다. 직렬화, 저장 상태, 실행 취소 / 다시 실행 등을 쉽게 구현할 수 있습니다. 데이터에 대해 추론하기 쉽기 때문에 최적화도 더 쉽습니다. 엔터티를 일괄 적으로 분할 (멀티 스레딩)하거나 다른 하드웨어로 오프로드하여 잠재력을 최대한 발휘할 수 있습니다.
Caramiriel

"렌더 구성 요소의 가상 그리기 방법을 재정의하는 스프라이트 렌더링 구성 요소가있을 수 있습니다." 나는 당신이 그것을 필요로 할 때 더 이상 ECS 가 아니라고 주장 합니다.
wondra

답변:


10

이것은 힘든 일입니다. 특정 경험 (YMMV)을 바탕으로 몇 가지 질문에 답하려고합니다.

컴포넌트는 다른 컴포넌트의 데이터에 액세스해야합니다. 예를 들어 렌더 구성 요소의 draw 메서드는 변환 구성 요소의 위치에 액세스해야합니다. 이것은 코드에 의존성을 만듭니다.

여기에서 커플 링 / 의존의 정도와 복잡성을 정도를 과소 평가하지 마십시오. 당신은 이것의 차이점을 볼 수 있습니다 (그리고이 다이어그램은 이미 장난감과 같은 수준으로 엄청나게 단순화되었으며 실제 예제에는 커플 링을 풀기위한 인터페이스가 있습니다).

여기에 이미지 설명을 입력하십시오

... 이:

여기에 이미지 설명을 입력하십시오

... 아니면 이거:

여기에 이미지 설명을 입력하십시오

구성 요소는 다형성 일 수 있으며, 이는 약간의 복잡성을 추가로 야기시킨다. 예를 들어 렌더링 구성 요소의 가상 그리기 방법을 재정의하는 스프라이트 렌더링 구성 요소가있을 수 있습니다.

그래서? vtable 및 virtual dispatch에 해당하는 유사 (또는 리터럴)는 기본 상태 / 데이터를 숨기는 객체 대신 시스템을 통해 호출 할 수 있습니다. 유사 vtable 또는 함수 포인터가 시스템을 호출하기 위해 일종의 "데이터"로 바뀌면 "순수한"ECS 구현으로 다형성이 여전히 실용적이고 실현 가능합니다.

다형성 거동 (예를 들어, 렌더링을 위해)이 어딘가에 구현되어야하므로 시스템으로 아웃소싱됩니다. (예 : 스프라이트 렌더 시스템은 렌더 노드를 상속하고 렌더 엔진에 추가하는 스프라이트 렌더 노드를 만듭니다)

그래서? 나는 이것이 빈정 거림으로 나오지 않기를 희망합니다 (내가 자주 비난을 받았지만 텍스트를 통해 감정을 더 잘 전달할 수 있기를 바랍니다). 그러나이 경우 다형성 행동을 "아웃소싱"한다고해서 반드시 추가가 발생하는 것은 아닙니다 생산성 비용.

시스템 간의 통신을 피하기 어려울 수 있습니다. 예를 들어 충돌 시스템에는 콘크리트 렌더 구성 요소가 무엇이든 계산 된 경계 상자가 필요할 수 있습니다.

이 예는 특히 이상하게 보입니다. 렌더러가 데이터를 씬으로 다시 출력하는 이유를 알지 못하거나 (이 컨텍스트에서는 일반적으로 렌더러를 읽기 전용으로 간주) 렌더러가 다른 시스템 대신 AABB를 파악하여 렌더러 및 충돌 / 물리학 (여기서는 "렌더링 컴포넌트"이름에 걸려있을 수 있습니다). 그러나 나는이 예제에 너무 매달리고 싶지는 않습니다. 여전히 시스템 간 통신 (다른 시스템에서 수행 한 변환에 직접 의존하는 시스템을 사용하여 중앙 ECS 데이터베이스에 대한 간접 읽기 / 쓰기 형식의 경우에도)은 자주 필요하지 않아도되어야합니다. 그'

시스템의 업데이트 기능을 호출하는 순서가 정의되어 있지 않으면 사전 문제가 발생할 수 있습니다.

이것은 절대적으로 정의되어야합니다. ECS는 코드베이스에서 가능한 모든 시스템의 시스템 처리 평가 순서를 재정렬하고 프레임과 FPS를 다루는 최종 사용자에게 정확히 동일한 종류의 결과를 다시 얻는 최종 솔루션이 아닙니다. 이것은 ECS를 설계 할 때 적어도 어느 정도 선행해야한다고 강하게 제안하는 것 중 하나입니다. 시스템 호출 / 평가).

그러나 매 프레임마다 전체 타일 맵을 다시 계산하는 것은 비용이 많이 듭니다. 따라서 시스템에서 변경하기 위해 수행 된 모든 변경 사항을 추적하려면 목록이 필요합니다. OOP 방식으로 이것은 타일 맵 컴포넌트에 의해 캡슐화 될 수 있습니다. 예를 들어 SetTile () 메서드는 호출 될 때마다 꼭짓점 배열을 업데이트합니다.

나는 이것이 데이터 지향적 인 관심사라는 것을 제외하고는 이것을 이해하지 못했습니다. 그리고 그러한 성능 함정을 피하기 위해 메모를 포함하여 ECS에 데이터를 표현하고 저장하는 데 따르는 함정은 없습니다 (ECS를 가진 가장 큰 것들은 ECS 최적화의 가장 어려운 측면). 논리와 데이터가 "순수한"ECS로 분리되었다고해서 OOP 표현에서 캐시 / 메모리화할 수있는 항목을 갑자기 재 계산해야한다는 의미는 아닙니다. 내가 매우 중요한 것을 고집하지 않는 한 그것은 똥 / 관련이없는 요점입니다.

"순수한"ECS를 사용하면이 데이터를 타일 맵 구성 요소에 계속 저장할 수 있습니다. 유일한 주요 차이점은이 정점 배열을 업데이트하는 논리가 시스템으로 이동한다는 것입니다.

ECS에 의존하여와 같은 별도의 구성 요소를 생성하는 경우 엔터티에서이 캐시의 무효화 및 제거를 단순화 할 수도 있습니다 TileMapCache. 캐시가 필요하지만 TileMap구성 요소 가있는 엔티티에서 사용할 수없는 시점에서 캐시 를 계산하고 추가 할 수 있습니다. 무효화되거나 더 이상 필요하지 않은 경우 무효화 및 제거를 위해 특별히 더 많은 코드를 작성하지 않고도 ECS를 통해 제거 할 수 있습니다.

시스템에 숨겨져 있지만 구성 요소 간의 종속성이 여전히 존재합니다.

"순수한"담당자의 구성 요소간에 종속성이 없습니다 (시스템에 의해 종속성이 숨겨져 있다고 말하는 것이 옳지 않다고 생각합니다). 말하자면 데이터는 데이터에 의존하지 않습니다. 논리는 논리에 의존합니다. "순수한"ECS는 시스템이 작동하는 데 필요한 데이터 및 로직의 최소 최소 서브 세트에 의존하도록 로직을 작성하는 경향이 있습니다. 실제 작업에 필요한 것보다 훨씬 많은 기능. 순수한 ECS 권한을 사용하는 경우 가장 먼저 알아야 할 사항 중 하나는 디커플링 이점이며 동시에 OOP에서 평가 한 정보와 캡슐화 및 특히 정보 숨기기에 대해 알게 된 모든 것에 의문을 제기합니다.

디커플링이란 시스템이 작동하는 데 필요한 정보가 거의 없다는 것을 의미합니다. 모션 시스템은 Particle또는 보다 복잡한 것에 대해 알 Character필요조차 없습니다 (시스템 개발자는 시스템에 존재하는 이러한 엔티티 아이디어를 알 필요조차 없습니다). 구조체에 몇 개의 부동 수만큼 단순 할 수있는 위치 구성 요소와 같은 최소 데이터에 대해 알아야합니다. 순수한 인터페이스 IMotion가 가지고 다니는 것보다 정보와 외부 의존성이 적습니다 . 각 시스템이 작동해야하는 최소한의 지식 덕분에 ECS는 종종 계단식 인터페이스 파손에 직면하지 않고 예상치 못한 예기치 않은 설계 변경을 처리 할 수 ​​있습니다.

"불완전한"접근 방식은 변경 사항이 계단식 파손을 일으키지 않는 시스템에만 로직이 현지화되지 않았기 때문에 이점이 다소 줄어들 것이라고 제안합니다. 로직은 이제 여러 시스템이 액세스하는 구성 요소에서 어느 정도 집중되어 있으며, 이제는이를 사용할 수있는 모든 다양한 시스템의 인터페이스 요구 사항을 충족해야합니다. 이제 모든 시스템에 대한 지식이 더 필요합니다. 정보가 해당 구성 요소와 함께 작동해야합니다.

데이터에 대한 의존성

ECS에 대해 논쟁의 여지가있는 것 중 하나는 추상 인터페이스에 대한 종속성을 원시 데이터로 대체하는 경향이 있으며 일반적으로 덜 바람직하고 더 단단한 형태의 커플 링으로 간주된다는 것입니다. 그러나 ECS가 매우 유리할 수있는 게임과 같은 종류의 영역에서는 시스템의 일부 중앙 레벨에서 해당 데이터로 수행 할 수있는 작업을 설계하는 것보다 데이터 표현을 미리 설계하고 안정적으로 유지하는 것이 더 쉬운 경우가 많습니다. 그것은 코드베이스의 노련한 베테랑들조차도 COM 스타일의 순수한 인터페이스 접근 방식을와 같은 것을 사용하여 고통스럽게 관찰 한 것 IMotion입니다.

개발자는이 중앙 인터페이스에 기능을 추가, 제거 또는 변경해야하는 이유를 계속 발견했으며, 각 변경 사항은 IMotion사용 된 시스템의 모든 장소와 함께 구현 된 모든 단일 클래스를 중단시키는 경향이 있기 때문에 비용과 비용이 많이 듭니다 IMotion. 그동안 많은 고통스럽고 계단식 변화가 일어났던 전체 시간 동안 구현 된 객체 IMotion는 모두 4x4 행렬의 플로트를 저장하고 전체 인터페이스는 이러한 플로트를 변환하고 액세스하는 방법에만 관심이있었습니다. 데이터 표현은 처음부터 안정적이었으며, 이러한 중앙 집중식 인터페이스가 예상치 못한 설계 요구에 따라 변경되기 쉬운 경우에도 처음부터 존재하지 않으면 많은 고통을 피할 수있었습니다.

이것은 전역 변수처럼 거의 역겨운 것처럼 들릴 수 있지만 ECS 가이 데이터를 시스템을 통해 유형별로 명시 적으로 검색 된 구성 요소로 구성하는 방식의 본질은 그렇게합니다. 컴파일러는 정보 숨기기와 같은 것을 숨길 수 없습니다. 데이터는 일반적으로 변이를 효과적으로 유지하고 한 시스템에서 다음 시스템으로 어떤 종류의 변환 및 부작용이 발생하는지 예측할 수있을 정도로 명확하고 명확합니다 (실제로는 특정 영역에서 OOP보다 간단하고 예측 가능할 수있는 방식으로). 시스템은 평평한 종류의 파이프 라인으로 바뀝니다).

여기에 이미지 설명을 입력하십시오

마지막으로 순수한 ECS에서 애니메이션을 처리하는 방법에 대한 질문을하고 싶습니다. 현재 애니메이션을 0과 1 사이의 진행 상황을 기반으로 엔티티를 조작하는 펑터로 정의했습니다. 애니메이션 구성 요소에는 애니메이션 목록이있는 애니메이터 목록이 있습니다. 업데이트 기능에서 현재 엔티티에 활성화 된 모든 애니메이션을 적용합니다.

우리는 모두 실용 주의자입니다. gamedev에서도 상충되는 아이디어 / 응답을 얻을 수 있습니다. 심지어 가장 순수한 ECS조차도 비교적 새로운 현상, 개척 영역이며, 사람들이 고양이를 피부에 대는 방법에 대해 가장 강한 의견을 제시하지는 않았습니다. 내 직감 반응은 렌더링 시스템이 표시하기 위해 애니메이션 구성 요소에서 이러한 종류의 애니메이션 진행을 증가시키는 애니메이션 시스템이지만 특정 응용 프로그램 및 컨텍스트에 대한 많은 뉘앙스를 무시합니다.

ECS를 사용하면 은색 총알이 아니며 여전히 새로운 시스템을 추가하고 일부를 제거하고 새로운 구성 요소를 추가하며 기존 시스템을 변경하여 새로운 구성 요소 유형을 선택하는 경향이 있습니다. 여전히 처음부터 제대로. 그러나 내 경우의 차이점은 특정 디자인 요구 사항을 미리 예측하지 못하면 중심을 바꾸지 않는다는 것입니다. 나는 계단식 파손의 파급 효과를 얻지 못하고있어서 모든 곳을 다니고 자르는 새로운 요구를 처리하기 위해 많은 코드를 변경해야하므로 시간을 크게 절약 할 수 있습니다. 또한 특정 시스템에 앉을 때 관련 구성 요소 (데이터) 이외의 다른 작업에 대해 많은 것을 알 필요가 없기 때문에 뇌에서 더 쉬워졌습니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.