Dans ce didacticiel, vous apprendrez à construire des donjons complexes à partir de pièces préfabriquées, sans contrainte pour les grilles 2D ou 3D. Vos joueurs ne seront jamais à court de donjons à explorer, vos artistes apprécieront la liberté de création et votre jeu jouera mieux.
Pour bénéficier de ce didacticiel, vous devez comprendre les transformations 3D de base et vous sentir à l'aise avec les graphes de scène et les systèmes à composants d'entité..
Rogue est l'un des premiers jeux à utiliser la génération procédurale mondiale. Créé en 1980, il comportait des donjons 2D générés de manière dynamique et basés sur une grille. Grâce à cela, deux parties ont été identiques, et le jeu a engendré un tout nouveau genre de jeux, appelés "roguelikes". Ce type de donjon est encore assez répandu plus de 30 ans plus tard.
En 1996, Daggerfall a été publié. Il comportait des donjons et des villes 3D procéduraux, ce qui permettait aux développeurs de créer des milliers d'emplacements uniques, sans avoir à les construire manuellement. Même si son approche 3D offre de nombreux avantages par rapport aux donjons à grille 2D classiques, elle n’est pas très courante..
Nous allons nous concentrer sur la génération de donjons similaires à ceux de Daggerfall..
Afin de construire un donjon, nous devons définir ce qu'est un donjon. Dans ce tutoriel, nous allons définir un donjon comme un ensemble de modules (modèles 3D) connectés les uns aux autres selon un ensemble de règles. Nous utiliserons pièces connecté par couloirs et jonctions:
Dans ce tutoriel, nous allons utiliser des modèles simples pour les modules - leurs maillages ne contiendront que le sol. Nous en utiliserons trois: pièces, couloirs et carrefours. Nous allons visualiser les marqueurs de sortie sous forme d’objets d’axe, l’axe -X / + X étant rouge, l’axe + Y vert et l’axe + Z bleu.
Modules utilisés pour construire un donjonNotez que l'orientation des issues n'est pas limitée à des incréments de 90 degrés.
En ce qui concerne la connexion des modules, nous définirons les règles suivantes:
Chaque module contient un ensemble de sorties-des objets marqueurs avec une position et une rotation connues. Chaque module est étiqueté pour en préciser le type et chaque sortie dispose d'une liste d'étiquettes à laquelle il peut se connecter..
Au plus haut niveau, le processus de construction du donjon est le suivant:
Le processus détaillé de connexion de deux modules ensemble est le suivant:
Pour connecter deux modules ensemble, nous devons les aligner (les faire pivoter et les traduire dans un espace 3D), de sorte qu'une sortie du premier module corresponde à une sortie du deuxième module. Les sorties sont correspondant à quand leur position est la même et que leurs axes + Z sont opposés, alors que leurs axes + Y sont identiques.
L'algorithme pour faire cela est simple:
Le pseudo-code est Python-ish, mais il devrait être lisible par quiconque. L'exemple de code source est un projet Unity.
Supposons que nous travaillons avec un système à composant entité qui contient les entités dans un graphe de scène, définissant leur relation parent-enfant. Unity est un bon exemple de moteur de jeu doté d’un tel système: Unity, avec ses objets et ses composants. Les modules et les sorties sont des entités; les sorties sont des enfants de modules. Les modules ont un composant qui définit leur balise et les exits ont un composant qui définit les balises auxquelles ils sont autorisés à se connecter..
Nous traiterons d'abord de l'algorithme de génération de donjon. La contrainte de fin que nous allons utiliser est un nombre d'itérations d'étapes de génération de donjon.
def generate_dungeon (starting_module_prefab, module_prefabs, itérations): starting_module = instantiate (startup_module_prefab) = en attente de_expert nouveau_module_dou ) waiting_exits = new_exits itérations - = 1
le instancier ()
function crée une instance d'un préfabriqué de module: il crée une copie du module, ainsi que ses sorties, et les place dans la scène. le get_random_with_tag ()
function itʻere dans tous les prefabs de module et en choisit un aléatoirement, avec le tag fourni. le random.choice ()
fonction obtient un élément aléatoire d'une liste ou d'un tableau passé en paramètre.
le match_exits
fonction est l'endroit où toute la magie se produit, et est montré en détail ci-dessous:
def match_exits (old_exit), nouveau_express translate_global (new_module, corrective_translation) def azimuth (vector): # Retourne l’angle signé ce vecteur est pivoté par rapport à l’axe global + Z forward = [0, 0, 1] renvoie vector_angle (forward, vector) * math.copysign (vector. X)
le backward_vector
La propriété d'une sortie est son vecteur -Z. le rotate_around_y ()
function fait pivoter l'objet autour d'un axe + Y avec son pivot en un point prévu, d'un angle spécifié. le translate_global ()
function traduit l'objet avec ses enfants dans l'espace global (scène), quelle que soit la relation enfantine à laquelle il appartient. le vecteur_angle ()
la fonction renvoie un angle entre deux vecteurs arbitraires et, enfin, le math.copysign ()
function copie le signe d'un numéro fourni: -1
pour un nombre négatif, 0
pour zéro, et +1
pour un nombre positif.
L'algorithme peut être appliqué à d'autres types de génération du monde, pas seulement aux donjons. Nous pouvons élargir la définition d'un module de manière à couvrir non seulement des parties de donjon telles que des salles, des couloirs et des jonctions, mais également des meubles, des coffres au trésor, des décorations de salles, etc. En plaçant les marqueurs de sortie au centre d'une pièce ou sur une pièce mur, et le taguer comme un butin
, décoration
, ou même monstre
, nous pouvons donner vie au donjon, avec des objets que vous pouvez voler, admirer ou tuer.
Un seul changement doit être effectué pour que l’algorithme fonctionne correctement: l’un des marqueurs présents dans un élément pouvant être placé doit être marqué comme suit: défaut
, afin qu'il soit toujours choisi comme celui qui sera aligné sur la scène existante.
Dans l'image ci-dessus, une pièce, deux coffres, trois piliers, un autel, deux lumières et deux objets ont été créés et étiquetés. Une salle contient un ensemble de marqueurs faisant référence aux balises d’autres modèles, telles que poitrine
, pilier
, autel
, ou Applique murale
. Un autel a trois article
des marqueurs dessus. En appliquant la technique de génération de donjon à une seule pièce, on peut en créer de nombreuses variantes..
Le même algorithme peut être utilisé pour créer des éléments de procédure. Si vous souhaitez créer une épée, vous pouvez définir sa prise en tant que module de départ. La poignée serait reliée au pommeau et au renfort. Le cross-guard se connecterait à la lame. En ne disposant que de trois versions de chacune des parties de l’épée, vous pouvez générer 81 épées uniques..
Vous avez probablement remarqué des problèmes avec le fonctionnement de cet algorithme.
Le premier problème est que sa version la plus simple construit des donjons sous la forme d’une arborescence de modules, dont la racine est le module de départ. Si vous suivez une branche de la structure du donjon, vous êtes assuré de vous retrouver dans une impasse. Les branches de cet arbre ne sont pas interconnectées et le donjon manquera de boucles de pièces ou de couloirs. Une façon de résoudre ce problème serait de réserver certaines sorties du module pour un traitement ultérieur et de ne pas connecter de nouveaux modules à ces sorties. Une fois que le générateur a eu suffisamment d'itérations, il choisissait une paire de sorties au hasard et essayait de les connecter avec un ensemble de corridors. Il faut un peu de travail algorithmique pour trouver un ensemble de modules et un moyen de les interconnecter de manière à créer un chemin passable entre ces sorties. Ce problème est en soi suffisamment complexe pour mériter un article séparé.
Un autre problème est que l’algorithme n’a pas conscience des caractéristiques spatiales des modules qu’il place; il ne connaît que les sorties marquées, ainsi que leurs orientations et leurs emplacements. Cela provoque un chevauchement des modules. L'ajout d'une simple vérification de collision entre un nouveau module à placer autour de modules existants permettrait à l'algorithme de créer des donjons ne souffrant pas de ce problème. Lorsque les modules entrent en collision, il peut abandonner le module qu’il a essayé de placer et essaierait un module différent à la place..
La gestion des sorties et de leurs balises est un autre problème. L'algorithme suggère de définir des balises sur chaque instance de sortie et de marquer toutes les salles. Il s'agit toutefois d'un gros travail de maintenance, s'il existe une manière différente de connecter les modules que vous souhaitez essayer. Par exemple, si vous souhaitez autoriser les pièces à se connecter aux couloirs et aux jonctions au lieu de seulement des couloirs, vous devez passer par toutes les sorties de tous les modules de salle et mettre à jour leurs balises. Une solution consiste à définir les règles de connectivité à trois niveaux distincts: donjon, module et sortie. Le niveau de donjon définirait des règles pour l'ensemble du donjon - il définirait les balises pouvant être interconnectées. Certaines salles pourraient remplacer les règles de connectivité lorsqu'elles sont traitées. Vous pourriez avoir une salle "patron" qui garantirait qu'il y a toujours une salle "trésor" derrière celle-ci. Certaines sorties remplaceraient les deux niveaux précédents. Définir des balises par sortie donne la plus grande flexibilité, mais parfois une trop grande flexibilité n’est pas très bonne.
Les calculs en virgule flottante ne sont pas parfaits et cet algorithme en dépend énormément. Toutes les transformations de rotation, les orientations de sortie arbitraires et les positions s'additionneront et peuvent provoquer des artefacts tels que des coutures ou des chevauchements là où les sorties se connectent, en particulier plus loin du centre du monde. Si cela était trop perceptible, vous pourriez étendre l'algorithme pour placer un accessoire supplémentaire à la rencontre des modules, tel qu'un cadre de porte ou un seuil. Votre artiste sympathique trouvera sûrement un moyen de cacher les imperfections. Pour les donjons d'une taille raisonnable (inférieure à 10 000 unités), ce problème n'est même pas perceptible, en supposant que vous ayez pris suffisamment de soin lors de la mise en place et de la rotation des marqueurs de sortie des modules..
L'algorithme, malgré certaines de ses faiblesses, offre une manière différente de regarder la génération des donjons. Vous ne serez plus limité aux virages à 90 degrés et aux pièces rectangulaires. Vos artistes apprécieront la liberté créative offerte par cette approche et vos joueurs profiteront de la sensation plus naturelle des donjons..