Separazione dello stato mondiale e dell'animazione in un gioco a turni


9

Come gestisci la separazione dell'animazione dallo stato mondiale all'interno di un gioco a turni? Attualmente sto lavorando a un gioco basato su griglia 2D. Il codice seguente è semplificato per spiegare meglio.

Quando un attore si muove, voglio mettere in pausa il flusso di turni mentre la creatura si anima e si sposta nella nuova posizione. Altrimenti, lo schermo potrebbe essere significativamente indietro rispetto allo stato mondiale, il che causerebbe uno strano aspetto visivo. Voglio anche avere animazioni che non blocchino il flusso del gioco - un effetto particellare potrebbe svolgersi su più turni senza influire sul gameplay.

Quello che ho fatto è introdurre due tipi di animazioni, che io chiamo animazioni bloccanti e animazioni non bloccanti. Quando il giocatore vuole muoversi, il codice che viene eseguito è

class Actor {
    void move(PositionInfo oldPosition, PositionInfo newPosition) {
        if(isValidMove(oldPosition, newPosition) {
             getGame().pushBlockingAnimation(new MoveAnimation(this, oldPosition, newPosition, ....));
             player.setPosition(newPosition);
        }
    }
}

Quindi il ciclo di aggiornamento principale fa:

class Game {
    void update(float dt) {
        updateNonBlockingAnimations(dt); //Effects like particle explosions, damage numbers, etc. Things that don't block the flow of turns.
        if(!mBlockingAnimations.empty()) {
            mBlockingAnimations.get(0).update(dt);
        } else {
             //.. handle the next turn. This is where new animations will be enqueued..//
        }
        cleanUpAnimations(); // remove finished animations
    }
}

... dove l'animazione aggiorna la posizione dello schermo dell'attore.

Un'altra idea che sto implementando è quella di avere un'animazione di blocco simultanea, in cui più animazioni di blocco sarebbero aggiornate simultaneamente, ma la svolta successiva non avverrebbe fino a quando non saranno state completate tutte.

Sembra un modo sano di fare le cose? Qualcuno ha qualche suggerimento o addirittura riferimenti a come altri giochi simili fanno una cosa del genere.

Risposte:


2

Se il tuo sistema funziona per te, non vedo alcun motivo per non farlo.

Bene, un motivo: potrebbe diventare confuso quando non riesci facilmente a giudicare le conseguenze di "player.setPosition (newPosition);" più.

Esempio (solo inventando cose): Immagina di muovere il giocatore con il tuo setPosition in cima ad una trappola. La chiamata a "player.setPosition" stessa innescherà qualche altra chiamata a ... dire un codice di ascolto trap che a sua volta spingerà da solo un'animazione di blocco che mostra uno "splash offensivo" sopra di sé.

Vedi il problema: lo splash viene visualizzato simultaneamente con lo spostamento del giocatore verso la trappola. (Supponiamo qui che tu non voglia questo, ma desideri che l'animazione della trappola venga riprodotta subito dopo il completamento della mossa;)).

Quindi, finché sei in grado di tenere a mente tutte le conseguenze delle tue azioni che fai dopo quelle chiamate "pushBlockingAnimation", tutto va bene.

Potrebbe essere necessario iniziare a avvolgere la logica di gioco nel "bloccare qualcosa" - classi in modo che possano essere messe in coda per essere eseguite al termine dell'animazione. Oppure si passa un callback "callThisAfterAnimationFinishes" al pushBlockingAnimation e si sposta setPosition nella funzione di callback.

Un altro approccio sarebbe i linguaggi di scripting. Ad esempio Lua ha "coroutine.yield" che restituisce la funzione con uno stato speciale in modo che possa essere richiamata in seguito per continuare alla dichiarazione successiva. In questo modo puoi facilmente attendere la fine dell'animazione per eseguire la logica di gioco senza ingombrare l'aspetto "normale" del flusso di programma del tuo codice. (significa che il tuo codice sembrerebbe ancora un po '"playAnimation (); setPosition ();" invece di passare i callback in giro e ingombrare la logica di gioco su più funzioni).

Unity3D (e almeno un altro sistema di gioco che conosco) presenta l'istruzione C "" return return "per simularla direttamente nel codice C #.

// instead of simple call to move(), you have to "iterate" it in a foreach-like loop
IEnumerable<Updatable> move(...) {
    // playAnimation returns an object that contain an update() and finished() method.
    // the caller will not iterate over move() again until the object is "finished"
    yield return getGame().playAnimation(...)
    // the next statement will be executed *after* the animation finished
    player.setPosition(newPosition);
}

Ovviamente, in questo schema la chiamata a move () ora fa tutto il lavoro sporco. Probabilmente applicheresti questa tecnica solo per funzioni specifiche in cui sai esattamente chi le chiama da dove. Quindi prendi questo solo come un'idea di base su come sono organizzati gli altri motori - probabilmente è troppo complicato per il tuo attuale problema specifico.

Vi consiglio di attenervi alla vostra idea di PushBlockingAnimation fino a quando non avrete problemi nel mondo reale. Quindi modificalo. ;)


Grazie mille per aver dedicato del tempo a scrivere questa risposta, lo apprezzo molto.
mdkess,
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.