Architettura del gioco - collegamento di diverse parti del gioco

4

Realizzo un po 'di sviluppo di giochi da solista e rimango sempre bloccato in una parte dell'architettura in cui non riesco a decidere quale sia il modo migliore per affrontare un problema. Mi sono imbattuto in un buon problema di architettura e spero di poter utilizzare le lezioni apprese da esso. Lo riassumerò un po 'per un problema teorico:

Supponiamo che il mio gioco abbia due abilità, "Heal Player" e "Stun Enemies". Ognuna delle abilità è riutilizzabile con un cooldown. Il gioco avrà bisogno di un pulsante separato per attivare ciascuna delle abilità.

La classe principale per il gameplay si chiama GameplayCore . La mia GUI è contenuta in una classe HUD e i nemici sono tutti controllati da un EnemyManager . La classe Giocatore contiene la salute del giocatore. GameplayCore crea e memorizza l'istanza HUD, l'istanza EnemyManager e l'istanza Player.

Ovviamente voglio separare il codice UI dal codice di funzionalità. Quindi, dal lato GUI, ho le classi HealButton , StunButton e HealthBar che definiscono l'aspetto e il comportamento di tali controlli. Dal lato della funzionalità, ho la classe Player con una proprietà health , una classe AbstractAbility che definisce il comportamento delle abilità condivise compreso il cooldown, e le classi HealAbility e StunAbility che la estendono per definire le implementazioni concrete.

Per evitare che GameplayCore diventi troppo gonfio, creo una classe AbilitySet per creare e gestire le istanze di HealAbility e StunAbility e creare un'istanza di AbilitySet in GameplayCore.

Ecco dove le cose cominciano a cadere a pezzi. Voglio essere in grado di cambiare l'aspetto dei pulsanti mentre i loro cooldowns salgono. Voglio che il codice di recupero sia autonomo in AbstractAbility, quindi HealAbility e StunAbility hanno bisogno di riferimenti ai pulsanti corrispondenti. Ora ho bisogno di passare quei riferimenti dall'HUD a GameplayCore e poi al costruttore di AbilitySet in modo che possano essere passati nei costruttori per i pulsanti di abilità. HealAbility ha bisogno di riferimenti a HealthBar (in HUD) e Player (in GameplayCore). StunAbility ha bisogno di un riferimento a EnemyManager in modo che possa recuperare la collezione di nemici da attraversare e stordire. Ma HUD ha bisogno di riferimenti a HealAbility e StunAbility in modo che possa chiamarli quando vengono premuti i pulsanti.

Tutto è diventato piuttosto aggrovigliato; c'è troppo accoppiamento e onestamente non sono sicuro di dove sia il posto migliore per costruire le classi di abilità. Sono molto tentato di unire tutti del codice di abilità nei pulsanti di abilità, il che semplificherebbe enormemente tutto, ma poi avrei un codice di funzionalità importante mescolato in una classe UI.

Qual è un buon approccio organizzativo a questo problema? Quali sono alcune buone strategie architettoniche che potrei usare per evitare che il codice si intasi troppo? Il mio intero approccio è strongmente imperfetto?

modifica: dovrei notare che questo particolare progetto è in ActionScript 3, che include un sistema di eventi (con le sue peculiarità e limitazioni).

    
posta user45623 10.02.2015 - 04:46
fonte

3 risposte

4

In realtà ho scritto una guida di gioco su larga scala che implementa (tra molte altre cose) proprio quelle azioni di cui stai parlando. Il modo in cui ha funzionato è che il gui era completamente separato dalla logica del gioco, quindi in teoria potevi giocare senza il gui, o creare due guis sullo stesso gamelogic. Nessuno ci ha mai provato, ma era teoricamente possibile, e il fatto che fosse possibile rivela una caratteristica importante del design:

The gui must know the gamelogic.

The gamelogic should know absolutely nothing about the gui.

Il modo in cui questo viene realizzato è rendere tutto osservabile in gamelogic e fare in modo che la GUI registri vari osservatori per ricevere notifiche dal gamelogic.

Quindi, quando l'utente fa clic sul pulsante, il pulsante dice alla gamelogic di eseguire l'azione corrispondente.

L'azione fa tutto ciò che deve fare, quindi imposta il proprio time-to-sbloccare al 100%. Poiché lo stato dell'oggetto azione è ora cambiato, emette una notifica in tal senso.

La GUI si è registrata con il gamelogic per ricevere eventi sull'azione, quindi riceve questa notifica, recupera il nuovo stato dell'azione e dipinge il pulsante come completamente disabilitato.

Quindi al prossimo tick (la successiva iterazione del ciclo principale del gioco) l'azione riduce il time-to-sbloccare, diciamo, al 95%, e rilascia un altro evento su quel cambiamento.

Il gui riceve questo evento e ripete il pulsante per mostrarlo parzialmente disabilitato. E così via, e così via.

    
risposta data 11.02.2015 - 11:21
fonte
6

Come Stephen ha suggerito nel suo commento , puoi usare il pattern Observer per connettere le diverse parti della tua applicazione.

Tuttavia, questo è un approccio piuttosto statico, e dovrai implementare interfacce ovunque e le dipendenze sono ancora forti. Poiché questo è un gioco, la tua struttura, la logica e il gameplay potrebbero cambieranno nel tempo.

Consiglierei di utilizzare un approccio basato sugli eventi. Funziona in modo simile all'Observer, con la differenza che né il mittente né il destinatario si conoscono l'un l'altro (nessuna "notifica" esplicita).

Sparerebbe semplicemente un evento "HealthChanged" e ogni componente interessato agirà di conseguenza. Puoi ottenerlo utilizzando un bus eventi .

Se vuoi rendere il programma davvero disgiunto e flessibile, potresti prendere in considerazione l'utilizzo della meccanica degli eventi insieme a un sistema Entity

È un approccio basato sui componenti: invece di avere un attributo di salute nella classe del giocatore, il giocatore sarebbe definito dai suoi componenti : salute, attacco e posizione (o qualsiasi altra cosa).

Dal momento che non sono sicuro che questo potrebbe essere troppo oneroso per un progetto a tempo libero come il tuo, non entrerò nei dettagli qui. Ma puoi controllare il link qui sopra e maggiori dettagli qui

    
risposta data 10.02.2015 - 09:00
fonte
0

Obviously I want to separate the UI code from the functionality code.

A mio parere, i giochi sono una categoria in cui è assolutamente da dimenticare abbandonare questo requisito e accoppiare semplicemente la logica di gioco all'interfaccia utente a meno che non si stia mirando al tipo di gioco in cui tale disaccoppiamento è vantaggioso, come uno che potrebbe funzionare in un modo completamente diverso Ambiente GUI con un set di grafica completamente diverso e forse anche un set di controlli completamente diverso.

Detto questo, se vuoi tenerlo disaccoppiato, puoi semplicemente "mappare / associare" il codice GUI a un determinato tipo di abilità, come "StunWidget" a "typeof (StunAbility)", in questo modo:

MapWidget (typeof (StunAbility), StunWidget)

Il widget mappato ottiene automaticamente l'istanza dell'abilità quando è costruita per un determinato tipo di abilità.

Quindi, quando hai selezionato un'unità, puoi scorrere le abilità disponibili usando qualcosa come la riflessione per quell'unità e creare / inserire / visualizzare i controlli della GUI associati (data la sua abilità type come chiave per trovare il tipo di abilità associato widget) per ogni abilità. Quindi il widget diventa responsabile dell'attivazione dell'abilità, bersagliandolo se richiede il targeting, visualizzando il cooldown, ecc., Magari con una visualizzazione a matrice come in Starcraft che mostra cosa puoi fare con l'unità:

Adoro il design a matrice 3x3 per la GUI in Starcraft 1, senza mai presentare più di 9 controlli hotkeyable all'utente. Penso che sia uno dei flussi di lavoro più ottimali per un gioco che consente di selezionare più unità, come un RTS o un gioco di ruolo in cui si hanno più membri del partito che è possibile selezionare singolarmente (compresi i TRPG). Penso che l'efficacia di un tale progetto si manifesti attraverso i giocatori che possono eseguire centinaia di azioni al minuto in un tale flusso di lavoro.

    
risposta data 17.12.2017 - 21:13
fonte

Leggi altre domande sui tag