Harici bileşen yöneticileriyle bir varlık sistemi mi düzenliyorsunuz?


13

Yukarıdan aşağıya çok oyunculu bir 2D shooter oyunu için bir oyun motoru tasarlıyorum, diğer yukarıdan aşağıya atıcı oyunlar için makul şekilde tekrar kullanılabilir olmak istiyorum. Şu anda içinde bir varlık sistemi gibi bir şeyin nasıl tasarlanması gerektiğini düşünüyorum. Önce şunu düşündüm:

EntityManager adlı bir sınıfım var. Update adlı bir yöntem ve Draw adlı başka bir yöntem uygulamalıdır. Mantık ve İşleme'yi ayırmamın nedeni, bağımsız bir sunucu çalıştırıyorsanız Draw yöntemini atlayabilmemdir.

EntityManager, BaseEntity türünde nesnelerin bir listesine sahiptir. Her varlığın EntityModel (bir varlığın çekilebilir temsili), EntityNetworkInterface ve EntityPhysicalBody gibi bileşenlerin bir listesi vardır.

EntityManager ayrıca EntityRenderManager, EntityNetworkManager ve EntityPhysicsManager gibi bileşen yöneticilerinin bir listesine sahiptir. Her bileşen yöneticisi varlık bileşenlerine başvuruları tutar. Bu kodu kurumun kendi sınıfının dışına taşımak ve toplu olarak yapmak için çeşitli nedenler vardır. Örneğin, oyun için harici bir fizik kütüphanesi olan Box2D kullanıyorum. Box2D'de, önce gövdeleri ve şekilleri bir dünyaya (bu durumda EntityPhysicsManager'a ait olan) ekler ve çarpışma geri çağrılarını (sistemimdeki varlık nesnesinin kendisine gönderilir) eklersiniz. Daha sonra sistemdeki her şeyi simüle eden bir işlev çalıştırırsınız. Bunu yapmak için böyle bir harici bileşen yöneticisinde yapmaktan daha iyi bir çözüm bulmakta zorlanıyorum.

Varlık oluşturma şu şekilde yapılır: EntityManager bu sınıfın bir varlığının nasıl oluşturulacağını kaydeden RegisterEntity (entityClass, factory) yöntemini uygular . Ayrıca BaseEntity türünde bir nesne döndürecek CreateEntity (entityClass) yöntemini uygular.

Şimdi sorunum geliyor: Bir bileşene yapılan başvuru, bileşen yöneticilerine nasıl kaydedilir? Bir fabrikadan / kapanıştan bileşen yöneticilerine nasıl başvuracağım konusunda hiçbir fikrim yok.


Belki de bunun melez bir sistem olması gerekip gerekmediğini bilmiyorum, ancak "yöneticilerinizin" genel olarak "sistemler" olarak adlandırdığım şey olduğu anlaşılıyor. yani Varlık soyut bir kimliktir; Bileşen bir veri havuzudur; ve "yönetici" olarak adlandırdığınız şey genellikle "Sistem" olarak adlandırılan şeydir. Kelime dağarcığını doğru şekilde mi yorumluyorum?
BRPocock


Gamadu.com/artemis adresine bir göz atın ve yöntemlerinin sorunuza cevap verip vermediğine bakın.
Patrick Hughes

1
Bir varlık sistemini tasarlamanın tek bir yolu yoktur, çünkü onun tanımı üzerinde çok az fikir birliği vardır. @BRPocock'un neyi anlattığı ve Artemis'in kullandığı şey bu blogda daha ayrıntılı olarak açıklanmıştır: t-machine.org/index.php/category/entity-systems bir wiki ile birlikte: entity-systems.wikidot.com
user8363

Yanıtlar:


6

Sistemler, bir Bileşen Varlığı için anahtar / değer çiftini bir tür Harita, Sözlük Nesnesi veya İlişkilendirilebilir Dizi içinde (kullanılan dile bağlı olarak) saklamalıdır. Dahası, Varlık Nesnenizi oluşturduğunuzda, herhangi bir Sistemin kaydını silmeniz gerekmedikçe bir yöneticide saklamaktan endişe etmem. Varlık, bileşenlerin bir bileşimidir, ancak bileşen güncellemelerinin hiçbirini işlememelidir. Bunun Sistemler tarafından ele alınması gerekir. Bunun yerine, Varlığınızı sistemlerde içerdiği tüm bileşenlerle eşlenen bir anahtar ve bu bileşenlerin birbirleriyle konuşması için bir iletişim merkezi olarak ele alın.

Entity-Component-System modellerinin en önemli yanı, mesajları bir bileşenden bir varlığın bileşenlerinin geri kalanına kolayca iletebilmenizdir. Bu, bir bileşenin gerçekte kim olduğunu veya değiştirdiği bileşeni nasıl işleyeceğini bilmeden başka bir bileşenle konuşmasına olanak tanır. Bunun yerine bir ileti iletir ve bileşenin kendisini değiştirmesine izin verir (varsa)

Örneğin, bir Konum Sistemi içinde çok fazla kod bulunmayacak ve yalnızca Konum Bileşenleri ile eşlenmiş Varlık Nesnelerinin izini sürecektir. Ancak bir pozisyon değiştiğinde, ilgili Varlığa bir mesaj gönderebilir ve bu mesaj da o varlığın tüm bileşenlerine dağıtılır. Her ne sebeple bir pozisyon değişir? Konum Sistemi, Varlığa konumun değiştiğini söyleyen bir mesaj gönderir ve bir yerde o varlığın görüntü oluşturma bileşeni bu mesajı alır ve bir sonraki çizeceği yeri günceller.

Tersine, bir Fizik Sisteminin tüm nesnelerinin ne yaptığını bilmesi gerekir; Çarpışmaları test etmek için tüm dünya nesnelerini görebilmelidir. Bir çarpışma meydana geldiğinde, doğrudan Varlığın bileşenine başvurmak yerine bir tür "Yön Değişiklik Mesajı" göndererek Varlığın yön bileşenini günceller. Bu, yöneticinin, orada bulunan belirli bir bileşene güvenmek yerine bir mesaj kullanarak yönleri nasıl değiştireceğini bilmesine ihtiyaç duymasını engeller (bu hiç olmayabilir), bu durumda mesaj sadece bir hata yerine sağır kulaklara düşer çünkü beklenen bir nesne yoktu).

Bir Ağ Arayüzünüz olduğundan bahsettiğiniz için bundan büyük bir avantaj göreceksiniz. Bir Ağ Bileşeni, herkesin bilmesi gereken tüm mesajları dinler. Dedikoduyu çok seviyor. Daha sonra Ağ Sistemi güncellendiğinde, Ağ bileşenleri bu mesajları diğer istemci makinelerde diğer Ağ Sistemlerine gönderir, bu da oyuncu konumlarını güncellemek için bu mesajları diğer tüm bileşenlere tekrar gönderir. Yalnızca belirli varlıkların ağ üzerinden mesaj gönderin ancak Sistemin güzelliği budur, sadece doğru mantığı ona kaydederek bu mantığı kontrol etmesini sağlayabilirsiniz.

Kısacası:

Varlık, mesaj alabilen Bileşenlerin bir bileşimidir. Varlık mesaj alabilir, söz konusu mesajları güncellemek için tüm bileşenlerine devredebilir. (Konum değişti Mesaj, Hız Değiştirme Yönü, vb.) Tüm bileşenlerin doğrudan birbirleriyle konuşmak yerine birbirlerinden duyabileceği merkezi bir posta kutusu gibidir.

Bileşen, bir varlığın bir kısmını saklayan bir Varlığın küçük bir parçasıdır. Bunlar belirli mesajları ayrıştırabilir ve diğer mesajları dışarı atabilir. Örneğin, bir "Yön Bileşeni" yalnızca "Yön Değişikliği Mesajları" nı dikkate alır, ancak "Konum Değişikliği Mesajları" nı önemsemez. Bileşenler iletileri temel alarak kendi durumlarını güncelleştirir ve ardından Sistemlerinden ileti göndererek diğer bileşenlerin durumlarını güncelleştirir.

Sistem belirli bir türdeki tüm bileşenleri yönetir ve adı geçen bileşenleri her bir karenin güncellemesinden ve yönettikleri bileşenin Bileşenlerin ait olduğu Varlıklara mesaj göndermekten sorumludur

Sistemler tüm bileşenlerini paralel olarak güncelleyebilir ve tüm mesajları giderken depolayabilir. Ardından, tüm Sistemlerin güncelleme yöntemlerinin yürütülmesi tamamlandığında, her sistemden mesajlarını belirli bir sırada göndermesini istersiniz. Muhtemelen önce kontroller, ardından Fizik, ardından yön, konum, görüntü oluşturma, vb. Gönderilir. Fizik Yönü Değişimi Daima dışarıya bağlı olarak kontrol tabanlı bir yön değişikliğini tartmalıdır.

Bu yardımcı olur umarım. Bu bir Tasarım Deseni cehennemi, ama doğru yapılırsa gülünç derecede güçlü.


0

Motorumda benzer bir sistem kullanıyorum ve bunu yaptığım şekilde, her Varlık bir Bileşenler listesi içeriyor. EntityManager'dan, Varlıkların her birini sorgulayabilir ve hangilerinin belirli bir Bileşen içerdiğini görebilirim. Misal:

class Component
{
    private uint ID;
    // etc...
}

class Entity
{
    List<Component> Components;
    // etc...
    public bool Contains(Type type)
    {
        foreach(Component comp in Components)
        {
            if(typeof(comp) == type)
                return true;
        }
        return false;
    }
}

class EntityManager
{
    List<Entity> Entities;
    // etc...
    public List<Entity> GetEntitiesOfType(Type type)
    {
        List<Entity> results = new List<Entity>();
        foreach(Entity entity in Entities)
        {
            if(entity.Contains(type))
                results.Add(entity);
        }
        return results;
    }
}

Açıkçası bu tam kod değil (aslında kullanmak yerine farklı bileşen türlerini kontrol etmek için bir şablon fonksiyonuna ihtiyacınız olacak typeof) ama konsept var. Ardından bu sonuçları alabilir, aradığınız bileşene başvurabilir ve fabrikanıza kaydedebilirsiniz. Bu, Bileşenler ve yöneticileri arasındaki doğrudan bağlantıyı önler.


3
Uyarı emptor: Varlığınızın veri içerdiği noktada, bir varlık değil, bir nesne… ECS'nin bu yapıdaki paralelleştirici (sic?) Faydalarının çoğunu kaybeder. "Saf" E / C / S sistemleri ilişkiseldir, nesne yönelimli değildir ... Bazı durumlar için mutlaka "kötü" değildir, ama kesinlikle "ilişkisel modeli
bozar

2
Seni anladığımdan emin değilim. Anlayışım (ve lütfen yanılıyorsam beni düzeltin) temel Entity-Component-System Bileşenleri barındıran ve bir ID, isim veya bazı tanımlayıcıya sahip olabilecek bir Entity sınıfına sahiptir. Bence Varlık "türü" ile kastettiğimde bir yanlış anlama olabilir. Varlık "türü" dediğimde, Bileşen türlerine atıfta bulunuyorum. Yani, Varlık bir Sprite Bileşeni içeriyorsa "Sprite" türüdür.
Mike Cluck

1
Saf bir Varlık / Bileşen sisteminde, bir Varlık genellikle atomiktir: örneğin typedef long long int Entity; Bileşen, structbağlı olduğu Varlığa referansı olan bir kayıttır (nesne sınıfı olarak uygulanabilir veya sadece a ); ve bir Sistem bir yöntem veya yöntemlerin toplanması olacaktır. ECS modeli OOP modeliyle çok uyumlu değildir, ancak bir Bileşen (çoğunlukla) yalnızca veri içeren bir Nesne ve Sistem, durumu bileşenlerde yaşayan ... yalnızca karma bir sistem olan ... "saf" olanlardan daha yaygın, doğuştan gelen faydaların çoğunu kaybederler.
BRPocock

2
@BRPocock yeniden "saf" varlık sistemleri. Bir nesne olarak bir objenin gayet iyi olduğunu düşünüyorum, basit bir kimlik olması gerekmiyor. Bir şey serileştirilmiş temsil, diğeri bir nesnenin / bir kavramın / bir varlığın bellek içi düzenidir. Veri odaklılığı koruyabildiğiniz sürece, sadece "saf" yol olduğu için deyimsel olmayan koda bağlı olmamalısınız.
Raine

1
@BRPocock bu adil bir uyarı ama "t-machine" benzeri varlık sistemleri için. Nedenini anlıyorum, ancak bunlar bileşen tabanlı varlıkları modellemenin tek yolu değil. aktörler ilginç bir alternatif. Onları daha çok takdir ediyorum, özellikle de tamamen mantıklı varlıklar için.
Raine

0

1) Fabrika yönteminiz, EntityManager'a çağrı yapan bir referans geçirilmelidir (örnek olarak C # kullanacağım):

delegate BaseEntity EntityFactory(EntityManager manager);

2) Ayrıca, CreateEntity öğesinin sınıfının / türünün yanı sıra bir kimlik (ör. Bir dize, tamsayı, size kalmış) almasını ve oluşturulan kimliği otomatik olarak bu kimliği anahtar olarak kullanarak bir Sözlüğe kaydettirmesini sağlar:

class EntityManager
{
    // Rest of class omitted

    BaseEntity CreateEntity(string id, Type entityClass)
    {
        BaseEntity entity = factories[entityClass](this);
        registry.Add(id, entity);
        return entity;
    }

    Dictionary<Id, BaseEntity> registry;
}

3) Herhangi bir varlığı kimliğe göre almak için EntityManager'a bir Alıcı ekleyin:

class EntityManager
{
    // Rest of class omitted

    BaseEntity GetEntity(string id)
    {
        return registry[id];
    }
}

Fabrika yönteminizden herhangi bir ComponentManager'a başvurmak için ihtiyacınız olan tek şey budur. Örneğin:

BaseEntity CreateSomeSortOfEntity(EntityManager manager)
{
    // Create and configure entity
    BaseEntity entity = new BaseEntity();
    RenderComponent renderComponent = new RenderComponent();
    entity.AddComponent(renderComponent);

    // Get a reference to the render manager and register component
    RenderEntityManager renderer = manager.GetEntity("RenderEntityManager") as RenderEntityManager;
    if(renderer != null)
        renderer.Register(renderComponent)

    return entity;
}

Id'in yanı sıra bir tür Type özelliği de kullanabilirsiniz (özel bir numaralandırma veya dilin tür sistemine güvenebilirsiniz) ve belirli bir türdeki tüm BaseEntities'i döndüren bir alıcı oluşturabilirsiniz.



@BRPocock: Saf erdemleri takip eden bir örnek oluşturabilir misiniz?
Zolomon

1
@Raine Belki de, bu konuda ilk elden deneyimim yok, ama okuduğum şey bu. Bileşenleri kimliğe göre aramak için harcanan zamanı azaltmak için uygulayabileceğiniz optimizasyonlar vardır. Önbellek tutarlılığına gelince, özellikle bileşenleriniz hafif veya basit özellikler olduğunda aynı türden verileri bellekte bitişik olarak sakladığınız için mantıklı olduğunu düşünüyorum. PS3'teki tek bir önbellek özleminin bin CPU talimatı kadar pahalı olabileceğini okudum ve benzer türdeki verileri sürekli olarak saklamanın bu yaklaşımı modern oyun geliştirmede çok yaygın bir optimizasyon tekniğidir.
David Gouveia

2
Ref: “salt” varlık sisteminde: Varlık Kimliği tipik olarak şuna benzer typedef unsigned long long int EntityID;:; ideal olan, her bir Sistemin ayrı bir CPU veya ana bilgisayarda yaşayabilmesi ve sadece o Sistemle ilgili / aktif olan bileşenleri getirmesi gerektiğidir. Bir Entity nesnesiyle, her ana bilgisayarda aynı Entity nesnesini somutlaştırmak gerekebilir ve bu da ölçeklemeyi daha zor hale getirir. Saf bir varlık-bileşen-sistem modeli, işlemi genellikle varlığa göre değil, düğümler (işlemler, CPU'lar veya ana bilgisayarlar) boyunca sisteme böler.
BRPocock

1
@DavidGouveia, “optimizasyonlar… varlıkları kimliğe göre arama” dedi. Aslında, bu şekilde uyguladığım (birkaç) sistem bunu yapma eğilimindedir. Daha sık olarak, yalnızca çapraz bileşenli Bağlantılar için Varlıkları (Kimlikler) kullanarak belirli bir Sistemle ilgilendiklerini belirten bir şekle göre Bileşenler'i seçin.
BRPocock
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.