REMARQUE: j'ai posé cette question sur Stack Overflow il y a quelques jours, mais j'ai eu très peu de vues et aucune réponse. J'ai pensé que je devrais plutôt demander sur gamdev.stackexchange.
Il s'agit d'une question / demande générale de conseils sur la maintenance d'un système de génération de procédures par le biais de plusieurs mises à jour post-publication, sans interrompre le contenu généré précédemment.
J'essaie de trouver des informations et des techniques pour éviter les problèmes d '"effet papillon" lors de la création de contenu procédural pour les jeux. Lorsque vous utilisez un générateur de nombres aléatoires prédéfini, une séquence répétée de nombres aléatoires peut être utilisée pour créer un monde reproductible. Alors que certains jeux enregistrent simplement le monde généré sur le disque une fois généré, l'une des fonctionnalités puissantes de la génération procédurale est le fait que vous pouvez compter sur la reproductibilité de la séquence de nombres pour recréer une région plusieurs fois de la même manière, supprimant ainsi le besoin de persistance. En raison des contraintes de ma situation particulière, je dois minimiser la persistance et je dois me fier autant que possible à un contenu purement semé.
Le principal danger de cette approche est que même le moindre changement dans le système de génération procédurale peut provoquer un effet papillon qui change le monde entier. Il est donc très difficile de mettre à jour le jeu sans détruire les mondes que les joueurs explorent.
La principale technique que j'ai utilisée pour éviter ce problème est de concevoir la génération procédurale en plusieurs phases, chacune ayant son propre générateur de nombres aléatoires prédéfini. Cela signifie que chaque sous-système est autonome et que si quelque chose se brise, cela n'affectera pas tout dans le monde. Cependant, cela semble avoir encore beaucoup de potentiel de "casse", même si dans une partie isolée du jeu.
Une autre façon possible de résoudre ce problème pourrait être de maintenir des versions complètes de vos générateurs dans le code et de continuer à utiliser le bon générateur pour une instance mondiale donnée. Cela me semble cependant être un cauchemar de maintenance, et je suis curieux de savoir si quelqu'un le fait réellement.
Donc, ma question est vraiment une demande de conseils généraux, de techniques et de modèles de conception pour traiter ce problème de l'effet papillon, en particulier dans le contexte des mises à jour du jeu après la sortie. (J'espère que ce n'est pas une question trop large.)
Je travaille actuellement dans Unity3D / C #, bien que ce soit une question indépendante du langage.
METTRE À JOUR:
Merci pour les réponses.
Il semble de plus en plus que les données statiques soient l'approche la meilleure et la plus sûre, et aussi que lorsque le stockage d'un grand nombre de données statiques n'est pas une option, avoir une longue campagne dans un monde généré nécessiterait une version stricte des générateurs utilisés. La raison de la limitation dans mon cas est la nécessité d'une sauvegarde / synchronisation cloud mobile. Ma solution peut être de trouver des moyens de stocker de petites quantités de données compactes sur des choses essentielles.
Je trouve que le concept de Stormwind de "Cages" est une manière particulièrement utile de penser aux choses. Une cage est fondamentalement un point de réensemencement, empêchant les effets d'entraînement de petits changements, c'est-à-dire la mise en cage du papillon.
Réponses:
Je pense que vous avez couvert les bases ici:
Utiliser plusieurs générateurs ou réensemencer à intervalles (par exemple en utilisant des hachages spatiaux) pour limiter les retombées des changements. Cela fonctionne probablement pour le contenu cosmétique, mais comme vous le signalez, il peut toujours provoquer une rupture contenue dans une section.
Garder une trace de la version du générateur utilisée dans le fichier de sauvegarde et répondre de manière appropriée. Que pourrait signifier «approprié» ...
n
versions du générateur dans votre exécutable. Si le fichier de sauvegarde utilise l'une de ces versions récentes, (proposez de) mettre à jour le fichier de sauvegarde vers la dernière version. Cela utilise le générateur approprié pour décompresser tout état obsolète en littéraux (ou en deltas de la sortie du nouveau générateur sur la même graine, s'ils sont très similaires). Tout nouvel état à partir de maintenant provient des nouveaux générateurs. Les joueurs qui ne jouent pas longtemps pourraient être laissés pour compte. Et dans le pire des cas, vous finissez par stocker tout l'état du jeu sous forme littérale, auquel cas vous pourriez aussi bien ...Si vous prévoyez de modifier fréquemment votre logique de génération et que vous ne voulez pas rompre la compatibilité avec les versions précédentes, ne vous fiez pas au déterminisme du générateur: enregistrez tout votre état dans votre fichier de sauvegarde. (c.-à-d. "Nuke it of orbite. C'est le seul moyen d'en être sûr")
la source
La principale source d'un tel effet papillon n'est sans doute pas la génération de nombres - ce qui devrait être assez facile pour rester déterministe à partir d'un seul générateur de nombres - mais plutôt l' utilisation de ces nombres par le code client. Les changements de code sont le vrai défi pour garder les choses stables.
Code: tests unitaires La meilleure façon de vous assurer qu'un changement mineur quelque part ne se manifeste pas involontairement ailleurs est d'inclure des tests unitaires approfondis pour chaque aspect génératif, dans votre build. Cela est vrai de n'importe quel morceau de code compact où le changement d'une chose peut avoir un impact sur beaucoup d'autres - vous avez besoin de tests pour tous afin que vous puissiez voir sur une seule construction ce qui a été impacté.
Numéros: séquences / emplacements périodiques Supposons que vous ayez un générateur de nombres qui sert à tout. Il n'attribue pas de sens, il crache simplement des nombres dans l'ordre - comme tout PRNG. Étant donné la même graine sur deux pistes, nous obtenons les mêmes séquences, oui? Maintenant, vous réfléchissez et décidez qu'il y aura peut-être 30 aspects de votre jeu qui devront régulièrement être fournis avec une valeur aléatoire. Ici, nous attribuons une séquence cyclique de 30 emplacements, par exemple, chaque premier numéro de la séquence est une disposition de terrain accidenté, chaque deuxième numéro est des perturbations du terrain ... etc ... Alors tes règles est donc de 30 ans.
Après 10, vous avez 20 emplacements libres que vous pouvez utiliser pour d'autres aspects à mesure que la conception du jeu progresse. Le coût ici est bien sûr que vous devez générer des numéros pour les emplacements 11-30 même s'ils ne sont pas actuellement utilisés , c'est-à-dire terminer la période, pour revenir à la séquence suivante de 1-10. Cela a un coût CPU, bien qu'il devrait être mineur (selon le nombre d'emplacements libres). L'autre inconvénient est que vous devez être sûr que votre conception finale peut être adaptée au nombre d'emplacements que vous avez mis à disposition au tout début de votre processus de développement ... et plus vous en attribuez au début, plus il y a d'emplacements "vides" vous devez potentiellement passer par chacun, pour faire fonctionner les choses.
Les effets de ceci sont:
Bien sûr, il y aura une longue période pendant laquelle votre jeu ne sera pas accessible au public - en alpha, pour ainsi dire - afin que vous puissiez réduire de 30 à 20 aspects sans affecter les joueurs, uniquement vous-même, si vous vous rendez compte que vous aviez attribué beaucoup trop d'emplacements au début. Cela permettrait bien sûr d'économiser certains cycles CPU. Mais gardez à l'esprit qu'une bonne fonction de hachage (que vous pouvez écrire vous-même) devrait de toute façon être rapide comme l'éclair. Donc, avoir à exécuter des emplacements supplémentaires ne devrait pas être coûteux.
la source
Si vous voulez persister avec PCG, je vous suggère de traiter le code PCG lui-même comme des données . Tout comme vous persisteriez des données entre les révisions avec un contenu régulier, avec le contenu généré, si vous souhaitez les conserver entre les révisions, vous devrez conserver le générateur.
Bien sûr, l'approche la plus populaire consiste à convertir les données générées en données statiques, comme vous l'avez mentionné.
Je ne connais pas d'exemples de jeux qui conservent beaucoup de versions de générateur, car la persistance est inhabituelle dans les jeux PCG - c'est pourquoi permadeath va souvent de pair avec PCG. Cependant, il existe de nombreux exemples de plusieurs PCG, même du même type, dans le même jeu. Par exemple, Unangband a de nombreux générateurs distincts pour les salles de donjons, et à mesure que de nouveaux sont ajoutés, les anciens fonctionnent toujours de la même manière. Que ce soit maintenable dépend de votre implémentation. Une façon de le maintenir maintenable est d'utiliser des scripts pour implémenter vos générateurs, en les gardant isolés avec le reste du code du jeu.
la source
Je maintiens une superficie d'environ 30000 kilomètres carrés, contenant environ 1 million de bâtiments et autres objets, en plus de placements aléatoires de choses diverses. Une simulation en plein air ofc. Les données stockées sont d'environ 4 Go. J'ai de la chance d'avoir de l'espace de stockage, mais ce n'est pas illimité.
Aléatoire est aléatoire, incontrôlé. Mais on peut le mettre en cage un peu:
C'est à peu près ça. Les cages consomment également des données, malheureusement.
Il y a un dicton en finnois, Hajota ja hallitse. Se traduit par Diviser et conquérir .
J'ai rapidement abandonné l'idée d'une définition précise des moindres détails. Random veut la liberté, alors il a obtenu la liberté. Laissez le papillon voler - à l'intérieur de sa cage. Au lieu de cela, je me suis concentré sur une manière riche de définir (et de maintenir !!) les cages. Peu importe quelles voitures elles sont, tant qu'elles sont bleues ou bleu foncé (un employeur ennuyeux a dit une fois :-)). "Bleu ou bleu foncé" étant la (très petite) cage ici, le long de la dimension de couleur.
Qu'est-ce qui est gérable, pour contrôler et gérer les espaces numériques?
En termes de maintenance et d'intercompatibilité de versions ... nous avons
: si version = n alors
: elseif version = m alors ...
Oui, la base de code s'agrandit :-).
Choses familières. Votre bonne façon d'aller de l'avant serait de définir une méthode riche pour diviser et conquérir , et sacrifier certaines données à ce sujet. Ensuite, si possible, donnez à la randomisation (locale) la liberté, où il n'est pas crucial de la contrôler.
Pas totalement incompatible avec le drôle "nuke it fom orbit" proposé par DMGregory, mais peut-être utiliser des armes nucléaires petites et précises? :-)
la source