Varlık Sistemi ve oluşturma


11

Tamam, şimdiye kadar bildiklerim; İşletme, aşağıdakiler gibi bilgileri tutan bir bileşen (veri depolama) içerir; - Doku / hareketli grafik - Gölgelendirici - vb.

Sonra tüm bunları çizen bir oluşturucu sistemim var. Ama anlamadığım şey, oluşturucunun nasıl tasarlanması gerektiğidir. Her "görsel tip" için bir bileşenim olmalı. Gölgelendiricisiz bir bileşen, gölgelendiricili bir bileşen, vb.

Sadece bunu yapmak için "doğru yolu" ne bazı giriş gerekir. Dikkat edilmesi gereken ipuçları ve tuzaklar.


2
İşleri çok genel yapmamaya çalışın. Sprite bileşenine değil Shader bileşenine sahip bir varlık olması garip görünebilir, bu nedenle Shader'ın Sprite bileşeninin bir parçası olması gerekir . Doğal olarak sadece bir görüntüleme sistemine ihtiyacınız olacaktır.
Jonathan Connell

Yanıtlar:


8

Bu, cevaplanması zor bir sorudur, çünkü herkesin bir varlık bileşeni sisteminin nasıl yapılandırılması gerektiği konusunda kendi fikri vardır. Yapabileceğim en iyi şey, benim için en yararlı olduğunu bulduğum bazı şeyleri sizinle paylaşmak.

varlık

ECS'ye yağ sınıfı yaklaşımı alıyorum, muhtemelen aşırı programlama yöntemlerini son derece verimsiz bulduğum için (insan verimliliği açısından). Bu amaçla benim için bir varlık, daha uzmanlaşmış sınıflar tarafından miras alınacak soyut bir sınıftır. Varlığın birkaç sanal özelliği ve bu varlığın var olup olmayacağını söyleyen basit bir bayrağı var. Bir render sistemi hakkındaki sorunuzla ilgili olarak, Entityşöyle görünür:

public abstract class Entity {
    public bool IsAlive = true;
    public virtual SpatialComponent   Spatial   { get; set; }
    public virtual ImageComponent     Image     { get; set; }
    public virtual AnimationComponent Animation { get; set; }
    public virtual InputComponent     Input     { get; set; }
}

Bileşenler

Bileşenler hiçbir şey yapmadıkları veya bilmedikleri için "aptalca" dırlar. Diğer bileşenlere hiçbir başvuruları yoktur ve genellikle işlevleri yoktur (C # 'da çalışırım, bu yüzden getters / setters işlemek için özellikler kullanırım - işlevlere sahiplerse, tuttukları verileri almayı temel alırlar).

Sistemler

Sistemler daha az "aptal" olmakla birlikte, hala aptal otomatlardır. Tüm sistemin bağlamı yoktur, diğer sistemlere referansları yoktur ve bireysel işlemlerini yapmak zorunda kalabilecekleri birkaç arabellek dışında veri içermezler. Sisteme bağlı olarak, bir uzman Updateveya Drawyöntemi veya bazı durumlarda her ikisi de olabilir.

Arayüzler

Arayüzler sistemimdeki kilit yapıdır. Bir Systemteneke kutuların neleri işleyebileceğini ve nelerin neler yapabileceğini tanımlamak için kullanılırlar Entity. Oluşturma ile ilgili Arayüzler: IRenderableve IAnimatable.

Arabirimler sisteme hangi bileşenlerin bulunduğunu söyler. Örneğin, oluşturma sisteminin varlığın sınırlayıcı kutusunu ve çizilecek görüntüyü bilmesi gerekir. Benim durumumda, olurdu SpatialComponentve ImageComponent. Yani şöyle görünüyor:

public interface IRenderable {
    SpatialComponent Component { get; }
    ImageComponent   Image     { get; }
}

RenderingSystem

Peki, oluşturma sistemi bir varlığı nasıl çiziyor? Aslında oldukça basit, bu yüzden size bir fikir vermek için soyulmuş sınıfı göstereceğim:

public class RenderSystem {
    private SpriteBatch batch;
    public RenderSystem(SpriteBatch batch) {
        this.batch = batch;
    }
    public void Draw(List<IRenderable> list) {
        foreach(IRenderable obj in list) {
            this.batch.draw(
                obj.Image.Texture,
                obj.Spatial.Position,
                obj.Image.Source,
                Color.White);
        }
    }
}

Sınıfa baktığımızda, render sistemi an'ın ne olduğunu bile bilmiyor Entity. Tek bildiği şey, IRenderableçizmek için sadece bir listesi verilir.

Her Şey Nasıl Çalışır

Yeni oyun nesnelerini nasıl oluşturduğumu ve bunları sistemlere nasıl beslediğimi de anlamaya yardımcı olabilir.

Varlıklar Oluşturma

Tüm oyun nesneleri Entity'den ve bu oyun nesnesinin neler yapabileceğini tanımlayan tüm uygulanabilir arayüzlerden devralınır. Ekranda canlandırılan hemen her şey şöyle görünür:

public class MyAnimatedWidget : Entity, IRenderable, IAnimatable {}

Sistemleri Besleme

Oyun dünyasında var olan tüm varlıkların listesini tek bir listede tutuyorum List<Entity> gameObjects. Her kare, sonra o liste üzerinden elemek ve List<IRenderable> renderableObjects, ve gibi arabirim türüne dayalı daha fazla listeye nesne başvuruları kopyalayın List<IAnimatable> animatableObjects. Bu şekilde, farklı sistemlerin aynı varlığı işlemesi gerekiyorsa yapabilirler. Daha sonra bu listeleri sistemlerin Updateveya Drawyöntemlerin her birine teslim ediyorum ve sistemlerin işlerini yapmasına izin veriyorum .

Animasyon

Animasyon sisteminin nasıl çalıştığını merak ediyor olabilirsiniz. Benim durumumda IAnimatable arayüzünü görmek isteyebilirsiniz:

public interface IAnimatable {
    public AnimationComponent Animation { get; }
    public ImageComponent Image         { get; set; }
}

Burada dikkat edilmesi gereken ImageComponenten önemli şey , IAnimatablearayüzün sadece okunabilir olmamasıdır; bir ayarlayıcı var .

Tahmin edebileceğiniz gibi, animasyon bileşeni sadece animasyon hakkındaki verileri tutar; kare listesi (görüntü bileşenleri olan), geçerli kare, çizilecek saniye başına kare sayısı, son kare artışından bu yana geçen süre ve diğer seçenekler.

Animasyon sistemi, oluşturma sistemi ve görüntü bileşeni ilişkisinden yararlanır. Animasyonun çerçevesini artırdıkça objenin görüntü bileşenini değiştirir. Bu şekilde, animasyon dolaylı olarak oluşturma sistemi tarafından oluşturulur.


Muhtemelen bunun insanların varlık bileşeni sistemi dediği şeye yakın olup olmadığını gerçekten bilmediğimi belirtmeliyim . Kompozisyona dayalı bir tasarım uygulama girişimimde kendimi bu örüntüye düştüm.
Cypher

İlginç! Varlığınız için soyut sınıfa çok meraklı değilim ama IRenderable arayüzü iyi bir fikir!
Jonathan Connell

5

Bu cevaba bakın bahsettiğim sistemin tür görmek.

Bileşen, ne çizileceği ve nasıl çizileceği ile ilgili ayrıntıları içermelidir. Oluşturma sistemi bu ayrıntıları alır ve varlığı bileşen tarafından belirtilen şekilde çizer. Sadece önemli ölçüde farklı çizim teknolojileri kullanacak olsaydınız, ayrı stiller için ayrı bileşenler olurdu.


3

Mantığı bileşenlere ayırmanın temel nedeni, bir varlıkta birleştirildiğinde yararlı, yeniden kullanılabilir davranış ürettikleri bir veri kümesi oluşturmaktır. Örneğin, bir Varlığı PhysicsComponent ve RenderComponent olarak ayırmak, tüm varlıkların Fizik'e sahip olmayacağı ve bazı varlıkların Sprite'a sahip olmayabileceği mantıklıdır.

Sorunuzu cevaplamak için mimarinize bakmanız ve kendinize iki soru sormanız gerekir:

  1. Dokusuz Gölgelendiricinin olması mantıklı mı?
  2. Gölgelendiriciyi Doku'dan ayırmak kod çoğaltmasını önlememe izin verir mi?

Bir bileşeni böldüğünüzde bu soruyu sormak önemlidir, eğer 1. yanıtı evet ise, biri Shader ve diğeri Texture olan iki ayrı bileşen oluşturmak için muhtemelen iyi bir adayınız vardır. Birden fazla bileşenin konumu kullanabileceği Konum gibi bileşenler için genellikle 2. yanıtı evettir.

Örneğin, hem Fizik hem de Ses aynı konumu kullanabilir, bunun yerine her iki konumu da yeniden konumlandırdığınız yinelenen konumları depolayan bileşenler ve PhysicsComponent / AudioComponent kullanan varlıkların da bir PositionComponent olmasını gerektirir.

Bize verdiğiniz bilgilere dayanarak, gölgelendiricilerin tamamen Doku'nın ve başka hiçbir şeye bağlı olmadığı için RenderComponent'inizin bir TextureComponent ve ShaderComponent'e bölmek için iyi bir aday gibi görünmüyor.

T-Machine: Entity Systems benzeri bir şey kullandığınızı varsayarsak , C ++ 'da bir RenderComponent & RenderSystem'ın örnek bir uygulaması şöyle görünecektir:

struct RenderComponent {
    Texture* textureData;
    Shader* shaderData;
};

class RenderSystem {
    public:
        RenderSystem(EntityManager& manager) :
            m_manager(manager) {
            // Initialize Window, rendering context, etc...
        }

        void update() {
            // Get all the entities with RenderComponent
            std::vector<RenderComponent>& components = m_manager.getComponents<RenderComponent>();

            for(auto component = components.begin(); entity != components.end(); ++components) {
                // Do something with the texture
                doSomethingWithTexture(component->textureData);

                // Do something with the shader if it's not null
                if(component->shaderData != nullptr) {
                    doSomethingWithShader(component->shaderData);
                }
            }
        }
    private:
        EntityManager& m_manager;
}

Bu tamamen yanlış. Bileşenlerin asıl amacı onları varlıklardan ayırmaktır, render sistemlerini onları bulmak için varlıklar arasında arama yapmak değildir. İşleme sistemleri kendi verilerini tam olarak kontrol etmelidir. PS Döngülere std :: vector (özellikle örnek verilerle) koymayın, bu korkunç (yavaş) C ++.
snake5

@ snake5 her iki konuda da haklısın. Kodu kafamın üstünden yazdım ve bazı sorunlar vardı, onları işaret ettiğiniz için teşekkürler. Etkilenen kodu daha az yavaş ve varlık sistemi deyimlerini doğru kullanmak için düzelttim.
Jake Woods

2
@ snake5 Her karede verileri yeniden hesaplamazsınız, getComponents m_manager'a ait olan ve zaten bileşenleri eklediğinizde / kaldırdığınızda değişen bir vektör döndürür. Aynı varlığın birden çok bileşenini, örneğin PositionComponent ve PhysicsComponent kullanmak isteyen bir PhysicsSystem kullanmak isteyen bir sisteminiz olması bir avantajdır. Diğer sistemler muhtemelen pozisyonu isteyecektir ve bir PositionComponent'e sahip olduğunuzda yinelenen verileriniz yoktur. Öncelikle bileşenlerin nasıl iletişim kurduğu sorununu çözer.
Jake Woods

5
@ snake5 Soru, EC sisteminin nasıl düzenleneceği ya da performansıyla ilgili değil. Soru, render sisteminin kurulmasıyla ilgilidir. Bir EC sistemini yapılandırmanın birden fazla yolu vardır, burada birinin performans sorunlarına yakalanmayın. OP muhtemelen cevaplarınızdan herhangi birinden tamamen farklı bir EC yapısı kullanıyor. Bu cevapta verilen kod, sadece performansı daha iyi göstermek için değil, örneği daha iyi göstermek içindir. Soru belki de performanstan daha fazla olsaydı, bu cevabı "yararlı değil" olurdu, ama değil.
MichaelHouse

2
Bu cevapta ortaya konulan tasarımı Cyphers'dan çok tercih ediyorum. Kullandığım şeye çok benziyor. Daha küçük bileşenler, sadece bir veya iki değişkenleri olsa bile daha iyi imo'dur. "Hasarlı" bileşenimin 2, belki 4 değişkene (her sağlık ve zırh için maksimum ve akım) sahip olması gibi, bir varlığın bir yönünü tanımlamalıdırlar. Bu yorumlar uzuyor, daha fazla tartışmak isterseniz sohbete geçelim .
John McDonald

2

Tuzak # 1: Aşırı tasarlanmış kod. Uyguladığınız her şeye gerçekten ihtiyacınız olup olmadığını düşünün, çünkü onunla bir süre yaşamak zorunda kalacaksınız.

Tuzak # 2: çok fazla nesne. Çok fazla nesne içeren bir sistem kullanmam (her tür, alt tip ve her şey için bir tane) çünkü otomatik işlemeyi zorlaştırır. Bence, her bir nesnenin belirli bir özellik setini kontrol etmesi (bir özelliğin aksine) çok daha güzel. Örneğin, oluşturmaya dahil edilen her veri biti için bileşenler yapmak (doku bileşeni, gölgelendirici bileşeni) çok bölünmüş - genellikle tüm bu bileşenleri bir araya getirmeniz gerekir, kabul etmiyor musunuz?

Tuzak # 3: çok katı dış kontrol. Nesneler oluşturucu / doku türü / gölgelendirici biçimi / ne olursa olsun değişebileceğinden, gölgelendirici / doku nesnelerine adları değiştirmeyi tercih edin. İsimler basit tanımlayıcılardır - bunlardan ne yapılacağına karar vermek oluşturucuya bağlıdır. Bir gün düz gölgelendiriciler yerine malzemelere sahip olmak isteyebilirsiniz (örneğin verilerden gölgelendiriciler, dokular ve karışım modları ekleyin). Metin tabanlı bir arayüzle, bunu uygulamak çok daha kolaydır.

Oluşturucuya gelince, bileşenler tarafından oluşturulan nesneleri oluşturan / yok eden / tutan / işleyen basit bir arayüz olabilir. En ilkel temsili şöyle olabilir:

class Renderer {
    function Draw() { ... }
    function AddSprite( ... ) { ... return sprite; }
    function RemoveSprite( sprite ) { ... }
    ...
};

Bu, bu nesneleri bileşenlerinizden yönetmenize ve bunları istediğiniz şekilde oluşturmanıza izin verecek kadar uzak tutmanıza olanak tanır.

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.