Alternative flessibili a molte piccole classi polimorfiche (da utilizzare come proprietà o messaggi o eventi) C ++


9

Ci sono due classi nel mio gioco che sono davvero utili, ma stanno lentamente diventando un dolore. Messaggio e proprietà (la proprietà è essenzialmente un componente).

Entrambi derivano da una classe base e contengono un ID statico in modo che i sistemi possano prestare attenzione solo a quelli che desiderano. Sta funzionando molto bene ... tranne ...

Mentre estendo il mio gioco, continuo a creare nuovi tipi di messaggi e tipi di proprietà. Ogni volta che devo scrivere 2 file (hpp e cpp) e una tonnellata di boilerplate per ottenere essenzialmente un classID e uno o due tipi di dati standard o un puntatore.

Sta iniziando a trasformare il gioco e la sperimentazione di nuove idee in un vero compito. Quando desidero creare un nuovo messaggio o tipo di proprietà, desidero poter digitare qualcosa del tipo

ShootableProperty:  int gunType, float shotspeed;

ItemCollectedMessage:  int itemType;

invece di creare un file header e cpp, scrivere un costruttore, inclusa la classe genitore, ecc.

Sono circa 20 - 40 linee (comprese le guardie e tutto il resto) solo per fare ciò che logicamente è 1 o 2 linee nella mia mente.

C'è qualche schema di programmazione per aggirare questo?

Che dire dello scripting (di cui non so nulla) ... c'è un modo per definire un gruppo di classi che sono quasi le stesse?


Ecco esattamente come appare una classe:

// Velocity.h

#ifndef VELOCITY_H_
#define VELOCITY_H_

#include "Properties.h"

#include <SFML/System/Vector2.hpp>

namespace LaB
{
namespace P
{

class Velocity: public LaB::Property
{
public:
    static const PropertyID id;

    Velocity(float vx = 0.0, float vy = 0.0)
    : velocity(vx,vy) {}
    Velocity(sf::Vector2f v) : velocity(v) {};

    sf::Vector2f velocity;
};

} /* namespace P */
} /* namespace LaB */
#endif /* LaB::P_VELOCITY_H_ */



// Velocity.cpp

#include "Properties/Velocity.h"

namespace LaB
{
namespace P
{

const PropertyID Velocity::id = Property::registerID();

} /* namespace P */
} /* namespace LaB */

Tutto questo solo per un vettore 2D e un ID che dice di aspettarsi un vettore 2D. (Concesso, alcune proprietà hanno elementi più complicati, ma è la stessa idea)


3
Dai un'occhiata a questo, potrebbe aiutarti. gameprogrammingpatterns.com/type-object.html

1
Tutto ciò sembra che i modelli possano essere d'aiuto, ma non con l'inizializzazione statica. In questo caso può certamente essere molto più semplice, ma è difficile dire senza ulteriori informazioni su cosa vuoi fare con quegli ID e perché stai usando l'ereditarietà. Usare l'eredità pubblica ma nessuna funzione virtuale è sicuramente un odore di codice ... Questo non è polimorfismo!
ltjax,

Hai trovato la libreria di terze parti che la implementa?
Boris,

Risposte:


1

Il C ++ è potente, ma è dettagliato . Se quello che vuoi sono molte piccole classi polimorfiche che sono tutte diverse, allora sì, ci vorrà un sacco di codice sorgente per dichiarare e definire. Non c'è niente da fare al riguardo, davvero.

Ora, come diceva ltjax, quello che stai facendo qui non è esattamente il polimorfismo , almeno per il codice che hai fornito. Non riesco a vedere un'interfaccia comune che nasconderebbe l'implementazione specifica delle sottoclassi. Tranne forse per l'ID classe, ma in realtà è ridondante con l'ID classe reale: è il nome. Questo sembra essere solo un mucchio di classi contenenti alcuni dati, niente di veramente complicato.

Ma questo non risolve il tuo problema: vuoi creare molti messaggi e proprietà con la minima quantità di codice. Meno codice significa meno bug, quindi questa è una decisione saggia. Sfortunatamente per te non esiste un'unica soluzione. È difficile dire cosa soddisferebbe maggiormente le tue esigenze senza sapere esattamente cosa intendi fare con quei messaggi e proprietà . Vorrei solo esporre le tue opzioni:

  1. Usa modelli C ++ . I modelli sono un ottimo modo per consentire al compilatore di "scrivere codice" per te. Sta diventando sempre più importante nel mondo C ++, poiché il linguaggio si evolve per supportarlo meglio (ad esempio, ora puoi usare un numero variabile di parametri modello ). Esiste un'intera disciplina dedicata alla generazione automatica di codice attraverso i template: la metaprogrammazione dei template . Il problema è: è difficile. Non aspettarti che i non programmatori possano aggiungere loro stessi nuove proprietà se prevedi di utilizzare una tale tecnica.

  2. Usa macro C semplici . È vecchia scuola, facile da abusare eccessivamente e soggetto a errori. Sono anche molto semplici da creare. Le macro sono solo paste glorificate eseguite dal preprocessore, quindi sono in realtà abbastanza adatte a creare tonnellate di cose che sono quasi le stesse con piccole variazioni. Ma non aspettarti che altri programmatori ti adorino per usarli, poiché le macro vengono spesso utilizzate per nascondere difetti nella progettazione generale del programma. A volte, tuttavia, sono utili.

  3. Un'altra opzione è quella di utilizzare uno strumento esterno per generare il codice per te. È già stato menzionato in una risposta precedente, quindi non mi espanderò su questo.

  4. Ci sono altre lingue che sono meno dettagliate e ti permetteranno di creare classi molto più facilmente. Quindi, se hai già dei bind di script (o prevedi di averli), definire quelle classi nello script potrebbe essere un'opzione. Accedervi dal C ++ sarà piuttosto complicato e perderai gran parte dei vantaggi della creazione semplificata di classi. Quindi questo è probabilmente fattibile solo se hai intenzione di fare la maggior parte della logica di gioco in un'altra lingua.

  5. Infine, ultimo ma non meno importante, dovresti considerare l'utilizzo di un design basato sui dati . Potresti definire quelle "classi" in semplici file di testo usando un layout simile a quello che ti sei proposto. Dovrai creare un formato personalizzato e un parser per esso o utilizzare una delle numerose opzioni già disponibili (.ini, XML, JSON e quant'altro). Quindi, dal lato C ++, dovrai creare un sistema che supporti una raccolta di questi diversi tipi di oggetti. Questo è quasi equivalente all'approccio di scripting (e probabilmente richiede ancora più lavoro), tranne che sarai in grado di adattarlo più precisamente alle tue esigenze. E se lo rendi abbastanza semplice, i non programmatori potrebbero essere in grado di creare nuove cose da soli.


0

Che ne dici di uno strumento di generazione di codice per aiutarti?

ad esempio, è possibile definire il tipo di messaggio e i membri in un piccolo file di testo e fare in modo che lo strumento code gen lo analizzi e scrivere tutti i file C ++ di plateplate come fase di pre-compilazione.

Esistono soluzioni esistenti come ANTLR o LEX / YACC e non sarebbe difficile implementare le tue in base alla complessità delle proprietà e dei messaggi.


Solo un'alternativa all'approccio LEX / YACC, quando devo generare file molto simili con modifiche minori, utilizzo uno script Python semplice e piccolo e un file modello di codice C ++ contenente tag. Lo script python cerca questi tag nel modello sostituendoli con i token rappresentativi dell'elemento che si sta creando.
vincitore il

0

Ti consiglio di fare ricerche sui buffer di protocollo di Google ( http://code.google.com/p/protobuf/ ). Sono un modo molto intelligente per gestire i messaggi generici. Devi solo specificare le proprietà in un modello simile a una struttura e un generatore di codice fa tutto il lavoro di generazione delle classi per te (Java, C ++ o C #). Tutte le classi generate hanno parser di testo e binari, che li rendono utili sia per l'inizializzazione dei messaggi basata sul testo sia per la serializzazione.


La mia centrale di messaggistica è fondamentale per il mio programma: sai se i buffer di protocollo sono soggetti a grandi spese generali?
Bryan,

Non credo che comportino un notevole sovraccarico, poiché l'API è solo una classe Builder per ogni messaggio e la classe per il messaggio stesso. Google li utilizza come nucleo della propria infrastruttura. Ad esempio, le entità di Google App Engine vengono tutte convertite in buffer di protocollo prima di persistere. In caso di dubbi, ti consiglio di implementare un test di confronto tra i tuoi buffer di implementazione e protocollo attuali. Se ritieni che l'overhead sia accettabile, usali.
dsilva.vinicius,
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.