Architettura per un gioco di Tower Defense con torri multiple di tipo simile?

5

Sto provando a creare un gioco Tower Defense dove i minion si muovono attraverso un labirinto e puoi costruire torri in cima dei muri dei labirinti per fermare i servi. Uso personalmente Python e pygame , ma sto cercando risposte progettuali generiche che possano essere applicate a qualsiasi linguaggio OOP.

Quindi, dato che entrambi i minion e le torri hanno una dimensione e una posizione, così come un'immagine sprite, ho pensato che sarebbe stato appropriato creare una classe base comune:

class Entity:

    def __init__(self, image, size, position):
        self.image = image
        self.size = size
        self.position = position

    def draw(self, surface):
        surface.blit(self.image, self.position, self.size)

Bene, quindi voglio che il mio gioco funzioni in modo simile a Vector TD , dove hai più diversi tipi di torri che è possibile creare, ma è possibile creare più istanze di ogni tipo e ciascuna istanza può essere aggiornata singolarmente. Ciò significa che ho bisogno di un qualche tipo di modo generico per spiegare come funzionano tutte le torri di tipo simile, ma ho bisogno che ogni istanza sia separata. Così ho iniziato con una classe base Tower con tutte le proprietà a cui posso pensare:

class Tower(Entity):

    def __init__(self, image, size, position, attack_speed, attack_damage, attack_splash_radius, attack_range, cost):
        super().__init__(image, size, position)
        self.attack_speed = attack_speed
        self.attack_damage = attack_damage
        ...  # etc

Ma il problema è che tutte le torrette dello stesso tipo hanno lo stesso intervallo, la stessa velocità di attacco, lo stesso costo, ecc. ma sono comunque singole istanze, quindi sarebbe stupido copiare tutte queste proprietà comuni con lo stesso valori per ogni istanza separatamente.

Il motivo per cui non faccio una sottoclasse di Tower per creare i miei tipi di torre personalizzati è perché non aggiungono nulla alla classe. Letteralmente, l'unica cosa che farebbero è cambiare i valori predefiniti per gli argomenti di __init__ . L'uso di sottoclassi come questo porterebbe anche ai valori comuni che vengono copiati in ogni istanza, quindi se la mia sottoclasse GreenTower ha default range=800 per tutte le mie torrette, significherebbe che il valore 800 verrebbe memorizzato separatamente in ogni istanza ( quando potevano semplicemente usare un valore comune). Mi sembra che ci sia un modo migliore.

Quindi la mia domanda riguarda interamente la struttura / architettura / struttura del sistema. Come dovrei raggruppare queste proprietà insieme consentendo comunque l'aggiornamento individuale delle torrette separatamente? Idealmente potrei essere in grado di progettare questi tipi di torri in qualcosa come JSON, dove posso caricarli dinamicamente al gioco.

    
posta Markus Meskanen 06.12.2017 - 18:15
fonte

3 risposte

7

Sembra un caso d'uso per un peso vivo . In pratica, trascina tutti i dettagli condivisi per le tue torrette come una singola istanza di peso mosca e ciascuna torretta fa quindi riferimento a quell'oggetto. Si noti che è possibile avere più di un peso mosca in modo da non limitare in alcun modo. Se ti ritroverai vicino a un'istanza di peso mosca per ogni torretta, allora probabilmente non ne valuterà la pena.

Puoi anche decorare le tue istanze di torretta in modo da ottenere la maggior parte delle proprietà dal peso piuma ma modificare specifiche quelli necessari per le torrette specifiche. Puoi persino avere dei pesi mosca che decorano altri pesi. Tutto dipende dalle tue esigenze.

    
risposta data 06.12.2017 - 18:37
fonte
5

Perché mai sulla Terra ti darebbe fastidio che un valore di range sia memorizzato in ogni istanza di una torre? Stai cercando di salvare i 2 byte aggiuntivi per istanza? Se la tua memoria è seriamente limitata, è una questione diversa, ma non sembra il problema. E se a un certo punto nel futuro vorresti creare torri che diano bonus temporanei ad altre torri, non vorresti vuoi per ognuna di avere i propri campi di statistiche?

Hai un punto valido che fare una sottoclasse solo per impostare alcuni valori di default sembra ridicolo. E questo è. Ecco a cosa servono le fabbriche . È sufficiente creare una classe che conosca le varie impostazioni predefinite e chiedergli di creare tutto il sapore della torre che si desidera in quel momento. Andrebbe qualcosa di simile (scusa la sintassi Java):

public static Tower makeGreenTower() {
    Tower newTower = new Tower();
    newTower.color = Color.Green;
    newTower.range = 800;
    return newTower;
}

È un po 'difficile collegarsi con una configurazione JSON, ma interamente possibile. Dovresti passare alcuni identificatori alle varie classi coinvolte, ma la tua fabbrica sarebbe quella che, quando richiesto per un particolare tipo di torre, guarderebbe i valori reali e costruirà l'oggetto appropriato. La firma sarebbe quindi più simile a:

public static Tower createBasicTower(String towerType) { ... }

Vorresti che la fabbrica gestisse anche aggiornamenti permanenti della torre, poiché sarà lui a sapere quali aggiornamenti a cosa e come.

    
risposta data 06.12.2017 - 18:44
fonte
2

Nel tuo caso userò solo questa classe base Tower e la sottoclassi con tutte le torri ad ogni singolo livello possibile che puoi istanziare come avvio, come BlueTowerLv1 , BlueTowerLv2 , GreenTowerLv1 , ecc, con un riferimento da BlueTowerLv1 a BlueTowerLv2 in modo che sappia quale torre usare durante il livellamento (un null/nil per next_tower potrebbe indicare che la torre ha raggiunto un livello massimo).

Tuttavia, queste torri sono "meta torri". Non vengono istanziati per ogni singola istanza tower creata dall'utente. Invece vengono puntati su (referenziati) da tutte le torri create dall'utente. È fondamentalmente il modello di peso mosca descritto da Jimmy, ma con dettagli un po 'più specifici sulla natura dei TD.

Questo è in un caso in cui l'ambito è abbastanza piccolo da poter semplicemente codificare tutti i tipi di torri e le loro proprietà ad ogni livello possibile, e che non ci sia un numero infinito di livelli possibili. Se si desidera utilizzare una formula per generare le proprietà di ciascun tipo di torre a tutti i livelli, è sufficiente generare le istanze tower all'avvio in base a quello punto in cui non è necessario creare un identificatore univoco per, ad esempio , BlueTowerLv3 (potrebbe essere semplicemente memorizzato in una lista).

Ecco un diagramma di base che illustra l'idea.

QuestopresupponeunTDincuil'utentenonpuòfarecosìtantoperrendereunicaunasingolaistanzacreatadaunatorresullamappadigioco,oltreascegliereiltipoditorrechedesideracreare,posizionarlaeaggiornarla.Ciòimplicapochissimidatiunicipertorrecreatidall'utente,quindipuoisemplicementefareinmodocheletorrifaccianoriferimentoaidatidisolalettura(MetaTowers)checreiinanticipoall'avviodelgioco.Potrestiaverealcunialtridatiunicicomeipuntiesperienzaaccumulatidaunatorreprimachesialoropermessodiessereaggiornatisehaiuntaleconcettoneltuogioco.Intalcaso,inserirestitalidatiinTower,noninMetaTowers,mailtuoMetaTowerspotrebbememorizzareilnumerodipuntiesperienzanecessariperl'upgrade.

LaragionepercuiMetaBlueTowerLv1ereditadaMetaTowerèprincipalmentesoloperilpolimorfismoepergarantirechetuttelemeta-torri(eletorrichefannoriferimentousandolacomposizione)forniscanotuttiicampi/funzionalitàcheilgiocosiaspetta.Senonhaitalenecessità,puoisemplicementecreareun'istanzadiMetaTowerinvecediereditarla.

Adognimodo,sievitailproblemadellaridondanzaseparandol'ideadeimetadatidisolaletturadadatichel'utentemodifica.Imetadatinondevonoesseredisolalettura.Potrebbeesserereferenziatodatutteletorridellostessotipo.

Adesempio,potrestioffrirealgiocatoreuncostosoaggiornamentocherendetuttitorriblupiùpotenti,nonunasingolatorreblu.Intalcaso,puoimodificareimetadatidellatorrebluchetutteleistanzeditorrefannoriferimentosenzaeseguireilcicloattraversoognisingolaistanzaditorrebluchel'utentehacreatoedoveraggiungerealtrocodiceognivoltachel'utentecreaun'altratorrebluesemaivuoiaggiungiunacaratteristicadelgenereconilsennodipoi,ilmodellodelpesomoscaèparticolarmenteutile.Nonriescoapensareadunnomemiglioredi"meta" in questo caso per i dati sui pesi mosca condivisi, ma spero che tu abbia l'idea.

Come sottolineato, non esiste una ragione molto strong per utilizzare questo progetto da un punto di vista dell'efficienza / memoria. In genere, un TD non intende istanziare milioni di torri in una sessione di gioco che l'utente può creare e gestire individualmente. Tuttavia, evitare la ridondanza qui usando i pesi massimi può darti un po 'di respiro in più con il design e renderti un po' più facile ragionare sulla logica del gioco.

    
risposta data 07.12.2017 - 09:48
fonte

Leggi altre domande sui tag