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 및 스택 오버플로 검색은 친구입니다.