Usa un'istanza o una classe per le risorse di gioco (legno, ferro, oro)


31

Quindi sto realizzando un gioco in cui è possibile inviare navi in ​​luoghi per vendere o acquistare risorse come legno, ferro, oro, ecc.

Ora mi chiedevo come creare le risorse in gioco. Ho pensato a 2 opzioni

  1. Crea una classe per ogni risorsa:

    public class ResourceBase {
        private int value;
        // other base properties
    }
    
    public class Gold : ResourceBase {
         public Gold {
             this.value = 40 // or whatever
         }
    }
  2. Creare istanze di una classe di risorse

    public class Resource {
        string name;
        int value;
    
        public Resource(string name, int value) {
            this.name = name;
            this.value = value;
         }
     }
    
    // later on...
    
    Resource gold = new Resource("Gold",40);

Con la seconda opzione è possibile riempire le risorse del gioco da un file resources.json, mi piace un po 'quella struttura.

Nuove idee / modelli / strutture di design sono sempre i benvenuti!

EDIT: È un po 'come l'app compagno di Assassins Creed Black Flag. Vedi la barra delle risorse sull'immagine qui sotto

EDIT 2: Ho fatto ulteriori ricerche sul "caricamento di elementi / risorse dal file JSON" e ho trovato questo blog: Potenza di JSON nello sviluppo di giochi - Articoli . Mostra il meglio dell'opzione 1 e dell'opzione 2. Sei ancora in grado di aggiungere funzionalità a ciascuna risorsa :)

inserisci qui la descrizione dell'immagine


1
Minecraft ha classi separate per ogni risorsa (non che dovresti)
MCMastery

@MCMastery ohh è Minecraft open-source? Vorrei dare un'occhiata a quella struttura. E grazie per aver menzionato
MDijkstra il

1
Non lo è, ma viene sempre de-offuscato per i server ... vedi github.com/Wolf-in-a-bukkit/Craftbukkit/tree/master/src/main/…
MCMastery

12
Non usare una stringa del genere. È fin troppo facile digitare "oro" in modo errato con "glod" o simili, ed è un grande dolore eseguire il debug. Usa enuminvece un .
Nolonar,

Minecraft non è tecnicamente open source, ma è stato quasi completamente decompilato e deobfuscato. Non dovresti essere in grado di trovare una fonte deobuscolata ovunque su Internet, ma puoi scaricare ed eseguire il processo per farlo da files.minecraftforge.net (il download MDK è quello che desideri)
JamEngulfer

Risposte:


51

Procedi con il secondo approccio, semplicemente perché puoi introdurre nuovi tipi di risorse o elementi in qualsiasi momento senza dover riscrivere o aggiornare il codice ( sviluppo guidato dai dati ).


Modificare:

Per approfondire un po 'di più il motivo per cui questo è in genere una buona pratica, anche se sei sicuro al 100% che un valore non cambierà mai.

Prendiamo l'esempio di gioco per console menzionato nei commenti, perché questo fornisce un'ottima ragione per cui non dovresti codificare nulla, a meno che tu non abbia davvero (o desideri), ad esempio chiavi di crittografia, il tuo nome, ecc.

Quando si rilascia un gioco su console, questi di solito devono passare attraverso un processo di revisione, in cui il QA del produttore della console testerà il gioco, ci giocherà, cercherà problemi, ecc. Questo è obbligatorio e costa denaro, molti soldi. Penso di aver letto che una versione potrebbe costare 30.000-50.000 $ solo per la sola certificazione.

Ora immagina di spingere il tuo gioco per il rilascio, pagando i 30.000 $ e attendi. E improvvisamente noti che alcuni dei tuoi valori di gioco sono letteralmente infranti. Diciamo che puoi comprare barre di ferro per 50 monete d'oro e venderle per 55 monete d'oro.

cosa fai? Se hai codificato queste cose, dovrai creare una nuova versione di aggiornamento / rilascio, che dovrà passare di nuovo attraverso la revisione, quindi nel caso peggiore pagherai ancora una volta per la ricertificazione! Scommetto che Ubisoft non ti dispiacerà, ma per la tua piccola tasca per sviluppatori di giochi indie ... ahi!

Ma immagina che il gioco verifichi solo occasionalmente le definizioni di gioco aggiornate (ad esempio sotto forma di un file JSON). Il gioco potrebbe scaricare e utilizzare la versione più recente in qualsiasi momento senza richiedere un nuovo aggiornamento. È possibile correggere tale squilibrio / exploit / bug in qualsiasi momento senza richiedere la ricertificazione o altri soldi pagati. Solo una piccola decisione di progettazione, non pensavi che ne valesse la pena, ti ho appena risparmiato una somma di 5 cifre! Non è fantastico? :)

Non fraintendetemi. Questo vale anche per altri tipi di software e piattaforme. Il download di file di dati aggiornati è in genere molto più semplice e fattibile per l'utente standard, non importa se si tratta di un gioco o di qualche tipo di applicazione / strumento. Immagina un gioco installato in Programmi in Windows. Il tuo aggiornamento necessita dei diritti di amministratore per modificare qualsiasi cosa e non puoi modificare i programmi in esecuzione. Con DDD il tuo programma scarica semplicemente i dati e li utilizza. Il giocatore potrebbe anche non notare che c'è stato un aggiornamento.


6
+1 per DDD. Questo è molto adatto per cose come le risorse.
Kromster dice di sostenere Monica il

@Funnydutchman se la risposta di qualcuno ti ha aiutato, è consuetudine su Stack Exchange votare la loro risposta facendo clic sulla freccia sopra il numero a sinistra. Se una risposta ti è stata di grande aiuto, fai clic sul segno di spunta sotto la freccia in basso per accettare la risposta. Entrambe le azioni conferiscono al risponditore un bonus in termini di reputazione e rendono le risposte utilizzabili più visibili alla comunità.
Nzall,

2
Non vedo come hai dovuto riscrivere il codice con il primo modo; tutto ciò che faresti è aggiungere una classe (a meno che non mi manchi qualcosa).
SirPython,

4
Ed ecco la differenza tra il codice Object Oriented e il codice Object Obsessed. Solo perché puoi creare una gerarchia di oggetti non significa necessariamente che dovresti.
corsiKa

2
@ AgustínLado Se avessi un dollaro ogni volta che un cliente diceva "Questo non cambierà mai" ed è cambiato, potrei portarci entrambi in un ristorante decente per cena (anche se dovremmo risparmiare un po 'sulla punta, quindi forse solo un ristorante mediocre ... ancora abbastanza volte per cena per due.)
corsiKa

29

Una regola empirica è l'uso di classi diverse quando gli oggetti richiedono codice e istanze differenti della stessa classe quando gli oggetti richiedono solo valori diversi.

Quando le risorse hanno diverse meccaniche di gioco che sono uniche per loro, potrebbe avere senso rappresentarle con le classi. Ad esempio, quando hai Plutonio che ha un'emivita e Conigli che procreano secondo la sequenza dei fibonacci , allora potrebbe avere senso avere una classe base Resourcecon un metodo Updateimplementato come no-op e due classi derivate HalfLifeResourcee SelfGrowingResourceche sovrascrivere quel metodo per ridurre / aumentare il valore (e forse includere anche la logica per governare cosa succede quando si memorizzano entrambi l'uno accanto all'altro).

Ma quando le tue risorse sono davvero solo numeri stupidi, non ha senso implementarle con qualcosa di più sofisticato di una semplice variabile.


Grazie @Phillipp per la tua risposta. Questo è quello che stavo pensando. Ma le risorse non cambieranno davvero in termini di proprietà / metodi, quindi andrò con l'opzione 2 per ora
MDijkstra

14

Vorrei aggiungere ci sono due opzioni extra:
Interfaccia: puoi considerarla se la classe di risorse sarebbe solo una "memoria" per 5 numeri interi e ogni altra entità avrebbe una logica diversa riguardo alle risorse (ad esempio spesa / bottino del giocatore, città produce risorsa). In tal caso potresti non voler affatto una classe: vuoi semplicemente esporre che alcune entità hanno una variabile con cui altre possono interagire:

interface IHasResources {
  int Gold { get; set; }
  int Wood { get; set; }
  int Cloth { get; set; }
  // ....
}

class Player : IHasResources { }
class City: IHasResources { }

inoltre, questo ha il vantaggio di poter saccheggiare una città con lo stesso codice di come si saccheggerebbe la flotta nemica, promuovendo il riutilizzo del codice.
L'aspetto negativo è che l'implementazione di tale interfaccia può rivelarsi noiosa se molte classi la implementano, quindi potresti finire per usare le interfacce ma non per il problema originale, risolvendolo con l'opzione 1 o 2:

interface IHasResources { 
   IEnumerable<Resource> Resources { get; }
}

Istanze (con enum): in alcuni casi, sarebbe auspicabile utilizzare enum invece di stringa quando si definisce il tipo di risorsa:

enum Resource {
   Gold, Wood, Cloth, //...
}

class ResourceStorage {
   public Resource ResourceType { get; set; }
   public int Value { get; set; }
}

questo fornirà un po 'di "sicurezza", perché il tuo IDE ti fornirà un elenco di tutte le risorse valide quando lo assegni dopo aver scritto il punto ResourceType = Resource., inoltre ci sono più "trucchi" con enumerazioni di cui potresti aver bisogno, come Tipo esplicito o composizione / flag che posso immaginare di essere utile durante la creazione:

[Flags]
enum Metal {
 Copper = 0x01/*01*/, Tin = 0x02/*10*/, Bronze = 0x03/*11*/
}
Metal alloy = Metal.Copper; //lets forget Value(s) for sake of simplicity
alloy |= Metal.Tin; //now add a bit of tin
Console.WriteLine(alloy); //will print "Bronze"


Per quanto riguarda ciò che è meglio - non esiste un proiettile d'argento, ma personalmente mi spingerei verso qualsiasi forma di istanza, potrebbero non essere fantasiose come l'interfaccia ma sono semplici, non ingombrano l'albero dell'eredità e sono ampiamente compresi.


2
Grazie per la tua risposta @wondra Mi piace l'opzione enum e come hai realizzato quel bronzo con rame e stagno; p
MDijkstra

2
Ho aderito a questa comunità in modo specifico per poter votare la soluzione enum. Puoi anche esaminare l'uso dell'attributo Descrizione enum per mappare da una rappresentazione JSON di enum ( my-great-enum) a una rappresentazione C # di enum ( MyGreatEnum).
Dan Forbes,

Apparentemente sono fuori dal circuito Java da troppo tempo. Da quando è permesso avere dichiarazioni sul campo per le interfacce? Per quanto ne so, quelli sono automaticamente statici e finali, e quindi non sono molto utili per gli scopi descritti nella domanda.
M.Herzkamp,

2
@ M.Herzkamp non è un campo, è una proprietà - e Java non supporta le proprietà. La domanda è taggata C #, C # consente le proprietà nelle interfacce - le proprietà sono metodi sottotopi dopo tutto. Temo che lo stesso vale per l'annotazione di flag, non supportata in Java anche se non ne sono completamente sicuro.
Wondra,

Grazie per averlo chiarito! Mi mancava il tag C # e il codice nella domanda sembrava così dannatamente simile a Java: D
M.Herzkamp

2

Dovresti usare l'opzione 1, che presenta 3 vantaggi:

  1. È più semplice creare un'istanza di una risorsa: invece di passare il tipo di risorsa come parametro, ad esempio, new Gold(50)
  2. Se devi dare un comportamento speciale a una risorsa, puoi cambiarne la classe.
  3. È più facile controllare se una risorsa è di un certo tipo - resource instanceof Gold

Tutte le risposte hanno i loro pro e contro, è difficile scegliere quale sia la migliore. Ho apportato una modifica alla domanda, cosa ne pensi di quella struttura?
MDijkstra,

1

Se le tue risorse non hanno una logica aziendale propria o scenari di casi speciali con il modo in cui interagiscono con l'ambiente, altre risorse (oltre agli scambi che hai trattato) o il giocatore, l'opzione due rende l'aggiunta di nuovi molto semplice.

Se devi considerare situazioni come ciò che accadrebbe se mescolassi due risorse per il crafting, se le risorse possono avere proprietà molto diverse (un secchio d'acqua è molto diverso da un grumo di carbone) o altre interazioni economiche complesse, l'approccio 1 è molto più flessibile per il motore, ma non per i dati.

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.