Kullanıcı dostu fakat yine de esnek olan bileşen tabanlı bir varlık sistemi için hangi tasarımlar var?


23

Bir süredir bileşen tabanlı varlık sistemine ilgi duydum ve sayısız makaleyi okudum ( Insomiac oyunları , oldukça standart olan Hiyerarşinizi Geliştirin , T-Machine , Chronoclast ... sadece birkaç isim).

Hepsinin dışarısında bir şey varmış gibi görünüyor:

Entity e = Entity.Create();
e.AddComponent(RenderComponent, ...);
//do lots of stuff
e.GetComponent<PositionComponent>(...).SetPos(4, 5, 6);

Ve paylaşılan veri fikrini getirirseniz (bu, şimdiye kadar gördüğüm en iyi tasarımdır, her yerde verileri kopyalamamak açısından)

e.GetProperty<string>("Name").Value = "blah";

Evet, bu çok verimli. Ancak, tam olarak okumak veya yazmak kolay değildir; sana karşı çok tuhaf ve sana karşı çalışan bir duygu.

Şahsen şöyle bir şey yapmak istiyorum:

e.SetPosition(4, 5, 6);
e.Name = "Blah";

Tabii ki, bu tür bir tasarıma ulaşmanın tek yolu, Varlık-> NPC-> Düşman-> FlyingEnemy-> FlyingEnemyWithAHatTarihine göre bu tasarımdan kaçınmaya çalışır.

Bu tür bir bileşen sistemi için hala esnek olan, ancak bir kullanıcı dostu olma düzeyi koruyan bir tasarım görmüş olan var mı? Ve bu konuda, veri depolama alanını (muhtemelen en zor olanı) iyi bir şekilde aşmayı başarır mı?

Kullanıcı dostu fakat yine de esnek olan bileşen tabanlı bir varlık sistemi için hangi tasarımlar var?

Yanıtlar:


13

Unity'nin yaptığı şeylerden biri, ortak bileşenlere daha kullanıcı dostu erişim sağlamak için ana oyun nesnesindeki bazı yardımcı erişim sağlayıcıları sağlamaktır.

Örneğin, konumunuzu bir Dönüştür bileşeninde saklamış olabilirsiniz. Örneğinizi kullanarak şöyle bir şey yazmanız gerekir

e.GetComponent<Transform>().position = new Vector3( whatever );

Ancak Birlik'te bu basitleştirildi

e.transform.position = ....;

transformKelimenin tam anlamıyla temel GameObjectsınıftaki basit bir yardımcı yöntem ( Entitydurumunuzdaki sınıf) nerede ?

Transform transform
{ 
    get { return this.GetComponent<Transform>(); }
}

Birlik, oyun nesnesinde alt bileşenleri yerine bir "Ad" özelliği belirlemek gibi başka birkaç şey de yapar.

Şahsen, paylaşılan veriye göre tasarım fikrinden hoşlanmıyorum. Bir değişken yerine isme göre özelliklere erişmek ve kullanıcının aynı zamanda ne tür bir şey olduğunu bilmek zorunda olması bana gerçekten eğilimli görünüyor. Unity, kardeş bileşenlere erişmek GameObject transformiçin Componentsınıf içindeki özelliklerle aynı yöntemleri kullanmalarıdır . Yani yazdığınız herhangi bir bileşen herhangi bir şeyi basitçe yapabilir:

var myPos = this.transform.position;

Konuma erişmek için. Bu transformmülkün böyle bir şey yaptığı yerde

Transform transform
{
    get { return this.gameObject.GetComponent<Transform>(); }
}

Tabii ki, sadece söylemekten biraz daha ayrıntılı e.position = whatever, ama alışırsın ve genel özellikler kadar kötü görünmüyor. Ve evet, müşteri bileşenleriniz için bunu dolambaçlı yoldan yapmak zorunda kalacaksınız, ancak fikir, tüm ortak "motor" bileşenleriniz (işleyiciler, çarpıştırıcılar, ses kaynakları, dönüşümler, vb.) Kolay erişicilere sahip olmasıdır.


Neden paylaşılan veri adına göre sistemin bir sorun olabileceğini anlayabiliyorum, ancak veri gerektiren birden fazla bileşenin sorununu (başka bir şeyi içinde saklayacağım) başka şekilde nasıl önleyebilirim? Sadece tasarım aşamasında çok dikkatli olmak mı?
Komünist Ördek

Ayrıca, 'Kullanıcının ne tür olduğunu da bilmek zorunda' ifadesiyle, onu get () yönteminde property.GetType () öğesine atamadım mı?
Komünist Ördek

1
Bu ortak veri havuzlarına ne tür bir küresel (varlık kapsamındaki) veri aktarıyorsunuz? Her türlü oyun nesnesi verilerine "motor" sınıflarınızdan (dönüşüm, çarpıştırıcılar, işleyiciler vb.) Kolayca erişilebilmelidir. Eğer bu "paylaşılan verilere" dayanarak oyun mantığı değişiklikleri yapıyorsanız, bir sınıfın kendisine sahip olması ve aslında bu bileşenin bu oyun nesnesinde olduğundan emin olun, sadece adlandırılmış bir değişkenin var olup olmadığını görmek istemekten çok daha güvenlidir. Yani evet, bunu tasarım aşamasında ele almalısınız. Gerçekten ihtiyacınız olursa, her zaman yalnızca verileri depolayan bir bileşen oluşturabilirsiniz.
Tetrad

@Tetrad - Önerilen GetComponent <Transform> yönteminin nasıl çalışacağını kısaca açıklayabilir misiniz? Tüm GameObject Bileşenleri arasında dolaşıp ilk T bileşenini döndürür mü? Yoksa orada başka bir şey mi oluyor?
Michael

@Michael işe yarayacaktı. Performans iyileştirme olarak yerel olarak önbelleğe almayı yalnızca arayanın sorumluluğunda yapabilirsiniz.
Tetrad

3

Varlık nesneleriniz için bir tür Arayüz sınıfının iyi olacağını düşünüyorum. Bir işletmenin uygun değerin bir bileşenini tek bir konumda içerdiğinden emin olmak için hata işlemeyi yapabilir, böylece değerlere eriştiğiniz her yerde yapmak zorunda kalmazsınız. Alternatif olarak, bileşen tabanlı bir sistemle yaptığım tasarımların çoğu, doğrudan bileşenlerle ilgileniyorum, örneğin konumsal bileşenlerini talep ediyorum ve sonra o bileşenin özelliklerine doğrudan erişiyorum / güncelliyorum.

Yüksek düzeyde çok basit olan sınıf, söz konusu varlığı üstlenir ve aşağıdaki bileşen parçalarına kullanımı kolay bir arayüz sağlar:

public class EntityInterface
{
    private Entity entity { get; set };
    public EntityInterface(Entity e)
    {
        entity = e;
    }

    public Vector3 Position
    {
        get
        {
            return entity.GetProperty<Vector3>("Position");
        }
        set
        {
            entity.GetProperty<Vector3>("Position") = value;
        }
    }
}

Sadece NoPositionComponentException veya başka bir şeyle uğraşmak zorunda kalacağımı varsa, bunun Entity sınıfına yerleştirilmesinin avantajı nedir?
Komünist Ördek

Varlık sınıfınızın gerçekte ne içerdiğinden emin olmak bir avantajı olup olmadığını söyleyemem. Ben şahsen '' Neye aittir? '' Sorusundan kaçınmak için söylenen baz bir varlığın olmadığı bileşen sistemlerini savunuyorum. Ancak bunun gibi bir arayüz sınıfını mevcut için iyi bir argüman olarak görürdüm.
James

Bunun ne kadar kolay olduğunu görebiliyorum (pozisyonu GetProperty aracılığıyla sorgulamak zorunda değilim), ancak Entity sınıfına yerleştirmek yerine bu şekilde avantajını göremiyorum.
Komünist Ördek

2

Python'da, 'setPosition' bölümünü Varlık üzerinde e.SetPosition(4,5,6)bir __getattr__işlev ilan ederek engelleyebilirsiniz . Bu işlev, bileşenler arasında yinelenebilir ve uygun yöntemi veya özelliği bulabilir ve onu geri döndürebilir, böylece işlev çağrısı veya ataması doğru yere gider. Belki de C # benzer bir sisteme sahiptir, ancak statik olarak yazılmasından dolayı mümkün olmayabilir - e.setPositione, arayüzde bir yere ayar yapmamışsa muhtemelen derleyemez.

Belki de tüm varlıklarınızın, kendilerine ekleyebileceğiniz tüm bileşenler için bir istisna oluşturan saplama yöntemleriyle ilgili arayüzleri uygulamalarını sağlayabilirsiniz. Sonra addComponent'i çağırdığınızda, o bileşenin arayüzü için varlığın işlevlerini bileşene yönlendirmeniz yeterlidir. Birazcık olsa olsa.

Fakat belki de en kolay olanı, bağlı bileşenleri geçerli bir özellik olup olmadığını araştırmak için Varlık sınıfınızdaki [] operatörüne aşırı yüklemek ve bunu döndürmek olabilir e["Name"] = "blah". Her bileşenin kendi GetPropertyByName uygulamasını kullanması gerekir ve Varlık söz konusu mülkten sorumlu bileşeni bulana kadar sırayla her birini çağırır.


Dizinleyicileri kullanmayı düşündüm ... bu gerçekten çok hoş. Başka neyin göründüğünü görmek için biraz bekleyeceğim, ama bunun, her zamanki GetComponent () ve ortak bileşenler için önerilen @James gibi bazı özelliklerin bir karışımına bakacağım.
Komünist Ördek

Burada söyleneni kaçırmış olabilirim ama bu daha çok e.GetProperty <type> ("NameOfProperty") yerine geçecek gibi görünüyor .. e ["NameOfProperty"] ile ne olursa olsun.
James

@James Bu onun için bir sarıcı (Tasarımınız gibi) .. daha dinamik olması dışında - otomatik olarak ve GetPropertyyöntemden daha temiz yapar ... Sadece olumsuz dize manipülasyon ve karşılaştırma. Ama bu bir görüşme noktası.
Komünist Ördek,

Ah, oradaki yanlış anlamam. GetProperty'nin varlığın bir parçası olduğunu varsaymıştım (Ve yukarıda açıklananları da yapmalıydı) ve C # yöntemini değil.
James

2

Kylotan'ın cevabını genişletmek için, eğer C # 4.0 kullanıyorsanız, dinamik olarak bir değişkeni statik olarak yazabilirsiniz .

System.Dynamic.DynamicObject öğesinden devralırsanız, bileşen adlarını kesmek ve istenen bileşeni geri döndürmek için TryGetMember ve TrySetMember'i (birçok sanal yöntem arasında) geçersiz kılabilirsiniz.

dynamic entity = EntityRegistry.Entities[1000];
entity.MyComponent.MyValue = 100;

Yıllar boyunca varlık sistemleri hakkında biraz yazdım, ancak devlerle rekabet edemeyeceğimi farz ediyorum. Bazı ES notları (biraz modası geçmiş ve zorunlu olarak şu anki varlık sistemleri anlayışımı yansıtmıyor, ama okumaya değer.


Bunun için teşekkürler, yardımcı olacak :) Dinamik yolun, sadece property.GetType () 'a basmakla aynı etkisi olmaz mıydı?
Komünist Ördek

TryGetMember'in bir out objectparametresi olduğundan, bir noktada oyuncular olacaktır - ancak bunların tümü çalışma zamanında çözülecektir. Bence entity.entGetComponent (compName, out bileşeni) üzerinden akıcı bir arayüz elde etmenin harika bir yolu. Ancak soruyu anladığımdan emin değilim :)
Raine

Sorun, her yerde dinamik türleri veya (AFAICS) returning (property.GetType ()) özelliğini kullanabildiğimdir.
Komünist Ördek

1

C # ile ES burada çok ilgi görüyorum. Mükemmel ES uygulaması Artemis’in limanına göz atın:

https://github.com/thelinuxlich/artemis_CSharp

Ayrıca, nasıl çalıştığını anlamanız için örnek bir oyun (XNA 4 kullanarak): https://github.com/thelinuxlich/starwarrior_CSharp

Önerilerinizi bekliyoruz!


Kesinlikle güzel görünüyor. Şahsen ben pek çok EntityProcessingSystems fikrinin hayranı değilim - kod olarak, kullanım açısından bu nasıl çalışır?
Komünist Ördek

Her Sistem, bağımlı Bileşenlerini işleyen bir Unsurdur. Fikir edinmek için bu bakınız: github.com/thelinuxlich/starwarrior_CSharp/tree/master/...
thelinuxlich

0

C # 'nın mükemmel yansıma sistemini kullanmaya karar verdim. Bunu genel bileşen sisteminden ve buradaki cevapların bir karışımından kurdum.

Bileşenler çok kolay bir şekilde eklenir:

Entity e = new Entity(); //No templates/archetypes yet.
e.AddComponent(new HealthComponent(100));

Ve özelliklerine göre adı, türü veya (eğer Sağlık ve Pozisyon gibi ortak olanları) sorgulanabilir:

e["Health"] //This, unfortunately, is a bit slower since it requires a dict search
e.GetComponent<HealthComponent>()
e.Health

Ve kaldırıldı:

e.RemoveComponent<HealthComponent>();

Verilere yine özelliklerle erişilir:

e.MaxHP

Hangi bileşen tarafında saklanır; HP yoksa daha az ek yük:

public int? MaxHP
{
get
{

    return this.Health.MaxHP; //Error handling returns null if health isn't here. Excluded for clarity
}
set
{
this.Health.MaxHP = value;
}

VS'deki Intellisense tarafından büyük miktarda yazmaya yardımcı oluyor, ancak çoğunlukla küçük yazım var - aradığım şey.

Perde arkasını saklıyorum Dictionary<Type, Component>ve isim kontrolü Componentsiçin kullanılan ToStringmetodu geçersiz kılıyorum. Özgün tasarımım öncelikle isimler ve şeyler için karakter dizileri kullandı, ancak çalışmak için bir domuz haline geldi (oh, bak, her yere döküm yapmam gerekiyor, aynı zamanda kolay erişilemeyen özellikler).

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.