2012-02-18 18 views
6

Questo è per un progetto di gioco di piccole dimensioni con SDL su MinGW/Windows.Decisioni di successione/interfaccia per il motore fisico

Sto lavorando su un motore fisico, e la mia idea era di avere un Physics::Object, che tutti gli oggetti fisici dovrebbero derivare da esso si registra con un Physics::System classe globale (si tratta di un modello monostate) in modo che l'utente non lo fa è necessario tenere traccia di quali oggetti sono inclusi nei calcoli fisici e basta chiamare una funzione come Physics::System::PerformTimestepCalculation(double dt).

Questo funziona bene, e ho anche implementato utilizzando una sola classe derivata Physics::Circle, che è un cerchio 2d. Sono stato abbastanza contento del rilevamento predittivo delle collisioni, anche se ho ancora bisogno di ottimizzarlo.

In ogni caso, ho riscontrato problemi quando ho iniziato ad aggiungere altri primitivi da includere nel calcolo, ad es. linea. Il numero Physics::System::PerformTimestepCalculation(double dt) è diventato cosparso di chiamate a Object::GetID() o funzioni simili (può essere un modo per evitare dynamic_cast <>), ma mi sento sporco.

Ho fatto un po 'di reading e ho capito che i miei elementi della mia gerarchia non sono sostituibili (vale a dire la collisione tra due cerchi è molto diversa tra la collisione di due linee).

Mi piace il modo in cui il mio "self register" viene registrato con la classe System in modo che venga automaticamente incluso nei calcoli e non voglio veramente perdere questo.

Ci devono essere altri percorsi di progettazione sensibili. Come posso riprogettare meglio le cose in modo che gli oggetti non sostituibili non interferiscano?

Edit FYI: Alla fine ho rotto con entità e forma le proprietà, in modo simile a come è stato descritto nella risposta accettata, e simile a un modello entità-componenti del sistema. Significa che ho ancora la logica yuk di "è un cerchio o una linea, ed è una linea o un cerchio?", Ma non sto più fingendo che il polimorfismo mi aiuti qui. Significa anche che io uso una sorta di fabbrica e che possono avere più mondi di calcolo contemporaneamente!

+1

Ti vuoi dire hanno un problema [doppio invio] (http://en.wikipedia.org/wiki/Double_dispatch)? Se ricordo bene, [Modern C++ Design] (http: //www.amazon.ca/Modern-Design-Generic-Programming-Patterns/dp/0201704315) ha un capitolo sulle tecniche di implementazione e potrebbe essere di vostro interesse. –

risposta

5

I motori di fisica di maggior successo disponibili pubblicamente non sono molto pesanti sui "modelli" o sul "design orientato agli oggetti".

Ecco una carrellata di eseguire il backup il mio, è vero grassetto, affermazione:

Chipmunk - scritto in C, detto abbastanza.

Box2d - Scritto in C++, e c'è qualche polimorfismo qui. c'è una gerarchia di forme (classe base b2Shape) con poche funzioni virtuali. Quell'astrazione trabocca come un colabrodo, però, e troverai molti cast per classificare il codice sorgente. C'è anche una gerarchia di "contatti", che si dimostra più efficace, anche se con una singola funzione virtuale sarebbe banale riscrivere questo senza polimorfismo (chipmunk utilizza un puntatore a funzione, credo). b2Body è la classe utilizzata per rappresentare corpi rigidi e non è virtuale.

Bullet - Scritto in C++, utilizzato in una tonnellata di giochi. Tonnellate di funzionalità, tonnellate di codice (relative alle altre due). Esiste in realtà una classe base che estende le rappresentazioni del corpo rigido e del corpo morbido, ma solo una piccola parte del codice può farne un uso.La maggior parte della funzione virtuale della classe base si riferisce alla serializzazione (salvataggio/caricamento dello stato del motore), delle due restanti funzioni virtuali il corpo morbido non riesce a implementarne una con un TODO che ci informa che alcuni hack devono essere ripuliti. Non è esattamente un risuonare del polimorfismo nei motori fisici.

Queste sono molte parole e non ho nemmeno iniziato a rispondere alla tua domanda. Tutto quello che voglio martellare a casa è che il polimorfismo non è qualcosa che viene applicato efficacemente nei motori fisici esistenti. E questo probabilmente non perché gli autori non "ottengono" OO.

Quindi, il mio consiglio: polimorfismo del fossato per la vostra classe di entità. Non finirai con 100 tipi diversi che non puoi eventualmente refactoring in un secondo momento, i dati di forma del tuo motore fisico saranno abbastanza omogenei (poligoni convessi, scatole, sfere, ecc.) E probabilmente i tuoi dati di entità saranno ancora più omogeneo (probabilmente solo corpi rigidi per iniziare).

Un altro errore che sento di supportare è solo un sistema: Physics :: System. C'è utilità nel riuscire a simulare corpi indipendentemente l'uno dall'altro (ad esempio, per un gioco a due giocatori), e il modo più semplice per farlo è quello di supportare più sistemi Physics ::.

Con questo in mente, il "modello" più pulito da seguire sarebbe un modello di fabbrica. Quando gli utenti desiderano creare un corpo rigido, hanno bisogno di raccontare la Fisica :: Sistema (in qualità di una fabbrica) di farlo per loro, così nel vostro Fisica :: Sistema:

// returning a smart pointer would not be unreasonable, but I'm returning a raw pointer for simplicity: 
rigid_body_t* AddBody(body_params_t const& body_params); 

E nel codice client :

circle_params_t circle(1.f /*radius*/); 
body_params_t b(1.f /*mass*/, &circle /*shape params*/, xform /*system transform*/); 
rigid_body_t* body = physics_system.AddBody(b); 

Anyhoo, una specie di errore. Spero che questo sia utile. Per lo meno voglio indicarti box2d. È scritto in un dialetto piuttosto semplice di C++ e gli schemi applicati in esso saranno rilevanti per il tuo motore, che sia 3D o 2D.

+0

+1 per "nessun altro lo fa in questo modo". Penso che sia utile. Immagino che se fossi andato con lo schema di fabbrica, avrei potuto avere liste separate per ogni tipo di oggetto ... hmmm. – Bingo

+0

Accettato come hai parlato di più, e ha dato i consigli più utili sulla progettazione del software, che è quello che la mia domanda ha posto. – Bingo

3

Il problema delle gerarchie è che non sempre hanno senso, e il tentativo di stipare tutto in una gerarchia si risolve in decisioni scomode e in un lavoro frustrante.

L'altra soluzione che può essere utilizzata è l'unione contrassegnata con, meglio rappresentata da boost::variant.

L'idea è quella di creare un oggetto che può contenere uno istanza di un determinato tipo (tra un elenco preselezionato) in un dato momento:

typedef boost::variant<Ellipsis, Polygon, Blob> Shape; 

E allora si può fornire la funzionalità commutando nell'elenco tipo:

struct AreaComputer: boost::static_visitor<double> { 
    template <typename T> 
    double operator()(T const& e) { return area(a); } 
}; 

void area(Shape const& s) { 
    AreaComputer ac; 
    return boost::apply_visitor(s, ac); 
} 

la performance è lo stesso di un dispaccio virtuale (in modo non troppo, di solito), ma si ottiene una maggiore flessibilità:

void func(boost::variant<Ellipsis, Blob> const& eb); 
void bar(boost::variant<Ellipsis, Polygon> const& ep); 
// ... 

È possibile fornire solo le funzioni quando è rilevante.

E a proposito della visita binario:

struct CollisionComputer: boost::static_visitor<CollisionResult> { 
    CollisionResult operator()(Circle const& left, Circle const& right); 
    CollisionResult operator()(Line const& left, Line const& right); 

    CollisionResult operator()(Circle const& left, Line const& right); 
    CollisionResult operator()(Line const& left, Circle const& right); 
}; 

CollisionResult collide(Shape const& left, Shape const& right) { 
    return boost::apply_visitor(CollisionComputer(), left, right); 
} 
+0

Grazie per la risposta. Non ho molta familiarità con i visitatori o le varianti. Penso di avere l'esempio di area, ma sembra banale perché sarebbe facilmente fatto usando una chiamata virtuale, giusto? Puoi espandere la sezione di flessibilità maggiore e magari mostrare qualcosa come 'double TimeOfCollision (Shape * s1, Shape * s2)' per vari tipi di forme? (cioè Cerchio + Cerchio, Cerchio + Linea, Linea + Linea, ecc.) – Bingo

+0

@Bingo: la flessibilità di cui ho parlato non è correlata alla visita (non proprio), ma più al fatto che puoi imballare gli elementi come desideri (su base per necessità), mentre con l'ereditarietà sei bloccato se non hanno un genitore comune e non è così grande se il loro genitore comune ha altre classi derivate di cui non ti interessa ... –

Problemi correlati