엔터티 시스템 및 렌더링


11

좋아요, 지금까지 내가 아는 것; 엔티티는 다음과 같은 정보를 보유하는 구성 요소 (데이터 저장 장치)를 포함합니다. -텍스쳐 / 스프라이트-쉐이더-등

그리고이 모든 것을 그리는 렌더러 시스템이 있습니다. 그러나 내가 이해하지 못하는 것은 렌더러가 어떻게 디자인되어야 하는가입니다. 각 "시각 유형"마다 하나의 구성 요소가 있어야합니다. 셰이더가없는 컴포넌트, 셰이더가있는 컴포넌트 등?

이를 위해 "올바른 방법"에 대한 정보가 필요합니다. 조심해야 할 팁과 함정.


2
일을 너무 일반적으로 만들지 마십시오. Sprite 구성 요소가 아닌 Shader 구성 요소가있는 엔터티를 갖는 것이 이상하게 보일 수 있으므로 Shader가 Sprite 구성 요소의 일부 여야합니다 . 당연히 하나의 렌더링 시스템 만 있으면됩니다.
Jonathan Connell 8

답변:


8

엔터티 구성 요소 시스템을 구성하는 방법에 대한 모든 사람들이 자신의 생각을 가지고 있기 때문에 대답하기 어려운 질문입니다. 내가 할 수있는 최선은 내가 찾은 것들 중 가장 유용한 것들을 당신과 나누는 것입니다.

실재

ECS에 대한 뚱뚱한 접근 방식을 취했을 것입니다. 아마도 프로그래밍의 극단적 인 방법이 매우 비효율적이라고 생각하기 때문입니다 (인간 생산성 측면에서). 이를 위해 엔터티는 더 특수화 된 클래스에 상속되는 추상 클래스입니다. 엔터티에는 많은 가상 속성과이 엔터티가 존재하는지 여부를 알려주는 간단한 플래그가 있습니다. 렌더 시스템에 대한 질문 Entity과 관련 하여 다음과 같습니다.

public abstract class Entity {
    public bool IsAlive = true;
    public virtual SpatialComponent   Spatial   { get; set; }
    public virtual ImageComponent     Image     { get; set; }
    public virtual AnimationComponent Animation { get; set; }
    public virtual InputComponent     Input     { get; set; }
}

구성 요소

구성 요소는 아무 것도 모르 거나 알지 못한다 점에서 "멍청한" 것입니다. 그들은 다른 구성 요소에 대한 참조가 없으며 일반적으로 함수가 없습니다 (C #에서 작동하므로 속성을 사용하여 getter / setter를 처리합니다. 함수가있는 경우 보유하고있는 데이터 검색을 기반으로합니다).

시스템

시스템은 덜 멍청하지만 여전히 멍청한 자동 장치입니다. 전체 시스템에 대한 컨텍스트가없고 다른 시스템에 대한 참조가 없으며 개별 처리에 필요한 몇 개의 버퍼를 제외하고 데이터를 보유하지 않습니다. 시스템에 따라 특화된 방법 Update또는 Draw방법이 있거나 경우에 따라 둘 다있을 수 있습니다.

인터페이스

인터페이스는 내 시스템의 핵심 구조입니다. 그들은 무엇 System을 처리 할 수 ​​있고 무엇을 할 수 있는지 정의하는 데 사용됩니다 Entity. 렌더링과 관련된 인터페이스는 다음 IRenderable과 같습니다 IAnimatable.

인터페이스는 시스템에 사용 가능한 구성 요소를 알려줍니다. 예를 들어, 렌더링 시스템은 엔티티의 경계 상자와 그릴 이미지를 알아야합니다. 내 경우에는 SpatialComponentand ImageComponent입니다. 따라서 다음과 같이 보입니다.

public interface IRenderable {
    SpatialComponent Component { get; }
    ImageComponent   Image     { get; }
}

렌더링 시스템

렌더링 시스템은 어떻게 엔티티를 그리나요? 실제로는 매우 간단하므로 아이디어를 제공하기 위해 제거 된 클래스를 보여 드리겠습니다.

public class RenderSystem {
    private SpriteBatch batch;
    public RenderSystem(SpriteBatch batch) {
        this.batch = batch;
    }
    public void Draw(List<IRenderable> list) {
        foreach(IRenderable obj in list) {
            this.batch.draw(
                obj.Image.Texture,
                obj.Spatial.Position,
                obj.Image.Source,
                Color.White);
        }
    }
}

수업을 보면 렌더 시스템은 무엇인지조차 모릅니다 Entity. 그것이 아는 전부는 IRenderable그리고 단순히 그 (것)들을 그릴 그들의 명부가 주어진다.

그것이 어떻게 작동 하는가

새 게임 오브젝트를 작성하는 방법과 시스템에 피드를 공급하는 방법을 이해하는 데 도움이 될 수도 있습니다.

엔터티 만들기

모든 게임 오브젝트는 Entity 및 해당 게임 오브젝트가 수행 할 수있는 작업을 설명하는 적용 가능한 인터페이스에서 상속됩니다. 화면에 애니메이션으로 표시되는 모든 내용은 다음과 같습니다.

public class MyAnimatedWidget : Entity, IRenderable, IAnimatable {}

시스템 공급

게임 세계에 존재하는 모든 엔티티의 목록을이라는 단일 목록으로 유지 List<Entity> gameObjects합니다. 그런 다음 각 프레임을 통해 해당 목록을 살펴보고 인터페이스 유형 (예 : List<IRenderable> renderableObjects및)을 기반으로 더 많은 목록에 객체 참조를 복사합니다 List<IAnimatable> animatableObjects. 이런 식으로 다른 시스템이 동일한 엔티티를 처리해야하는 경우 가능합니다. 그런 다음 해당 목록을 각 시스템 Update또는 Draw방법에 전달하고 시스템에서 작업을 수행하도록합니다.

생기

애니메이션 시스템이 어떻게 작동하는지 궁금 할 것입니다. 필자의 경우 IAnimatable 인터페이스를보고 싶을 수도 있습니다.

public interface IAnimatable {
    public AnimationComponent Animation { get; }
    public ImageComponent Image         { get; set; }
}

여기서 주목해야 할 것은 인터페이스 의 ImageComponent측면이 IAnimatable읽기 전용이 아니라는 것입니다. 그것은이 세터 .

짐작할 수 있듯이 애니메이션 구성 요소는 애니메이션에 대한 데이터 만 보유합니다. 프레임 목록 (이미지 구성 요소), 현재 프레임, 그릴 초당 프레임 수, 마지막 프레임 증분 이후의 경과 시간 및 기타 옵션.

애니메이션 시스템은 렌더링 시스템과 이미지 구성 요소 관계를 이용합니다. 단순히 애니메이션의 프레임이 증가함에 따라 엔터티의 이미지 구성 요소를 변경하기 만합니다. 이렇게하면 애니메이션이 렌더링 시스템에 의해 간접적으로 렌더링됩니다.


나는 이것이 사람들이 엔티티 구성 요소 시스템 이라고 부르는 것에 가깝다는 것을 실제로 알지 못한다는 것을 알아야 합니다 . 컴포지션 기반 디자인을 구현하려는 시도에서이 패턴에 빠지는 것을 발견했습니다.
Cypher

흥미 롭습니다! 나는 당신의 엔티티에 대한 추상 클래스에 관심이 없지만 IRenderable 인터페이스는 좋은 생각입니다!
Jonathan Connell

5

참조 이 대답 내가 이야기하고있는 시스템의 종류를 볼 수 있습니다.

구성 요소에는 그릴 내용 및 그리는 방법에 대한 세부 정보가 포함되어야합니다. 렌더링 시스템은 이러한 세부 사항을 취하여 구성 요소에 지정된 방식으로 엔터티를 그립니다. 크게 다른 그리기 기술을 사용하는 경우에만 별도의 스타일을위한 별도의 구성 요소가 있습니다.


3

로직을 구성 요소로 분리하는 주요 이유는 엔티티로 결합 될 때 유용하고 재사용 가능한 동작을 생성하는 데이터 세트를 작성하는 것입니다. 예를 들어 엔터티를 PhysicsComponent와 RenderComponent로 분리하면 모든 엔터티에 물리가없고 일부 엔터티에 스프라이트가 없을 수도 있으므로 의미가 있습니다.

질문에 대답하려면 아키텍처를 살펴보고 두 가지 질문을해야합니다.

  1. 텍스처없이 셰이더를 갖는 것이 합리적입니까?
  2. 텍스처에서 셰이더를 분리하면 코드 복제를 피할 수 있습니까?

컴포넌트를 분할 할 때이 질문을하는 것이 중요합니다. 1에 대한 대답이 예라면 Shader와 Texture가있는 두 개의 개별 컴포넌트를 만들 수 있습니다. 2에 대한 답은 일반적으로 여러 구성 요소가 위치를 사용할 수있는 위치와 같은 구성 요소의 경우 예입니다.

예를 들어, 물리와 오디오 모두 동일한 위치를 사용할 수 있습니다. 대신 중복 위치를 저장하는 두 구성 요소는 하나의 PositionComponent로 리팩토링하고 PhysicsComponent / AudioComponent를 사용하는 엔티티에도 PositionComponent가 있어야합니다.

우리가 제공 한 정보에 따르면, RenderComponent가 TextureComponent와 ShaderComponent로 분할하기에 좋은 후보는 아닌 것 같습니다. 셰이더는 Texture에 전적으로 의존하고 다른 것은 없습니다.

T-Machine : Entity Systems 와 비슷한 것을 사용한다고 가정하면 C ++에서 RenderComponent & RenderSystem의 샘플 구현은 다음과 같습니다.

struct RenderComponent {
    Texture* textureData;
    Shader* shaderData;
};

class RenderSystem {
    public:
        RenderSystem(EntityManager& manager) :
            m_manager(manager) {
            // Initialize Window, rendering context, etc...
        }

        void update() {
            // Get all the entities with RenderComponent
            std::vector<RenderComponent>& components = m_manager.getComponents<RenderComponent>();

            for(auto component = components.begin(); entity != components.end(); ++components) {
                // Do something with the texture
                doSomethingWithTexture(component->textureData);

                // Do something with the shader if it's not null
                if(component->shaderData != nullptr) {
                    doSomethingWithShader(component->shaderData);
                }
            }
        }
    private:
        EntityManager& m_manager;
}

그것은 완전히 잘못되었습니다. 구성 요소의 핵심은 구성 요소를 개체를 통해 검색하는 것이 아니라 개체와 개체를 분리하는 것입니다. 렌더 시스템은 자체 데이터를 완전히 제어해야합니다. 추신 : std :: vector (특히 인스턴스 데이터 포함)를 루프에 넣지 마십시오. 끔찍한 (느린) C ++입니다.
snake5

@ snake5 당신은 두 카운트에서 정확합니다. 머리 위로 코드를 입력했는데 문제를 지적 해 주셔서 감사합니다. 영향을받는 코드의 속도가 느리고 엔티티 시스템 관용구를 올바르게 사용하도록 수정했습니다.
Jake Woods

2
@ snake5 매 프레임마다 데이터를 다시 계산하지 않고 getComponents는 이미 알려진 m_manager 소유의 벡터를 반환하며 구성 요소를 추가 / 제거 할 때만 변경됩니다. PositionEntity 및 PhysicsComponent를 사용하려는 PhysicsSystem과 같이 동일한 엔터티의 여러 구성 요소를 사용하려는 시스템이있는 경우 이점이 있습니다. 다른 시스템은 위치를 원할 것이고 PositionComponent를 사용하면 중복 데이터가 없습니다. 주로 구성 요소가 통신하는 방식의 문제를 해결합니다.
Jake Woods

5
@ snake5 문제는 EC 시스템의 배치 방법이나 성능에 관한 것이 아닙니다. 문제는 렌더 시스템 설정에 관한 것입니다. EC 시스템을 구성하는 여러 가지 방법이 있습니다. 여기에서 서로의 성능 문제를 파악하지 마십시오. OP는 귀하의 답변과 완전히 다른 EC 구조를 사용하고있을 것입니다. 이 답변에 제공된 코드는 예제를 더 잘 보여주기위한 것이며 성능에 대한 비판을받지 않습니다. 질문이 아마도 성능보다 성능에 관한 것이면 대답이 "유용하지 않다"고 할 수 있지만, 그렇지 않습니다.
MichaelHouse

2
Cyphers 보다이 답변에 배치 된 디자인을 훨씬 선호합니다. 내가 사용하는 것과 매우 비슷합니다. 하나 또는 두 개의 변수 만 있어도 작은 구성 요소가 더 좋습니다. 그들은 나의 "Damagable"컴포넌트가 2 개, 4 개의 변수 (각 건강과 갑옷에 대한 최대와 전류)를 갖는 것처럼 엔티티의 측면을 정의해야합니다. 댓글이 길어지고 있습니다. 자세한 내용을 알아 보려면 채팅 으로 넘어가 겠습니다 .
John McDonald

2

함정 # 1 : 과도하게 설계된 코드. 꽤 오랫동안 구현해야하기 때문에 구현하는 모든 것이 실제로 필요한지 생각해보십시오.

함정 # 2 : 너무 많은 물체. 자동화 된 처리를 어렵게하기 때문에 너무 많은 객체 (각 유형, 하위 유형 및 기타 유형마다 하나씩)가있는 시스템을 사용하지 않습니다. 제 생각에는 각 객체가 특정 기능 세트를 제어하는 ​​것이 훨씬 좋습니다 (한 기능과 반대). 예를 들어 렌더링에 포함 된 각 데이터 비트 (텍스처 구성 요소, 셰이더 구성 요소)에 대한 구성 요소를 만드는 것은 너무 나쁩니다. 일반적으로 이러한 구성 요소를 모두 함께 사용해야한다면 동의하지 않습니까?

함정 # 3 : 너무 엄격한 외부 제어. 렌더러 / 텍스처 유형 / 셰이더 형식 / 어떤 형식 으로든 오브젝트를 변경할 수 있으므로 이름을 셰이더 / 텍스처 오브젝트로 변경하는 것이 좋습니다. 이름은 간단한 식별자입니다. 렌더러는 그 중에서 무엇을 만들지 결정해야합니다. 언젠가 일반 셰이더 대신 재질을 원할 수도 있습니다 (예 : 데이터의 셰이더, 텍스처 및 블렌드 모드 추가). 텍스트 기반 인터페이스를 사용하면 훨씬 쉽게 구현할 수 있습니다.

렌더러는 컴포넌트에 의해 생성 된 객체를 생성 / 파기 / 유지 / 렌더링하는 간단한 인터페이스 일 수 있습니다. 가장 원시적 인 표현은 다음과 같습니다.

class Renderer {
    function Draw() { ... }
    function AddSprite( ... ) { ... return sprite; }
    function RemoveSprite( sprite ) { ... }
    ...
};

이를 통해 컴포넌트에서 이러한 오브젝트를 관리하고 원하는 방식으로 렌더링 할 수있을만큼 멀리 유지할 수 있습니다.

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