엔터티 구성 요소 시스템 기반 엔진


9

참고 : 나는 이것을 자바 스크립트로 프로그래밍하고 있지만, 대부분 언어에 무관심해야합니다.

엔진을 ECS 기반 엔진으로 변환하려고합니다.

나는 기본 아이디어를 얻습니다 ( 참고 : 이것은 잘못되었습니다, 내 대답 참조 ) :

엔터티 는 게임 개체입니다.
컴포넌트 는 엔티티에 "접착"될 수있는 기능성 비트 ( reactToInput()) 또는 상태 ( position)입니다.
시스템 에는 관리하고 업데이트하는 엔터티 목록이 있습니다.

그러나 구현과 세부 정보를 얻을 수 있는지 확실하지 않습니다 ...

질문 : 시스템이 다른 종류의 엔티티에서 작동 할 수 있습니까? 나는 보통 Scene내 엔진에서 호출 된 클래스의 예를 제시하며 ,이 목적에도 적용됩니다. 장면은 렌더링, 업데이트, 렌더링 (조명)에 영향을 줄 수 있으며 향후에는 심지어 2DSoundEmitter개체 까지도 포함 할 수있는 모든 개체의 컨테이너입니다 . 높은 수준의 인터페이스를 제공하므로 사용자는 자신이 사용 scene.add()하는 객체의 유형 과 모든 종류에 대해 걱정할 필요가 없습니다 .

나는 Scene시스템이 될 수 있다는 것을 알고 있습니다. 엔터티를 가져 와서 저장 한 다음 업데이트 메서드를 호출하고 상태 변경을 수행 할 수도 있습니다. 그러나 문제가 있습니다. 위에서 설명한 것처럼 다른 유형 의 객체에 Scene공급할 수 있습니다 ! 장면에 렌더링 가능한 객체 ( "드로어 블") 조명 이 모두있는 상황에서 어떻게해야 합니까? 상호 작용하기 전에 형식 확인 엔터티를 만들어야합니까? a를 : 또는, 나는 더 낮은 수준에서 해결해야 에 추가 할 수있는 구성 요소 모든 개체를 빛이 단지와 기업이 될 것입니다 및 구성 요소. 괜찮습니까?LightSourceLightSourcePosition

또한 전통적인 상속과 전통적인 클래스를 계속 사용하는 것이 좋은 습관입니까? 예를 들어, 나는 내 Renderer것이 무엇인지 알 수 없습니다 ! 카메라와 장면을 촬영하고 모든 것을 렌더링하며 효과 (예 : 그림자)를 적용하는 것이 유일한 기능이기 때문에 시스템이 아닙니다. 또한 게임의 컨텍스트, 너비 및 높이를 관리하고 번역을 수행합니다. 그러나 여전히 시스템은 아닙니다!

편집 : ECS에서 찾은 리소스를 연결할 수 있습니까? 좋은 것을 찾는 데 어려움을 겪고 있습니다.


2
이 페이지의 답변을 다시 게시하는 대신 다음 링크를 제공하겠습니다. gamedev.stackexchange.com/questions/23533/… 엔터티는 파생되지 않아야하며 엔터티 간 차이는 구성 요소를 통해 달성해야합니다. 일반적으로 각 주요 시스템 (렌더링, 물리, 네트워킹, 입력, 오디오 등)에 대한 인터페이스가 필요합니다. 렌더러를 설정하는 방법은 렌더링 가능한 엔티티에 대해 장면을 쿼리하고 렌더링 관리자는 렌더링 구성 요소가있는 각 엔티티에 렌더링 정보를 요청하는 것보다 장면 관리자를 쿼리하는 것입니다.
Nic Foster

1
T = Machine 블로그 의 컴포넌트 디자인 (좋은 것을 요청한 이후)
John McDonald

엔티티 프레임 워크에 대한 코드 및 토론 : gamadu.com/artemis
Patrick Hughes

@JohnMcDonald, 나는 그 기사에 대한 의견을 썼다. 당신은 여기에서 볼 수 있습니다 t-machine.org/index.php/2007/12/22/...를 . 저는 "얀반"입니다.
jcora

또한 John이 T = Machine에서 링크 한 기사 @NicFoster는 귀하의 답변과 다른 점을 설명합니다. Dave에게는 엔터티에 구성 요소 목록이 없으며 단지 이름 일뿐입니다. "flsjn304"와 같이 – 그것은 엔티티입니다. "어딘가에"저장됩니다. 그리고 나는 그가 실제로 내부 구성 요소를 유지하면 이해하기 위해 일을 다시 읽어야 시스템 보인다, 아주 나에게 이상한!
jcora

답변:


6

웹 / UI JS 개발자로 이해하려고 노력하면 도움이 될 수 있습니다. 또한 언어 불가지론으로 너무 멀리 가지 마십시오. 다른 언어로 설정된 많은 패턴은 공부할 가치가 있지만 유연성으로 인해 JS에서 매우 다르게 적용될 수 있거나 언어의 가변성 때문에 실제로 필요하지 않습니다. JS에 대한 코드 생각을 좀 더 고전적인 OOP 지향 언어와 동일한 경계를 갖는 것으로 작성하면 약간의 기회를 날릴 수 있습니다.

우선, "OOP를 사용하지 마십시오"요소에서 JavaScript 객체는 다른 언어와 비교하여 재생되는 것과 비슷하며 JS가 클래스가 아니기 때문에 계단식 상속 체계를 악용하기 위해 실제로 나가야한다는 것을 기억하십시오. 기반과 합성은 훨씬 더 자연스럽게 온다. JS에서 바보 같은 클래스 또는 프로토 타입 핸드 다운 시스템을 구현하는 경우에는 버리는 것을 고려하십시오. JS에서는 클로저, 프로토 타입을 사용하고 사탕처럼 함수를 전달합니다. 역겹고 불결하고 잘못되었지만 강력하고 간결하며 그것이 우리가 좋아하는 방식입니다.

상속 무거운 접근 방식은 실제로 디자인 패턴에서 반 패턴 (anti-pattern)으로 작성되었으며 15 가지 이상의 수준의 클래스 또는 클래스와 같은 구조를 걸어 본 사람이 어디에서 버려지는 방법의 버전을 찾아 내려고 노력했는지 당신에게서 말할 수 있었다.

왜 그렇게 많은 프로그래머들이이 작업을 좋아하는지 (특히 어떤 이유로 자바 스크립트를 작성하는 자바 사람들) 모르지만, 너무 많이 사용하면 끔찍하고 읽을 수 없으며 완전히 유지할 수 없습니다. 상속은 여기 저기 괜찮지 만 JS에서는 실제로 필요하지 않습니다. 좀 더 매력적인 지름길 인 언어에서는 BunnyRabbit가 포함 된 상속 체인을 통해 좀비 구현을 프랑켄슈타인 화하는 것보다 좀 더 추상적 인 아키텍처 관련 문제에 대비하여 더 추상적 인 아키텍처 문제에 대비해야합니다. 좋은 코드 재사용이 아닙니다. 유지 보수의 악몽입니다.

JS dev Entity / Component / System 기반 엔진은 설계 문제를 분리 한 다음 구현할 객체를 매우 세분화하여 시스템 / 패턴으로 생각합니다. 다시 말해, 어린이는 JavaScript와 같은 언어로 게임을합니다. 그러나 내가 이것을 올바르게 구웠는지 보자.

  • 엔터티-디자인중인 특정 항목입니다. 우리는 적절한 명사 (실제로는 물론 아닙니다)의 방향으로 더 많이 이야기하고 있습니다. 'Scene'이 아니라 'IntroAreaLevelOne'입니다. IntroAreaLevelOne은 일종의 sceneEntity 상자 안에있을 수 있지만 다른 관련 항목과 다른 특정 항목에 중점을두고 있습니다. 코드에서 엔터티는 실제로 유용하거나 사용하기 위해 구현하거나 설정해야하는 여러 가지 요소에 묶인 이름 (또는 ID) 일뿐입니다.

  • 구성 요소-기업이 필요로하는 것들의 유형. 이들은 일반적인 명사입니다. WalkingAnimation처럼. WalkingAnimation 내에서 "Shambling"(좀비 및 식물 몬스터에게 적합) 또는 "ChickenWalker"(역 접합 ed-209ish 로봇 유형에 적합)와 같이보다 구체적으로 얻을 수 있습니다. 참고 : 3D 모델의 렌더링과 어떻게 분리 될 수 있는지 잘 모르겠습니다. 따라서 끔찍한 예일 수도 있지만 숙련 된 게임 개발자보다 JS 전문가에 더 가깝습니다. JS에서는 매핑 메커니즘을 구성 요소와 동일한 상자에 넣었습니다. 자체 구성 요소는 논리 및 시스템이 필요한 경우 구현해야 할 사항을 알려주는 로드맵에 대한 정보를 제공 할 가능성이 높습니다 (ECS에서 일부 구성 요소는 속성 집합 모음 일 뿐임). 구성 요소가 설정되면

  • 시스템-실제 프로그램 고기가 여기에 있습니다. AI 시스템이 구축되고 연결되고 렌더링이 이루어지며 애니메이션 시퀀스가 ​​설정됩니다. 나는 대부분을 상상하고 상상해 보지만 System.AI는 많은 속성을 취하고 함수를 뱉어냅니다. 구현에 궁극적으로 사용되는 객체에 이벤트 핸들러를 추가하는 데 사용됩니다. System.AI의 핵심은 여러 구성 요소 유형을 포함한다는 것입니다. 하나의 구성 요소로 모든 AI를 정리할 수는 있지만 세분화하는 점을 오해하는 것입니다.

목표를 염두에 두십시오 : 우리는 디자이너가 아닌 사람들이 이해하기 쉬운 패러다임 내에서 구성 요소를 최대화하고 일치 시켜서 다양한 종류의 물건을 쉽게 조정할 수 있도록 일종의 GUI 인터페이스를 쉽게 연결할 수 있기를 원합니다. 수정하거나 유지 관리하는 것보다 작성하기가 훨씬 쉬운 대중적인 임의 코드 체계.

JS에서는 아마도 이런 식일 것입니다. 게임 개발자는 내가 끔찍하게 잘못했는지 알려주세요.

//I'm going with simple objects of flags over arrays of component names
//easier to read and can provide an opt-out default
//Assume a genre-bending stealth assassin game

//new (function etc... is a lazy way to define a constructor and auto-instantiate
var npcEntities = new (function NpcEntities(){

    //note: {} in JS is an object literal, a simple obj namespace (a dictionary)
    //plain ol' internal var in JS is akin to a private member
    var default={ //most NPCs are humanoids and critters - why repeat things?
        speedAttributes:true,
        maneuverAttributes:true,
        combatAttributes:true,
        walkingAnimation:true,
        runningAnimation:true,
        combatAnimation:true,
        aiOblivious:true,
        aiAggro:true,
        aiWary:true, //"I heard something!"
        aiFearful:true
    };

    //this. exposes as public

    this.zombie={ //zombies are slow, but keep on coming so don't need these
        runningAnimation:false,
        aiFearful:false
    };

    this.laserTurret={ //most defaults are pointless so ignore 'em
        ignoreDefault:true,
        combatAttributes:true,
        maneuverAttrubtes:true, //turning speed only
    };
    //also this.nerd, this.lawyer and on and on...

    //loop runs on instantiation which we're forcing on the spot

    //note: it would be silly to repeat this loop in other entity collections
    //but I'm spelling it out to keep things straight-forward.
    //Probably a good example of a place where one-level inheritance from
    //a more general entity class might make sense with hurting the pattern.
    //In JS, of course, that would be completely unnecessary. I'd just build a
    //constructor factory with a looping function new objects could access via
    //closure.

    for(var x in npcEntities){

        var thisEntity = npcEntities[x];

        if(!thisEntity.ignoreDefaults){

            thisEntity = someObjectXCopyFunction(defaults,thisEntity);
            //copies entity properties over defaults

        }
        else {
            //remove nonComponent property since we loop again later
            delete thisEntity.ignoreDefaults;
        }
    }
})() //end of entity instantiation

var npcComponents = {
    //all components should have public entityMap properties

    //No systems in use here. Just bundles of related attributes
    speedAttributes: new (function SpeedAttributes(){
        var shamblingBiped = {
            walkingAcceleration:1,
            topWalking:3
        },
        averageMan = {
            walkingAcceleration:3,
            runningAcceleration:4,
            topWalking: 4,
            topRunning: 6
        },
        programmer = {
            walkingAcceleration:1,
            runningAcceleration:100,
            topWalking:2
            topRunning:2000
        }; //end local/private vars

        //left is entity names | right is the component subcategory
        this.entityMap={
            zombie:shamblingBiped,
            lawyer:averageMan,
            nerd:programmer,
            gCostanza:programmer //makes a cameo during the fire-in-nursery stage
        }
    })(), //end speedAttributes

    //Now an example of an AI component - maps to function used to set eventHandlers
    //functions which, because JS is awesome we can pass around like candy
    //I'll just use some imaginary systems on this one

    aiFearful: new (function AiFearful(){
        var averageMan = Systems.AI({ //builds and returns eventSetting function
            fearThreshold:70, //%hitpoints remaining
            fleeFrom:'lastAttacker',
            tactic:'avoidIntercept',
            hazardAwareness:'distracted'
        }),
        programmer = Systems.AI({
            fearThreshold:95,
            fleeFrom:'anythingMoving',
            tactic:'beeline',
            hazardAwareness:'pantsCrappingPanic'
        });//end local vars/private members


         this.entityMap={
            lawyer:averageMan,
            nerd:averageMan, //nerds can run like programmers but are less cowardly
            gCostanza:programmer //makes a cameo during the fire-in-nursery stage
        }
    })(),//and more components...

    //Systems.AI is general and would get called for all the AI components.
    //It basically spits out functions used to set events on NPC objects that
    //determine their behavior. You could do it all in one shot but
    //the idea is to keep it granular enough for designers to actually tweak stuff
    //easily without tugging on developer pantlegs constantly.
    //e.g. SuperZombies, zombies, but slightly tougher, faster, smarter
}//end npcComponents

function createNPCConstructor(npcType){

    var components = npcEntities[npcType],

    //objConstructor is returned but components is still accessible via closure.

    objConstructor = function(){
        for(var x in components){
            //object iteration <property> in <object>

            var thisComponent = components[x];

            if(typeof thisComponent === 'function'){
                thisComponent.apply(this);
                //fires function as if it were a property of instance
                //would allow the function to add additional properties and set
                //event handlers via the 'this' keyword
            }
            else {
                objConstructor.prototype[x] = thisComponent;
                //public property accessed via reference to constructor prototype
                //good for low memory footprint among other things
            }
        }
    }
    return objConstructor;
}

var npcBuilders= {}; //empty object literal
for (var x in npcEntities){
    npcConstructors[x] = createNPCConstructor(x);
}

이제 NPC가 필요할 때마다 npcBuilders.<npcName>();

GUI는 npcEntities 및 components 객체에 연결하여 디자이너가 단순히 구성 요소를 혼합하고 일치시켜 기존 엔터티를 조정하거나 새 엔터티를 만들 수 있습니다 (기본 구성 요소가 아닌 경우에는 별도의 메커니즘이 없지만 특별한 구성 요소는 즉시 추가 할 수 있음) 정의 된 컴포넌트가있는 한 코드.


이 6 년 후, 나는 내 자신의 답을 이해하지 못한다. 이것이 개선 될 수 있습니까?
Erik Reppen

1

나는 사람들이 의견에 제공 한 기사에서 엔터티 시스템에 대해 읽었지만 여전히 의심의 여지가 있으므로 다른 질문을했습니다 .

우선, 내 정의가 잘못되었습니다. 엔터티구성 요소 는 단순한 데이터 홀더이며 시스템은 모든 기능을 제공 합니다 .

나는 여기서 대부분의 질문을 다룰만큼 충분히 배웠으므로 대답하겠습니다.

Scene내가 이야기 했던 수업은 시스템이되어서는 안됩니다. 그러나 모든 엔터티를 보유하고 메시지를 용이하게하며 시스템을 관리 할 수있는 중앙 관리자 여야합니다. 또한 엔터티에 대한 일종의 팩토리 역할을 할 수 있으며 그렇게 사용하기로 결정했습니다. 그것은 수있는 기업의 모든 유형을하지만, 그것은 (성능상의 이유로, 어떤 유형 검사를 수행하지 말아야 그들이하지 않는있는 거 비트) 적절한 시스템에 그 실체를 공급해야합니다.

ES를 구현하는 동안 OOP를 사용해서는 안되며 Adam 은 제안 하지만 바보 같은 데이터 홀더뿐만 아니라 엔티티와 구성 요소에 대한 메소드가있는 객체를 가질 이유가 없습니다.

Renderer단순히 시스템으로 구현 될 수있다. 드로어 블 객체 목록을 유지하고 draw()16ms마다 렌더 구성 요소의 메소드를 호출합니다 .


1
"엔티티와 컴포넌트는 단순한 데이터 홀더이며, 시스템은 모든 기능을 제공합니다." "렌더 컴포넌트의 draw () 메소드를 호출하십시오." 또한 장면 그래프가 렌더러의 일부가 될 수없는 이유를 이해하지 못합니다. 편리한 도구 일뿐입니다. 항상 "드로어 블"컴포넌트를 노드로 구현할 수 있습니다. 장면 그래프가 장면보다 더 많은 것을 담당하게하는 것은 불필요하며 디버그하기가 엉망이 될 것입니다.
dreta

렌더러 인 @dreta (현재 엔진의 비 ES 구현)는 변환, 카메라 변경, 알파 작업을 수행하며 향후에는 다양한 효과, GUI 및 그림자를 그립니다. 그 물건을 그룹화하는 것이 자연스럽게 보였습니다. 장면이 저장 엔터티를 생성해야합니까? 아니면 다른 물건을 보관해야합니까? 생성 부분은 아마도 사용자가 제공 한 구성 요소를한데 모으는 몇 줄일 것입니다. 실제로 아무것도 만들지 않고 인스턴스화하는 것입니다.
jcora

모든 오브젝트를 렌더링 할 수있는 것은 아니며, 모든 오브젝트가 충돌 할 수있는 것은 아니며, 극단적 인 결합을하는 장면 오브젝트와 함께 소리를내는 이유는 무엇입니까? 이것은 작성하고 디버깅하는 데 어려움이 될 것입니다. 엔티티는 오브젝트를 식별하는 데 사용되고 컴포넌트는 데이터를 보유하며 시스템은 데이터를 조작합니다. 왜 RenderingSystem 및 SoundSystem과 같은 적절한 시스템을 사용하지 않고 모든 것을 함께 뭉치고 엔티티에 필요한 모든 구성 요소가있는 경우 해당 시스템을 귀찮게하는 이유는 무엇입니까?
dreta

1
섀도 캐스팅은 일반적으로 광원에 연결되지만 "CastsShadow"구성 요소를 만들어 동적 객체를 렌더링 할 때 찾을 수 있습니다. 2D를하고 있다면 이것은 주문의 기본 문제 일뿐이며 간단한 화가 알고리즘 이이 문제를 해결합니다. TBH 당신은 너무 일찍 걱정하고 있습니다. 당신이 그것을 할 시간이 될 때 이것을 알아낼 것입니다 그리고 당신은 당신의 마음에 그것을 가지고, 지금 당신은 자신을 혼란스럽게하고 있습니다. 처음부터 모든 것을 제대로 얻을 수는 없습니다. 다리에 도착하면 다리를 건너갑니다.
dreta

1
"엔티티 및 구성 요소는 단순한 데이터 홀더이며 시스템은 모든 기능을 제공합니다." 반드시 그런 것은 아닙니다. 그들은 어떤 사람들의 접근 방식에 있습니다. 그러나 다른 사람은 아닙니다. Unity 엔진을 살펴보십시오. 모든 동작은 컴포넌트에 있습니다.
Kylotan

-2

의존성 관리 소개 101.

이 과정은 종속성 주입 및 저장소 디자인에 대한 기본 지식이 있다고 가정합니다.

의존성 주입은 객체가 직접 연결되지 않고 (메시지 / 신호 / 위임 / 무엇을 통해) 서로 대화 할 수있는 멋진 방법입니다.

" new접착제입니다." 라는 문구로갑니다 .

C #에서 이것을 시연 할 것입니다.

public interface IEntity
{
    int[] Position { get; }
    int[] Size { get; }
    bool Update();
    void Render();
}

public interface IRenderSystem
{
    void Draw(IEntity entity);
}

public interface IMovementSystem
{
    bool CanMoveLeft();
    void MoveLeft();
    bool CanMoveRight();
    void MoveRight();
    bool CanMoveUp();
    void MoveUp();
    bool CanMoveDown();
    void MoveDown();
    bool Moved();
    int[] Position { get; set; }
}

public interface IInputSystem
{
    string Direction { get; set; }
}

public class Player : IEntity
{
    private readonly IInputSystem _inputSystem;
    private readonly IMovementSystem _movementSystem;
    private readonly IRenderSystem _renderSystem;
    private readonly int[] _size = new[] { 10, 10 };

    public Player(IRenderSystem renderSystem, IMovementSystem movementSystem, IInputSystem inputSystem)
    {
        _renderSystem = renderSystem;
        _movementSystem = movementSystem;
        _inputSystem = inputSystem;
    }

    public bool Update()
    {
        if (_inputSystem.Direction == "Left" && _movementSystem.CanMoveLeft())
            _movementSystem.MoveLeft();
        if (_inputSystem.Direction == "Right" && _movementSystem.CanMoveRight())
            _movementSystem.MoveRight();
        if (_inputSystem.Direction == "Up" && _movementSystem.CanMoveUp())
            _movementSystem.MoveUp();
        if (_inputSystem.Direction == "Down" && _movementSystem.CanMoveDown())
            _movementSystem.MoveDown();

        return _movementSystem.Moved();
    }

    public void Render()
    {
        if (_movementSystem.Moved())
            _renderSystem.Draw(this);
    }

    public int[] Position
    {
        get { return _movementSystem.Position; }
    }

    public int[] Size
    {
        get { return _size; }
    }
}

이것은 입력, 이동 및 렌더링을위한 기본 시스템입니다. Player클래스는이 경우의 실체와 구성 요소는 인터페이스입니다. Player클래스는 어떻게 콘크리트의 내부에 대해 아무것도 모르는 IRenderSystem, IMovementSystem또는 IInputSystem일을. 그러나 인터페이스 Player는 최종 결과가 달성되는 방식에 관계없이 신호를 보내는 방법 (예 : IRenderSystem에서 Draw 호출)을 제공합니다.

예를 들어 IMovementSystem의 구현을 살펴보십시오.

public interface IGameMap
{
    string LeftOf(int[] currentPosition);
    string RightOf(int[] currentPosition);
    string UpOf(int[] currentPosition);
    string DownOf(int[] currentPosition);
}

public class MovementSystem : IMovementSystem
{
    private readonly IGameMap _gameMap;
    private int[] _previousPosition;
    private readonly int[] _currentPosition;
    public MovementSystem(IGameMap gameMap, int[] initialPosition)
    {
        _gameMap = gameMap;
        _currentPosition = initialPosition;
        _previousPosition = initialPosition;
    }

    public bool CanMoveLeft()
    {
        return _gameMap.LeftOf(_currentPosition) == "Unoccupied";
    }

    public void MoveLeft()
    {
        _previousPosition = _currentPosition;
        _currentPosition[0]--;
    }

    public bool CanMoveRight()
    {
        return _gameMap.RightOf(_currentPosition) == "Unoccupied";
    }

    public void MoveRight()
    {
        _previousPosition = _currentPosition;
        _currentPosition[0]++;
    }

    public bool CanMoveUp()
    {
        return _gameMap.UpOf(_currentPosition) == "Unoccupied";
    }

    public void MoveUp()
    {
        _previousPosition = _currentPosition;
        _currentPosition[1]--;
    }

    public bool CanMoveDown()
    {
        return _gameMap.DownOf(_currentPosition) == "Unoccupied";
    }

    public void MoveDown()
    {
        _previousPosition = _currentPosition;
        _currentPosition[1]++;
    }

    public bool Moved()
    {
        return _previousPosition == _currentPosition;
    }

    public int[] Position
    {
        get { return _currentPosition; }
    }
}

MovementSystem자신의 의존성을 가질 수 있으며 Player신경 쓰지 않아도됩니다. 인터페이스를 사용하여 게임 상태 머신을 만들 수 있습니다 :

public class GameEngine
{
    private readonly List<IEntity> _entities;
    private List<IEntity> _renderQueue; 

    public GameEngine()
    {
        _entities = new List<IEntity>();
    }

    public void RegisterEntity(IEntity entity)
    {
        _entities.Add(entity);
    }

    public void Update()
    {
        _renderQueue = new List<IEntity>();
        foreach (var entity in _entities)
        {
            if(entity.Update())
                _renderQueue.Add(entity);
        }
        // Linq version for those interested
        //_renderQueue.AddRange(_entities.Where(e => e.Update()));
    }

    public void Render()
    {
        foreach (var entity in _renderQueue)
        {
            entity.Render();
        }
    }
}

그리고 그것은 멋진 게임의 시작입니다 (또한 단위 테스트 가능).

그리고 몇 가지 추가 사항과 다형성이 있습니다.

public interface IEntity
{
}

public interface IRenderableEntity : IEntity
{
    void Render();        
}

public interface IUpdateableEntity : IEntity
{
    void Update();
    bool Updated { get; }
}

public interface IRenderSystem
{
    void Draw(IRenderableEntity entity);
}

// new player class
public class Player : IRenderableEntity, IUpdateableEntity
{
    private readonly IInputSystem _inputSystem;
    private readonly IMovementSystem _movementSystem;
    private readonly IRenderSystem _renderSystem;
    private readonly int[] _size = new[] { 10, 10 };

    public Player(IRenderSystem renderSystem, IMovementSystem movementSystem, IInputSystem inputSystem)
    {
        _renderSystem = renderSystem;
        _movementSystem = movementSystem;
        _inputSystem = inputSystem;
    }

    public void Update()
    {
        if (_inputSystem.Direction == "Left" && _movementSystem.CanMoveLeft())
            _movementSystem.MoveLeft();
        if (_inputSystem.Direction == "Right" && _movementSystem.CanMoveRight())
            _movementSystem.MoveRight();
        if (_inputSystem.Direction == "Up" && _movementSystem.CanMoveUp())
            _movementSystem.MoveUp();
        if (_inputSystem.Direction == "Down" && _movementSystem.CanMoveDown())
            _movementSystem.MoveDown();
    }

    public bool Updated
    {
        get { return _movementSystem.Moved(); }
    }

    public void Render()
    {
        if (_movementSystem.Moved())
            _renderSystem.Draw(this);
    }

    public int[] Position
    {
        get { return _movementSystem.Position; }
    }

    public int[] Size
    {
        get { return _size; }
    }
}

public class GameEngine
{
    private readonly List<IEntity> _entities;
    private List<IRenderableEntity> _renderQueue; 

    public GameEngine()
    {
        _entities = new List<IEntity>();
    }

    public void RegisterEntity(IEntity entity)
    {
        _entities.Add(entity);
    }

    public void Update()
    {
        _renderQueue = new List<IRenderableEntity>();
        foreach (var entity in _entities)
        {
            if (entity is IUpdateableEntity)
            {
                var updateEntity = entity as IUpdateableEntity;
                updateEntity.Update();
            }

            if (entity is IRenderableEntity)
            {
                var renderEntity = entity as IRenderableEntity;
                _renderQueue.Add(renderEntity);
            }
        }
    }

    public void Render()
    {
        foreach (var entity in _renderQueue)
        {
            entity.Render();
        }
    }
}

이제 집계 인터페이스 및 느슨한 상속을 기반으로하는 기본 엔티티 / 구성 요소 시스템이 있습니다.


1
이것은 구성 요소 디자인과 반대입니다. :) 한 플레이어가 소리를 내고 다른 플레이어가 소리를 내지 않게하려면 어떻게하겠습니까?
Kikaimaru

@Kikaimaru 소리가 재생되지 않는 ISoundSystem에 전달하십시오. 즉, 아무것도하지 않습니다.
Dustin Kingen

3
-1은 잘못된 코드가 아니라 구성 요소 기반 아키텍처와 전혀 관련이 없기 때문에 구성 요소가 피하려고하는 인터페이스의 확산입니다.
Kylotan

@ Kylotan 내 이해가 잘못되어야한다고 생각합니다.
Dustin Kingen
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.