Perché dovrei separare gli oggetti dal rendering?


11

Disclamer: So quello che un modello di sistema di entità è e sto non usarlo.

Ho letto molto sulla separazione di oggetti e rendering. Sul fatto che la logica di gioco dovrebbe essere indipendente dal motore di rendering sottostante. Va tutto bene e dandy e ha perfettamente senso, ma provoca anche molti altri dolori:

  • necessità di sincronizzazione tra l'oggetto logico e l'oggetto di rendering (quello che mantiene lo stato dell'animazione, gli sprite ecc.)
  • è necessario aprire l'oggetto logico al pubblico affinché l'oggetto di rendering legga lo stato reale dell'oggetto logico (spesso induce l'oggetto logico a trasformarsi facilmente in un oggetto getter e setter stupido)

Non mi sembra una buona soluzione. D'altra parte è molto intuitivo immaginare un oggetto come la sua rappresentazione 3d (o 2d) e anche molto facile da mantenere (e forse anche molto più incapsulato).

C'è un modo per mantenere la rappresentazione grafica e la logica di gioco accoppiati (evitando problemi di sincronizzazione) ma astrarre il motore di rendering? O c'è un modo per separare la logica di gioco e il rendering che non causa gli svantaggi di cui sopra?

(possibilmente con esempi, non sono molto bravo a capire discorsi astratti)


1
Sarebbe anche utile se fornissi un esempio di cosa intendi quando dici che non stai usando il modello di sistema di entità, e come pensi che sia correlato alla necessità o meno di separare la preoccupazione del rendering dalla preoccupazione dell'entità / logica di gioco.
michael.bartnett il

@ michael.bartnett, non sto separando gli oggetti in piccoli componenti riutilizzabili gestiti dai sistemi, come fanno la maggior parte delle implementazioni dei modelli. Il mio codice è più un tentativo per il modello MVC. Ma non importa davvero perché la domanda non dipende da nessun codice (nemmeno da una lingua). Ho messo il divulgatore perché sapevo che alcuni avrebbero tentato di convincermi ad usare l'ECS, che sembra curare il cancro. E, come puoi vedere, è successo comunque.
Scarpa

Risposte:


13

Supponiamo di avere una scena composta da un mondo , un giocatore e un boss. Oh, e questo è un gioco in terza persona, quindi hai anche una macchina fotografica .

Quindi la tua scena è così:

class Scene {
    World* world
    Player* player
    Enemy* boss
    Camera* camera
}

(Almeno, questi sono i dati di base . Come si contengono i dati, dipende da te.)

Vuoi aggiornare e rendere la scena solo quando stai giocando, non quando in pausa, o nel menu principale ... in modo da collegarla allo stato del gioco!

State* gameState = new State();
gameState->addScene(scene);

Ora il tuo stato di gioco ha una scena. Successivamente, si desidera eseguire la logica sulla scena e renderizzare la scena. Per la logica, è sufficiente eseguire una funzione di aggiornamento.

State::update(double delta) {
    scene->update(delta);
}

In questo modo puoi mantenere tutta la logica di gioco della Sceneclasse. E solo per motivi di riferimento, un sistema di componenti di entità potrebbe farlo in questo modo invece:

State::update(double delta) {
    physicsSystem->applyPhysics(scene);
}

Ad ogni modo, ora sei riuscito ad aggiornare la tua scena. Ora vuoi visualizzarlo! Per il quale facciamo qualcosa di simile al precedente:

State::render() {
    renderSystem->render(scene);
}

Ecco qua. RenderSystem legge le informazioni dalla scena e visualizza l'immagine appropriata. Semplificato, il metodo di rendering della scena potrebbe essere simile al seguente:

RenderSystem::renderScene(Scene* scene) {
    Camera* camera = scene->camera;
    lookAt(camera); // Set up the appropriate viewing matrices based on 
                    // the camera location and direction

    renderHeightmap(scene->getWorld()->getHeightMap()); // Just as an example, you might
                                                        // use a height map as your world
                                                        // representation.
    renderModel(scene->getPlayer()->getType()); // getType() will return, for example "orc"
                                                // or "human"

    renderModel(scene->getBoss()->getType());
}

Davvero semplificato, avresti comunque bisogno di applicare una rotazione e una traduzione basate su dove si trova il tuo giocatore e dove sta guardando. (Il mio esempio è un gioco 3D, se vai con il 2D, sarà una passeggiata nel parco).

Spero che questo sia quello che stavi cercando? Come puoi sperare di ricordare da quanto sopra, il sistema di rendering non si preoccupa della logica del gioco . Utilizza solo lo stato corrente della scena per eseguire il rendering, ovvero estrae da esso le informazioni necessarie per eseguire il rendering. E la logica del gioco? Non importa cosa fa il renderer. Cavolo, non importa se viene visualizzato!

E non è nemmeno necessario allegare informazioni di rendering alla scena. Dovrebbe essere sufficiente che il renderer sappia che deve eseguire il rendering di un orco. Avrai già caricato un modello di orchi, che il renderer allora sa visualizzare.

Questo dovrebbe soddisfare le tue esigenze. La rappresentazione grafica e la logica sono accoppiate , poiché entrambi utilizzano gli stessi dati. Eppure sono separati , perché nessuno dei due si basa sull'altro!

EDIT: E solo per rispondere perché uno dovrebbe farlo in questo modo? Perché è più facile è la ragione più semplice. Non è necessario pensare a "cosa è successo, dovrei ora aggiornare la grafica". Invece fai accadere cose, e ogni fotogramma del gioco guarda cosa sta succedendo, e lo interpreta in qualche modo, dandoti un risultato sullo schermo.


7

Il tuo titolo pone una domanda diversa rispetto al contenuto del tuo corpo. Nel titolo, chiedi perché la logica e il rendering debbano essere separati, ma nel corpo chiedi implementazioni di sistemi logici / grafici / di rendering.

La seconda domanda è stata affrontata in precedenza , quindi mi concentrerò sulla prima domanda.

Ragioni per separare logica e rendering:

  1. L'idea diffusa che gli oggetti dovrebbero fare una cosa
  2. Cosa succede se si desidera passare dal 2D al 3D? Cosa succede se si decide di passare da un sistema di rendering a un altro nel mezzo del progetto? Non vuoi eseguire la scansione di tutto il codice e apportare enormi cambiamenti nel mezzo della logica di gioco.
  3. Probabilmente avresti motivo di ripetere sezioni di codice, che generalmente sono considerate una cattiva idea.
  4. È possibile creare sistemi per controllare aree potenzialmente enormi di rendering o logica senza comunicare individualmente con piccoli pezzi.
  5. Cosa succede se si desidera assegnare una gemma a un giocatore ma il sistema è rallentato da quante sfaccettature ha la gemma? Se il sistema di rendering è stato estratto abbastanza bene, è possibile aggiornarlo a velocità diverse per tenere conto delle costose operazioni di rendering.
  6. Ti consente di pensare a cose che contano davvero per quello che stai facendo. Perché avvolgere il cervello attorno alle trasformazioni della matrice e scrivere offset e coordinate dello schermo quando tutto ciò che vuoi fare è implementare un meccanico a doppio salto, pescare una carta o equipaggiare una spada? Non vuoi che lo sprite rappresenti il ​​rendering della tua spada equipaggiata in rosa brillante solo perché volevi spostarlo dalla tua mano destra alla tua sinistra.

In un'impostazione OOP un'istanza di nuovi oggetti ha un costo, ma nella mia esperienza il costo per le risorse di sistema è un piccolo prezzo da pagare per la capacità di pensare e implementare le cose specifiche che devo fare.


6

Questa risposta serve solo a intuire il motivo per cui è importante separare rendering e logica, piuttosto che suggerire direttamente esempi pratici.

Supponiamo di avere un grande elefante , nessuno nella stanza può vedere l'intero elefante. forse tutti non sono nemmeno d'accordo su cosa sia in realtà. Perché tutti vedono una parte diversa dell'elefante e possono occuparsi solo di quella parte. Ma alla fine questo non cambia il fatto che è un grande elefante.

L'elefante rappresenta l'oggetto di gioco con tutti i suoi dettagli. Ma nessuno ha davvero bisogno di sapere tutto sull'elefante (oggetto di gioco) per poter fare la sua funzionalità.

Associare la logica di gioco e il rendering è in realtà come far vedere a tutti l'elefante. Se qualcosa è cambiato, tutti devono saperlo. Mentre nella maggior parte dei casi hanno solo bisogno di vedere la parte a cui sono solo interessati. Se qualcosa ha cambiato la persona che ne è a conoscenza, deve solo dire all'altra persona il risultato di quel cambiamento, questo è ciò che è solo importante per lui (pensa a questo come comunicazione tramite messaggi o interfacce).

inserisci qui la descrizione dell'immagine

I punti che hai citato non sono degli svantaggi, sono degli svantaggi solo se c'erano più dipendenze di quelle che dovrebbero esserci nel motore, in altre parole, i sistemi vedono parti dell'elefante più di quanto dovrebbero. E questo significa che il motore non è stato "correttamente" progettato.

Hai solo bisogno della sincronizzazione con la sua definizione formale se stai utilizzando un motore multi-thread in cui inserisce la logica e il rendering in due thread diversi e anche un motore che necessita di molta sincronizzazione tra i sistemi non è progettato in modo particolare.

Altrimenti, il modo naturale di affrontare questo caso è progettare il sistema come input / output. L'aggiornamento esegue la logica e genera il risultato. Il rendering viene alimentato solo con i risultati dell'aggiornamento. Non hai davvero bisogno di esporre tutto. Si espone solo un'interfaccia che comunica tra le due fasi. La comunicazione tra le diverse parti del motore dovrebbe avvenire tramite astrazioni (interfacce) e / o messaggi. Nessuna logica interna o stati dovrebbero essere esposti.

Facciamo un semplice esempio di grafico di scena per spiegare l'idea.

L'aggiornamento di solito avviene tramite un singolo loop chiamato loop di gioco (o eventualmente tramite più loop di gioco, ciascuno in esecuzione in un thread separato). Una volta aggiornato il loop, ogni oggetto di gioco. Deve solo dire tramite messaggistica o interfacce che gli oggetti 1 e 2 sono stati aggiornati e alimentarli con la trasformazione finale.

Il sistema di rendering accetta solo la trasformazione finale e non sa cosa è effettivamente cambiato sull'oggetto (ad esempio, si è verificata una collisione specifica ecc.). Ora per rendere quell'oggetto è necessario solo l'ID di quell'oggetto e la trasformazione finale. Successivamente il renderer alimenterà l'API di rendering con la mesh e la trasformazione finale senza sapere altro.

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.