Codage basé sur les données
Chaque chose que vous mentionnez est quelque chose qui peut être spécifié dans les données. Pourquoi chargez-vous aspecificmap
? Parce que la configuration du jeu indique qu'il s'agit du premier niveau lorsqu'un joueur démarre une nouvelle partie, ou parce que c'est le nom du point de sauvegarde actuel dans le fichier de sauvegarde du joueur qu'il vient de charger, etc.
Comment trouvez-vous aspecificmap
? Parce qu'il se trouve dans un fichier de données qui répertorie les identifiants de carte et leurs ressources sur disque.
Il ne doit y avoir qu'un ensemble particulièrement restreint de ressources "de base" qui sont légitimement dures ou impossibles à éviter le codage en dur. Avec un peu de travail, cela peut être limité à un seul nom d'actif par défaut codé en dur comme main.wad
ou similaire. Ce fichier peut potentiellement être modifié au moment de l'exécution en passant un argument de ligne de commande au jeu, alias game.exe -wad mymain.wad
.
L'écriture de code piloté par les données repose sur quelques autres principes. Par exemple, on peut éviter que des systèmes ou des modules demandent une ressource particulière et inversent plutôt ces dépendances. Autrement dit, ne faites pas de DebugDrawer
chargement debug.font
dans son code d'initialisation; au lieu de cela, DebugDrawer
prenez un descripteur de ressource dans son code d'initialisation. Cette poignée peut être chargée à partir du fichier de configuration de jeu principal.
Comme exemples concrets de notre base de code, nous avons un objet "données globales" qui est chargé à partir de la base de données des ressources (qui est lui-même par défaut le ./resources
dossier mais peut être surchargé avec un argument de ligne de commande). L'ID de la base de données des ressources de ces données globales est le seul nom de ressource codé en dur nécessaire dans la base de code (nous en avons d'autres parce que parfois les programmeurs deviennent paresseux, mais nous finissons généralement par les corriger / supprimer éventuellement). Cet objet de données global regorge de composants dont le seul but est de fournir des données de configuration. L'un des composants est le composant UI Global Data qui contient des descripteurs de ressources pour toutes les principales ressources de l'interface utilisateur (polices, fichiers Flash, icônes, données de localisation, etc.) parmi un certain nombre d'autres éléments de configuration. Lorsqu'un développeur d'interface utilisateur décide de renommer l'actif d'interface utilisateur principal de /ui/mainmenu.swf
à/ui/lobby.swf
ils mettent simplement à jour cette référence de données globale; aucun code moteur n'a besoin de changer du tout.
Nous utilisons ces données globales pour tout. Tous les personnages jouables, tous les niveaux, l'interface utilisateur, l'audio, les ressources principales, la configuration du réseau, tout. (enfin, pas tout , mais ces autres choses sont des bugs à corriger.)
Cette approche présente de nombreux autres avantages. D'une part, il intègre le regroupement et le regroupement des ressources à l'ensemble du processus. Les chemins de codage en dur dans le moteur ont également tendance à signifier que ces mêmes chemins doivent être codés en dur dans les scripts ou les outils qui emballent les actifs du jeu, et ces chemins peuvent alors se désynchroniser. En s'appuyant plutôt sur un seul actif principal et des chaînes de référence à partir de là, nous pouvons créer un ensemble d'actifs avec une seule commande comme bundle.exe -root config.data -out main.wad
et savoir qu'il comprendra tous les actifs dont nous avons besoin. De plus, étant donné que le bundler ne ferait que suivre les références de ressources, nous savons qu'il n'inclura que les actifs dont nous avons besoin et ignorera toutes les peluches résiduelles qui s'accumulent inévitablement pendant la durée de vie d'un projet (en plus, nous pouvons générer automatiquement des listes de ces ressources). peluches pour l'élagage).
Un cas de coin délicat de tout cela est dans les scripts. Conceptuellement, il est facile de rendre le moteur piloté par les données, mais j'ai vu tellement de projets (passe-temps pour AAA) où les scripts sont considérés comme des données et sont donc "autorisés" à utiliser les chemins de ressources sans discrimination. Ne fais pas ça. Si un fichier Lua a besoin d'une ressource et qu'il appelle simplement une fonction comme celle-ci, textures.lua("/path/to/texture.png")
le pipeline d'actifs aura beaucoup de mal à savoir que le script nécessite /path/to/texture.png
de fonctionner correctement et pourrait considérer cette texture comme inutilisée et inutile. Les scripts doivent être traités comme tout autre code: toutes les données dont elles ont besoin, y compris les ressources ou les tables, doivent être spécifiées dans une entrée de configuration que le moteur et le pipeline de ressources peuvent inspecter pour les dépendances. Les données qui indiquent "charger le script foo.lua
" devraient plutôt indiquer "foo.lua
et lui donner ces paramètres "où les paramètres incluent toutes les ressources nécessaires. Si un script engendre aléatoirement des ennemis par exemple, passez la liste des ennemis possibles dans le script à partir de ce fichier de configuration. Le moteur peut alors pré-charger les ennemis avec le niveau ( car il connaît la liste complète des apparitions possibles) et le pipeline de ressources sait regrouper tous les ennemis avec le jeu (car ils sont définitivement référencés par les données de configuration). Si les scripts génèrent des chaînes de noms de chemin et appellent simplement une load
fonction, alors ni l' un ni l'autre le moteur ou le pipeline de ressources n'ont aucun moyen de savoir précisément quels actifs le script peut essayer de charger.
De la même manière, vous évitez le codage en dur dans les fonctions générales.
Vous passez des paramètres et vous conservez vos informations dans des fichiers de configuration.
Dans cette situation, il n'y a absolument aucune différence en génie logiciel entre l'écriture d'un moteur et l'écriture d'une classe.
Ensuite, votre code client lit un fichier de configuration "maître" ( celui-ci est codé en dur ou passé en argument de ligne de commande) qui contient les informations qui indiquent où se trouvent les fichiers d'actifs et quelle carte ils contiennent.
De là, tout est piloté par le fichier de configuration "maître".
la source
J'aime les autres réponses, donc je vais être un peu contraire. ;)
Vous ne pouvez pas éviter de coder les connaissances sur vos données dans votre moteur. D'où que viennent les informations, le moteur doit savoir les chercher. Cependant, vous pouvez éviter d'encoder les informations elles-mêmes dans votre moteur.
Une approche «pure» basée sur les données vous obligerait à démarrer l'exécutable avec les paramètres de ligne de commande nécessaires pour qu'il charge la configuration initiale, mais le moteur devra être codé pour savoir comment interpréter ces informations. Par exemple, si vos fichiers de configuration sont JSON, vous devez coder en dur les variables que vous recherchez, par exemple le moteur devra savoir rechercher
"intro_movies"
et"level_list"
et ainsi de suite.Cependant, un moteur "bien construit" peut fonctionner pour de nombreux jeux différents simplement en échangeant les données de configuration et les données auxquelles il fait référence.
Le mantra n'est donc pas tant pour éviter un codage dur que pour s'assurer que vous pouvez apporter des modifications avec le moins d'effort possible.
Pour contraster avec l'approche des fichiers de données (que je soutiens sans réserve), il se peut que vous soyez d'accord pour compiler les données dans votre moteur. Si le «coût» de cette opération est plus faible, il n'y a pas de réel préjudice; si vous êtes le seul à y travailler, vous pouvez différer la gestion des fichiers pour une date ultérieure et ne pas vous visser nécessairement. Mes premiers projets de jeu avaient de grandes tables de données codées en dur dans le jeu lui-même, par exemple une liste d'armes et leurs données assorties:
Vous mettez donc ces données dans un endroit facile à référencer et il est facile de les modifier si nécessaire. L'idéal serait de mettre ces trucs dans un fichier de configuration, mais ensuite vous devez faire de l'analyse et de la traduction et tout ce jazz, plus le raccordement de références inter-structures peut devenir une douleur supplémentaire que vous ne voulez vraiment pas traiter avec.
la source