Oyun döngüsü, koşulları bir kez kontrol etme, bir şeyler yapma, sonra tekrar yapma


13

Örneğin, bir Game sınıfım var ve intoyuncunun hayatlarını takip eden bir dersim var. Şartlıyım

if ( mLives < 1 ) {
    // Do some work.
}

Ancak bu durum ateş etmeye devam eder ve iş tekrar tekrar yapılır. Örneğin, oyundan 5 saniye içinde çıkmak için bir zamanlayıcı ayarlamak istiyorum. Şu anda, her karede 5 saniyeye ayarlamaya devam edecek ve oyun asla bitmeyecek.

Bu sadece bir örnek ve oyunumun çeşitli alanlarında aynı problemi yaşıyorum. Ben bazı durumu kontrol etmek ve daha sonra bir kez ve sadece bir kez bir şey yapmak istiyorum, ve sonra kontrol veya if-deyimindeki kodu tekrar yapmak istemiyorum. Akla gelen bazı olası çözümler, boolher koşul için bir boola'ya sahiptir ve durumun ne zaman tetikleneceğini ayarlar . Bununla birlikte, uygulamada bools, sınıf alanları olarak veya statik olarak yöntemin içinde saklanması gerektiğinden, bu çok fazla işlem yapar .

Bunun için doğru çözüm nedir (örneğim için değil, sorunlu etki alanı için)? Bunu bir oyunda nasıl yapardınız?


Cevaplar için teşekkürler, benim çözümüm şimdi ktodisco ve kranran cevaplarının bir kombinasyonudur.
EddieV223

Yanıtlar:


13

Ben sadece olası kod yolları üzerinde daha dikkatli bir kontrol uygulayarak bu sorunu çözebilirsiniz düşünüyorum. Örneğin, oyuncunun yaşam sayısının bir değerin altına düşüp düşmediğini kontrol ettiğinizde, neden her çerçeve yerine oyuncu sadece bir can kaybettiğinde kontrol etmiyorsunuz?

void subtractPlayerLife() {
    // Generic life losing stuff, such as mLives -= 1
    if (mLives < 1) {
        // Do specific stuff.
    }
}

Bu da, subtractPlayerLifebelki de her durumda kontrol etmeniz gereken koşullardan (belki de çarpışmalar) kaynaklanabilecek , yalnızca belirli durumlarda çağrılacağını varsayar .

Kodunuzun nasıl yürütüldüğünü dikkatlice kontrol ederek, statik booleans gibi dağınık çözümlerden kaçınabilir ve tek bir çerçevede ne kadar kod yürütüldüğüne göre yavaş yavaş azaltabilirsiniz.

Refactor için imkansız görünen bir şey varsa ve gerçekten sadece bir kez kontrol etmeniz gerekiyorsa, o zaman devlet (an gibi enum) veya statik boolean'lar gitmenin yoludur. Statiği kendiniz bildirmekten kaçınmak için aşağıdaki hileyi kullanabilirsiniz:

#define ONE_TIME_IF(condition, statement) \
    static bool once##__LINE__##__FILE__; \
    if(!once##__LINE__##__FILE__ && condition) \
    { \
        once##__LINE__##__FILE__ = true; \
        statement \
    }

Bu da aşağıdaki kodu yapar:

while (true) {
    ONE_TIME_IF(1 > 0,
        printf("something\n");
        printf("something else\n");
    )
}

baskı somethingve something elsesadece bir kez. En iyi görünen kodu vermez, ancak çalışır. Ayrıca, benzersiz değişken isimlerini daha iyi sağlayarak, onu geliştirmenin yolları olduğundan eminim. Bu #define, çalışma zamanında yalnızca bir kez çalışacaktır. Sıfırlamanın bir yolu yoktur ve kaydetmenin bir yolu yoktur.

Hile rağmen, önce kod akışınızı daha iyi kontrol etmenizi ve bunu #defineson çare olarak veya sadece hata ayıklama amacıyla kullanmanızı şiddetle tavsiye ederim .


+1 güzel bir kez çözüm varsa. Dağınık, ama havalı.
kender

1
ONE_TIME_IF cihazınızla ilgili büyük bir sorun var, tekrar gerçekleştiremezsiniz. Örneğin, oyuncu ana menüye geri döner ve daha sonra oyun sahnesine geri döner. Bu ifade tekrar ateşlenmeyecek. ve uygulama çalıştırmaları arasında tutmanız gerekebilecek koşullar vardır, örneğin önceden alınmış kupalar listesi. iki farklı uygulama koşusunda oyuncu kazanırsa yönteminiz kupayı iki kez ödüllendirir.
Ali1S232

1
@Gajoo Bu doğru, durumun sıfırlanması veya kaydedilmesi gerekiyorsa bir sorun. Bunu açıklığa kavuşturmak için düzenledim. Bu yüzden son çare olarak veya sadece hata ayıklama için tavsiye.
kevintodisco

Do {} while (0) deyimini önerebilir miyim? Şahsen bir şeyi mahvedeceğimi ve olmamam gereken yere noktalı virgül koyacağımı biliyorum. Serin makro olsa, bravo.
michael.bartnett

5

Bu durum, durum modelini kullanmanın klasik durumu gibi geliyor. Bir durumda yaşam durumunuzu <1 kontrol edersiniz. Bu koşul gerçekleşirse, gecikme durumuna geçersiniz. Bu gecikme durumu belirtilen süreyi bekler ve ardından çıkış durumunuza geçer.

En önemsiz yaklaşımda:

switch(mCurrentState) {
  case LIVES_GREATER_THAN_0:
    if(lives < 1) mCurrentState = LIVES_LESS_THAN_1;
    break;
  case LIVES_LESS_THAN_1:
    timer.expires(5000); // expire in 5 seconds
    mCurrentState = TIMER_WAIT;
    break;
  case TIMER_WAIT:
    if(timer.expired()) mCurrentState = EXIT;
    break;
  case EXIT:
    mQuit = true;
    break;
}

Bir switch deyimi yerine durumlar arasında değişen / itme / geçişi işlemek için bir StateManager / StateMachine ile birlikte bir State sınıfı kullanmayı düşünebilirsiniz.

Durum yönetimi çözümünüzü, birden çok etkin durum, bazı hiyerarşi kullanan birçok etkin alt durum içeren tek bir etkin üst durum, vb. Dahil olmak üzere istediğiniz kadar karmaşık hale getirebilirsiniz. durum kalıbı çözümü, kodunuzu çok daha yeniden kullanılabilir ve bakımı ve takibi daha kolay hale getirecektir.


1

Performans konusunda endişeleniyorsanız, birden fazla kez kontrol etme performansı genellikle önemli değildir; bool koşullarına sahip olmak için ditto.

Başka bir şeyle ilgileniyorsanız, bu sorunu çözmek için boş tasarım desenine benzer bir şey kullanabilirsiniz.

Bu durumda, foomüzikçalarda hiç can kalmamışsa bir yöntemi çağırmak istediğinizi varsayalım . Bunu, biri çağıran foove diğeri hiçbir şey yapmayan iki sınıf olarak uygularsınız . Onları arayalım DoNothingve beğenelim CallFoo:

class SomeLevel {
  int numLives = 3;
  FooClass behaviour = new DoNothing();
  ...
  void tick() { 
    if (numLives < 1) {
      behaviour = new CallFoo();
    }

    behaviour.execute();
  }
}

Bu biraz "ham" bir örnektir; kilit noktalar:

  • Veya FooClassolabilecek bir örneğini takip edin . Bir temel sınıf, soyut sınıf veya arabirim olabilir.DoNothingCallFoo
  • Her zaman executebu sınıfın yöntemini çağırın .
  • Varsayılan olarak, sınıf hiçbir şey yapmaz ( DoNothingörnek).
  • Yaşamlar bittiğinde, örnek aslında bir şey yapan bir örnek ( CallFooörnek) için değiştirilir.

Bu aslında bir strateji ve boş kalıpların karışımı gibidir.


Ancak, yeni karelerden önce silmeniz gereken her kareyi yeni CallFoo () yapmaya devam edecek ve aynı zamanda sorunu çözmeyecek. Örneğin, CallFoo () yöntemim varsa, bir zamanlayıcıyı birkaç saniye içinde çalışacak şekilde ayarlayan bir yöntemi çağırırsam, bu zamanlayıcı her karede aynı zamana ayarlanır. Çözümünüzün söyleyebildiğim kadarıyla (numLives <1) {// do stuff} else {// empty}
EddieV223

Hmm, ne dediğini anlıyorum. Çözüm, ifdenetimi FooClassörneğin kendisine taşımak olabilir , bu nedenle yalnızca bir kez yürütülür ve örnekleme için ditto yapılır CallFoo.
ashes999
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.