Come si progetta in modo pulito un ciclo di rendering / animazione centrale?

3

Sto imparando alcuni programmi di grafica e sono nel bel mezzo del mio primo progetto di questo genere. Ma, in questo momento, sto davvero lottando per come architettarlo in modo pulito. Lasciami spiegare.

Per visualizzare una grafica complicata nella mia lingua preferita (JavaScript - ne hai sentito parlare?), devi disegnare il contenuto grafico su un elemento <canvas> . E per fare l'animazione, devi cancellare <canvas> dopo ogni fotogramma (a meno che non rimanga la grafica precedente).

Pertanto, la maggior parte dei demo JavaScript correlati al canvas che ho visto hanno una funzione simile a questa:

function render() {
   clearCanvas();
   // draw stuff here
   requestAnimationFrame(render);
}

render , come si può ipotizzare, incapsula il disegno di un singolo fotogramma. Che cosa contiene un singolo fotogramma in un momento specifico, beh ... questo è determinato dallo stato del programma. Quindi, per fare in modo che il mio programma faccia la sua cosa, ho solo bisogno di guardare lo stato e decidere cosa renderizzare. Giusto?

destro. Ma è più complicato di quanto sembri.

Il mio programma si chiama "Critter Clicker". Nel mio programma, vedi diverse simpatiche critters rimbalzare sullo schermo. Cliccando su uno di loro lo agita facendolo rimbalzare ancora di più. C'è anche una schermata iniziale, che dice "Clicca per iniziare!" prima che vengano visualizzate le creature.

Ecco alcuni degli oggetti con cui sto lavorando nel mio programma:

StartScreenView  // represents the start screen
CritterTubView   // represents the area in which the critters live
CritterList      // a collection of all the critters
Critter          // a single critter model
CritterView      // view of a single critter

Niente di troppo eclatante per questo, penso. Tuttavia, quando ho deciso di arricchire la mia funzione render , mi blocco, perché tutto ciò che scrivo sembra assolutamente brutto e ricorda un certo piatto popolare italiano. Qui ci sono un paio di approcci che ho provato, con il mio processo di pensiero interno incluso, e pezzi non correlati esclusi per chiarezza.

Metodo 1: "Le condizioni sono fino in fondo"

// "I'll just write the program as I think it, one frame at a time."

if (assetsLoaded) {
  if (userClickedToStart) {
    if (critterTubDisplayed) {
      if (crittersDisplayed) {
        forEach(crittersList, function(c) {
          if (c.wasClickedRecently) {
             c.getAgitated();
          }
        });
      } else {
        displayCritters();
      }
    } else {
      displayCritterTub();
    }
  } else {
    displayStartScreen();
  }
}

Questo è un esempio molto semplificato. Tuttavia, anche con solo una frazione di tutte le condizioni di rendering visibili, render sta già iniziando a sfuggire di mano. Quindi, non esitare e prova un'altra idea:

Approach 2: Under the Rug

// "Each view object shall be responsible for its own rendering.
// "I'll pass each object the program state, and each can render itself."

startScreen.render(state);
critterTub.render(state);
critterList.render(state);

In questa configurazione, ho essenzialmente spinto queste pazzesche condizioni nidificate a un livello più profondo del codice, nascondendole dalla vista. In altre parole, startScreen.render controllerebbe state per vedere se effettivamente doveva essere disegnato o meno, e prendere l'azione corretta. Ma questo sembra più che solo risolva un problema estetico del codice.

Il terzo e ultimo approccio che sto considerando di condividere è l'idea che potrei inventare la mia "ruota" per occuparmi di questo. Sto immaginando una funzione che prende una struttura dati che definisce ciò che dovrebbe accadere in un dato punto della chiamata di render - rivelando le condizioni e le dipendenze come un tipo di albero.

Approach 3: Mad Scientist

renderTree({
  phases: ['startScreen', 'critterTub', 'endCredits'],
  dependencies: {
    startScreen: ['assetsLoaded'],
    critterTub: ['startScreenClicked'],
    critterList ['critterTubDisplayed']
    // etc.
  },
  exclusions: {
    startScreen: ['startScreenClicked'],
    // etc.
  }
});

Sembra un po 'fico. Non sono esattamente sicuro di come funzionerebbe, ma posso vedere che è un modo piuttosto elegante per esprimere le cose, specialmente se fletto alcuni degli eventi di JavaScript.

In ogni caso, sono un po 'perplesso perché non vedo un modo ovvio per farlo. Se non si riusciva a capirlo, sono arrivato a questo dal mondo dello sviluppo web e scoprire che fare animazione è un po 'più esotico di organizzare un'applicazione MVC per gestire richieste semplici - > risposte.

Qual è la soluzione chiara e consolidata per questo problema comune di I-would-think?

    
posta GladstoneKeep 13.11.2013 - 00:01
fonte

1 risposta

4

Il concetto di una "scena" potrebbe essere utile. Una scena potrebbe essere un menu iniziale, un livello tra molti livelli o i crediti alla fine del gioco. Ogni scena ha i propri comportamenti, stati e entità renderizzabili. Alcuni stati possono essere condivisi tra scene (punteggi, aggiornamenti di caratteri, ecc.), Ma la maggior parte dello stato è specifica di una scena. L'astrazione per scena rende più semplice pensare a un gioco ed è anche utile per mantenere le risorse di sistema al minimo: si caricano solo le risorse necessarie per la scena corrente. In pseudo codice:

currentScene = nothing

game-start:
    currentScene = load-start-menu()

game-loop:
    currentScene.update()
    currentScene.render()

menu-start-button-clicked:
    currentScene = load-level1()

All'interno di ogni scena, hai una collezione di entità di gioco. Per semplicità, mostrerò semplici contenitori. In realtà, potresti voler dividere le entità concettuali in un modello solo dati e in una vista renderizzabile. Le scene possono anche delegare l'interazione dell'utente.

definition Critter:
    isAgitated = false
    x = get-appropriate-starting-x() 
    y = get-appropriate-starting-y()

    update:
        if isAgitated:
            x = some-random-move(x)
            y = some-random-move(y)

    render:
        draw(image or whatever, x, y)

definition Level1:
    entities = new collection()

    init:
        for 1 to some-number:
            entities.add(new Critter)
        entities.add(new CritterTub) // definition not shown

    update:
        for each entity in entities:
            entity.update()

    render:
        for each entity in entities:
            entity.render()

    mouse-click(x,y):
        entity = find-entity(entities, x, y)
        if entity is Critter:
            entity.isAgitated = true

Ci sono molte varianti: il tuo motore di gioco può consentire alle entità di ricevere direttamente azioni dell'utente. Come accennato in precedenza, le tue entità possono contenere solo dati e alcuni altri componenti sono responsabili del rendering. Il tuo gioco potrebbe essere più funzionale rispetto a object-oriented, es: render(entity) versus entity.render()

Una volta definita la scena, ci sono molti problemi interessanti da risolvere: quali entità sono fuori schermo e non dovrebbero essere renderizzate, in quale ordine devono essere rese le entità, e come posso decidere queste cose rapidamente? Dai un'occhiata ai grafici delle scene per maggiori informazioni. Gamerendering.com offre una panoramica di diverse tecniche per la gestione delle scene dalla semplice all'avanzata.

Per ulteriori informazioni sul concetto di scena, di seguito vengono fornite spiegazioni di alto livello:

risposta data 13.11.2013 - 06:01
fonte

Leggi altre domande sui tag