Après la génération procédurale, voici que Martin Menzel, développeur solo sur Knights of Frontier Valley, explique comment il a construit le moteur personnalisé du RPG à partir de zéro, couvrant tous les aspects du jeu, de la recherche de chemin et du temps adaptatif à la météo dynamique et au comportement des PNJ. Un jeu qui n’a pas de date de sortie pour le moment.
Voici le post :
En parcourant Frontier Valley à la recherche d’aventure et de gloire, vous traverserez un monde dynamique en constante évolution. Vous verrez le soleil se coucher au loin, les arbres onduler au vent et les flocons de neige colorer le paysage de blanc. Lors de vos déplacements, votre chemin sera automatiquement configuré pour contourner les obstacles efficacement.
Ces fonctionnalités, et bien d’autres, sont gérées par le moteur personnalisé développé pour Knights of Frontier Valley . Pour reprendre les exemples précédents : l’heure à laquelle le soleil doit se coucher est gérée par le « Gestionnaire de temps », un composant qui contrôle et suit le temps de jeu et la lumière ambiante. Les précipitations et le balancement des arbres sous le vent dépendent du « Gestionnaire météo », et les flocons de neige sont dessinés par le moteur de rendu de la carte grâce à des effets de particules. Les personnages se déplaçant sur la carte utilisent l’algorithme personnalisé Pathfinder.
Si vous êtes intéressé par le fonctionnement de certaines de ces choses et pourquoi j’ai choisi d’écrire mon propre moteur en premier lieu, vous êtes au bon endroit !
Il y a aussi une vidéo où je parle du moteur :
Motivation
Commençons par le « Pourquoi ». Créer un moteur de jeu de A à Z pour un jeu comme celui-ci est un projet colossal. Parmi les fonctionnalités du moteur, on compte le rendu en vue isométrique (2,5 D), une gestion sophistiquée de la mémoire, la génération procédurale de différents types de cartes, des interactions et des comportements complexes des personnages, la persistance (sauvegarde/chargement), des mécaniques temps réel et au tour par tour, la recherche de chemin personnalisée, et bien plus encore. Attendez-vous à des années de travail.
Puisqu’il existe des moteurs de jeu matures, dont certains sont gratuits, pourquoi se donner autant de mal ? Ce qui compte, c’est l’objectif que l’on cherche à atteindre .
Lorsque j’ai commencé à travailler sur le jeu en 2016, mes modèles étaient les pionniers de l’industrie comme Richard Garriott, Sid Meier et Peter Molyneux, qui ont créé leurs premiers jeux à l’âge d’or des RPG, dans les années 80 et 90. À l’époque, ils n’utilisaient pas de moteurs tiers, et, selon moi, suivre leurs traces signifiait apprendre à créer un jeu de A à Z. Développer mon jeu sur la base de code de quelqu’un d’autre, que je ne comprenais pas parfaitement et que je n’arrivais pas à ajuster complètement pour réaliser ma vision, ne m’intéressait pas. De plus, je pensais que ce serait amusant (et c’est souvent le cas).
Bien que Knights of Frontier Valley ait été mon premier jeu, je me souviens d’une carrière d’ingénieur logiciel de deux décennies, au cours de laquelle j’ai travaillé sur des logiciels pour ordinateurs de bureau, web, systèmes embarqués et appareils mobiles. Parmi mes projets passés dans la Silicon Valley, on compte les logiciels GPS, les premiers iPhone et l’iPad, ainsi que les systèmes d’infodivertissement et de pilotage automatique des voitures Tesla.

J’avais donc une formation en programmation, mais le développement de jeux est un domaine très particulier et j’avais beaucoup à apprendre . J’avais un emploi du temps chargé lorsque j’ai commencé le jeu, donc je ne pouvais travailler dessus que les soirs et les week-ends. Au bout d’environ trois mois, j’avais un prototype de boucle de jeu basique où un personnage pouvait se déplacer sur une carte et effectuer des interactions simples. Bien que ce prototype ait fonctionné, j’ai réalisé que l’approche actuelle ne produirait jamais un jeu aux graphismes intéressants. Les technologies que j’utilisais n’étaient tout simplement pas adaptées à la création de jeux, alors j’ai tout abandonné et j’ai recommencé , cette fois en utilisant libGDX , un framework Java open source qui simplifie le rendu OpenGL et d’autres fonctions liées aux jeux. J’ai choisi Java comme langage et OpenGL pour le rendu, car ces technologies avaient été utilisées dans d’autres jeux à succès, notamment Minecraft et RuneScape, et je connaissais ce langage.
J’étais désormais sur la bonne voie et, au cours des années suivantes, le jeu et son moteur allaient continuer à se développer.

La carte du monde : prototype précoce vs aujourd’hui
Développer ce moteur de A à Z a nécessité environ cinq ans de travail à temps partiel , et si quelqu’un me demandait s’il devrait le faire pour son propre jeu, je dirais : ce n’est pas pour tout le monde . Je ne le recommanderais pas à un débutant en programmation. Mais même pour les développeurs expérimentés, les avantages d’ un contrôle total du code, de la tranquillité d’esprit concernant d’éventuelles royalties, des effets d’apprentissage supplémentaires et du grand sentiment d’accomplissement doivent être mis en balance avec des années de travail sans revenus et une bonne dose de frustration . Si vous ne savez pas si vous devez le faire, vous ne devriez probablement pas le faire. La volonté de terminer ce que vous avez commencé doit être forte, jeune Padawan, sinon vous risquez de consacrer beaucoup de temps à quelque chose que vous pourriez éventuellement abandonner.
Notions de base sur le moteur
Mais qu’est-ce que le « moteur de jeu » exactement ? Les réponses peuvent varier selon la personne à qui vous posez la question, mais dans mon cas, je considère que le moteur désigne tous les éléments du logiciel qui ne gèrent pas la logique ou l’apparence d’un jeu spécifique . Il s’agit d’une fonctionnalité essentielle, séparée, qui peut également être utilisée pour d’autres jeux, s’ils sont du même type.
Deux choses à savoir sur la conception logicielle : premièrement, pour que le code soit lisible, maintenable et réutilisable, les fonctionnalités doivent être encapsulées. Cela signifie que les différentes parties du code ne doivent pas contenir la logique des différentes fonctionnalités. Deuxièmement, le comportement et la visualisation doivent toujours être séparés.
Suivant ces deux principes, mon moteur personnalisé a été conçu pour être composé de plusieurs modules, chacun gérant une fonctionnalité de jeu particulière . À l’exécution, ces modules sont utilisés selon les besoins, contrôlés par un composant central qui assure la cohésion de l’ensemble : la boucle de jeu principale .
La boucle principale est une fonction qui s’exécute plusieurs fois par seconde. À chaque exécution, l’univers du jeu est mis à jour et une image est générée , affichant l’univers dans sa forme la plus récente à l’écran. Idéalement, la boucle principale s’exécute au moins 60 fois par seconde (soit 60 FPS). Ce chiffre est lié aux anciens moniteurs, qui se rafraîchissaient jusqu’à 60 fois par seconde (soit 60 Hertz). Aujourd’hui, ce chiffre est souvent plus élevé, généralement 144 Hz.
Si votre système n’est pas assez puissant pour atteindre 60 FPS (le goulot d’étranglement est généralement le GPU), vous pourriez remarquer des lags et des animations saccadées. Mais avec Knights of Frontier Valley , vous n’avez pas à vous soucier de votre carte graphique : les graphismes sont généralement pré-rendus, ce qui permet au GPU de prendre une pause pendant que le jeu tourne. Sur mon ancien ordinateur portable, je ne constate aucun lag, même lorsque le jeu tourne sur la carte graphique intégrée.
Toutes les pièces de mon moteur comprennent :
- Boucle principale
- Machine d’état
- Gestion de la mémoire
- Poignées de persistance
- Moteurs de rendu pour la carte et les entités
- Système de lecture vidéo
- Sortie audio
- Gestion des entrées souris/clavier
- Fonctionnalités réseau
- Gestion du temps de jeu
- Recherche de chemin
- Modèles de cartes
- Modèle météorologique
- Modèle de personnage et files d’attente d’actions de personnage
- Module de voyage des personnages
Ne font pas partie du moteur :
- Tout ce qui concerne l’histoire et les quêtes
- Ressources graphiques, musique et effets sonores
- L’interface utilisateur
- Logique de création de cartes procédurales
- Personnages, objets et éléments spécifiques au jeu
- Comportement des PNJ
- Tout ce qui est spécifique au jeu, y compris la création de personnages, le flux de travail de combat, les effets spéciaux (comme la magie), les shaders personnalisés, les mini-jeux et bien plus encore
Je détaillerai deux modules du moteur : le modèle de personnage (et les files d’attente d’actions associées) et le module de déplacement des personnages , qui contrôle les déplacements des PNJ . Pour en savoir plus, vous trouverez également des journaux de développement sur la recherche de chemin et le rendu sur le site web du jeu.
Modèle de personnage et files d’attente d’actions
Le modèle de personnage est un conteneur logiciel qui contient tous les personnages susceptibles d’avoir besoin d’agir . Ces personnages doivent être chargés en mémoire pour mettre à jour leur comportement. Il inclut tous ceux qui se trouvent sur la même carte que le PJ, ainsi que d’autres qui pourraient avoir besoin d’agir pour d’autres raisons : chefs de faction, personnages en déplacement (nous y reviendrons plus tard) et personnages de quête importants. Lorsque le PJ est en ville, le modèle de personnage contient des centaines de PNJ, dont beaucoup se trouvent dans des bâtiments, d’autres se promènent à l’extérieur.
Le monde du jeu est peuplé de bien plus de personnages que ceux présents dans le modèle à un instant T. Cependant, comme la plupart d’entre eux se trouvent sur des cartes différentes (invisibles pour le joueur) et n’ont aucune raison d’agir, il est inutile de les conserver en mémoire et de gaspiller des cycles CPU à les mettre à jour. Ces personnages « dormants » sont stockés dans la base de données et seront chargés dans le modèle en cas de besoin.
Lorsque le PJ change de carte, le modèle de personnage détermine quels personnages ajouter (et lesquels peuvent être exclus), puis effectue les étapes nécessaires. Les personnages nouvellement ajoutés passent ensuite par une fonction qui évalue leur dernier état enregistré : si leur position et leur activité ne correspondent plus à leur cycle de comportement actuel (travail, sommeil, loisirs, etc.), ils sont téléportés au bon endroit sur la carte et commencent une activité appropriée.
En interne, le modèle de personnage ne tient pas compte des personnages individuels, mais des « groupes de personnages ». Chaque personnage, même agissant seul (y compris le PJ), fait toujours partie d’un groupe , éventuellement composé d’un seul membre. Si le groupe est composé de plusieurs membres, ils restent toujours sur la même carte et se déplacent en formation, suivant un chef de groupe désigné. Si un membre du groupe meurt, son corps est transféré dans un nouveau groupe composé d’un seul membre. Si le chef de groupe meurt, un nouveau chef est désigné, en fonction de sa puissance et de son rang.
Lorsque les personnages d’un groupe doivent agir individuellement (c’est-à-dire sans se déplacer en formation), comme lors d’un combat tactique, le groupe se divise temporairement et chaque personnage obtient son propre groupe. Une fois le combat terminé, les PNJ survivants rejoignent leurs anciens groupes ou en forment de nouveaux.

Maintenant que vous savez quels personnages sont conservés dans le modèle et sont capables d’agir, examinons ce que signifie « agir » dans le contexte du moteur.
Chaque personnage dispose d’une file d’attente d’actions , qui est une liste d’actions qu’il exécute dans l’ordre. Ces actions incluent « se déplacer sur un chemin », « interagir avec un objet », « parler », « attaquer », « jouer de la musique », « lire un parchemin », etc., ou même simplement « attendre ».
Lorsqu’un personnage n’est pas déjà occupé à effectuer une action, le personnage le plus haut placé est retiré de la file d’attente et commence son action. Ce n’est que si la file d’attente est vide que le personnage est réellement inactif.
La file d’attente peut contenir de nombreuses actions, dont certaines peuvent se diviser en plusieurs nouvelles actions dès leur exécution. Il s’agit d’un système complexe où chaque action de personnage doit être prédéfinie et clairement définie quant à son exécution. Il existe actuellement 36 types d’actions différents, certains représentant des ensembles de plusieurs sous-actions. Une action est appelée « action personnalisée » et peut pratiquement tout faire ; il n’y a donc aucune limite aux actions des personnages dans le jeu .
Si quelque chose d’imprévisible se produit pendant qu’un PNJ travaille dans sa file d’attente (comme le PC qui l’attaque), la file d’attente est vidée et le comportement est réévalué, engendrant de nouvelles actions dans la file d’attente en réponse.
Lorsque le jeu est sauvegardé, les files d’attente d’actions sont également sauvegardées et seront restaurées lors du chargement, de sorte que tous les personnages continuent là où ils se sont arrêtés.

Module de voyage des personnages
Les PNJ du modèle de personnage peuvent voyager de n’importe quel point du monde vers n’importe quel autre, en temps réel et à tout moment , même sur des cartes non chargées en mémoire. Le module de déplacement du personnage du moteur détermine de manière fiable toutes les actions nécessaires au voyage et les place dans la file d’attente du chef de groupe. Si, par exemple, un personnage devait voyager du grenier de sa maison à la cave d’une autre maison dans une autre ville, les actions suivantes seraient nécessaires :
- Démarrez l’animation de mouvement appropriée (marcher, courir ou voler… dans d’autres scénarios, peut-être aussi rouler ou nager)
- Marchez vers les escaliers qui descendent
- Empruntez les escaliers de niveau en niveau jusqu’à arriver au niveau d’entrée du bâtiment
- Marchez jusqu’à la porte d’entrée
- Interagissez avec la porte pour sortir du bâtiment
- Marchez jusqu’à un point où vous pouvez sortir de la ville
- Sortir de la ville
- Traversez le monde jusqu’à l’entrée de la ville de destination
- Entrez dans la ville
- Depuis l’entrée, traversez les quartiers de la ville selon vos besoins pour arriver à celui où se trouve la maison de destination
- Allez à l’entrée de la maison
- Interagissez avec la porte pour entrer dans le bâtiment
- Descendez les escaliers jusqu’à arriver au niveau de la cave de destination
- Allez à la position de destination dans la cave
Ces actions seront exécutées une par une, mais précalculer la liste complète des actions requises avant le voyage n’est pas optimal. Un imprévu pourrait survenir en cours de route et forcer le personnage à réévaluer son comportement : une porte critique pourrait se verrouiller, ou le personnage pourrait simplement être trop fatigué pour continuer. Et bien sûr, voyager entre les villes n’est pas sans danger dans la Vallée. Le personnage ne sait pas vraiment ce qui l’attend pendant le voyage, c’est pourquoi le moteur ne calcule qu’une étape à la fois à partir de la liste ci-dessus. À l’arrivée à un point de passage, la situation est réévaluée (en tenant compte du nouvel environnement du personnage) avant de calculer l’étape suivante, si aucune autre étape prioritaire ne se présente.
Prendre la décision sur la marche à suivre à chaque étape plutôt qu’à l’avance rend le système très robuste et évite de calculer des actions qui pourraient être abandonnées . Cela répartit également l’effort de calcul, ce qui est un avantage lorsque de nombreux personnages voyagent simultanément.
Les personnages en déplacement restent dans le modèle même s’ils quittent la carte du PJ, ce qui leur permet de poursuivre leur action jusqu’à leur destination finale. Désormais, tant que le personnage est sur la carte du PJ, tout obstacle sur son chemin est mémorisé et connu, et peut être contourné grâce au composant Pathfinder. Mais si le personnage est parti vers une autre carte, comment le système sait-il comment contourner les obstacles et déterminer le temps nécessaire si cette carte n’est pas mémorisée ?
J’ai résolu ce problème en ajoutant le concept de « routage hors carte », un système qui utilise les temps de trajet moyens pour parcourir une certaine distance sur différents types de cartes. J’ai déterminé ces temps de trajet moyens en effectuant un grand nombre d’itinéraires sur chaque type de carte. Ainsi, lorsqu’un PNJ quitte la carte du PJ et disparaît de sa vue, et que le PJ le suit un peu plus tard, le joueur le retrouvera exactement là où il devrait être.
C’est une approximation, pas un vrai calcul, et c’est un peu une simulation sur cette partie du voyage. Mais ça fonctionne plutôt bien et les joueurs ne se rendront jamais compte que certaines parties du parcours étaient fausses, MUHAHAHAHA. Bon, eh bien, VOUS le savez maintenant ; considérez cela comme votre récompense pour avoir lu jusqu’au bout. Votre capacité d’attention est admirable et vous avez probablement ce qu’il faut pour développer votre propre moteur de jeu.

