Génération procédurale pour énigmes simples

Ce que vous allez créer

Les puzzles font partie intégrante du jeu pour de nombreux genres. Qu'il soit simple ou complexe, le développement manuel de puzzles peut rapidement devenir encombrant. Ce tutoriel vise à alléger ce fardeau et à ouvrir la voie à d’autres aspects de la conception plus amusants..

Ensemble, nous allons créer un générateur permettant de composer de simples puzzles procéduraux «imbriqués». Le type de puzzle sur lequel nous allons nous concentrer est le “verrou et la clé” traditionnels, le plus souvent itérés comme suit: obtenez x pour déverrouiller la zone y. Ces types de puzzles peuvent devenir fastidieux pour les équipes qui travaillent sur certains types de jeux, en particulier les robots d'exploration de donjons, les bacs à sable et les jeux de rôle où les casse-tête sont plus souvent utilisés pour le contenu et l'exploration..

En utilisant la génération procédurale, notre objectif est de créer une fonction qui prend quelques paramètres et renvoie un actif plus complexe pour notre jeu. L'application de cette méthode procurera un retour exponentiel sur le temps passé par le développeur sans sacrifier la qualité de jeu. La consternation des développeurs peut également décliner comme un effet secondaire heureux.

Que dois-je savoir?

Pour suivre, vous devrez vous familiariser avec le langage de programmation de votre choix. Puisque la plupart de ce que nous discutons sont des données uniquement et généralisées en pseudocode, tout langage de programmation orienté objet suffira. 

En fait, certains éditeurs glisser-déposer fonctionneront également. Si vous souhaitez créer une démonstration jouable du générateur mentionné ici, vous devez également vous familiariser avec votre bibliothèque de jeux préférée..

Créer le générateur

Commençons par regarder un pseudocode. Les blocs de construction les plus élémentaires de notre système sont les clés et les pièces. Dans ce système, il est interdit à un joueur d'entrer par la porte d'une pièce à moins d'en posséder la clé. Voici à quoi ressembleraient ces deux objets en tant que classes:

Clé de classe Var playerHas; Emplacement du Var; Fonction init (setLocation) Location = setLocation; PlayerHas = false;  Fonction pickUp () this.playerHas = true;  class Room Var isLocked; Var assocKey; Fonction init () isLocked = true; assocKey = nouvelle clé (this);  Fonction unlock () this.isLocked = false;  Fonction canUnlock If (this.key.PlayerHas) Retourne true;  Else Retour false; 

Notre classe de clés ne contient pour l'instant que deux informations: l'emplacement de la clé et si le joueur a cette clé dans son inventaire. Ses deux fonctions sont l'initialisation et le ramassage. L'initialisation détermine les bases d'une nouvelle clé, tandis que la récupération est pour lorsqu'un joueur interagit avec la clé.

Notre classe de chambre contient également deux variables: est verrouillé, qui détient l'état actuel du verrou de la pièce, et assocKey, qui détient l'objet clé qui déverrouille cette pièce spécifique. Il contient une fonction d'initialisation, une pour appeler pour déverrouiller la porte et une autre pour vérifier si la porte peut être ouverte..

Une seule porte et une clé sont amusantes, mais nous pouvons toujours les pimenter avec des emboîtements. La mise en œuvre de cette fonction nous permettra de créer des portes à l’intérieur des portes tout en servant de générateur principal. Pour maintenir la nidification, nous devrons ajouter quelques variables supplémentaires à notre porte:

class Room Var isLocked; Var assocKey; Var parentRoom; Profondeur Var; Fonction init (setParentRoom, setDepth) If (setParentRoom) parentRoom = setParentRoom;  Else parentRoom = none;  Profondeur = setDepth; isLocked = true; assocKey = nouvelle clé (this);  Fonction unlock () this.isLocked = false;  Fonction canUnlock If (this.key.playerHas) Retourne true;  Else Retour false;  FunctionGenerator (profondeurMax) Array roomsToCheck; Tableau finishRooms; Room initialRoom.init (none, 0); roomsToCheck.add (salle initiale); While (roomsToCheck! = Vide) If (currentRoom.depth == profondeurMax) finiRooms.add (currentRoom); roomsToCheck.remove (currentRoom);  Else Room newRoom.init (currentRoom, currentRoom.depth + 1); roomsToCheck.add (newRoom); finishRooms.add (currentRoom); roomsToCheck.remove (currentRoom); 

Ce code de générateur effectue les opérations suivantes:

  1. Prise en compte du paramètre de notre puzzle généré (en particulier le nombre de couches d'une pièce imbriquée devrait aller).

  2. Création de deux tableaux: l'un pour les salles en cours de vérification pour une imbrication potentielle et l'autre pour l'enregistrement de salles déjà imbriquées.

  3. Créer une salle initiale pour contenir la scène entière, puis l'ajouter à la matrice pour que nous la vérifions plus tard.

  4. Prendre la pièce à l'avant du tableau pour passer par la boucle.

  5. Vérification de la profondeur de la pièce actuelle par rapport à la profondeur maximale fournie (cela décide si nous créons une pièce supplémentaire pour les enfants ou si nous terminons le processus).

  6. Etablir une nouvelle pièce et la renseigner avec les informations nécessaires de la pièce parent.

  7. Ajout de la nouvelle salle au chambres à vérifier tableau et déplacement de la pièce précédente vers le tableau fini.

  8. Répéter ce processus jusqu'à ce que chaque pièce du tableau soit terminée.

Nous pouvons maintenant avoir autant de pièces que notre machine peut gérer, mais nous avons toujours besoin de clés. Le placement des clés présente un défi majeur: la solvabilité. Partout où nous plaçons la clé, nous devons nous assurer qu'un joueur peut y accéder! Même si le cache de clé cachée semble excellent, s'il est incapable de l'atteindre, il est effectivement pris au piège. Pour que le joueur puisse continuer dans le puzzle, les clés doivent être disponibles.

La méthode la plus simple pour assurer la résolution du problème consiste à utiliser le système hiérarchique de relations d’objets parent-enfant. Puisque chaque pièce réside dans une autre, nous nous attendons à ce qu'un joueur ait accès au parent de chaque pièce pour l'atteindre. Donc, tant que la clé se trouve au-dessus de la pièce sur la chaîne hiérarchique, nous garantissons que notre joueur peut accéder.

Pour ajouter la génération de clé à notre génération procédurale, nous allons mettre le code suivant dans notre fonction principale:

 Salle de fonctionGénérateur (profondeurMax) Salles de tableaux à vérifier; Tableau finishRooms; Room initialRoom.init (none, 0); roomsToCheck.add (salle initiale); While (roomsToCheck! = Vide) If (currentRoom.depth == profondeurMax) finiRooms.add (currentRoom); roomsToCheck.remove (currentRoom);  Else Room newRoom.init (currentRoom, currentRoom.depth + 1); roomsToCheck.add (newRoom); finishRooms.add (currentRoom); roomsToCheck.remove (currentRoom); Array allParentRooms; roomCheck = newRoom; While (roomCheck.parent) allParentRooms.add (roomCheck.parent); roomCheck = roomCheck.parent;  Key newKey.init (Random (allParentRooms)); newRoom.Key = newKey; Renvoyer les pièces finies;  Else finishRooms.add (currentRoom); roomsToCheck.remove (currentRoom);  

Ce code supplémentaire produira désormais une liste de toutes les salles situées au-dessus de votre salle actuelle dans la hiérarchie des cartes. Ensuite, nous en choisissons une au hasard et définissons l'emplacement de la clé dans cette pièce. Après cela, nous attribuons la clé à la pièce à déverrouiller.

Lorsqu’elle est appelée, notre fonction de générateur créera et retournera un nombre donné de pièces avec des clés, permettant ainsi d’économiser des heures de développement.!

Cela enveloppe la partie pseudocode de notre générateur de puzzle simple, alors mettons-le en action.

Démo de procédure procédurale

Nous avons construit notre démo en utilisant JavaScript et la bibliothèque Crafty.js pour la garder aussi légère que possible, nous permettant ainsi de garder notre programme sous 150 lignes de code. Notre démonstration comporte trois éléments principaux:

  1. Le joueur peut se déplacer dans chaque niveau, les clés de ramassage et déverrouiller les portes.

  2. Le générateur que nous utiliserons pour créer automatiquement une nouvelle carte à chaque exécution de la démo.

  3. Une extension pour notre générateur à intégrer à Crafty.js, ce qui nous permet de stocker des informations sur les objets, les collisions et les entités..

Le pseudo-code ci-dessus sert d’outil d’explication. Par conséquent, l’implémentation du système dans votre propre langage de programmation nécessitera quelques modifications..

Pour notre démo, une partie des classes est simplifiée pour une utilisation plus efficace en JavaScript. Cela inclut la suppression de certaines fonctions liées aux classes, car JavaScript permet un accès plus facile aux variables dans les classes..

Pour créer la partie jeu de notre démo, nous initialisons Crafty.js, puis une entité de joueur. Ensuite, nous donnons à notre joueur les commandes de base à quatre directions et quelques détections de collision mineures pour empêcher l’entrée.

Une entité Crafty est maintenant attribuée aux salles, stockant des informations sur leur taille, leur emplacement et leur couleur pour une représentation visuelle. Nous allons également ajouter une fonction de dessin pour nous permettre de créer une pièce et de la dessiner à l'écran..

Nous fournirons des clés avec des ajouts similaires, notamment le stockage de son entité Crafty, de sa taille, de son emplacement et de sa couleur. Les clés seront également codées par couleur pour correspondre aux salles qu'elles déverrouillent. Enfin, nous pouvons maintenant placer les clés et créer leurs entités en utilisant une nouvelle fonction de dessin.

Enfin, nous allons développer une petite fonction d’aide qui crée et renvoie une valeur de couleur hexadécimale aléatoire afin d’éliminer le fardeau du choix des couleurs. A moins que vous n'aimiez swatching de couleurs, bien sûr.

Que dois-je faire ensuite?

Maintenant que vous avez votre propre générateur simple, voici quelques idées pour étendre nos exemples:

  1. Portez le générateur pour permettre son utilisation dans le langage de programmation de votre choix.

  2. Étendre le générateur pour inclure la création de salles de branchement pour une personnalisation ultérieure.

  3. Ajoutez la possibilité de gérer plusieurs entrées de pièce à notre générateur pour permettre des énigmes plus complexes.

  4. Étendez le générateur pour permettre le placement de clés dans des emplacements plus complexes afin d'améliorer la résolution des problèmes du joueur. Ceci est particulièrement intéressant lorsqu'il est associé à plusieurs chemins pour les joueurs.

Emballer

Maintenant que nous avons créé ce générateur de puzzle, utilisez les concepts présentés pour simplifier votre propre cycle de développement. Quelles tâches répétitives te trouves-tu en train de faire? Ce qui vous dérange le plus dans la création de votre jeu? 

Il est fort probable qu'avec un peu de planification et de génération de procédures, vous puissiez simplifier considérablement le processus. Espérons que notre générateur vous permettra de vous concentrer sur les parties les plus attrayantes du jeu tout en éliminant le quotidien..

Bonne chance et à bientôt dans les commentaires!