구성 요소를 업데이트 할시기 및 위치 장면 트리를 살펴본 다음 각 엔티티가 각

일반적인 상속 무거운 게임 엔진 대신 더 많은 구성 요소 기반 접근 방식을 사용하고 있습니다. 그러나 구성 요소가 수행 할 수있는 위치를 정당화하는 데 어려움을 겪고 있습니다.

구성 요소 목록이있는 간단한 엔티티가 있다고 가정 해보십시오. 물론 기업은 이러한 구성 요소가 무엇인지 모릅니다. 엔터티에 화면상의 위치를 ​​제공하는 구성 요소가있을 수 있으며 다른 하나는 화면에 엔터티를 그릴 수 있습니다.

이러한 구성 요소가 작동하려면 모든 프레임을 업데이트해야합니다.이를 수행하는 가장 쉬운 방법은 장면 트리를 살펴본 다음 각 엔티티가 각 구성 요소를 업데이트하는 것입니다. 그러나 일부 구성 요소는 약간 더 많은 관리가 필요할 수 있습니다. 예를 들어 엔터티를 충돌 가능하게 만드는 구성 요소는 모든 충돌 가능한 구성 요소를 감독 할 수있는 것으로 관리해야합니다. 엔티티 드로어 블을 만드는 컴포넌트는 다른 모든 드로어 블 컴포넌트를 감독하여 드로우 순서 등을 파악해야합니다.

내 질문은 구성 요소를 어디에서 업데이트합니까? 관리자에게 구성 요소를 가져 오는 확실한 방법은 무엇입니까?

각 구성 요소 유형마다 싱글 톤 관리자 객체를 사용하는 것에 대해 생각했지만 싱글 톤을 사용하는 일반적인 단점이 있습니다.이를 완화하는 방법은 종속성 주입을 사용하는 것이지만이 문제에 대한 과잉 소리처럼 들립니다. 또한 장면 트리를 살펴본 다음 일종의 관찰자 패턴을 사용하여 다른 구성 요소를 목록으로 수집 할 수는 있지만 모든 프레임에서 약간의 낭비가되는 것 같습니다.



답변

Mike Acton의 3 가지 거짓말을 읽어 보는 것이 좋습니다. 두 가지를 위반했기 때문입니다. 나는 이것이 코드 디자인 방식을 바꿀 것이다 : http://cellperformance.beyond3d.com/articles/2008/03/three-big-lies.html

그래서 당신은 어느 것을 위반합니까?

거짓말 # 3-코드가 데이터보다 중요하다

의존성 주입에 대해 이야기하지만, 일부 (및 일부) 인스턴스에서 유용 할 수 있지만, 특히 게임 개발에서 사용하면 항상 큰 알람 벨을 울려 야합니다! 왜? 종종 불필요한 추상화이기 때문입니다. 그리고 잘못된 곳에서의 추상화는 끔찍합니다. 게임이 있습니다. 이 게임에는 다양한 구성 요소에 대한 관리자가 있습니다. 구성 요소가 모두 정의되었습니다. 따라서 메인 게임 루프 코드의 어딘가에 관리자를 “수속”하는 클래스를 만드십시오. 처럼:

private CollissionManager _collissionManager;
private BulletManager _bulletManager;

각 관리자 클래스 (getBulletManager ())를 가져 오는 getter 함수를 제공하십시오. 어쩌면이 클래스 자체는 싱글 톤이거나 하나에서 접근 할 수 있습니다 (어쨌든 중앙 게임 싱글 톤을 가질 수 있습니다). 잘 정의 된 하드 코딩 된 데이터 및 동작에는 아무런 문제가 없습니다.

키를 사용하여 관리자를 등록 할 수있는 ManagerManager를 만들지 마십시오. 관리자를 사용하려는 다른 클래스에서 해당 키를 사용하여 검색 할 수 있습니다. 훌륭한 시스템이며 매우 유연하지만 여기서 게임에 대해 이야기합니다. 게임에 어떤 시스템이 있는지 정확히 있습니다. 왜 당신이 아닌 척? 이것은 코드가 데이터보다 중요하다고 생각하는 사람들을위한 시스템이기 때문입니다. “코드가 유연하고 데이터가 채 웁니다”라고 말합니다. 그러나 코드는 단지 데이터입니다. 내가 설명한 시스템은 훨씬 쉽고 안정적이며 유지 관리가 쉽고 유연합니다. 예를 들어 한 관리자의 동작이 다른 관리자와 다른 경우 전체 시스템을 재 작업하는 대신 몇 줄만 변경하면됩니다.

거짓말 # 2-코드는 세계 모델을 중심으로 설계되어야합니다

게임 세계에는 실체가 있습니다. 엔터티에는 동작을 정의하는 많은 구성 요소가 있습니다. 따라서 Component 객체 목록과 각 Component의 Update () 함수를 호출하는 Update () 함수를 사용하여 Entity 클래스를 만듭니다. 권리?

아니오 🙂 그것은 세계의 모델을 중심으로 설계되었습니다 : 게임에 총알이 있으므로 총알 클래스를 추가하십시오. 그런 다음 각 글 머리 기호를 업데이트하고 다음 글 머리 기호로 넘어갑니다. 이것은 성능을 절대적으로 떨어 뜨리고 비슷한 코드를 논리적으로 구조화하지 않고 어디에나 중복 코드가있는 끔찍한 복잡한 코드베이스를 제공합니다. ( 기존의 OO 디자인이 왜 짜증나는지 또는 데이터 지향 디자인을 찾는 이유에 대한 자세한 설명은 여기 에서 내 대답을 확인하십시오 )

우리의 OO 편견이없는 상황을 살펴 봅시다. 우리는 더 이상 다음을 원치 않습니다 (엔티티 또는 객체에 대한 클래스를 만들 필요는 없습니다).

  • 당신은 엔티티의 무리가
  • 엔터티는 엔터티의 동작을 정의하는 여러 구성 요소로 구성됩니다.
  • 게임의 각 구성 요소를 각 프레임, 바람직하게는 제어 된 방식으로 업데이트하려고합니다
  • 구성 요소가 함께 속하는 것으로 식별하는 것 외에는 엔티티 자체가 수행 할 작업이 없습니다. 몇 가지 구성 요소에 대한 링크 / ID입니다.

그리고 상황을 보자. 컴포넌트 시스템은 프레임 마다 게임 의 모든 오브젝트 의 행동을 업데이트합니다 . 이것은 확실히 엔진의 중요한 시스템입니다. 여기서 성능이 중요합니다!

컴퓨터 아키텍처 또는 데이터 지향 디자인에 익숙한 경우 단단히 압축 된 메모리와 그룹 코드 실행을 통해 최상의 성능을 얻는 방법을 알 수 있습니다. ABCABCABC와 같이 코드 A, B 및 C의 스 니펫을 실행하면 AAABBBCCC와 같이 코드를 실행할 때와 동일한 성능을 얻을 수 없습니다. 이는 명령 및 데이터 캐시가보다 효율적으로 사용되기 때문일뿐만 아니라 모든 “A”를 서로 실행하면 최적화 할 여지가 많기 때문에 중복 코드 제거, 사용 된 데이터 사전 계산 모든 “A”등

따라서 모든 구성 요소를 업데이트하려면 업데이트 기능을 사용하여 클래스 / 객체를 만들지 마십시오. 각 엔터티의 각 구성 요소에 대해 해당 업데이트 기능을 호출하지 마십시오. 이것이 “ABCABCABC”솔루션입니다. 동일한 구성 요소 업데이트를 모두 그룹화합시다. 그런 다음 모든 A 구성 요소를 업데이트 한 다음 B 등을 업데이트 할 수 있습니다.이를 위해 무엇이 필요합니까?

먼저 구성 요소 관리자가 필요합니다. 게임의 모든 유형의 구성 요소에 대해 관리자 클래스가 필요합니다. 해당 유형의 모든 구성 요소를 업데이트하는 업데이트 기능이 있습니다. 해당 유형의 새 구성 요소를 추가하는 작성 기능과 지정된 구성 요소를 제거하는 제거 기능이 있습니다. 해당 구성 요소에 특정한 데이터를 가져오고 설정하는 다른 도우미 기능이있을 수 있습니다 (예 : 모델 구성 요소의 3D 모델 설정). 관리자는 어떤 식 으로든 외부 세계에 대한 블랙 박스입니다. 각 구성 요소의 데이터가 어떻게 저장되는지 모릅니다. 우리는 각 구성 요소가 어떻게 업데이트되는지 모릅니다. 구성 요소가 제대로 작동하는 한 신경 쓰지 않습니다.

다음으로 엔티티가 필요합니다. 이것을 수업으로 만들 수는 있지만 거의 필요하지 않습니다. 엔터티는 고유 한 정수 ID 또는 해시 문자열 (정수) 일 수 있습니다. 엔터티의 구성 요소를 만들 때 ID를 인수로 Manager에 전달합니다. 구성 요소를 제거하려면 ID를 다시 전달하십시오. ID를 만드는 대신 엔티티에 조금 더 많은 데이터를 추가하면 몇 가지 이점이 있지만 요구 사항에 나열된 것처럼 모든 엔티티 동작은 구성 요소 자체에 의해 정의되므로 도우미 기능 일뿐입니다. 그것은 당신의 엔진이므로, 당신에게 맞는 것을하십시오.

우리에게 필요한 것은 엔터티 관리자입니다. 이 클래스는 ID 전용 솔루션을 사용하는 경우 고유 ID를 생성하거나 Entity 오브젝트를 작성 / 관리하는 데 사용될 수 있습니다. 필요한 경우 게임의 모든 엔티티 목록을 유지할 수도 있습니다. Entity Manager는 게임의 모든 ComponentManager에 대한 참조를 저장하고 업데이트 기능을 올바른 순서로 호출하여 구성 요소 시스템의 중심 클래스가 될 수 있습니다. 그렇게하면 모든 게임 루프가 EntityManager.update ()를 호출하고 전체 시스템이 나머지 엔진과 잘 분리됩니다.

그것은 조감도입니다. 구성 요소 관리자의 작동 방식을 살펴 보겠습니다. 필요한 것은 다음과 같습니다.

  • create (entityID)가 호출 될 때 구성 요소 데이터 작성
  • remove (entityID)가 호출 될 때 구성 요소 데이터 삭제
  • update ()가 호출 될 때 모든 (적용 가능한) 구성 요소 데이터를 업데이트합니다 (즉, 모든 구성 요소가 각 프레임을 업데이트 할 필요는 없음)

마지막은 구성 요소 동작 / 논리를 정의하는 위치이며 작성중인 구성 요소 유형에 전적으로 의존합니다. AnimationComponent는 현재 프레임에 따라 애니메이션 데이터를 업데이트합니다. DragableComponent는 마우스로 끌고있는 구성 요소 만 업데이트합니다. PhysicsComponent는 물리 시스템의 데이터를 업데이트합니다. 여전히 동일한 유형의 모든 구성 요소를 한 번에 업데이트하기 때문에 각 구성 요소가 언제든지 호출 할 수있는 업데이트 기능이있는 별도의 개체 인 경우에는 불가능한 최적화를 수행 할 수 있습니다.

구성 요소 데이터를 보유하기 위해 XxxComponent 클래스를 작성하도록 요청한 적이 없습니다. 그것은 당신에게 달려 있습니다. 데이터 지향 디자인을 좋아합니까? 그런 다음 각 변수에 대해 별도의 배열로 데이터를 구성하십시오. 당신은 객체 지향 디자인을 좋아합니까? (권장하지는 않지만 여전히 많은 장소에서 성능이 저하됩니다) 그런 다음 각 구성 요소의 데이터를 보유 할 XxxComponent 객체를 만듭니다.

관리자의 가장 큰 장점은 캡슐화입니다. 이제 캡슐화는 프로그래밍 세계에서 가장 끔찍하게 오용 된 철학 중 하나입니다. 이것이 사용되는 방법입니다. 관리자 만이 구성 요소의 논리가 작동하는 위치, 구성 요소 데이터를 알고 있습니다. 데이터를 가져 오거나 설정하는 몇 가지 기능이 있지만 그게 전부입니다. 전체 관리자와 기본 클래스를 다시 작성할 수 있으며 공개 인터페이스를 변경하지 않으면 아무도 알지 못합니다. 물리 엔진을 변경 했습니까? PhysicsComponentManager를 다시 작성하면 완료됩니다.

마지막으로 컴포넌트 간 통신 및 데이터 공유가 있습니다. 이제 이것은 까다 롭고 모든 솔루션에 적합하지 않습니다. 충돌 구성 요소가 위치 구성 요소 (예 : PositionManager.getPosition (entityID))에서 위치를 가져 오도록 관리자에서 get / set 함수를 작성할 수 있습니다. 이벤트 시스템을 사용할 수 있습니다. 엔티티 (내 의견으로는 가장 추악한 해결책)에 공유 데이터를 저장할 수 있습니다. 메시징 시스템을 사용할 수 있습니다 (종종 자주 사용됨). 또는 여러 시스템의 조합을 사용하십시오! 이러한 각 시스템에 참여할 시간이나 경험이 없지만 Google 및 스택 오버플로 검색은 친구입니다.


답변

이러한 구성 요소가 작동하려면 모든 프레임을 업데이트해야합니다.이를 수행하는 가장 쉬운 방법은 장면 트리를 살펴본 다음 각 엔티티가 각 구성 요소를 업데이트하는 것입니다.

이것은 구성 요소 업데이트에 대한 일반적인 순진한 접근 방식입니다 (그리고 그것이 당신에게 효과적이라면 순진한 것은 아닙니다). 실제로 다루었던 큰 문제 중 하나는 예를 들어 구성 요소의 인터페이스를 통해 작동 IComponent하므로 방금 업데이트 한 내용에 대해 아무것도 알 수 없습니다. 엔터티 내의 구성 요소 순서에 대해 전혀 모를 수도 있습니다.

  1. 다른 유형의 구성 요소를 자주 업데이트 할 가능성이 높습니다 (기본적으로 잘못된 코드 참조)
  2. 이 시스템은 데이터 종속성을 식별 할 수있는 위치에 있지 않으므로 업데이트를 관련없는 개체의 로컬 그룹으로 분할하기 때문에 동시 업데이트에 적합하지 않습니다.

각 구성 요소 유형마다 싱글 톤 관리자 객체를 사용하는 것에 대해 생각했지만 싱글 톤을 사용하는 일반적인 단점이 있습니다.이를 완화하는 방법은 종속성 주입을 사용하는 것이지만이 문제에 대한 과잉 소리처럼 들립니다.

여기에는 싱글 톤이 필요하지 않으므로 언급 한 단점이 있으므로 피해야합니다. 의존성 주입은 과도하지 않습니다. 개념의 핵심은 객체에서 필요한 것들을 이상적으로 생성자에서 전달한다는 것입니다. 이를 위해 헤비급 DI 프레임 워크 (예 : Ninject ) 가 필요하지 않습니다 . 추가 매개 변수를 생성자에 어딘가에 전달하면됩니다.

렌더러는 기본 시스템이며 게임의 시각적 요소 (스프라이트 또는 모델 등)에 해당하는 여러 렌더러 블 객체의 수명을 만들고 관리 할 수 ​​있습니다. 마찬가지로 물리 엔진은 물리 시뮬레이션에서 움직일 수있는 엔티티 (강체)를 나타내는 것들을 평생 제어 할 수 있습니다. 이러한 관련 시스템 각각은 일부 용량에서 해당 오브젝트를 소유하고 업데이트해야합니다.

게임 엔터티 컴포지션 시스템에서 사용하는 구성 요소는 저수준 시스템의 인스턴스를 감싸는 래퍼 여야합니다. 위치 구성 요소는 딱딱한 바디를 감싸고, 시각적 구성 요소는 렌더링 가능한 스프라이트 또는 모델을 감싸는 등입니다.

그런 다음 하위 수준 개체를 소유 한 시스템 자체에서 개체를 업데이트해야하며, 필요한 경우 해당 업데이트를 멀티 스레드 할 수있는 방식으로 대량으로 업데이트 할 수 있습니다. 메인 게임 루프는 해당 시스템이 업데이트되는 원유 순서를 제어합니다 (먼저 물리, 렌더러 등). 수명이 없거나 서브 시스템에 대한 업데이트 제어가없는 서브 시스템이있는 경우 간단한 랩퍼를 작성하여 해당 시스템과 관련된 모든 구성 요소의 업데이트를 일괄 처리하고 배치 할 위치를 결정할 수 있습니다. 나머지 시스템 업데이트와 관련하여 업데이트하십시오 (이것은 종종 “스크립트”구성 요소에서 발생합니다).

더 자세한 내용을 원한다면이 접근 방식을 외부 구성 요소 접근 방식 이라고도합니다 .