Bileşenler ne zaman / nerede güncellenmeli


10

Her zamanki miras ağır oyun motorlarım yerine daha bileşen tabanlı bir yaklaşımla oynuyorum. Ancak bileşenlerin işlerini nerede yapmasına izin vereceğimi açıklamakta zorlanıyorum.

Bileşenler listesi olan basit bir varlığım olduğunu varsayalım. Tabii ki işletme bu bileşenlerin ne olduğunu bilmiyor. Varlığa ekranda bir konum veren bir bileşen mevcut olabilir, diğeri varlığı ekrana çizmek için orada olabilir.

Bu bileşenlerin çalışması için her kareyi güncellemeleri gerekir, bunu yapmanın en kolay yolu sahne ağacının üzerinde yürümek ve her varlık için her bir bileşeni güncellemektir. Ancak bazı bileşenlerin biraz daha yönetilmesi gerekebilir. Örneğin, bir varlığı katlanabilir hale getiren bir bileşen, tüm katlanabilir bileşenleri denetleyebilecek bir şey tarafından yönetilmelidir. Bir varlığı çekilebilir hale getiren bir bileşen, birimin çekiliş sırasını, vb.

Sorum şu: Bileşenleri nerede güncelleyeceğim, bunları yöneticilere ulaşmanın temiz bir yolu nedir?

Bileşen türlerinin her biri için tekil bir yönetici nesnesi kullanmayı düşündüm ama tek birtonun her zamanki dezavantajları var, bunu hafifletmenin bir yolu bağımlılık enjeksiyonu kullanmaktır ama bu sorun için aşırıya kaçmak gibi geliyor. Ayrıca sahne ağacının üzerinde yürüyebilir ve daha sonra farklı bileşenleri bir çeşit gözlemci deseni kullanarak listeler halinde toplayabilirim, ancak bu her kareyi yapmak için biraz israf görünüyor.


1
Sistemleri bir şekilde mi kullanıyorsunuz?
Asakeron

Bileşen sistemleri bunu yapmanın genel yoludur. Şahsen ben sadece tüm bileşenlerde güncelleme çağıran tüm varlıkları güncellemeyi çağırıyorum ve birkaç "özel" vaka (statik olan çarpışma tespiti için mekansal yönetici gibi) var.
ashes999

Bileşen sistemleri? Bunları daha önce hiç duymamıştım. Google'a başlayacağım, ancak önerilen bağlantıları memnuniyetle karşılarım.
Roy T.

1
Varlık Sistemleri, MMOG gelişiminin geleceği için büyük bir kaynaktır. Ve dürüst olmak gerekirse her zaman bu mimari isimlerle kafam karıştı. Önerilen yaklaşımdaki fark, bileşenlerin yalnızca veri tutması ve sistemlerin veriyi işlemesidir. Bu cevap da çok alakalı.
Asakeron

1
Burada bu konuda bir çeşit kıvrımlı blog yazısı yazdım
AlexFoxGill

Yanıtlar:


15

Mike Acton'un 3 büyük yalanı okuyarak başlamanızı öneririm, çünkü ikisini ihlal ediyorsunuz. Ciddiyim, bu kodunuzu tasarlama şeklinizi değiştirecek: http://cellperformance.beyond3d.com/articles/2008/03/three-big-lies.html

Peki hangisini ihlal ediyorsunuz?

Yalan # 3 - Kod verilerden daha önemlidir

Bazı (ve sadece bazı) durumlarda yararlı olabilecek, ancak özellikle oyun geliştirmede kullanırsanız, her zaman büyük bir alarm zili çalması gereken bağımlılık enjeksiyonundan bahsedersiniz! Neden? Çünkü bu genellikle gereksiz bir soyutlamadır. Ve yanlış yerlerde soyutlamalar korkunç. Yani bir oyunun var. Oyun farklı bileşenler için yöneticilere sahiptir. Bileşenlerin tümü tanımlanmıştır. Yani ana oyun döngü kodunda yöneticileri "olan" bir yere bir sınıf yapın. Sevmek:

private CollissionManager _collissionManager;
private BulletManager _bulletManager;

Her yönetici sınıfını (getBulletManager ()) alması için bazı alıcı işlevleri verin. Belki bu sınıfın kendisi bir Singleton'dur ya da birinden ulaşılabilir (muhtemelen bir yerde merkezi bir Oyun singletonuna sahipsiniz). İyi tanımlanmış sabit kodlanmış veri ve davranışlarda yanlış bir şey yoktur.

Yöneticiyi kullanmak isteyen diğer sınıflar tarafından alınabilecek bir anahtar kullanarak Yöneticileri kaydettirmenize izin veren bir ManagerManager yapmayın. Harika bir sistem ve çok esnek, ama burada bir oyundan bahsediyoruz. Oyunda hangi sistemlerin olduğunu tam olarak biliyorsunuz . Neden senin gibi davranmıyorsun? Çünkü bu, kodun verilerden daha önemli olduğunu düşünen insanlar için bir sistemdir. "Kod esnektir, veriler sadece doldurur" derler. Ancak kod sadece veridir. Açıkladığım sistem çok daha kolay, daha güvenilir, bakımı daha kolay ve çok daha esnektir (örneğin, bir yöneticinin davranışı diğer yöneticilerden farklıysa, tüm sistemi yeniden çalışmak yerine yalnızca birkaç satırı değiştirmeniz gerekir)

Yalan # 2 - Kod dünyanın bir modeli etrafında tasarlanmalıdır

Yani oyun dünyasında bir varlığınız var. İşletmenin davranışını tanımlayan birkaç bileşeni vardır. Böylece, Bileşen nesnelerinin bir listesini ve her bir Bileşenin Update () işlevini çağıran bir Update () işlevini içeren bir Entity sınıfı oluşturursunuz. Sağ?

Hayır :) Bu bir dünya modeli tasarlıyor: Oyunda merminiz var, bu yüzden bir Mermi sınıfı ekliyorsunuz. Sonra her bir madde işaretini güncelleyip bir sonrakine geçersiniz. Bu kesinlikle performansınızı öldürecek ve size her yerde yinelenen kod içeren ve benzer kodun mantıksal yapılandırması olmayan korkunç kıvrımlı bir kod tabanı sağlar. ( Geleneksel OO tasarımının neden berbat olduğuna dair daha ayrıntılı bir açıklama için buradaki cevabımı kontrol edin veya Veri Odaklı Tasarım konusuna bakın)

OO önyargımız olmadan duruma bakalım. Aşağıdakileri istiyoruz, daha az değil (lütfen varlık veya nesne için sınıf oluşturmaya gerek olmadığını unutmayın):

  • Bir sürü varlığın var
  • Varlıklar, varlığın davranışını tanımlayan bir dizi bileşenden oluşur
  • Oyundaki her bir bileşeni her bir kareyi, tercihen kontrollü bir şekilde güncellemek istiyorsunuz
  • Bileşenleri birbirine ait olarak tanımlamaktan başka, işletmenin yapması gereken bir şey yoktur. Birkaç bileşen için bir bağlantı / kimlik.

Ve duruma bakalım. Sizin bileşen sistem davranışını güncelleyecektir her oyunda nesne her çerçeve. Bu kesinlikle motorunuzun kritik bir sistemidir. Performans burada önemlidir!

Bilgisayar mimarisine veya Veri Odaklı Tasarıma aşina iseniz, en iyi performansın nasıl elde edildiğini bilirsiniz: sıkıca doldurulmuş bellek ve kod yürütmesini gruplayarak. A, B ve C kod snippet'lerini şu şekilde çalıştırırsanız: ABCABCABC, aşağıdaki gibi çalıştırdığınızda aynı performansı elde edemezsiniz: AAABBBCCC. Bunun nedeni, yalnızca talimat ve veri önbelleğinin daha verimli kullanılması değil, aynı zamanda tüm "A" ları birbiri ardına yürütmeniz durumunda, optimizasyon için çok fazla alanın bulunmasıdır: yinelenen kodu kaldırmak, tarafından kullanılan verileri önceden hesaplamak tüm "A" lar vs.

Bu nedenle, tüm bileşenleri güncellemek istiyorsak, bunları bir güncelleme işleviyle sınıf / nesne yapmayalım. Her varlıktaki her bileşen için bu güncelleme işlevini çağırmayalım. Bu "ABCABCABC" çözümü. Tüm özdeş bileşen güncellemelerini birlikte gruplayalım. Ardından tüm A bileşenlerini, ardından B vb. Güncelleyebiliriz. Bunu yapmak için neye ihtiyacımız var?

İlk olarak Bileşen Yöneticilerine ihtiyacımız var. Oyundaki her bileşen türü için bir yönetici sınıfına ihtiyacımız var. Bu türdeki tüm bileşenleri güncelleyecek bir güncelleme işlevine sahiptir. Bu türde yeni bir bileşen ekleyecek bir oluşturma işlevi ve belirtilen bileşeni yok edecek bir kaldırma işlevi vardır. Bu bileşene özgü verileri almak ve ayarlamak için başka yardımcı işlevler olabilir (örneğin: Model Bileşeni için 3D modeli ayarlamak). Yönetici bazı açılardan dış dünyaya bir kara kutu olduğunu unutmayın. Her bir bileşenin verilerinin nasıl saklandığını bilmiyoruz. Her bir bileşenin nasıl güncellendiğini bilmiyoruz. Bileşenler gerektiği gibi davrandığı sürece umursamıyoruz.

Sonra bir varlığa ihtiyacımız var. Bunu bir sınıf haline getirebilirsiniz, ancak bu pek gerekli değildir. Bir varlık, benzersiz bir tamsayı kimliğinden veya bir karma dizeden (dolayısıyla bir tamsayı) başka bir şey olamaz. Varlık için bir bileşen oluşturduğunuzda, kimliği Yönetici olarak bağımsız değişken olarak iletirsiniz. Bileşeni kaldırmak istediğinizde, kimliği tekrar iletirsiniz. Varlığa yalnızca bir kimlik yapmak yerine biraz daha fazla veri eklemenin bazı avantajları olabilir, ancak bunlar yalnızca yardımcı işlevler olacaktır, çünkü gereksinimlerde listelediğim gibi, tüm varlık davranışı bileşenlerin kendileri tarafından tanımlanır. Yine de sizin motorunuz, sizin için anlamlı olan şeyleri yapın.

İhtiyacımız olan şey bir Varlık Yöneticisi. Bu sınıf, yalnızca kimlik çözümünü kullanırsanız benzersiz kimlikler oluşturur veya Entity nesnelerini oluşturmak / yönetmek için kullanılabilir. İhtiyacınız olursa oyundaki tüm varlıkların bir listesini de tutabilir. Varlık Yöneticisi, bileşen sisteminizin merkezi sınıfı olabilir, oyununuzdaki tüm ComponentManager'lara referansları saklayabilir ve güncelleme işlevlerini doğru sırayla çağırabilir. Bu şekilde tüm oyun döngüsünün yapması gereken EntityManager.update () öğesini çağırmaktır ve tüm sistem motorunuzun geri kalanından güzelce ayrılmıştır.

Bu kuşbakışı görünüm, bileşen yöneticilerinin nasıl çalıştığına bir göz atalım. İhtiyacınız olan şey:

  • Create (entityID) çağrıldığında bileşen verileri oluşturma
  • Remove (entityID) çağrıldığında bileşen verilerini sil
  • Update () çağrıldığında tüm (uygulanabilir) bileşen verilerini güncelle (yani tüm bileşenlerin her kareyi güncellemesi gerekmez)

Sonuncusu, bileşenlerin davranışını / mantığını tanımladığınız yerdir ve tamamen yazdığınız bileşenin türüne bağlıdır. AnimationComponent, Animasyon verilerini bulunduğu kareye göre güncelleyecektir. DragableComponent yalnızca fare tarafından sürüklenen bir bileşeni güncelleyecektir. PhysicsComponent fizik sistemindeki verileri güncelleyecektir. Yine de, aynı türdeki tüm bileşenleri tek seferde güncellediğiniz için, her bileşen, herhangi bir zamanda çağrılabilen bir güncelleme işlevine sahip ayrı bir nesne olduğunda mümkün olmayan bazı optimizasyonlar yapabilirsiniz.

Bileşen verilerini tutmak için hala bir XxxComponent sınıfı oluşturulması için çağrılmadığımı unutmayın. Sana bağlı. Data Oriented Design hoşunuza gitti mi? Daha sonra verileri her değişken için ayrı diziler halinde yapılandırın. Object Oriented Design hoşunuza gitti mi? (Bunu tavsiye etmem, yine de birçok yerde performansınızı öldürecek) Sonra her bileşenin verilerini tutacak bir XxxComponent nesnesi oluşturun.

Yöneticilerle ilgili en güzel şey kapsüllemedir. Şimdi kapsülleme, programlama dünyasındaki en korkunç suistimal edilmiş felsefelerden biridir. Bu şekilde kullanılmalıdır. Hangi bileşen verilerinin nerede depolandığını, bir bileşenin mantığının nasıl çalıştığını yalnızca yönetici bilir. Veri almak / ayarlamak için birkaç işlev vardır, ancak hepsi bu kadar. Tüm yöneticiyi ve temel sınıflarını yeniden yazabilirsiniz ve ortak arabirimi değiştirmezseniz, hiç kimse fark etmez. Fizik motoru değişti mi? Sadece PhysicsComponentManager'ı yeniden yazın ve işiniz bitti.

Sonra son bir şey var: bileşenler arasında iletişim ve veri paylaşımı. Şimdi bu zor ve tek bedene uyan bir çözüm yok. Örneğin çarpışma bileşeninin konum bileşeninden (örneğin PositionManager.getPosition (entityID)) konumu almasına izin vermek için yöneticilerde get / set işlevleri oluşturabilirsiniz. Bir olay sistemi kullanabilirsiniz. Bazı paylaşılan verileri varlıkta saklayabilirsiniz (bence en çirkin çözüm). Bir mesajlaşma sistemi kullanabilirsiniz (bu sıklıkla kullanılır). Veya birden fazla sistemin bir kombinasyonunu kullanın! Bu sistemlerin her birine girmek için zamanım veya deneyimim yok, ancak google ve stackoverflow arama arkadaşlarınız.


Bu cevabı çok ilginç buluyorum. Sadece bir soru (umarım siz veya birileri bana cevap verebilir). DOD bileşen tabanlı bir sistemdeki varlığı ortadan kaldırmayı nasıl başarabilirsiniz? Artemis bile Entity'i bir sınıf olarak kullanıyor, bunun çok dod olduğundan emin değilim.
Wolfrevo Kcats 20:13

1
Bunu ortadan kaldırarak ne demek istiyorsun? Varlık sınıfı olmayan bir varlık sistemi mi demek istediniz? Artemis'in bir Varlığa sahip olmasının nedeni, Artemis'te Varlık sınıfının kendi bileşenlerini yönetmesidir. Önerdiğim sistemde, ComponentManager sınıfları bileşenleri yönetir. Yani bir Entity sınıfına ihtiyaç duymak yerine, benzersiz bir tamsayı kimliğiniz olabilir. Diyelim ki bir pozisyon bileşeni olan 254 varlığınız var. Konumu değiştirmek istediğinizde id parametresi olarak 254 ile PositionCompMgr.setPosition (int id, Vector3 newPos) öğesini çağırabilirsiniz.
Mart

Ancak kimlikleri nasıl yönetiyorsunuz? Bir bileşeni daha sonra başka bir bileşene atamak için bir öğeden kaldırmak isterseniz ne olur? Bir varlığı kaldırmak ve yeni bir varlık eklemek isterseniz ne olur? Bir bileşenin iki veya daha fazla varlık arasında paylaşılmasını isterseniz ne olur? Buna gerçekten ilgi duyuyorum.
Wolfrevo Kcats

1
EntityManager yeni kimlikler vermek için kullanılabilir. Önceden tanımlanmış şablonlara dayalı tam varlıklar oluşturmak için de kullanılabilir (örn. Yeni bir kimlik oluşturan ve yenilenebilir, çarpışma, AI, belki de yakın dövüş savaşı için bir bileşen gibi bir düşman ninjası oluşturan tüm bileşenleri oluşturan "EnemyNinja" oluşturun , vb). Ayrıca tüm ComponentManager kaldırma işlevlerini otomatik olarak çağıran bir removeEntity işlevi de olabilir. ComponentManager, verilen Varlık için bileşen verileri olup olmadığını kontrol edebilir ve varsa verileri silebilir.
Mart

1
Bir bileşen bir varlıktan diğerine taşınsın mı? Her ComponentManager'a swapComponentOwner (int oldEntity, int newEntity) işlevini eklemeniz yeterlidir. ComponentManager'da veriler tamamen oradadır, ihtiyacınız olan tek şey hangi sahibine ait olduğunu değiştirmek için bir işlevdir. Her ComponentManager'da, hangi verilerin hangi varlık kimliğine ait olduğunu saklamak için dizin veya harita gibi bir şey bulunur. Varlık kimliğini eskiden yeni kimliğe değiştirmeniz yeterlidir. Düşündüğüm sistemde bileşen paylaşmanın kolay olup olmadığından emin değilim, ama ne kadar zor olabilir? Dizin tablosundaki bir Varlık Kimliği <-> Bileşen Verileri bağlantısı yerine birden çok var.
Mart

3

Bu bileşenlerin çalışması için her kareyi güncellemeleri gerekir, bunu yapmanın en kolay yolu sahne ağacının üzerinde yürümek ve her varlık için her bir bileşeni güncellemektir.

Bu, bileşen güncellemelerine tipik naif yaklaşımdır (ve sizin için işe yararsa, naif olmasıyla ilgili yanlış bir şey yoktur). Aslında üzerinde dokunduğunuz en büyük sorunlardan biri - bileşenin arabirimi (örneğin IComponent) aracılığıyla çalışıyorsunuz, böylece yeni güncellediğiniz şey hakkında hiçbir şey bilmiyorsunuz. Varlık içindeki bileşenlerin sıralaması hakkında da hiçbir şey bilmiyorsunuzdur, dolayısıyla

  1. farklı türlerdeki bileşenleri sık sık güncelleme olasılığınız vardır (referansın kötü kod konumu, esasen)
  2. bu sistem eşzamanlı güncellemelere iyi borç vermez, çünkü veri bağımlılıklarını belirleyecek bir konumda değilsiniz ve böylece güncellemeleri yerel ilişkisiz nesne gruplarına ayırırsınız.

Bileşen türlerinin her biri için tekil bir yönetici nesnesi kullanmayı düşündüm ama tek birtonun her zamanki dezavantajları var, bunu hafifletmenin bir yolu bağımlılık enjeksiyonu kullanmaktır ama bu sorun için aşırıya kaçmak gibi geliyor.

Burada bir singletona gerçekten ihtiyaç duyulmaz ve bu yüzden bundan kaçınmalısınız çünkü bahsettiğiniz dezavantajları taşır. Bağımlılık enjeksiyonu aşırıya kaçmaz - kavramın kalbi, bir nesnenin ihtiyacı olan şeyleri, ideal olarak yapıcıda, o nesneye iletmenizdir. Ağır bir DI çerçevesine ihtiyacınız yok ( Ninject gibi ) - sadece bir yere bir ekstra bir parametre iletin.

Bir oluşturucu temel bir sistemdir ve muhtemelen oyununuzdaki görsel şeylere (muhtemelen sprite veya model) karşılık gelen bir grup yenilenebilir nesnenin ömrünü oluşturmayı ve yönetmeyi destekler. Benzer şekilde, bir fizik motoru muhtemelen fizik simülasyonunda (katı cisimler) hareket edebilen varlıkları temsil eden şeyler üzerinde ömür boyu kontrole sahiptir. Bu ilgili sistemlerin her biri bir ölçüde bu nesnelere sahip olmalı ve bunları güncellemekten sorumlu olmalıdır.

Oyun varlığı kompozisyon sisteminizde kullandığınız bileşenler sadece bu alt düzey sistemlerden gelen örneklerin etrafına sarılmalıdır - pozisyon bileşeniniz sadece katı bir gövdeyi sarabilir, görsel bileşeniniz yenilenebilir bir hareketli grafik veya modeli, vb.

Daha sonra, alt düzey nesnelere sahip olan sistemin kendisi bunları güncellemekten sorumludur ve bunu toplu olarak ve uygunsa bu güncelleştirmeyi çok yönlü olarak kullanmasına izin verecek şekilde yapabilir. Ana oyun döngünüz, bu sistemlerin güncelleneceği ham sırayı kontrol eder (önce fizik, ardından oluşturucu veya her neyse). Elde ettiği örnekler üzerinde ömür boyu veya güncelleme denetimine sahip olmayan bir alt sisteminiz varsa, o sistemle ilgili tüm bileşenlerin güncellemesini toplu olarak ele almak için basit bir sarmalayıcı oluşturabilir ve bunu nereye yerleştireceğinize karar verebilirsiniz. sistem güncellemelerinizin geri kalanına göre güncelleme (bu genellikle, "script" bileşenleri ile bulur).

Daha fazla ayrıntı arıyorsanız, bu yaklaşım zaman zaman dış bileşen bileşeni yaklaşımı olarak bilinir .

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.