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.