Utilisez une instance ou une classe pour les ressources de jeu (bois, fer, or)

31

Je fais donc un jeu où vous pouvez envoyer des navires à des emplacements pour vendre ou acheter des ressources comme le bois, le fer, l'or, etc.

Maintenant, je me demandais comment les ressources devraient être créées dans le jeu. Je suis venu avec 2 options

  1. Créez une classe pour chaque ressource:

    public class ResourceBase {
        private int value;
        // other base properties
    }
    
    public class Gold : ResourceBase {
         public Gold {
             this.value = 40 // or whatever
         }
    }
  2. Créer des instances d'une classe de ressources

    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);

Avec la deuxième option, il est possible de remplir les ressources du jeu à partir d'un fichier resources.json, j'aime un peu cette structure.

De nouvelles idées / modèles de conception / structures sont toujours les bienvenus!

EDIT: C'est un peu comme l'application compagnon d'Assassins Creed Black Flag. Voir la barre de ressources sur l'image ci-dessous

EDIT 2: J'ai fait quelques recherches supplémentaires sur le "chargement des éléments / ressources du fichier JSON" et j'ai trouvé ce blog: Power of JSON in Game Development - Items . Il montre le meilleur de l'option 1 et de l'option 2. Vous pouvez toujours ajouter des fonctionnalités à chaque ressource :)

entrez la description de l'image ici

MDijkstra
la source
1
Minecraft a des classes séparées pour chaque ressource (pas que vous devriez)
MCMastery
@MCMastery ohh minecraft est-il open-source? J'aimerais voir cette structure. Et merci pour mentionner
MDijkstra
1
Ce n'est pas le cas, mais il est toujours désobscurci
MCMastery
12
N'utilisez pas une chaîne comme ça. Il est trop facile de taper "or" avec "glod" ou similaire, et c'est une énorme douleur à déboguer. Utilisez enumplutôt un .
Nolonar
Minecraft n'est pas techniquement open source, mais il a été presque complètement décompilé et désobfusqué. Vous ne devriez pas être en mesure de trouver une source désobfusquée n'importe où sur Internet, mais vous pouvez télécharger et exécuter le processus à partir de files.minecraftforge.net (le téléchargement MDK est celui que vous voulez)
JamEngulfer

Réponses:

51

Optez pour la deuxième approche, simplement parce que vous pouvez introduire de nouveaux types de ressources ou éléments à tout moment sans avoir à réécrire ou mettre à jour le code ( développement piloté par les données ).


Modifier:

Pour en savoir un peu plus sur les raisons pour lesquelles il s'agit d'une bonne pratique générale, même si vous êtes sûr à 100% qu'une valeur ne changera jamais.

Prenons l'exemple du jeu sur console mentionné dans les commentaires, car cela fournit une très bonne raison pour laquelle vous ne devriez rien coder en dur, sauf si vous en avez vraiment (ou voulez), par exemple des clés de cryptage, votre propre nom, etc.

Lors de la sortie d'un jeu sur des consoles, celles-ci doivent généralement passer par un processus d'examen, où le propre AQ du fabricant de la console testera le jeu, le jouera, recherchera les problèmes, etc. Ceci est obligatoire et coûte de l'argent, beaucoup d'argent. Je pense avoir lu une fois qu'une version pourrait coûter entre 30 000 et 50 000 $ rien que pour la certification.

Imaginez maintenant que vous poussiez votre jeu à sortir, en payant les 30 000 $ et attendez. Et soudain, vous remarquez que certaines de vos valeurs de jeu sont littéralement brisées. Disons que vous pouvez acheter des barres de fer pour 50 pièces d'or et les vendre pour 55 pièces d'or.

Que faire? Si vous avez codé en dur ce genre de choses, vous devrez créer une nouvelle version de mise à jour / version, qui devra être revue une fois de plus, alors dans le pire des cas, vous paierez à nouveau pour la recertification! Je parie qu'Ubisoft ne m'en voudra pas, mais pour votre propre poche de développeur de jeux indépendants… aïe!

Mais imaginez que le jeu vérifie occasionnellement les définitions de jeu mises à jour (par exemple sous la forme d'un fichier JSON). Votre jeu peut télécharger et utiliser la dernière version à tout moment sans nécessiter de nouvelle mise à jour. Vous pouvez corriger ce déséquilibre / exploiter / bug à tout moment sans exiger de recertification ou tout autre argent payé. Juste une petite décision de conception, vous ne pensiez pas que cela en valait la peine, vous avez simplement économisé une somme d'argent à 5 chiffres! N'est-ce pas génial? :)

Ne vous méprenez pas. Cela s'applique également à d'autres types de logiciels et de plates-formes. Le téléchargement de fichiers de données mis à jour est en général beaucoup plus facile et réalisable pour l'utilisateur standard, qu'il s'agisse d'un jeu ou d'une sorte d'application / d'outil. Imaginez un jeu installé sous Program Files dans Windows. Votre programme de mise à jour a besoin de droits d'administrateur pour modifier quoi que ce soit et vous ne pouvez pas modifier les programmes en cours d'exécution. Avec DDD, votre programme télécharge simplement les données et les utilise. Le joueur pourrait même ne pas remarquer qu'il y a eu une mise à jour.

Mario
la source
6
+1 pour DDD. C'est très approprié pour des choses telles que les ressources.
Kromster dit soutenir Monica le
@Funnydutchman si la réponse de quelqu'un vous a aidé, il est de coutume sur Stack Exchange de voter pour sa réponse en cliquant sur la flèche au-dessus du numéro à gauche. Si une réponse vous a le plus aidé, vous devez cliquer sur la coche sous la flèche du bas pour accepter leur réponse. Ces deux actions confèrent au répondeur un bonus de réputation et rendent les réponses utilisables plus visibles pour la communauté.
Nzall
2
Je ne vois pas comment vous devez réécrire le code avec la première façon; tout ce que vous feriez est d'ajouter une classe (sauf si je manque quelque chose).
SirPython
4
Et voici la différence entre le code orienté objet et le code obsédé par l'objet. Ce n'est pas parce que vous pouvez créer une hiérarchie d'objets que vous le devez.
corsiKa
2
@ AgustínLado Si j'avais un dollar pour chaque fois qu'un client disait "Cela ne changera jamais" et que cela a changé, je pourrais nous emmener tous les deux dans un restaurant décent pour le dîner (bien que nous devions lésiner un peu sur le pourboire, alors peut-être seulement un restaurant médiocre ... encore assez de temps pour le dîner pour deux.)
corsiKa
29

En règle générale, vous utilisez différentes classes lorsque les objets nécessitent un code différent et des instances de la même classe lorsque les objets ne nécessitent que des valeurs différentes.

Lorsque les ressources ont des mécanismes de jeu différents qui leur sont propres, il peut être judicieux de les représenter avec des classes. Par exemple, lorsque vous avez du Plutonium qui a une durée de demi-vie et des lapins qui procréent selon la séquence des fibonacci , alors il pourrait être judicieux d'avoir une classe de base Resourceavec une méthode Updateimplémentée en tant que no-op et deux classes dérivées HalfLifeResourceet SelfGrowingResourcequi remplacer cette méthode pour diminuer / augmenter la valeur (et peut-être aussi inclure une logique pour gouverner ce qui se passe lorsque vous stockez les deux côte à côte).

Mais lorsque vos ressources ne sont en réalité que des nombres stupides, il est inutile de les implémenter avec quelque chose de plus sophistiqué qu'une simple variable.

Philipp
la source
Merci @Phillipp pour votre réponse. Voilà ce que je pensais. Mais les ressources ne changeront pas vraiment en termes de propriétés / méthodes, donc je vais choisir l'option 2 pour l'instant
MDijkstra
14

Je voudrais ajouter qu'il y a deux options supplémentaires:
Interface: vous pouvez le considérer si la classe de ressources ne serait qu'un "stockage" pour 5 entiers et chaque autre entité aurait une logique différente concernant les ressources (par exemple, dépenses / butin du joueur, production de la ville Ressource). Dans ce cas, vous ne voudrez peut-être pas du tout une classe - vous vouliez simplement exposer qu'une entité a une variable avec laquelle d'autres peuvent interagir:

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

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

en outre, cela a l'avantage que vous pouvez piller une ville avec exactement le même code que vous pilleriez la flotte ennemie, ce qui favorise la réutilisation du code.
L'inconvénient est que l'implémentation d'une telle interface peut s'avérer fastidieuse si de nombreuses classes l'implémentent, vous pourriez donc finir par utiliser des interfaces mais pas pour le problème d'origine, en le résolvant avec l'option 1 ou 2:

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

Instances (avec enum): dans certains cas, il serait souhaitable d'utiliser enum au lieu de chaîne lors de la définition du type de ressource:

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

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

cela fournira un peu de "sécurité", car votre IDE vous donnera la liste de toutes les ressources valides lors de l'affectation après avoir écrit le point ResourceType = Resource., en plus il y a plus de "trucs" avec des énumérations dont vous pourriez avoir besoin, comme Type explicite ou composition / drapeaux que je peux imaginer être utile lors de l'élaboration:

[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"


Quant à ce qui est le mieux - il n'y a pas de solution miracle, mais personnellement, je pencherais pour toute forme d'instances, elles ne sont peut-être pas aussi sophistiquées que l'interface, mais elles sont simples, n'encombrent pas l'arbre d'héritage et sont largement comprises.

Wondra
la source
2
Merci pour votre réponse @wondra J'aime l'option enum et comment vous avez fait ce bronze à partir de cuivre et d'étain; p
MDijkstra
2
J'ai rejoint cette communauté spécifiquement afin que je puisse voter pour la solution enum. Vous pouvez également étudier l'utilisation de l'attribut enum Description pour mapper une représentation JSON de l'énum ( my-great-enum) à une représentation C # de l'énum ( MyGreatEnum).
Dan Forbes
Apparemment, je suis hors de la boucle Java depuis trop longtemps. Depuis quand est-il permis d'avoir des déclarations de champs pour les interfaces? Pour autant que je sache, ceux-ci sont automatiquement statiques et définitifs, et donc peu utiles aux fins décrites dans la question.
M.Herzkamp
2
@ M.Herzkamp ce n'est pas un champ, c'est une propriété - et Java ne prend pas en charge les propriétés. La question est étiquetée C #, C # autorise les propriétés dans les interfaces - les propriétés sont des méthodes sous-capot après tout. Je crains qu'il en soit de même pour l'annotation des drapeaux, non prise en charge en Java bien que je ne sois pas complètement sûr.
wondra
Merci d'avoir clarifié ça! J'ai raté la balise C # et le code de la question ressemblait tellement à Java: D
M.Herzkamp
2

Vous devez utiliser l'option 1, qui présente 3 avantages:

  1. Il est plus facile d'instancier une ressource - au lieu d'avoir à passer le type de la ressource en paramètre, vous feriez simplement, par exemple, new Gold(50)
  2. Si vous devez donner un comportement spécial à une ressource, vous pouvez le faire en changeant sa classe.
  3. Il est plus facile de vérifier si une ressource est d'un certain type - resource instanceof Gold
Zac
la source
Toutes les réponses ont leurs avantages et leurs inconvénients, il est difficile de choisir la meilleure solution. J'ai apporté une modification à la question, que pensez-vous de cette structure?
MDijkstra
1

Si vos ressources n'ont pas de logique métier propre, ou des scénarios de cas particuliers avec la façon dont elles interagissent avec l'environnement, d'autres ressources (en plus du commerce que vous avez couvert) ou le joueur, alors l'option deux facilite l'ajout de nouvelles.

Si vous devez considérer des situations comme ce qui se passerait si vous mélangez deux ressources pour l'artisanat, si les ressources peuvent avoir des propriétés très différentes (un seau d'eau est très différent d'un morceau de charbon) ou d'autres interactions économiques complexes, alors l'approche 1 est beaucoup plus flexible pour le moteur, mais pas les données.

Kristian Williams
la source