Bir oyun nesnesinin her bir örneği için DrawableGameComponent kullanmanın eksileri nelerdir?


25

DrawableGameComponents'ın "seviyeler" gibi şeyler veya bunları kullanmak yerine bazı tür yöneticiler için, örneğin karakter veya fayans için kaydedilmesi gerektiğini birçok yerde okudum (Bu adamın burada yazdığı gibi ). Ama bunun neden böyle olduğunu anlamıyorum. Bu yazıyı okudum ve bana çok mantıklı geldi, ama bunlar azınlık.

Genelde böyle şeylere fazla dikkat etmem, ama bu durumda görünen çoğunluğun neden bunun böyle olmadığına inandığını bilmek isterim. Belki bir şeyleri özlüyorumdur.


Ben, iki yıl sonra, aralarından "kabul" işareti kaldırdık neden olarak merak ediyorum Cevabıma ? Normalde Gerçekten umurumda olmazdı - ama bu durumda VeraShackle en neden çileden :( üstüne kabarcık cevabım yanlış tanıtımını
Andrew Russell

@AndrewRussell Uzun zaman önce bu konuyu okudum (bazı tepkiler ve oylar nedeniyle) ve cevabınızın doğru olup olmadığı hakkında bir fikir vermeyeceğimi düşündüm. Dediğiniz gibi, VeraShackle'ın cevabının en çok oy alan cevap olması gerektiğini sanmıyorum, bu yüzden cevabınızı tekrar doğru olarak işaretliyorum.
Kenji Kina

1
Buradaki cevabımı şu şekilde yoğunlaştırabilirim: " Kullanma, DrawableGameComponentsizi tek bir yöntem imza kümesine ve belirli bir tutulan veri modeline kilitler. Bunlar genellikle başlangıçta yanlış seçimdir ve kilitleme, kodun gelişimini önemli ölçüde engeller. "Ama sana burada neler olduğunu söyleyeyim ...
Andrew Russell

1
DrawableGameComponentçekirdek XNA API'sinin bir parçasıdır (yine: çoğunlukla tarihsel nedenlerden dolayı). Acemi programcılar onu kullanıyorlar çünkü "orada" ve "çalışıyor" ve daha iyisini bilmiyorlar. Onlara " yanlış " olduklarını söyleyen cevabımı görüyorlar ve - insan psikolojisinin doğası gereği - bunu reddediyor ve diğer "tarafı" seçiyorlar. Bu VeraShackle'ın tartışmasız cevabı; momentum almaya başladı, çünkü hemen aramadım (bu yorumları görün). Nihayetinde oradaki çürütücümün yeterli kaldığını hissediyorum.
Andrew Russell,

1
Herkes upvotes temelinde cevabım meşruiyetini sorgulayan ilgilenen varsa: o (GDSE yukarıda belirtilen acemiler ile aflush olmak üzere) VeraShackle cevabı bir elde başardı doğrudur iken çift daha son yıllarda benim daha upvotes, unutma sahip olduğum binlerce daha büyük ölçüde XNA üzerinde ağ genelindeki upvotes. XNA API'sini birden fazla platformda uyguladım. Ve XNA kullanan profesyonel bir oyun geliştiricisiyim. Cevabımın soyağacı kusursuz.
Andrew Russell,

Yanıtlar:


22

"Oyun Bileşenleri", XNA 1.0 tasarımının çok erken bir parçasıydı. WinForms için kontroller gibi çalışması gerekiyordu. Buradaki fikir, bir GUI kullanarak (zamanlayıcılar, vb. Görsel olmayan kontroller eklemek için kullanılan bit gibi) oyununuza sürükleyip bırakmanız ve özellikleri ayarlamanızdı.

Yani belki bir Kamera veya ... FPS sayacı? ... veya ...? Gibi bileşenlere sahip olabilirsiniz.

Anlıyorsun? Ne yazık ki bu fikir gerçek dünya oyunları için gerçekten işe yaramaz. Bir oyun için istediğiniz bileşen türü gerçekten yeniden kullanılamaz. Bir "oyuncu" bileşenine sahip olabilirsiniz, ancak diğerlerinin oyununun "oyuncu" bileşeninden çok farklı olacaktır.

Bu nedenle ve basit bir zaman eksikliği için GUI'nin XNA 1.0'dan önce çekildiğini hayal ediyorum.

Ancak API hala kullanılabilir durumda ... İşte kullanmamalısın nedeni:

Temel olarak, bir oyun geliştiricisi olarak yazması ve okuması, hata ayıklaması ve bakımı en kolay olan şeydir. Yükleme kodunuzda böyle bir şey yaparak:

player.DrawOrder = 100;
enemy.DrawOrder = 200;

Bunu yapmaktan çok daha az açıktır:

virtual void Draw(GameTime gameTime)
{
    player.Draw();
    enemy.Draw();
}

Özellikle, gerçek dünyadaki bir oyunda, bunun gibi bir şeyle sonuçlanabilir:

GraphicsDevice.Viewport = cameraOne.Viewport;
player.Draw(cameraOne, spriteBatch);
enemy.Draw(cameraOne, spriteBatch);

GraphicsDevice.Viewport = cameraTwo.Viewport;
player.Draw(cameraTwo, spriteBatch);
enemy.Draw(cameraTwo, spriteBatch);

(Kuşkusuz, benzer Servicesmimariyi kullanarak kamerayı ve spriteBatch'i paylaşabilirsin . Ama aynı sebeple bu da kötü bir fikir.)

Bu mimari - ya da eksikliği - çok daha hafif ve esnektir!

Çizim sırasını kolayca değiştirebilir veya birden fazla görünüm alanı ekleyebilir veya yalnızca birkaç satırı değiştirerek bir oluşturma hedefi efekti ekleyebilirim. Bunu diğer sistemde yapmak, tüm bu bileşenlerin - birden fazla dosyaya yayılmış - ne yaptığını ve hangi sırada olduğunu düşünmemi gerektiriyor .

Zorunlu programlama vs zorunlu programlama arasındaki fark gibi bir şey. Oyun gelişimi için zorunlu programlamanın çok daha fazla tercih edildiği ortaya çıktı.


2
Görünüşe göre oyun programlaması için yaygın olarak kullanılan Bileşen tabanlı programlama için izlenimleri altındaydım .
BlueRaja - Danny Pflughoeft

3
XNA oyun bileşeni sınıfları, bu forma gerçekten doğrudan uygulanamaz. Bu model, birden fazla bileşenden nesneler oluşturmakla ilgilidir. XNA sınıfları, nesne başına bir bileşene yöneliktir. Tasarımınıza tam olarak uyuyorlarsa, elbette onları kullanın. Ama gerektiği değil çevrelerindeki Tasarımınızı kurmak! Ayrıca cevabımı burada gör - nereye söylediğime dikkat et DrawableGameComponent.
Andrew Russell

1
GameComponent / Service mimarisinin o kadar kullanışlı olmadığı ve Office benzeri veya web uygulaması kavramları gibi bir oyun çerçevesine zorlanan sesler olduğunu kabul ediyorum. Ama player.DrawOrder = 100;kuralı emir almak için yöntemi zorunlu olanın üzerinde kullanmayı tercih ederim . Şu anda bir Flixel oyunu bitirdim, bu da nesneleri sahneye eklediğiniz sıraya göre çizer. FlxGroup sistemi bunların bir kısmını hafifletir, ancak yerleşik bir çeşit Z-endeksi sıralamasının olması iyi olurdu.
michael.bartnett

@ michael.bartnett Flixel'in tutulan modlu bir API olduğunu ve XNA'nın (açıkçası, oyun bileşenleri sistemi hariç) bir anlık mod API olduğunu unutmayın. Tutulan modlu bir API kullanıyorsanız veya kendinizinkini uygularsanız , anında çekiliş sırasını değiştirme yeteneği kesinlikle önemlidir. Aslında, çalışma sırasındaki çizim sırasını değiştirme ihtiyacı, bir şeyi korunma modunda yapmak için iyi bir nedendir (bir pencere sistemi örneği akla gelir). Ama eğer olabilir (XNA gibi) platform API bu şekilde inşa edilir, o zaman IMO yapmanız gerekir, hemen-modu olarak bir şeyler yazmak.
Andrew Russell,


26

Hmmm. Korkarım ki biraz dengenin iyiliği için buna meydan okudum.

Andrew’un cevabında 5 suçlama var gibi gözüküyor, bu yüzden sırasıyla her birini ele almaya çalışacağım:

1) Bileşenler eski ve terk edilmiş bir tasarımdır.

Bileşen tasarım deseni fikri, XNA'dan çok önce varolmuştu - özünde, sadece kendi Güncelleme ve Beraberlik davranışlarının evi olan aşırı kemerli oyun nesnesi yerine nesneler yapma kararıydı. Bileşen koleksiyonundaki nesneler için, oyun uygun bir zamanda otomatik olarak bu yöntemleri (ve diğer birkaç kişiyi) çağıracak - oyun nesnesinin kendisi bu kodun hiçbirini “bilmek” zorunda değildir. Oyun sınıflarınızı farklı oyunlarda (ve dolayısıyla farklı Oyun nesnelerinde) yeniden kullanmak istiyorsanız, nesnelerin bu eşleştirilmesi hala temelde iyi bir fikirdir. AppHub'tan XNA4.0'ın en yeni (profesyonelce üretilmiş) demolarına kısa bir bakış, Microsoft'un hala (benim görüşüme göre haklı olarak) bunun arkasında durduğu izlenimini doğruluyor.

2) Andrew 2'den fazla yeniden kullanılabilir oyun dersi düşünemez.

Yapabilirim. Aslında yükleri düşünebilirim! Şu andaki paylaşılan kütüphanemi kontrol ettim ve 80'den fazla yeniden kullanılabilir bileşen ve hizmet içeriyor . Size bir lezzet vermek için şunları yapabilirsiniz:

  • Oyun Durumu / Ekran Yöneticileri
  • ParticleEmitters
  • Çizilebilir ilkel maddeler
  • AI kontrolörleri
  • Giriş kontrolörleri
  • Animasyon denetleyicileri
  • Billboardlar
  • Fizik ve Çarpışma Yöneticileri
  • Kameralar

Herhangi bir özel oyun fikri bu setten en az 5-10 şeye ihtiyaç duyacaktır - garantili - ve tamamen yeni bir oyunda bir kod satırı ile tamamen işlevsel olabilirler.

3) Kesin çekiliş sırasını kesin çekiliş çağrıları sırasına göre kontrol etmek, bileşenin DrawOrder özelliğini kullanmak için tercih edilir.

Temel olarak bu konuda Andrew ile aynı fikirdeyim. Ancak, tüm bileşeninizin DrawOrder özelliklerini varsayılan olarak bırakırsanız, çizim sırasını, koleksiyona eklediğiniz sıraya göre açıkça denetleyebilirsiniz. Bu, onların el ile arama çağrılarının açık bir şekilde sıralanması için 'görünürlük' terimleriyle gerçekten aynıdır.

4) Nesne Çizme yöntemlerini kendiniz çağırmak, varolan çerçeve yöntemiyle çağrıldığından daha hafiftir.

Niye ya? Ayrıca, güncelleme mantığınız ve Beraberlik kodunuz hariç, en önemlisi olan projeler dışında, her durumda, sonuç olarak ortaya çıkan performans açısından yöntem çağrınızı tamamen gölgeleyecektir.

5) Kodunuzu tek bir dosyaya yerleştirmek, hata ayıklama sırasında tek tek nesnenin dosyasına koymaktan daha çok tercih edilir.

Öncelikle, Visual Studio'da hata ayıklama yaptığım zaman, diskte bulunan dosyalar açısından bir kesme noktasının konumu bugünlerde neredeyse görünmez - IDE hiç bir drama yapmadan yerle ilgileniyor. Ancak, bir an için Skybox çiziminizde bir sorun olduğunu düşünün. Loca Kurası yöntemine edecek mi gerçekten (muhtemelen çok uzun) kodu çizim skybox yerini daha külfetli usta Oyun nesnesinde beraberlik yöntemine? Hayır, gerçekten değil - aslında bunun tam tersi olduğunu iddia ediyorum.

Bu yüzden, özet olarak - lütfen Andrew'un argümanlarına uzunca bir göz atın. Dolandırılmış oyun kodu yazmak için aslında sağlam bir temel verdiklerini sanmıyorum. Ayrıştırılmış bileşen modelinin üst kısımları (akıllıca kullanılır) büyüktür.


2
Ben sadece bu yazıyı farkettim ve güçlü bir rebuttal yayınlamalıyım: Yeniden kullanılabilir sınıflarla ilgili bir sorunum yok! (Çizmek ve güncelleştirme yöntemleri ve benzeri olabilir hangi.) Ben alıyorum spesifik (kullanarak sorunu Drawable) GameComponentnedeniyle sertliği (eğer 3. yılında katılıyorum) üzerinde çizim açık kontrol kaybı / güncelleme düzene, oyun mimarisini sağlamak Arayüzlerinin (ele almadığınız).
Andrew Russell

1
Dördüncü puanınız, aslında açıkça mimari esnekliği kastettiğim performans anlamına gelen "hafif" kelimesini kullanmamı sağlıyor. Kalan puanlarınız # 1, # 2 ve özellikle de # 5, çoğu / tüm kodların ana oyun sınıfınıza girmesi gerektiğini düşündüğü saçma sapan adamı dikmiş gibi görünüyor ! Mimariyle ilgili daha ayrıntılı düşüncelerim için cevabımı buradan okuyabilirsiniz .
Andrew Russell

5
Son olarak: Cevabımı yanıtlayacak ve yanlış olduğumu söyleyerek cevap yazacak olursam, cevaplarıma cevap vermek de yerinde olur, bu yüzden üzerine rastlamak yerine, bu konuda bir bildirim görürüm. o iki ay sonra.
Andrew Russell

Yanlış anlamış olabilirim, ancak bu soruyu nasıl cevaplıyor? Spritelarım için bir GameComponent kullanmalı mıyım yoksa kullanmamalı mıyım?
Superbest

Daha fazla rebuttal
Kenji Kina

4

Andrew'a biraz katılmıyorum, ama ayrıca her şey için bir zaman ve yer olduğunu düşünüyorum. Drawable(GameComponent)Sprite veya Düşman kadar basit bir şey için kullanmam . Ancak, bir SpriteManagerveya için kullanırdım EnemyManager.

Andrew'un çizim ve güncelleme emriyle ilgili söylediklerine geri Sprite.DrawOrderdönersek, birçok sprite kafa karıştırıcı olacağını düşünüyorum . Dahası, küçük ve (ya da makul miktarda) bir Müteahhitlik kurmanın DrawOrderve bunun için nispeten kolay .UpdateOrder(Drawable)GameComponents

Bence bu sert ve kimsenin kullanmadığı yerlerde, Microsoft onlardan uzaklaşacaktı. Fakat rol yapmadıysa, mükemmel çalışıyorlardı. Ben kendim onları Game.Servicesarka planda güncellenen geliştirmek için kullanıyorum .


2

Ben de bunu düşünüyordum ve aklıma geldi: Mesela, linkindeki örneği çalmak için, bir RTS oyununda her birimi bir oyun bileşeni örneği yapabilirsin. Tamam.

Ancak, bir birimlerin listesini tutan, gerektiğinde çizen ve güncelleyen bir UnitManager bileşeni de yapabilirsiniz. Sanırım birimlerinizi oyun bileşenlerine dönüştürmek istemenizin nedeni, ilk önce kodu bölümlere ayırmaktır, bu nedenle bu çözüm bu hedefe yeterli bir şekilde ulaşır mı?


2

Yorumlarda, cevabımın eski cevabının özetini yayınladım:

Kullanımı DrawableGameComponentsizi tek bir yöntem imzaları kümesine ve belirli bir tutulan veri modeline kilitler. Bunlar genellikle başlangıçta yanlış bir seçimdir ve kilitleme, kodun geleceğinin gelişimini önemli ölçüde engellemektedir.

Burada, mevcut projemden bazı kodlar göndererek bunun neden böyle olduğunu açıklamaya çalışacağım.


Oyun dünyasının durumunu koruyan kök nesnenin Updateşuna benzeyen bir yöntemi vardır:

void Update(UpdateContext updateContext, MultiInputState currentInput)

Giriş noktaları vardır UpdateOyunun ağda çalışıp çalışmadığına bağlı , . Örneğin, oyun durumunun önceki bir noktaya geri döndürülmesi ve daha sonra yeniden simüle edilmesi mümkündür.

Ayrıca aşağıdakiler gibi bazı güncelleme benzeri yöntemler de vardır:

void PlayerJoin(int playerIndex, string playerName, UpdateContext updateContext)

Oyunun içinde güncellenecek aktörlerin bir listesi var. Ama bu basit bir şey değilUpdate çağrıdan . Fiziği işlemek için önce ve sonra ek geçişler var. Ayrıca, aktörlerin ertelenmesi / ertelenmesi ve aynı zamanda seviye geçişleri için bazı süslü şeyler var.

Oyun durumu dışında, bağımsız bir şekilde yönetilmesi gereken bir UI sistemi var. Ancak, kullanıcı arayüzündeki bazı ekranların bir ağ bağlamında da çalışabilmesi gerekir.


Çizim için, oyunun doğası gereği, bu yöntemlerden girdi alan özel bir sıralama ve toplama sistemi vardır:

virtual void RegisterToDraw(SortedDrawList sortedDrawList, Definitions definitions)
virtual void RegisterShadow(ShadowCasterList shadowCasterList, Definitions definitions)

Ve sonra ne çizileceğini anladıktan sonra, otomatik olarak çağırır:

virtual void Draw(DrawContext drawContext, int tag)

tagParametresi bazı aktörler diğer aktörlerin tutunmaya çünkü gereklidir - ve ihtiyaç böylece bekletilen aktör üstünde ve altında hem çizmek edebilmek için. Sıralama sistemi bunun doğru sırada olmasını sağlar.

Çizilecek menü sistemi de var. Ve oyunun etrafında, kullanıcı arayüzü, HUD etrafında çizmek için hiyerarşik bir sistem.


Şimdi bu gereksinimleri aşağıdakilerle karşılaştırın DrawableGameComponent:

public class DrawableGameComponent
{
    public virtual void Update(GameTime gameTime);
    public virtual void Draw(GameTime gameTime);
    public int UpdateOrder { get; set; }
    public int DrawOrder { get; set; }
}

DrawableGameComponentYukarıda tarif ettiğim sistemi kullanan bir sistemden ulaşmanın makul bir yolu yoktur . (Ve bunlar mütevazı karmaşıklıkta bile bir oyun için olağandışı şartlar değildir).

Ayrıca, bu gereksinimlerin projenin başlangıcında basitçe ortaya çıkmadığına dikkat etmek önemlidir. Aylarca süren geliştirme çalışmaları ile gelişti.


Genelde insanların yaptığı, DrawableGameComponentrotadan aşağıya inerlerse, saldırıların üzerine inşa etmeye başlarlar:

  • Yöntem eklemek yerine, kendi temel türlerine biraz aşağı inme yapıyorlar (neden bunu doğrudan kullanmıyorsunuz?).

  • Sıralama ve çağırma kodunu değiştirmek yerine Draw/ çağırmak yerine, hareket halindeki Updatesipariş numaralarını değiştirmek için çok yavaş şeyler yaparlar.

  • Ve hepsinden kötüsü: Yöntemlere parametre eklemek yerine, yığın olmayan bellek konumlarında "" parametreleri "geçmeye başlarlar (bunun kullanımı Servicespopülerdir). Bu sarsılmış, hataya açık, yavaş, kırılgan, nadiren iş parçacığı güvenlidir ve ağ eklerseniz, harici araçlar yaparsanız vb. Sorun yaşar.

    Ayrıca kodun kullanımını daha da zorlaştırır , çünkü bağımlılıklarınız artık API sınırında yayınlanmak yerine "bileşen" içinde gizlidir.

  • Ya da bunların neredeyse ne kadar çılgınca olduğunu görüyorlar ve DrawableGameComponentbu sorunların üstesinden gelmek için "yönetici" ler yapmaya başlıyorlar . Bunun dışında hala "yönetici" sınırlarında bu problemleri var.

    Muhtemelen bu "yöneticiler" kolayca istenen sıraya göre bir dizi yöntem çağrısı ile değiştirilebilir. Başka bir şey fazla karmaşık.

Tabii ki, bir kez bu saldırıları kullanmaya başladığınızda, sağladığınızdan daha fazlasına ihtiyacınız olduğunu fark ettiğinizde bu saldırıları geri almak gerçek bir iş gerektirir DrawableGameComponent. " Kilitli " oldun . Baştan çıkarıcı olmak çoğu zaman kesmek zorunda kalmaya devam etmektir - pratikte sizi yavaşlatacak ve nispeten basit olanlarla yapabileceğiniz oyun türlerini sınırlayacaktır.


Birçok insan bu noktaya ulaşıyor ve içgüdüsel bir tepki gösteriyor - muhtemelen kullanıyorlar DrawableGameComponentve "yanlış" olmayı kabul etmek istemiyorlar. Bu yüzden en azından "çalışmasını" mümkün kıldığı gerçeğine dayanıyorlar .

Tabii ki "çalışmak" için yapılabilir ! Bizler programcıyız - olağandışı sınırlamalarla işleri yürütmek pratikte bizim işimiz Ancak bu kısıtlama gerekli, faydalı veya rasyonel değildir ...


Özellikle korkutucu olan şey, bu konunun tarafına atılmasının şaşırtıcı derecede önemsiz olmasıdır. Burada, size kodu bile vereceğim:

public class Actor
{
    public virtual void Update(GameTime gameTime);
    public virtual void Draw(GameTime gameTime);
}

List<Actor> actors = new List<Actor>();

foreach(var actor in actors)
    actor.Update(gameTime);

foreach(var actor in actors)
    actor.Draw(gameTime);

(It sade göre sıralama eklemek DrawOrderve UpdateOrder. Basit bir yolu iki listeleri ve kullanımını sağlamaktır List<T>.Sort(). Ayrıca sap için Önemsiz Load/ ' UnloadContent.)

O kod aslında neyin önemli değildir olduğunu . Önemli olan, önemsiz bir şekilde değiştirebilmem .

Farklı bir zamanlama sistemine ihtiyacım olduğunu gameTimeveya daha fazla parametreye (veya UpdateContextiçinde yaklaşık 15 şey olan bir nesnenin tümüne ihtiyacım olduğunu) veya çizim için tamamen farklı bir işlem sırasına ihtiyacım olduğunu veya bazı ekstra yöntemlere ihtiyacım olduğunu fark ettiğimde farklı güncelleme geçişleri için, devam edip bunu yapabilirim .

Ve hemen yapabilirim. DrawableGameComponentKodumu seçmem konusunda endişelenmeme gerek yok . Daha da kötüsü: Böyle bir ihtiyaç ortaya çıktığında daha da zorlaşacak bir şekilde hackleyin.


Kullanmanın iyi bir seçim olduğu tek bir senaryo var DrawableGameComponent: ve eğer tam anlamıyla XNA için sürükle ve bırak-bileşen-tarzı ara katman yazılımı yapıyor ve yayınlıyorsanız . Asıl amacı buydu. Hangisini ekleyeceğim - kimse yapmıyor!

(Benzer şekilde, yalnızca içinServices özel içerik türleri yaparken kullanımı gerçekten mantıklı geliyorContentManager .)


Puanlarınızla aynı fikirdeyken DrawableGameComponent, özellikle bazı bileşenler için miras kalan şeyleri, özellikle de menü ekranları (menüler, çok sayıda düğme, seçenek ve sayfalar) veya FPS / hata ayıklama bindirmeleri gibi kullanımlarda yanlış bir şey görmüyorum sen bahsettin Bu durumlarda genellikle üzerinde sipariş çizmek ve size (eğer sürece, iyi bir şeydir elle yazma gereken az kod ile sona ince taneli kontrolü gerekmez gerek bu kod yazmak). Fakat sanırım bahsettiğiniz sürükle ve bırak senaryosu budur.
Groo
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.