Game loop, cara memeriksa kondisi sekali, melakukan sesuatu, lalu tidak melakukannya lagi


13

Sebagai contoh, saya memiliki kelas Game dan itu membuat intyang melacak kehidupan pemain. Saya memiliki persyaratan

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

Namun kondisi ini terus menyala dan pekerjaan dilakukan berulang kali. Misalnya, saya ingin mengatur timer untuk keluar dari game dalam 5 detik. Saat ini, ia akan terus menyetelnya menjadi 5 detik setiap frame dan permainan tidak pernah berakhir.

Ini hanyalah satu contoh, dan saya memiliki masalah yang sama di beberapa area permainan saya. Saya ingin memeriksa beberapa kondisi dan kemudian melakukan sesuatu sekali dan hanya sekali, dan kemudian tidak memeriksa atau melakukan kode dalam pernyataan if lagi. Beberapa solusi yang mungkin terlintas dalam pikiran adalah memiliki booluntuk setiap kondisi dan mengatur boolkapan kondisi menyala. Namun dalam praktiknya hal ini menjadi sangat berantakan dalam menangani banyak hal bools, karena harus disimpan sebagai bidang kelas, atau secara statis dalam metode itu sendiri.

Apa solusi yang tepat untuk ini (bukan untuk contoh saya, tetapi untuk domain masalah)? Bagaimana Anda melakukan ini dalam permainan?


Terima kasih atas jawabannya, solusi saya sekarang adalah kombinasi dari jawaban ktodisco dan crancran.
EddieV223

Jawaban:


13

Saya pikir Anda dapat menyelesaikan masalah ini hanya dengan menggunakan kontrol yang lebih hati-hati atas jalur kode yang mungkin Anda miliki. Misalnya, dalam kasus di mana Anda memeriksa apakah jumlah nyawa pemain telah turun di bawah satu, mengapa tidak memeriksa hanya ketika pemain kehilangan nyawa, bukan setiap frame?

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

Ini, pada gilirannya, mengasumsikan bahwa subtractPlayerLifehanya akan dipanggil pada kesempatan tertentu, yang mungkin akan dihasilkan dari kondisi bahwa Anda harus memeriksa setiap frame (tabrakan, mungkin).

Dengan mengontrol dengan hati-hati bagaimana kode Anda dieksekusi, Anda dapat menghindari solusi berantakan seperti boolean statis dan juga mengurangi sedikit demi sedikit pada seberapa banyak kode dieksekusi dalam satu frame.

Jika ada sesuatu yang sepertinya tidak mungkin untuk diperbaiki, dan Anda benar-benar hanya perlu memeriksa sekali, maka nyatakan (seperti a enum) atau boolean statis adalah cara yang harus dilakukan. Untuk menghindari menyatakan statika sendiri, Anda dapat menggunakan trik berikut:

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

yang akan membuat kode berikut:

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

cetak somethingdan something elsehanya sekali. Itu tidak menghasilkan kode berpenampilan terbaik, tetapi berhasil. Saya juga cukup yakin ada cara untuk memperbaikinya, mungkin dengan lebih baik memastikan nama variabel yang unik. Ini #definehanya akan bekerja sekali selama runtime. Tidak ada cara untuk mengatur ulang, dan tidak ada cara untuk menyimpannya.

Terlepas dari triknya, saya sangat menyarankan lebih baik mengendalikan aliran kode Anda terlebih dahulu, dan menggunakan ini #definesebagai upaya terakhir, atau hanya untuk tujuan debug.


+1 bagus sekali jika solusinya. Berantakan, tapi keren.
kender

1
ada satu masalah besar dengan ONE_TIME_IF Anda, Anda tidak dapat mewujudkannya lagi. Misalnya pemain kembali ke menu utama dan kemudian kembali ke adegan permainan. Pernyataan itu tidak akan menyala lagi. dan ada kondisi yang mungkin perlu Anda pertahankan antara menjalankan aplikasi, misalnya daftar piala yang telah diperoleh. metode Anda akan memberi hadiah trofi dua kali jika pemain memperolehnya dalam dua menjalankan aplikasi yang berbeda.
Ali1S232

1
@ Gajoo Itu benar, itu masalah jika kondisi perlu diatur ulang atau disimpan. Saya telah mengedit untuk menjelaskannya. Itu sebabnya saya merekomendasikannya sebagai pilihan terakhir, atau hanya untuk debugging.
kevintodisco

Bisakah saya menyarankan idiom do {} while (0)? Saya tahu saya pribadi akan mengacaukan sesuatu dan meletakkan tanda titik koma di mana saya seharusnya tidak. Makro keren, bravo.
michael.bartnett

5

Ini terdengar seperti kasus klasik dalam menggunakan pola keadaan. Di satu negara Anda memeriksa kondisi Anda untuk hidup <1. Jika kondisi itu menjadi kenyataan, Anda beralih ke kondisi penundaan. Status penundaan ini menunggu durasi yang ditentukan dan kemudian transisi ke status keluar Anda.

Dalam pendekatan yang paling sepele:

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;
}

Anda bisa mempertimbangkan menggunakan kelas Negara bersama dengan StateManager / StateMachine untuk menangani perubahan / mendorong / transisi antar negara daripada pernyataan switch.

Anda dapat membuat solusi manajemen negara Anda secanggih yang Anda inginkan, termasuk beberapa status aktif, status induk aktif tunggal dengan banyak status anak aktif menggunakan beberapa hierarki, dll. Gunakan apa yang masuk akal bagi Anda untuk saat ini tentu saja, tetapi saya benar-benar berpikir bahwa solusi pola keadaan akan membuat kode Anda jauh lebih dapat digunakan kembali dan lebih mudah untuk mempertahankan dan mengikuti.


1

Jika Anda khawatir tentang kinerja, kinerja pemeriksaan beberapa kali biasanya tidak signifikan; juga karena memiliki kondisi bool.

Jika Anda tertarik pada sesuatu yang lain, Anda dapat menggunakan sesuatu yang mirip dengan pola desain nol untuk menyelesaikan masalah ini.

Dalam hal ini, mari kita asumsikan Anda ingin memanggil metode foojika pemain tidak memiliki nyawa yang tersisa. Anda akan mengimplementasikan ini sebagai dua kelas, satu yang memanggil foo, dan satu yang tidak melakukan apa-apa. Ayo panggil mereka DoNothingdan CallFoosuka:

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

    behaviour.execute();
  }
}

Ini sedikit contoh "mentah"; poin kuncinya adalah:

  • Melacak beberapa contoh FooClassyang dapat DoNothingatauCallFoo . Ini bisa berupa kelas dasar, kelas abstrak, atau antarmuka.
  • Selalu panggil execute metode kelas itu.
  • Secara default, kelas tidak melakukan apa-apa (DoNothing instance).
  • Ketika kehidupan habis, instance ditukar dengan instance yang benar-benar melakukan sesuatu ( CallFooinstance).

Ini pada dasarnya seperti campuran strategi dan pola nol.


Tapi itu akan tetap membuat CallFoo baru () setiap frame, yang harus Anda hapus sebelum Anda baru, dan juga terus terjadi yang tidak memperbaiki masalah. Sebagai contoh jika saya memiliki CallFoo () memanggil metode yang menetapkan timer untuk dieksekusi dalam beberapa detik, timer itu akan diatur ke waktu yang sama setiap frame. Sejauh yang saya tahu solusi Anda sama seperti jika (numLives <1) {// do stuff} else {// empty}
EddieV223

Hmm, aku mengerti apa yang kamu katakan. Solusinya mungkin dengan memindahkan ifcek ke FooClassinstance itu sendiri, jadi itu hanya dieksekusi sekali, dan ditto untuk instantiating CallFoo.
ashes999
Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.