Silnik oparty na systemie Entity Component System


9

Uwaga: Programuję to w Javascripcie, ale w większości powinien to być język.

Zastanawiam się nad zmianą silnika na ECS.

Dostaję podstawowy pomysł ( uwaga: to źle, patrz moja odpowiedź ):

Istoty są obiektami gry.
Składniki to bity funkcjonalności ( reactToInput()) lub state ( position), które można „przykleić” do elementów.
Systemy mają listę podmiotów, którymi zarządzają i aktualizują.

Ale nie jestem pewien, czy otrzymałem implementację i kilka szczegółów ...

Pytanie: czy system może działać na różnych rodzajach podmiotów? Zazwyczaj podam przykład klasy wywoływanej Scenew moim silniku, która również teraz będzie służyć temu celowi. Scena jest kontenerem wszystkich obiektów, które można renderować, aktualizować, wpływać na rendering (światła), a może w przyszłości nawet 2DSoundEmitterobiekty. Ma interfejs wysokiego poziomu, dzięki czemu użytkownik nie musi się martwić o typ obiektu, który scene.add()wymyśla i tego typu rzeczy.

Zdaję sobie sprawę, że Scenemoże to być system. Pobiera jednostki, przechowuje je, a następnie może wywoływać ich metody aktualizacji, a może nawet wprowadzić pewne zmiany stanu. Ale jest problem: jak opisałem powyżej, Scenemożna karmić różnego rodzaju obiektami! Co powinienem zrobić, powiedzmy, w sytuacji, gdy scena zawiera zarówno obiekty do renderowania („drawable”), jak i światła? Czy powinienem sprawić, by sprawdzał typ jednostek przed interakcją? Czy należy go rozwiązać na jeszcze niższym poziomie: dokonać LightSourceskładnik, który można dodać do dowolnego obiektu, a światło będzie tylko podmiot posiadający LightSourcei Positionkomponenty. Czy to jest dopuszczalne?

Ponadto, czy dobrą praktyką jest nadal korzystanie z tradycyjnego dziedziczenia i klas tradycyjnych? Na przykład po prostu nie mogę ustalić, jaki byłby mój Renderer! To nie jest system, ponieważ jego jedyną funkcją jest rejestrowanie kamery i sceny, renderowanie wszystkiego i stosowanie efektów (takich jak cienie). Zarządza także kontekstem, szerokością i wysokością gry, wykonuje tłumaczenia ... Ale to wciąż nie jest system!

Edycja: czy możesz połączyć jakieś zasoby znalezione w ECS? Mam problem ze znalezieniem dobrych.


2
Zamiast ponownie opublikować odpowiedź na tej stronie, podam tylko ten link: gamedev.stackexchange.com/questions/23533/... Nie należy wyprowadzać encji, wszelkie różnice między jednostkami powinny być osiągane poprzez komponenty. Zasadniczo potrzebujesz interfejsu dla każdego głównego systemu (renderowanie, fizyka, sieć, wejście, audio itp.). Sposób, w jaki skonfigurowałem mój moduł renderujący, polega na zapytaniu w scenie o elementy do renderowania, a menedżer scen pyta następnie każdą jednostkę, która ma komponent renderujący, o informacje dotyczące renderowania.
Nic Foster,

1
Projektowanie komponentów na blogu T = Machine (ponieważ prosiłeś o dobry)
John McDonald

Kod i omówienie frameworku encji: gamadu.com/artemis
Patrick Hughes

@JohnMcDonald, napisałem komentarz do tego artykułu, chociaż oczekuje on moderacji. Możesz to zobaczyć tutaj: t-machine.org/index.php/2007/12/22/… . Jestem „Yannbane”.
jcora,

Ponadto, @NicFoster, artykuł, do którego John podłączył link na T = Machine, opisuje coś innego niż twoja odpowiedź ... Do tego Dave'a istoty nie mają listy komponentów, są tylko nazwą. Jak „flsjn304” - to byt. Jest przechowywany „gdzieś”. I muszę ponownie przeczytać rzecz, aby zrozumieć, czy rzeczywiście trzyma komponenty w systemach , co wydaje mi się bardzo dziwne!
jcora,

Odpowiedzi:


6

Pokażę, czy próbując zrozumieć jako programistę JS web / UI, mogę pomóc. Nie sięgaj też zbyt daleko w agnostycyzm językowy. Warto zapoznać się z wieloma wzorcami ustalonymi w innych językach, ale można je stosować w JS w różny sposób ze względu na jego elastyczność lub naprawdę nie są konieczne ze względu na plastyczny charakter języka. Możesz skorzystać z okazji, jeśli napiszesz swój kod, myśląc, że JS ma ten sam zestaw granic, co bardziej klasyczny język zorientowany na OOP.

Przede wszystkim, jeśli chodzi o czynnik „nie używaj OOP”, pamiętaj, że obiekty JavaScript są jak placek zabaw w porównaniu do innych języków i faktycznie musisz zrobić wszystko, aby zbudować koszmar kaskadowo-spadkowy, ponieważ JS nie jest klasą na podstawie i komponowanie przychodzi o wiele bardziej naturalnie. Jeśli wdrażasz jakiś głupiutki lub prototypowy system hand-me-down w swoim JS, rozważ to porzucenie. W JS używamy zamknięć, prototypów i przekazujemy funkcje jak cukierki. Jest obrzydliwe, brudne i złe, ale także mocne, zwięzłe i tak to lubimy.

Podejścia oparte na dziedziczeniu są w rzeczywistości przedstawiane jako anty-wzorzec we wzorach projektowych i nie bez powodu, jak każdy, kto zszedł ponad 15 poziomów klas lub struktur podobnych do klas, aby dowiedzieć się, gdzie, do cholery, wypaliła wersję metody przychodził z może ci powiedzieć.

Nie wiem, dlaczego tak wielu programistów uwielbia to robić (zwłaszcza faceci Java z jakiegoś powodu piszą JavaScript), ale jest to okropne, nieczytelne i całkowicie nie do utrzymania, gdy jest przyzwyczajone do nadmiaru. Dziedziczenie jest w porządku tu i tam, ale tak naprawdę nie jest konieczne w JS. W językach, w których jest to bardziej kuszący skrót, powinien być naprawdę zarezerwowany dla bardziej abstrakcyjnych problemów związanych z architekturą, a nie dla bardziej dosłownych schematów modelowania, takich jak frankensteining implementacji zombie poprzez łańcuch dziedziczenia obejmujący BunnyRabbit, ponieważ tak się stało. To nie jest dobre ponowne użycie kodu. To koszmar konserwacji.

Jako silniki oparte na jednostkach / komponentach / systemach JS dev uderzyły mnie jako system / wzorzec do rozłączania problemów projektowych, a następnie komponowania obiektów do implementacji na bardzo szczegółowym poziomie. Innymi słowy, zabawa dziecka w języku takim jak JavaScript. Ale pozwól, że zobaczę, czy najpierw mam problemy z tym.

  • Podmiot - konkretna rzecz, którą projektujesz. Mówimy bardziej w kierunku właściwych rzeczowników (ale oczywiście nie). Nie „Scena”, ale „IntroAreaLevelOne”. IntroAreaLevelOne może znajdować się wewnątrz jakiegoś pola sceneEntity, ale skupiamy się na czymś innym niż inne powiązane rzeczy. W kodzie jednostka jest tak naprawdę tylko nazwą (lub identyfikatorem) powiązaną z szeregiem rzeczy, które musi zaimplementować lub ustanowić (komponenty), aby była użyteczna.

  • Komponenty - rodzaje rzeczy, których potrzebuje jednostka. To są rzeczowniki ogólne. Jak WalkingAnimation. W WalkingAnimation możemy uzyskać bardziej szczegółowe, takie jak „Shambling” (dobry wybór dla zombie i potworów roślinnych) lub „ChickenWalker” (doskonały dla robotów typu ed-209ish z odwrotnym połączeniem). Uwaga: Nie jestem pewien, jak to może oddzielić od renderowania takiego modelu 3D - więc może to bzdura, ale jestem bardziej pro JS niż doświadczonym twórcą gier. W JS umieściłbym mechanizm mapowania w tym samym pudełku z komponentami. Komponenty same w sobie prawdopodobnie nie są logiczne i stanowią raczej mapę drogową informującą systemy, co należy wdrożyć, jeśli systemy są nawet potrzebne (w mojej próbie ECS niektóre komponenty to tylko zbiory zestawów właściwości). Po ustanowieniu komponentu „

  • Systemy - prawdziwe mięso programowe jest tutaj. Systemy AI są zbudowane i połączone, renderowanie zostało osiągnięte, ustalone sekwencje animacji itp. Rozwalam i pozostawiam je głównie wyobraźni, ale w przykładzie System.AI bierze wiele właściwości i wydziela funkcję, która służy do dodawania procedur obsługi zdarzeń do obiektu, który ostatecznie zostaje wykorzystany przy implementacji. Kluczową rzeczą w System.AI jest to, że obejmuje wiele typów komponentów. Możesz uporządkować wszystkie elementy AI za pomocą jednego komponentu, ale zrobienie tego to niezrozumienie sensu granulacji.

Pamiętaj o celach: Chcemy ułatwić podłączenie interfejsu GUI, aby osoby niebędące projektantami mogły łatwo modyfikować różne rzeczy poprzez maksymalizowanie i dopasowywanie komponentów w paradygmacie, który ma dla nich sens, i chcemy uciec od popularne schematy dowolnego kodu, które są o wiele łatwiejsze do napisania niż do modyfikacji lub utrzymania.

W JS może coś takiego. Deweloperzy gier, proszę powiedz mi, czy okropnie się mylę:

//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);
}

Teraz, kiedy potrzebujesz NPC, możesz budować z nim npcBuilders.<npcName>();

GUI może podłączyć się do obiektów npcEntities i komponentów i pozwolić projektantom na modyfikowanie starych encji lub tworzenie nowych encji poprzez proste mieszanie i dopasowanie komponentów (chociaż nie ma tam żadnego mechanizmu dla komponentów innych niż domyślne, ale specjalne komponenty mogą być dodawane na bieżąco kod, o ile istnieje dla niego zdefiniowany komponent.


Patrząc na to sześć lat później, nie jestem pewien, czy rozumiem własną odpowiedź. Czy można to poprawić?
Erik Reppen

1

Przeczytałem o Entity Systems w artykułach, które mili ludzie zamieszczali w komentarzach, ale wciąż miałem wątpliwości, więc zadałem kolejne pytanie .

Po pierwsze, moje definicje były błędne. Podmioty i komponenty to po prostu głupie posiadacze danych, podczas gdy systemy zapewniają całą funkcjonalność.

Nauczyłem się wystarczająco, aby ująć tutaj większość mojego pytania, więc odpowiem na to pytanie.

SceneKlasa mówiłam o nie powinien być system. Powinien to być jednak centralny menedżer, który może przechowywać wszystkie podmioty, ułatwiać wiadomości, a może nawet zarządzać systemami. Może również funkcjonować jako rodzaj fabryki dla podmiotów i postanowiłem go tak wykorzystać. To może wziąć każdy rodzaj podmiotu, ale to musi karmić temu podmiotowi odpowiedniego systemu (które, ze względu na wydajność, nie należy wykonywać żadnych typu kontroli, chyba że są one mnożenie).

Nie powinienem używać żadnego OOP podczas implementowania ES, sugeruje Adam, ale nie znajduję powodu, by nie mieć obiektów z metodami dla encji i komponentów, a nie tylko dla głupich posiadaczy danych.

RendererMogą być realizowane po prostu jako system. Utrzymywałby listę obiektów rysowalnych i wywoływałby draw()metodę ich komponentu renderowania co 16 ms.


1
„Podmioty i komponenty są po prostu głupimi posiadaczami danych, podczas gdy systemy zapewniają całą funkcjonalność”. „Wywołaj metodę draw () swojego komponentu renderującego” „wciąż jesteś zdezorientowany, poza tym, że metoda„ draw ”całkowicie pokonuje cel systemu renderowania. Nie rozumiem też, dlaczego wykres Sceny nie może być częścią Renderera, jest to po prostu wygodne narzędzie, zawsze możesz zaimplementować swój „wyciągalny” komponent jako węzeł. Sprawienie, by wykres sceny był odpowiedzialny za więcej niż scena, jest po prostu niepotrzebne i jestem pewien, że będzie to bałagan do debugowania.
dreta

@dreta, obecnie renderer (implementacja silnika poza ES) dokonuje transformacji, zmian w kamerze, rzeczy alfa, aw przyszłości rysuje różne efekty, GUI i cienie. Po prostu wydawało się naturalne grupowanie tych rzeczy. Czy scena nie powinna być odpowiedzialna za tworzenie obiektów do przechowywania? A może coś innego je przechowywać? Część tworząca to prawdopodobnie tylko kilka linii agregacji komponentów dostarczonych przez użytkownika, nie jest tak naprawdę „tworzeniem” niczego, tylko instancją.
jcora

nie każdy obiekt jest renderowany, nie każdy obiekt może zderzać się lub emitować dźwięk, z obiektem sceny wykonujesz ekstremalne sprzężenie, dlaczego? będzie to po prostu pisanie i debugowanie. Jednostki służą do identyfikacji obiektu, komponenty przechowują dane, a systemy operują na danych. Dlaczego miałbyś to wszystko złączyć zamiast posiadania odpowiednich systemów, takich jak RenderingSystem i SoundSystem, i niepokoić te systemy tylko, jeśli jednostka ma wszystkie wymagane komponenty.
dreta

1
Rzucanie cieni jest zwykle dołączane do źródeł światła, ale można po prostu utworzyć komponent „CastsShadow” i poszukać go podczas renderowania obiektów dynamicznych. jeśli robisz 2D, to jest to po prostu podstawowa kwestia zamawiania, prosty algorytm malarza rozwiąże ten problem. TBH martwisz się zbyt wcześnie. odkryjesz to, kiedy nadejdzie czas, aby to zrobić, i masz tylko to na myśli, teraz po prostu się mylisz. nie możesz mieć nadziei, że wszystko będzie dobrze za pierwszym razem, to po prostu się nie stanie. przekroczysz ten most, kiedy do niego dojdziesz.
dreta

1
„Podmioty i komponenty to po prostu głupie posiadacze danych, podczas gdy systemy zapewniają całą funkcjonalność”. Niekoniecznie. Są w podejściu niektórych ludzi. Ale nie inni. Spójrz na silnik Unity - całe zachowanie jest w komponentach.
Kylotan

-2

Wprowadzenie do zarządzania zależnościami 101.

Kurs zakłada, że ​​masz podstawową wiedzę na temat wstrzykiwania zależności i projektowania repozytorium.

Wstrzykiwanie zależności jest tylko fantazyjnym sposobem, aby obiekty rozmawiały ze sobą (za pośrednictwem wiadomości / sygnałów / delegatów / cokolwiek) bez bezpośredniego połączenia.

Jest tak: „ newjest klejem”.

Pokażę to w języku 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; }
    }
}

Jest to podstawowy system do wprowadzania, przemieszczania i renderowania. W Playertym przypadku klasa jest bytem, ​​a komponenty to interfejsy. PlayerKlasa wie nic na temat wewnętrznych, w jaki sposób konkretny IRenderSystem, IMovementSystemlub IInputSystempracy. Jednak interfejsy umożliwiają Playerwysyłanie sygnałów (np. Wywołanie Draw na IRenderSystem) bez uzależnienia od tego, w jaki sposób osiągnięty zostanie wynik końcowy.

Weźmy na przykład moją implementację 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; }
    }
}

MovementSystemmoże mieć swoje zależności i Playernawet by się tym nie przejmował. Za pomocą interfejsów można utworzyć automat stanów gry:

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();
        }
    }
}

I to jest początek uroczej gry (która może być również testowana jednostkowo).

I z kilkoma dodatkami i pewnym polimorfizmem:

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();
        }
    }
}

Mamy teraz prymitywny system encji / komponentów oparty na interfejsach agregujących i luźnym dziedziczeniu.


1
Jest to sprzeczne z projektowaniem komponentów :) Co byś zrobił, gdyby jeden odtwarzacz wydawał dźwięki, a drugi nie?
Kikaimaru,

@Kikaimaru Pass w systemie ISoundSystem, który nie odtwarza dźwięku. tzn. nie robi nic.
Dustin Kingen,

3
-1, nie dlatego, że jest to zły kod, ale dlatego, że nie ma on żadnego związku z architekturą opartą na komponentach - w rzeczywistości jest to rozpowszechnianie takich interfejsów, których komponenty starają się unikać.
Kylotan,

@Kylotan Myślę, że moje zrozumienie musi być błędne.
Dustin Kingen,
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.