C ++ motor programlamasında singletonları nasıl doğru bir şekilde kullanabilirim?


16

Singletonların kötü olduğunu biliyorum, eski oyun motorum, tüm verileri tutmaktan gerçek oyun döngüsüne kadar her şeyi işleyen tek bir 'Oyun' nesnesi kullandı. Şimdi yeni bir tane yapıyorum.

Sorun, SFML içinde pencere çizmek için kullandığınız bir şey çizmek window.draw(sprite)için sf::RenderWindow. Burada gördüğüm 2 seçenek var:

  1. Oyundaki her varlığın geri aldığı tek bir Game nesnesi yap (daha önce kullandığım şey)
  2. Bunu varlıklar için yapıcı yap: Entity(x, y, window, view, ...etc)(bu sadece saçma ve sinir bozucu)

Bir Varlığın yapıcısını sadece x ve y'ye tutarken bunu yapmanın uygun yolu ne olurdu?

Ana oyun döngüsünde yaptığım her şeyi takip edip takip edebilirim ve sadece oyun döngüsünde spritelarını elle çizebilirim, ancak bu da dağınık görünüyor ve ayrıca varlık için tüm bir çekme işlevi üzerinde tam bir kontrol istiyorum.


1
Pencereyi 'render' işlevinin bir argümanı olarak iletebilirsiniz.
dari

25
Singletons kötü değil! yararlı olabilir ve bazen gerekli olabilir (elbette tartışmalıdır).
ExOfDe

3
Singletonları düz globallerle değiştirmekten çekinmeyin. Küresel olarak ihtiyaç duyulan kaynakları "talep üzerine" yaratmanın anlamı yok, onları aktarmanın anlamı yok. Varlıklar için, hepsiyle alakalı belirli şeyleri tutmak için bir "seviye" sınıfı kullanabilirsiniz.
snake5

Penceremi ve diğer bağımlılıkları ana alanımda beyan ederim ve sonra diğer sınıflarımda işaretçilerim var.
KaareZ

1
@JAB main () 'dan manuel başlatma ile kolayca sabitlenir. Tembel başlatma, bilinmeyen bir anda gerçekleşmesini sağlar, bu da çekirdek sistemler için iyi bir fikir değildir.
snake5

Yanıtlar:


3

Yalnızca hareketli grafiği her varlığın içinde oluşturmak için gereken verileri depolayın, daha sonra onu varlıktan alın ve oluşturma için pencereye aktarın. Varlıklar içinde herhangi bir pencere depolamaya veya verileri görüntülemeye gerek yok.

Bir Level sınıfı (şu anda kullanılmakta olan tüm objeleri tutan) ve bir Renderer sınıfına (pencereyi, görünümü ve oluşturma için başka her şeyi içeren ) sahip bir üst düzey Game veya Engine sınıfınız olabilir .

Böylece, üst düzey sınıfınızdaki oyun güncelleme döngüsü şöyle görünebilir:

EntityList entities = mCurrentLevel.getEntities();
for(auto& i : entities){
  // Run game logic...
  i->update(...);
}
// Render all the entities
for(auto& i : entities){
  mRenderer->draw(i->getSprite());
}

3
Singleton hakkında ideal bir şey yok. Gerekmediğinde uygulama içi neden herkese açık hale getirilsin? Neden Logger::getInstance().Log(...)sadece yazmak yerine Log(...)? Manuel olarak sadece bir kez yapıp yapamayacağınızı sorduğunuzda neden sınıfı rastgele başlatmalısınız? Statik globalleri referans alan küresel bir işlev oluşturmak ve kullanmak çok daha kolaydır.
snake5

@ snake5 Stack Exchange'deki singletonları gerekçelendirmek Hitler ile sempati duymak gibidir.
Willy Goat

30

Basit yaklaşım eskiden Singleton<T>küresel olan şeyi yapmaktır T. Globallerin de sorunları var, ancak önemsiz bir kısıtlamayı zorlamak için bir sürü ekstra çalışma ve ortak kod içermiyorlar. Bu temel olarak varlık kurucusuna dokunmayı (potansiyel olarak) içermeyen tek çözümdür.

Daha zor, ama muhtemelen daha iyi yaklaşım, bağımlılıklarınızı ihtiyacınız olan yere iletmektir . Evet, bu Window *, bir grup nesneye (varlığınız gibi) kaba görünen bir şekilde geçmeyi içerebilir . Brüt görünmesi gerçeği size bir şey söylemelidir: tasarımınız brüt olabilir.

Bunun daha zor olmasının nedeni (daha fazla yazmanın ötesinde), bunun sıklıkla arayüzlerinizi yeniden düzenlemeye yol açmasıdır, böylece geçmek için "ihtiyaç duyduğunuz" şey daha az yaprak seviyesi sınıfı tarafından gereklidir. Bu, oluşturucunuzu her şeye giderken geçmekte olan çirkinliğin çoğunu yapar ve ayrıca bağımlılıkları parametre olarak alarak çok açık hale getirdiğiniz bağımlılık ve bağlantı miktarını azaltarak kodunuzun genel sürdürülebilirliğini artırır. . Bağımlılıklar singleton veya global olduğunda, sistemlerinizin birbirine ne kadar bağlı olduğu daha az açıktı.

Ancak potansiyel olarak büyük bir girişimdir. Gerçekten sonra bir sisteme yapmak düpedüz acı verici olabilir. Şimdilik sisteminizi tek başına, teklitonla yalnız bırakmanız çok daha pragmatik olabilir (özellikle de aksi halde iyi çalışan bir oyun göndermeye çalışıyorsanız; oyuncular genellikle orada bir singleton veya dört tane).

Mevcut tasarımınızla bunu denemek istiyorsanız, mevcut uygulamanız hakkında çok daha fazla ayrıntı göndermeniz gerekebilir, çünkü bu değişiklikleri yapmak için gerçekten genel bir kontrol listesi yoktur. Veya bunu tartışmak gelip sohbet .

Gönderdiğiniz şeyden, "tekil olmayan" yönde büyük bir adımın, varlıklarınızın pencereye veya görünüme erişme ihtiyacından kaçınmak olacağını düşünüyorum. Kendilerini çizdiklerini ve varlıkların kendilerini çizmelerine gerek olmadığını gösterir . Varlıkların yalnızca izin verecek bilgileri içerdiği bir metodoloji benimseyebilirsiniz(pencere ve görünüm referanslarına sahip) bazı harici sistem tarafından çizilmeleri gerekir. Varlık sadece konumunu gösterir ve kullanması gereken hareketli grafik (veya yinelenen örneklerin oluşmasını önlemek için oluşturucunun içindeki gerçek hareketli karakterleri önbelleğe almak istiyorsanız söz konusu hareketli grafiğe bir tür referans). Oluşturucuya basitçe, içinden geçtiği, verileri okuduğu ve dahili olarak tutulan pencere nesnesini drawvarlık için aranan hareketli grafikle çağırmak için kullandığı belirli bir varlık listesi çizmesi söylenir .


3
C ++ aşina değilim, ama bu dil için rahat bağımlılık enjeksiyon çerçeveleri yok mu?
bgusach

1
Bunlardan hiçbirini "rahat" olarak tanımlamam ve onları genel olarak özellikle yararlı bulmuyorum, ancak başkaları onlarla farklı deneyime sahip olabilirler, bu yüzden onları yetiştirmek için iyi bir nokta.

1
Varlıklar olarak tanımladığı yöntem, varlıkların kendilerini çizmemesine, ancak bilgileri tutmasına ve tüm varlıkların çizimini tek bir sistemde işlemesinin günümüzde en popüler oyun motorlarında çokça kullanılmasıdır.
Patrick W. McMahon

1
+1 için "Kaba görünüyor olması size bir şey söylemelidir: tasarımınız brüt olabilir."
Shadow503

Hem ideal davayı hem de pragmatik cevabı vermek için +1.

6

Sf'den devral :: RenderWindow

SFML aslında sizi sınıflarından miras almaya teşvik eder.

class GameWindow: public sf::RenderWindow{};

Buradan, çizim objeleri için üye çizim fonksiyonları oluşturursunuz.

class GameWindow: public sf::RenderWindow{
public:
 void draw(const Entity& entity);
};

Şimdi bunu yapabilirsiniz:

GameWindow window;
Entity entity;

window.draw(entity);

Varlıklarınız sf :: Sprite'dan devralınarak Varlıklarınız kendi benzersiz spritelarını yapacaksa bunu bir adım daha ileri taşıyabilirsiniz.

class Entity: public sf::Sprite{};

Şimdi sf::RenderWindowsadece Varlıkları çizebilir ve varlıklar artık setTexture()ve gibi işlevlere sahiptir setColor(). Varlık, hareketli grafiğin konumunu kendi konumu olarak bile kullanabilir, böylece setPosition()hem Varlığı hem de hareketli grafiğini taşımak için işlevi kullanmanıza izin verir .


Sonunda, eğer sadece:

window.draw(game);

Aşağıda bazı hızlı örnek uygulamalar yer almaktadır

class GameWindow: public sf::RenderWindow{
 sf::Sprite entitySprite; //assuming your Entities don't need unique sprites.
public:
 void draw(const Entity& entity){
  entitySprite.setPosition(entity.getPosition());
  sf::RenderWindow::draw(entitySprite);
 }
};

VEYA

class GameWindow: public sf::RenderWindow{
public:
 void draw(const Entity& entity){
  sf::RenderWindow::draw(entity.getSprite()); //assuming Entities hold their own sprite.
 }
};

3

Oyun geliştirmede tekilleri, diğer her türlü yazılım geliştirmede önlediğiniz gibi önlersiniz: bağımlılıkları geçersiniz .

Bu arada o dışarı ile, (gibi çıplak türleri olarak doğrudan bağımlılıkları geçmek seçebilir int, Window*vs.) ya da (gibi türleri sarıcı bir veya daha fazla özel onları geçmek için seçebilirsiniz EntityInitializationOptions).

İlk yol sinir bozucu olabilir (öğrendiğiniz gibi), ikincisi her nesneyi tek bir nesnede geçirmenize ve alanları değiştirmeden ve her varlık kurucusunu değiştirmeden alanları değiştirmenize (ve hatta seçenekler türünü özelleştirmenize) izin verir. İkinci yolun daha iyi olduğunu düşünüyorum.


3

Singletons kötü değil. Bunun yerine kötüye kullanımı kolaydır. Öte yandan, küresellerin kötüye kullanımı daha da kolaydır ve daha fazla sorun yaşarlar.

Bir singletonu global ile değiştirmenin tek geçerli nedeni, dini singleton düşmanlarını yatıştırmaktır.

Sorun, sadece tek bir küresel örneği olan ve her yerden erişilebilir olması gereken sınıfları içeren bir tasarıma sahip olmaktır. Teklitonun birden fazla örneğine sahip olduğunuzda, örneğin bölünmüş ekran uyguladığınızda bir oyunda veya tek bir kaydedicinin her zaman harika bir fikir olmadığını fark ettiğinizde yeterince büyük bir kurumsal uygulamada bu parçalanır. .

Sonuç olarak, referans olarak makul bir şekilde aktaramayacağınız tek bir global örneğinizin olduğu bir sınıfınız varsa , singleton genellikle en uygun olmayan çözümler havuzundaki daha iyi çözümlerden biridir.


1
Ben dini bir single düşmanı ve küresel bir çözüm de görmüyorum. : S
Dan Pantry

1

Bağımlılıkları enjekte edin. Bunu yapmanın bir yararı, şimdi bir fabrika aracılığıyla bu bağımlılıkların çeşitli türlerini oluşturabilmenizdir. Ne yazık ki, tekilleri onları kullanan bir sınıftan sökmek, bir kediyi halı boyunca arka ayakları tarafından çekmek gibidir. Ancak, onları enjekte ederseniz, belki de anında uygulamaları değiştirebilirsiniz.

RenderSystem(IWindow* window);

Şimdi çeşitli pencere türlerini enjekte edebilirsiniz. Bu, RenderSystem'a karşı çeşitli pencerelerle testler yazabilmenizi sağlar, böylece RenderSystem'inizin nasıl kırılacağını veya performans göstereceğini görebilirsiniz. Doğrudan "RenderSystem" içinde tekiltonlar kullanıyorsanız, bu mümkün değildir veya daha zor olabilir.

Şimdi daha test edilebilir, modüler ve ayrıca belirli bir uygulamadan ayrılıyor. Somut bir uygulamaya değil, yalnızca bir arayüze bağlıdı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.