사용자 친화적이지만 여전히 유연한 구성 요소 기반 엔터티 시스템에는 어떤 설계가 있습니까?


23

나는 구성 요소 기반 엔터티 시스템에 잠시 동안 관심이 있었고 그것에 대한 수많은 기사를 읽었습니다 ( 불면증 게임 , 표준 Evolve Your Hierarchy , T-Machine , Chronoclast ...).

그들은 모두 다음과 같은 외부에 구조를 가지고있는 것 같습니다.

Entity e = Entity.Create();
e.AddComponent(RenderComponent, ...);
//do lots of stuff
e.GetComponent<PositionComponent>(...).SetPos(4, 5, 6);

공유 데이터에 대한 아이디어를 가져 오면 (이것은 지금까지 본 데이터 중에서 중복되지 않는 측면에서 가장 좋은 디자인입니다)

e.GetProperty<string>("Name").Value = "blah";

예, 이것은 매우 효율적입니다. 그러나 읽고 쓰는 것이 가장 쉬운 것은 아닙니다. 그것은 매우 어렴풋하고 당신을 대항합니다.

나는 개인적으로 다음과 같은 일을하고 싶습니다 :

e.SetPosition(4, 5, 6);
e.Name = "Blah";

물론 이러한 종류의 디자인을 얻는 유일한 방법은 엔티티-> NPC-> 적-> 플라잉 적-> 플라잉 EnemyWithAHatOn 종류의 계층 구조로 돌아 오는 것입니다.이 디자인은 피하려고합니다.

여전히 유연하면서도 사용자 친화적 인 수준을 유지하는 이러한 종류의 구성 요소 시스템에 대한 디자인을 본 사람이 있습니까? 그리고 그 문제에 대해 (아마 가장 어려운 문제) 데이터 스토리지를 좋은 방법으로 돌파 할 수 있습니까?

사용자 친화적이지만 여전히 유연한 구성 요소 기반 엔터티 시스템에는 어떤 설계가 있습니까?

답변:


13

Unity가하는 일 중 하나는 상위 게임 오브젝트에 대한 도우미 접근자를 제공하여 공통 컴포넌트에보다 사용자 친화적 인 액세스를 제공하는 것입니다.

예를 들어, 위치를 변환 컴포넌트에 저장했을 수 있습니다. 예제를 사용하면 다음과 같은 것을 작성해야합니다.

e.GetComponent<Transform>().position = new Vector3( whatever );

그러나 Unity에서는

e.transform.position = ....;

transform말 그대로 기본 GameObject클래스 ( Entity귀하의 클래스)에서 간단한 도우미 메소드는 어디에 있습니까?

Transform transform
{ 
    get { return this.GetComponent<Transform>(); }
}

Unity는 또한 하위 컴포넌트 대신 게임 오브젝트 자체에 "Name"속성을 설정하는 것과 같은 다른 몇 가지 작업도 수행합니다.

개인적으로 저는 이름 별 공유 데이터 디자인 아이디어가 마음에 들지 않습니다. 이름 대신 변수 속성을 액세스하고 사용자를 가지고 그냥 정말 나에게 발생하기 쉬운 오류가 보인다 입력 한 내용을 알아야합니다. Unity가하는 일은 클래스 GameObject transform내 속성 과 유사한 메서드를 사용하여 Component형제 구성 요소에 액세스한다는 것입니다. 따라서 임의의 구성 요소를 작성하면 다음과 같이 간단하게 수행 할 수 있습니다.

var myPos = this.transform.position;

위치에 액세스합니다. 그 transform속성이 이런 식으로하는 곳

Transform transform
{
    get { return this.gameObject.GetComponent<Transform>(); }
}

물론, 그냥 말하는 것보다 조금 더 장황 e.position = whatever하지만 익숙해지면 일반적인 속성만큼 불쾌하게 보이지 않습니다. 그리고 그렇습니다. 클라이언트 구성 요소에 대한 로터리 방식을 사용해야하지만 모든 일반적인 "엔진"구성 요소 (렌더러, 충돌 자, 오디오 소스, 변환 등)에 쉽게 접근자가있을 수 있습니다.


이름 별 공유 데이터 시스템이 왜 문제가되는지 알 수 있지만 데이터가 필요한 여러 구성 요소의 문제 (어떤 것을 저장해야하는지)를 피하려면 어떻게해야합니까? 디자인 단계에서 매우 신중합니까?
공산주의 오리

또한 '사용자에게 어떤 유형인지 알아야합니다'라는 관점에서 get () 메소드의 property.GetType ()으로 캐스팅 할 수 없었습니까?
공산주의 오리

1
이러한 공유 데이터 저장소로 어떤 종류의 글로벌 (엔터티 범위) 데이터를 추진하고 있습니까? 모든 종류의 게임 오브젝트 데이터는 "엔진"클래스 (트랜스포머, 콜 라이더, 렌더러 등)를 통해 쉽게 액세스 할 수 있어야합니다. 이 "공유 데이터"를 기반으로 게임 로직을 변경하는 경우, 하나의 클래스를 소유하는 것이 훨씬 안전하며 실제로는 명명 된 변수가 있는지 여부를 맹목적으로 묻는 것보다 컴포넌트가 해당 게임 오브젝트에 있는지 확인하는 것이 훨씬 안전합니다. 예, 디자인 단계에서 처리해야합니다. 실제로 필요한 경우 데이터를 저장하는 구성 요소를 항상 만들 수 있습니다.
Tetrad

@Tetrad-제안 된 GetComponent <Transform> 메소드의 작동 방식에 대해 간략하게 설명 할 수 있습니까? 게임 오브젝트의 모든 컴포넌트를 반복하고 T 타입의 첫 번째 컴포넌트를 반환합니까? 아니면 다른 일이 있습니까?
Michael

@ 마이클은 작동합니다. 성능 향상을 위해 로컬로 캐시하는 것이 호출자의 책임이 될 수 있습니다.
Tetrad

3

Entity 객체에 대한 일종의 인터페이스 클래스가 좋을 것이라고 제안합니다. 엔터티에 적절한 값의 구성 요소가 한 위치에 포함되어 있는지 확인하여 오류 처리를 수행 할 수 있으므로 값에 액세스하는 모든 곳에서 수행 할 필요가 없습니다. 또는 구성 요소 기반 시스템으로 수행 한 대부분의 디자인은 구성 요소를 직접 처리하여 위치 구성 요소를 요청한 다음 해당 구성 요소의 속성에 직접 액세스 / 업데이트합니다.

매우 기본적으로,이 클래스는 문제의 엔터티를 사용하고 다음과 같이 기본 구성 요소 부분에 사용하기 쉬운 인터페이스를 제공합니다.

public class EntityInterface
{
    private Entity entity { get; set };
    public EntityInterface(Entity e)
    {
        entity = e;
    }

    public Vector3 Position
    {
        get
        {
            return entity.GetProperty<Vector3>("Position");
        }
        set
        {
            entity.GetProperty<Vector3>("Position") = value;
        }
    }
}

NoPositionComponentException 또는 무언가를 처리해야한다고 가정하면 Entity 클래스에 모든 것을 넣는 것의 이점은 무엇입니까?
공산주의 오리

엔터티 클래스에 실제로 무엇이 포함되어 있는지 확실하지 않으면 이점이 있다고 말할 수 없습니다. 나는 기본적으로 존재하지 않는 컴포넌트 시스템을 옹호한다. 그러나 나는 이와 같은 인터페이스 클래스가 기존에 대한 좋은 주장이라고 생각합니다.
제임스

이것이 더 쉬운 방법을 알 수 있지만 (GetProperty를 통해 위치를 쿼리 할 필요는 없지만) Entity 클래스 자체에 배치하는 대신이 방법의 이점을 보지 못합니다.
공산주의 오리

2

파이썬에서는 Entity e.SetPosition(4,5,6)에서 __getattr__함수 를 선언 하여 'setPosition'부분을 가로 챌 수 있습니다 . 이 함수는 컴포넌트를 반복하고 적절한 메소드 또는 특성을 찾고이를 리턴하여 함수 호출 또는 지정이 올바른 위치로 이동할 수 있습니다. 아마도 C #은 비슷한 시스템을 가지고 있지만 정적으로 입력되어 불가능할 수도 있습니다. e.setPositione가 인터페이스에 setPosition이 설정되어 있지 않으면 컴파일 할 수 없습니다 .

예외를 발생시키는 스텁 메서드를 사용하여 모든 엔터티에 추가 할 수있는 모든 구성 요소에 대한 관련 인터페이스를 구현할 수도 있습니다. 그런 다음 addComponent를 호출하면 해당 구성 요소 인터페이스의 엔티티 기능을 구성 요소로 리디렉션하기 만하면됩니다. 조금 어리석은.

그러나 가장 쉬운 방법은 Entity 클래스에서 [] 연산자를 오버로드하여 첨부 된 구성 요소에서 유효한 속성이 있는지 검색하고이를 반환하여 다음과 같이 할당 할 수 있다는 것 e["Name"] = "blah"입니다. 각 구성 요소는 자체 GetPropertyByName을 구현해야하며 Entity는 해당 속성을 담당하는 구성 요소를 찾을 때까지 차례로 각 구성 요소를 호출합니다.


나는 인덱서를 사용하는 것을 생각했다.. 이것은 정말로 멋지다. 다른 내용이 표시 될 때까지 조금 기다릴 것이지만, 일반적인 GetComponent ()와 @James와 같은 일반적인 구성 요소에 제안 된 일부 속성을 혼합하려고합니다.
공산주의 오리

여기서 말하는 내용이 누락되었을 수 있지만 e.GetProperty <type> ( "NameOfProperty")를 대체하는 것으로 보입니다. e [ "NameOfProperty"]. Whatever ??
제임스

@James 그것은 그것의 포장지입니다 (당신의 디자인과 비슷합니다). 더 역동적 인 것을 제외하고-그것은 방법보다 자동적이고 깨끗합니다. GetProperty단지 단점은 문자열 조작과 비교입니다. 그러나 그것은 요점입니다.
공산주의 오리

아, 내 오해. GetProperty가 엔티티의 일부이고 C # 메소드가 아니라 위에서 설명한 것을 수행한다고 가정했습니다.
제임스

2

Kylotan의 답변을 확장하기 위해 C # 4.0을 사용하는 경우 동적 으로 변수를 정적으로 입력 할 수 있습니다 .

System.Dynamic.DynamicObject에서 상속 한 경우 많은 가상 메서드 중에서 TryGetMember 및 TrySetMember를 재정 의하여 구성 요소 이름을 가로 채고 요청 된 구성 요소를 반환 할 수 있습니다.

dynamic entity = EntityRegistry.Entities[1000];
entity.MyComponent.MyValue = 100;

나는 수년간 엔터티 시스템에 대해 조금 썼지 만 거인과 경쟁 할 수 없다고 가정합니다. 일부 ES 노트 (약간 구식이며 엔터티 시스템에 대한 현재의 이해를 반영하지는 않지만 읽을 가치가 있습니다).


감사합니다, 도움이 될 것입니다 :) 동적 방법이 property.GetType ()으로 캐스팅하는 것과 같은 종류의 효과를 가지지 않습니까?
공산주의 오리

TryGetMember에 out object매개 변수 가 있으므로 어느 시점에 캐스트가 발생 하지만이 모든 것이 런타임에 해결됩니다. 나는 그것이 entity.TryGetComponent (compName, out component) 위에 유창한 인터페이스를 유지하는 영광스러운 방법이라고 생각합니다. 그러나 나는 그 질문을 이해하지 못했다. :)
Raine

문제는 어디서나 동적 유형을 사용하거나 (property.GetType ()) 속성을 반환하는 (AFAICS)를 사용할 수 있다는 것입니다.
공산주의 오리

1

C #의 ES에 많은 관심이 있습니다. 훌륭한 ES 구현 Artemis 포트를 확인하십시오.

https://github.com/thelinuxlich/artemis_CSharp

또한 XNA 4를 사용하여 작동 방식을 시작하는 예제 게임 : https://github.com/thelinuxlich/starwarrior_CSharp

제안은 환영합니다!


확실히 좋아 보인다. 나는 개인적으로 너무 많은 EntityProcessingSystems의 아이디어에 대한 팬이 아닙니다. 코드에서 사용 측면에서 어떻게 작동합니까?
공산주의 오리

모든 시스템은 종속 구성 요소를 처리하는 측면입니다. 아이디어를 얻으려면 이것을 참조하십시오 : github.com/thelinuxlich/starwarrior_CSharp/tree/master/…
thelinuxlich

0

나는 C #의 탁월한 반사 시스템을 사용하기로 결정했습니다. 나는 그것을 일반적인 구성 요소 시스템과 여기에 대답의 혼합을 기반으로했습니다.

구성 요소가 매우 쉽게 추가됩니다.

Entity e = new Entity(); //No templates/archetypes yet.
e.AddComponent(new HealthComponent(100));

이름, 유형 또는 (일반적으로 건강 및 위치와 같은 경우) 속성별로 쿼리 할 수 ​​있습니다.

e["Health"] //This, unfortunately, is a bit slower since it requires a dict search
e.GetComponent<HealthComponent>()
e.Health

그리고 제거 :

e.RemoveComponent<HealthComponent>();

데이터는 다시 속성에 의해 일반적으로 액세스됩니다.

e.MaxHP

어느 쪽이 컴포넌트 측에 저장되어 있는지; HP가없는 경우 오버 헤드가 줄어 듭니다.

public int? MaxHP
{
get
{

    return this.Health.MaxHP; //Error handling returns null if health isn't here. Excluded for clarity
}
set
{
this.Health.MaxHP = value;
}

VS의 Intellisense는 많은 양의 타이핑을 지원하지만 대부분은 타이핑이 거의 없습니다.

내부적으로 나는를 저장하고 Dictionary<Type, Component>와 가진 Components재정의 ToString이름 확인을위한 방법. 내 원래 디자인은 주로 이름과 사물에 문자열을 사용했지만 작업 할 돼지가되었습니다.

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