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.