Ciclo di gioco, come verificare le condizioni una volta, fare qualcosa, quindi non farlo di nuovo


13

Ad esempio, ho una classe di gioco e ne tiene una intche tiene traccia delle vite del giocatore. Ho un condizionale

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

Tuttavia, questa condizione continua a funzionare e il lavoro viene eseguito ripetutamente. Ad esempio, voglio impostare un timer per uscire dal gioco in 5 secondi. Attualmente, continuerà a impostarlo su 5 secondi ogni fotogramma e il gioco non finisce mai.

Questo è solo un esempio e ho lo stesso problema in diverse aree del mio gioco. Voglio verificare qualche condizione e quindi fare qualcosa una volta e una sola volta, quindi non controllare o ripetere il codice nell'istruzione if. Alcune possibili soluzioni che vengono in mente sono avere un boolper ogni condizione e impostare il boolmomento in cui la condizione si attiva. Tuttavia, in pratica, ciò diventa molto complicato da gestire bools, poiché devono essere archiviati come campi di classe o staticamente all'interno del metodo stesso.

Qual è la soluzione adeguata a questo (non per il mio esempio, ma per il dominio problematico)? Come lo faresti in un gioco?


Grazie per le risposte, la mia soluzione è ora una combinazione di risposte ktodisco e crancran.
EddieV223,

Risposte:


13

Penso che tu possa risolvere questo problema semplicemente esercitando un controllo più attento sui tuoi possibili percorsi di codice. Ad esempio, nel caso in cui stai verificando se il numero delle vite del giocatore è sceso al di sotto di uno, perché non controllare solo quando il giocatore perde una vita, invece di ogni fotogramma?

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

Questo, a sua volta, presuppone che subtractPlayerLifevenga chiamato solo in occasioni specifiche, che potrebbe derivare da condizioni che è necessario controllare ogni frame (collisioni, forse).

Controllando attentamente come viene eseguito il codice, è possibile evitare soluzioni disordinate come i booleani statici e anche ridurre bit per bit la quantità di codice eseguita in un singolo frame.

Se c'è qualcosa che sembra impossibile da refactoring e devi davvero controllare solo una volta, allora dichiarare (come un enum) o booleani statici sono la strada da percorrere. Per evitare di dichiarare tu stesso la statica, puoi usare il seguente trucco:

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

che renderebbe il seguente codice:

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

stampa somethinge something elsesolo una volta. Non produce il codice più bello, ma funziona. Sono anche abbastanza sicuro che ci sono modi per migliorarlo, forse garantendo meglio nomi di variabili univoci. Questo #definefunzionerà solo una volta durante il runtime. Non è possibile ripristinarlo e non è possibile salvarlo.

Nonostante il trucco, consiglio vivamente di controllare prima il flusso di codice e di utilizzarlo #definecome ultima risorsa o solo a scopo di debug.


+1 piacevole una volta se soluzione. Disordinato, ma bello.
kender il

1
c'è un grosso problema con il ONE_TIME_IF, non puoi farlo accadere di nuovo. Ad esempio, il giocatore torna al menu principale e successivamente torna alla scena del gioco. Questa affermazione non si attiverà più. e ci sono condizioni che potresti dover mantenere tra le esecuzioni dell'applicazione, ad esempio un elenco di trofei già acquisiti. il tuo metodo premierà il trofeo due volte se il giocatore lo guadagna in due diverse esecuzioni dell'applicazione.
Ali1S232,

1
@Gajoo È vero, è un problema se la condizione deve essere ripristinata o salvata. Ho modificato per chiarire questo. È per questo che l'ho consigliato come ultima risorsa, o solo per il debug.
kevintodisco,

Posso suggerire il do {} mentre (0) idioma? So che avrei rovinato personalmente qualcosa e messo un punto e virgola dove non avrei dovuto. Macro fantastica però, bravo.
michael.bartnett,

5

Sembra il classico caso di utilizzo del modello di stato. In uno stato controlli la tua condizione per la vita <1. Se quella condizione diventa vera, passi a uno stato di ritardo. Questo stato di ritardo attende la durata specificata, quindi passa allo stato di uscita.

Nell'approccio più banale:

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

Si potrebbe prendere in considerazione l'utilizzo di una classe State insieme a StateManager / StateMachine per gestire la modifica / push / transizione tra stati anziché un'istruzione switch.

Puoi rendere la tua soluzione di gestione dello stato sofisticata quanto vuoi, inclusi più stati attivi, un singolo stato genitore attivo con molti stati figlio attivi usando una certa gerarchia, ecc. Usa ciò che per te ha senso per ora ovviamente, ma penso davvero che un La soluzione modello di stato renderà il codice molto più riutilizzabile e più facile da mantenere e seguire.


1

Se sei preoccupato per le prestazioni, le prestazioni del controllo più volte non sono in genere significative; idem per avere condizioni di bool.

Se sei interessato a qualcos'altro, puoi utilizzare qualcosa di simile al modello di progettazione null per risolvere questo problema.

In questo caso, supponiamo che tu voglia chiamare un metodo foose al giocatore non rimangono vite. Lo implementeresti come due classi, una che chiama fooe una che non fa nulla. Chiamiamoli DoNothinge CallFoocosì:

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

    behaviour.execute();
  }
}

Questo è un po 'un esempio "grezzo"; i punti chiave sono:

  • Tieni traccia di alcune delle FooClassquali può essere DoNothingo CallFoo. Può essere una classe di base, una classe astratta o un'interfaccia.
  • Chiama sempre il executemetodo di quella classe.
  • Per impostazione predefinita, la classe non fa nulla ( DoNothingistanza).
  • Quando la vita si esaurisce, l'istanza viene scambiata con un'istanza che effettivamente fa qualcosa ( CallFooistanza).

Questo è essenzialmente come un mix di strategia e schemi nulli.


Ma continuerà a creare nuovi CallFoo () ogni fotogramma, che è necessario eliminare prima di nuovi, e continua anche a succedere che non risolve il problema. Ad esempio, se CallFoo () chiamasse un metodo che imposta l'esecuzione di un timer in pochi secondi, quel timer verrebbe impostato alla stessa ora per ogni fotogramma. Per quanto ne so, la tua soluzione è la stessa di if (numLives <1) {// do stuff} else {// empty}
EddieV223

Capisco cosa stai dicendo. La soluzione potrebbe essere quella di spostare il ifcontrollo FooClassnell'istanza stessa, quindi viene eseguito una sola volta e idem per l'istanza CallFoo.
ashes999
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.