Varlık Bileşen Sistemi tabanlı motor


9

Not: Bunu Javascript'te programlıyorum, ancak çoğunlukla dil agnostik olmalı.

Motorumu ECS tabanlı bir motora dönüştürmeyi düşünüyorum.

Temel fikri anladım ( not: bu yanlış, cevabıma bakın ):

Varlıklar oyun nesneleridir.
Bileşenler , varlıklara "yapıştırılabilen" işlevsellik ( reactToInput()) veya durum ( position) parçalarıdır.
Sistemlerin yönettikleri ve güncelledikleri varlıkların bir listesi vardır.

Ancak, uygulamayı ve bazı ayrıntıları alacağımdan emin değilim ...

Soru: Bir sistem farklı türden işletmeler üzerinde çalışabilir mi? Genellikle motorumda adı verilen bir sınıf örneği veririm ve Sceneşimdi de bu amaca hizmet edecektir. Bir sahne, oluşturulabilen, güncellenebilen, oluşturmayı (ışıklar) etkileyebilen ve belki de gelecekte 2DSoundEmitternesneler bile olabilecek tüm nesnelerin bir kabıdır . Yüksek seviye bir arayüze sahiptir, böylece kullanıcının yaptığı nesnenin türü scene.add()ve bu tür şeyler hakkında endişelenmesine gerek yoktur .

Bunun Scenebir sistem olabileceğinin farkındayım . Varlıkları alır, saklar ve ardından güncelleme yöntemlerini çağırabilir ve hatta bazı durum değişiklikleri bile yapabilir. Ancak, bir sorun var: yukarıda tarif ettiğim gibi, farklı nesne türleriScene beslenebilir ! Diyelim ki, bir sahnede hem yenilenebilir nesnelerin ("çekilebilir") ve ışıkların bulunduğu bir durumda ne yapmalıyım ? Etkileşimden önce yazım denetimlerini yapmalı mıyım? Veya daha düşük bir seviyede çözmem gerekir mi: herhangi bir nesneye eklenebilecek bir bileşen yapın ve ışık sadece bileşenler ve bileşenlerle bir varlık olacaktır . Kabul edilebilir mi?LightSourceLightSourcePosition

Ayrıca, hala geleneksel miras ve geleneksel sınıfları kullanmak iyi bir uygulama mudur? Örneğin, ne olacağımı Rendereranlayamıyorum! Bu bir sistem değildir, çünkü tek işlevi bir kamerayı ve bir sahneyi almak, her şeyi oluşturmak ve efektler uygulamaktır (gölgeler gibi). Ayrıca oyunun bağlamını, genişliğini ve yüksekliğini yönetir, çeviri yapar ... Ama yine de bir sistem değil!

Düzenleme: ECS'de bulduğunuz kaynakları bağlayabilir misiniz? İyi olanları bulmakta zorlanıyorum.


2
Bu sayfadaki cevabı tekrar göndermek yerine, sadece bu bağlantıyı vereceğim: gamedev.stackexchange.com/questions/23533/… Varlıktan türetilmemelidir, varlıklar arasındaki herhangi bir fark bileşenler aracılığıyla gerçekleştirilmelidir. Genel olarak her ana sistem için bir arayüze ihtiyacınız olacaktır (Görüntü Oluşturma, Fizik, Ağ, Giriş, Ses, vb.). Oluşturucumu kurmamın yolu, sahneyi yenilenebilir varlıklar için sorgulamak ve sahne yöneticisi, oluşturma bilgileri için üzerinde bir oluşturma bileşeni olan her bir varlık ister.
Nic Foster

1
T = Makine blogunda bileşen tasarımı (iyi bir tane istediğinden beri)
John McDonald

Bir varlık çerçevesinin kodu ve tartışması: gamadu.com/artemis
Patrick Hughes

@JohnMcDonald, ılımlılık beklemesine rağmen bu yazı hakkında bir yorum yazdım. Burada görebilirsiniz: t-machine.org/index.php/2007/12/22/… . Ben "Yannbane".
jcora

Ayrıca, @NicFoster, John'un T = Machine'de bağlandığı makale cevabınızdan biraz farklı bir şeyi açıklıyor ... Dave'e göre, varlıkların bir bileşen listesi yok, sadece bir isim. "Flsjn304" gibi - bu bir varlıktır. "Bir yerde" saklanır. Ve bileşenleri gerçekten sistemlerin içinde tutuyorsa , bu bana çok garip gelen şeyi anlamak için tekrar okumalıyım !
jcora

Yanıtlar:


6

Bakalım bir web / UI JS dev olarak anlamaya çalışarak yardımcı olabilir miyim. Ayrıca, dil agnostisizminde fazla ileri gitmeyin. Diğer dillerde kurulan birçok desen çalışmaya değer, ancak esnekliği nedeniyle JS'de çok farklı uygulanabilir veya dilin dövülebilir doğası nedeniyle gerçekten gerekli değildir. JS kod kodunuzu, daha klasik bir OOP-yönelimli dille aynı sınırlara sahip olarak yazarsanız, bazı fırsatlar yaratabilirsiniz.

Her şeyden önce, "OOP kullanma" faktörü üzerinde, JavaScript nesnelerinin diğer dillerle karşılaştırıldığında playdough gibi olduğunu ve JS sınıf olmadığı için basamaklı bir miras şeması kabusu oluşturmak için yolunuzdan çıkmanız gerektiğini unutmayın. temelli ve kompozisyon çok daha doğal gelir. JS'nize aptalca bir sınıf veya prototip teslim etme sistemi uyguluyorsanız, onu kaldırmayı düşünün. JS'de kapaklar, prototipler kullanıyoruz ve şekerleme gibi işlevlerden geçiyoruz. İğrenç ve pis ve yanlış ama aynı zamanda güçlü, özlü ve sevdiğimiz yol bu.

Kalıtım ağır yaklaşımları aslında Tasarım Desenlerinde bir anti-desen olarak ve iyi bir nedenden dolayı, bir yöntemin bozulan versiyonunun nerede olduğunu anlamak ve denemek için 15+ sınıf veya sınıf benzeri yapıları gözden geçiren herkes olarak iyi bir şekilde belirtilmiştir. sana geliyordu diyebilirim.

Neden bu kadar çok programcı bunu sevdiğini bilmiyorum (özellikle bir sebepten dolayı JavaScript yazan java çocuklar), ama aşırı kullanıldığında korkunç, okunaksız ve tamamen sürdürülemez. Kalıtım burada ve orada iyidir, ama JS'de gerçekten gerekli değildir. Daha cazip bir kısayol olduğu dillerde, bir BunnyRabbit içeren bir miras zinciri aracılığıyla bir zombi uygulamasını frankensteining gibi daha gerçek modelleme şemalarından ziyade daha soyut mimari kaygılar için ayrılmalıdır. Bu kodun tekrar kullanılması iyi değil. Bu bir bakım kabusu.

JS dev Entity / Component / System tabanlı motorlar olarak bana tasarım kaygılarını ayırmak için bir sistem / model olarak vururlar ve daha sonra nesneleri çok ayrıntılı bir düzeyde uygulama için birleştiririz. Başka bir deyişle, çocuk oyuncağı JavaScript gibi bir dilde. Ama önce bunu doğru bir şekilde sallıyor muyum bakayım.

  • Varlık - Tasarladığınız belirli şey. Daha çok özel isimler yönünde konuşuyoruz (ama aslında değil). 'Sahne' değil, 'IntroAreaLevelOne'. IntroAreaLevelOne, bir sahneEntity kutusunun içinde oturabilir, ancak diğer ilgili şeylerden farklı belirli bir şeye odaklanıyoruz. Kodda, bir varlık gerçekten yararlı olması için uygulanması veya kurulması (bileşenler) gereken bir grup şeyle bağlantılı bir addır.

  • Bileşenler - Bir işletmenin ihtiyaç duyduğu şey türleri. Bunlar genel isimler. WalkingAnimation gibi. WalkingAnimation içinde "Shambling" (zombiler ve bitki canavarları için iyi bir seçim) veya "ChickenWalker" (ters eklemli ed-209ish robot tipleri için harika) gibi daha spesifik olabiliriz. Not: Bunun böyle bir 3D modelin oluşturulmasından nasıl ayrılabileceğinden emin değilim - bu yüzden belki bir saçmalık örneği ama deneyimli bir oyun geliştiricisinden daha çok bir JS pro'suyum. JS'de eşleme mekanizmasını bileşenlerle aynı kutuya koyardım. Bileşenlerin kendi başlarına mantıklı olmaları ve sistemlerin hatta sistemlere ihtiyaç duyulursa ne uygulayacaklarını söyleyen bir yol haritasının daha hafif olması muhtemeldir (ECS denememde bazı bileşenler sadece özellik kümeleri koleksiyonlarıdır). Bir bileşen kurulduktan sonra, '

  • Sistemler - Gerçek programmey et burada. AI sistemleri inşa edilir ve bağlantılandırılır, Oluşturma gerçekleştirilir, animasyon dizileri oluşturulur, vb ... Bunları toparlayıp çoğunlukla hayal gücüne bırakıyorum, ancak System.AI'de bir grup özellik alır ve bir işlevi tükürür. sonuçta uygulamada kullanılan nesneye olay işleyicileri eklemek için kullanılır. System.AI ile ilgili en önemli şey, çoklu bileşen türlerini kapsamasıdır. Tüm AI öğelerini tek bir bileşenle sıralayabilirsiniz, ancak bunu yapmak, şeyleri ayrıntılı hale getirme noktasını yanlış anlamaktır.

Hedeflere Dikkat Edin: Tasarımcı olmayanlar için, bir paradigma içindeki bileşenleri maksimize ederek ve eşleştirerek farklı şeyleri kolayca değiştirmeleri için bir tür GUI arabirimini takmayı kolaylaştırmak istiyoruz ve uzak durmak istiyoruz. Yazmak, değiştirmek veya korumaktan çok daha kolay olan popüler rasgele kod şemaları.

Yani JS'de, belki böyle bir şey. Oyun geliştiricileri lütfen bana çok yanlış anladıysam söyle:

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

Artık bir NPC'ye ihtiyacınız olduğunda, npcBuilders.<npcName>();

Bir GUI npcEntities ve bileşenler nesnelerine takılabilir ve tasarımcıların eski varlıkları değiştirmesine veya bileşenleri basitçe karıştırarak ve eşleştirerek yeni varlıklar oluşturmasına izin verebilir (ancak varsayılan olmayan bileşenler için herhangi bir mekanizma olmasa da, özel bileşenler anında eklenebilir. bunun için tanımlanmış bir bileşen olduğu sürece kodlayın.


Buna altı yıl sonra baktığımda kendi cevabımı anladığımdan emin değilim. Bu geliştirilebilir mi?
Erik Reppen

1

Varlık Sistemlerini, insanların yorumlarda yer aldığı türden makalelerde okudum, ancak hala bazı şüphelerim vardı, bu yüzden başka bir soru sordum .

Öncelikle tanımlarım yanlıştı. Varlıklar ve Bileşenler sadece aptal veri tutucularken, sistemler tüm işlevleri sağlar.

Sorumun çoğunu burada kapsayacak kadar öğrendim, bu yüzden cevaplayacağım.

SceneBahsettiğim sınıf bir sistem olmamalıdır. Bununla birlikte, tüm varlıkları tutabilen, mesajları kolaylaştıran ve hatta sistemleri yönetebilen merkezi bir yönetici olmalıdır. Aynı zamanda varlıklar için bir tür fabrika olarak da işlev görebilir ve bunu böyle kullanmaya karar verdim. Bu edebilir bir varlık her türlü alacak, ancak o zaman (performans nedenleriyle, herhangi bir tip kontrolleri gerçekleştirin olmamalı onlar sürece ediyoruz bitsel) uygun bir sisteme bu varlık beslemek gerekir.

Bir ES uygularken OOP kullanmamalıyım, diyor Adam, ama sadece aptal veri sahipleri için değil, hem varlıklar hem de bileşenler için yöntemlere sahip nesneler olmamak için hiçbir neden bulamıyorum.

RendererBasit bir sistem olarak uygulanabilir. Çekilebilir nesnelerin bir listesini tutar ve draw()her 16 ms'de bir oluşturma bileşeninin yöntemini çağırır .


1
"Varlıklar ve Bileşenler sadece aptal veri tutucularken, sistemler tüm işlevselliği sağlar" "oluşturma bileşenlerinin draw () yöntemini" hala karıştırmayın. " Ayrıca Sahne grafiğinizin neden Renderer'ın bir parçası olamayacağını anlamıyorum, sadece kullanışlı bir araç, her zaman bir düğüm olarak "çekilebilir" bileşeninizi uygulayabilirsiniz. Sahne grafiğini sahneden daha fazla sorumlu kılmak sadece gereksiz ve hata ayıklamak için bir karışıklık olacağından eminim.
dreta

@dreta, şu anda oluşturucu (motorun ES olmayan uygulaması) dönüşümler, kamera değişiklikleri, alfa şeyler yapıyor ve gelecekte çeşitli efektler, GUI ve gölgeler çizecek. Bu şeyleri gruplandırmak doğal görünüyordu. Sahne, depolayan bir varlık yaratmaktan sorumlu olmamalı mı? Yoksa başka bir şey onları mı saklamalı? Yaratılış kısmı, muhtemelen kullanıcı tarafından sağlanan bileşenleri bir araya getirmenin sadece birkaç satırıdır, gerçekten hiçbir şey "yaratmaz", sadece somutlaştırır.
jcora

her nesne yenilenemez, her nesne çarpılamaz ya da ses çıkaramaz, sahne nesnenizle aşırı bağlantı yapıyorsunuz, neden? bu sadece yazmak ve hata ayıklamak için bir acı olacak. Varlıklar bir nesneyi tanımlamak için kullanılır, bileşenler veri tutar ve veriler üzerinde çalışır. RenderingSystem ve SoundSystem gibi uygun sistemlere sahip olmak yerine neden hepsini bir araya getirip bu sistemleri yalnızca bir varlığın gerekli tüm bileşenlere sahip olması durumunda rahatsız edersiniz?
dreta

1
gölge döküm genellikle ışık kaynaklarına bağlıdır, ancak sadece bir bileşen "CastsShadow" oluşturabilir ve dinamik nesneleri oluştururken arayabilirsiniz. 2D yapıyorsanız, bu sadece siparişin temel bir sorunudur, basit bir ressam algoritması bu sorunu sizin için çözecektir. TBH çok erken endişeleniyorsun. bunu yapma zamanı geldiğinde bunu anlayacaksınız ve sadece aklınızda olan şey var, şu anda sadece kendinizi karıştırıyorsunuz. her şeyi ilk seferde doğru yapmayı ümit edemezsiniz, bu gerçekleşmeyecek. o köprüyü geçtiğinizde geçeceksiniz.
dreta

1
"Sistemler tüm işlevleri sağlarken, Varlıklar ve Bileşenler sadece aptal veri sahipleri." Şart değil. Bazı insanların yaklaşımları içindeler. Ama diğerleri değil. Unity motoruna bakın - tüm davranış bileşenlerde.
Kylotan

-2

Bağımlılık yönetimine giriş 101.

Bu ders, bağımlılık enjeksiyonu ve depo tasarımı hakkında temel bilgiye sahip olduğunuzu varsayar.

Bağımlılık enjeksiyonu, nesnelerin doğrudan bağlanmadan birbirleriyle (mesajlar / sinyaller / delegeler / herhangi bir şekilde) konuşmaları için süslü bir yoldur.

" newYapıştırıcıdır" ifadesiyle geçer.

Bunu C # 'da göstereceğim.

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

Bu Giriş, Hareket ve Görüntü Oluşturma için temel bir sistemdir. PlayerSınıf bu durumda varlıktır ve bileşenleri Arayüzleri vardır. PlayerSınıf nasıl somut donanımları hakkında hiçbir şey bilmeyen IRenderSystem, IMovementSystemya IInputSystemişin. Ancak, arabirimler Player, son sonucun nasıl elde edildiğine bağlı olmaksızın sinyal göndermesi için bir yol sağlar (örn. IRenderSystem üzerinde Draw çağırmak).

Örneğin, IMovementSystem uygulamamı ele alalım:

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

MovementSystemkendi bağımlılıkları olabilir ve Playerumursamaz bile. Arayüzler kullanılarak bir oyun durumu makinesi oluşturulabilir:

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

Ve bu güzel bir oyunun başlangıcıdır (aynı zamanda birim test edilebilir).

Ve birkaç ekleme ve bazı polimorfizm ile:

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

Artık toplanan arayüzlere ve gevşek mirasa dayanan ilkel bir varlık / bileşen sistemimiz var.


1
Bu, bileşen tasarımına karşıdır :) Bir oyuncunun ses çıkarmasını ve diğerinin çıkmasını istemezseniz ne yapardınız?
Kikaimaru

@Kikaimaru Ses çalmayan bir ISoundSystem ile geçin. Yani hiçbir şey yapmaz.
Dustin Kingen

3
-1, kötü kod olduğu için değil, bileşen tabanlı mimariyle hiç ilgili olmadığı için - aslında bileşenlerin kaçınmaya çalıştığı bu gibi arabirimlerin çoğalmasıdır.
Kylotan

@Kylotan Sanırım anlayışım yanlış olmalı.
Dustin Kingen
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.