Les membres ayant 30 points peuvent parler sur les canaux annonces, projets et hs du chat.

Forum Casio - Projets de programmation


Index du Forum » Projets de programmation » Asci : un moteur pour jeux de rôles en Python
Shadow15510 Hors ligne Administrateur Points: 4981 Défis: 16 Message

Asci : un moteur pour jeux de rôles en Python

Posté le 13/08/2021 11:36

Bonjour à tous !
Avec la Graph 90 et son Python très… limité pas simple de faire un jeu de rôle… Asci est un moteur qui va être conçu pour vous simplifier la vie !

En terme d'utilisation, je vise quelque chose d'assez ergonomique, mais il y aura sans doute quelques points un peu délicat…

Concrètement, à quoi va servir ce moteur ? Asci a avant tout pour but de rendre plus simple la création de petits jeux de rôles en Python. Ainsi, le moteur va se charger en grande partie de l'affichage, de l'avancée du scénario et de toute l'interface utilisateur. Vous de votre côté, il faudra que vous fournissiez une carte et un scénario sous forme d'évènements.

>> Le dépôt gitea <<


La carte du monde
  • c'est à vous de la faire dans un variable à part, le nom ne sera pas imposé, mais il faudra respecter quelques règles (mettre la carte dans un tuple, en premier la carte du monde, après les maisons, les coordonnées des portes…)
  • vous évoluez dans le monde façon pokémon : la carte bouge tout autour de vous


Les mécaniques
  • les combats sont laissés au développeur du jeu
  • les points d'expérience sert au moteur pour savoir où il en est dans l'histoire, vous ne pouvez pas y toucher
  • une liste de statistique est laissée libre, vous pouvez l'utiliser comme vous voulez. Ces statistiques peuvent être modifiée par les dialogues
  • les touches 4, 6, 7 et 8 sont libres, vous pouvez y associer n'importe quelle fonction


La mise en place du scénario
  • les évèvements se découpent en catégories, à chaque catégorie correspond une fonction dans le code et un (ou des) symbole(s) qui déclenche(nt) ces évènements lorsque le joueur le(s) rencontre
  • les évènements ne sont pas très dur à prendre en main, il s'agit de liste : [XP_gagnée, "texte"].
  • vous pouvez tout à fait mettre en place des dialogues complexes dans lesquels le joueur choisit sa réplique dans une liste




Lephenixnoir Hors ligne Administrateur Points: 20809 Défis: 143 Message

Citer : Posté le 13/08/2021 11:59 | #


Superbe ! Une bonne bibliothèque Python sera un début vraiment excellent, comme pour celle de Farhi. N'oublie pas d'inclure des exemples de code pour permettre aux intéressés de copier/coller un peu et de se lancer rapidement !
Shadow15510 Hors ligne Administrateur Points: 4981 Défis: 16 Message

Citer : Posté le 13/08/2021 12:47 | #


Pour les exemples, Asci (le jeu de rôle) en constituera un beau, mais le moteur ne sera pas tout à fait compatible avec ^^'
Je me note dans un coin d'en faire avec sans doute en petit tuto ou un truc du genre

Ajouté le 13/08/2021 à 15:16 :
Bon, j'ai décidé de me lancer à corps perdu dans la bataille avec le moteur, sans m'occuper des autres opus (oui le choix est discutable mais j'avais envie de faire un truc généraliste xD)

Quelques points sont déjà bien plus clair et d'autres se sont obscurcis xD

La map
  • le format sera une chaîne de caractère sous la forme d'une r-doc-string
  • cette map devra impérativement être stockée dans une variable world elle-même stockée dans un fichier world_map pour simplifier l'édition, je pense regrouper les fichiers que vous devrez faire en un seul, je vous tient au jus
  • lors des déplacements, le joueur reste au centre, tout est géré (au niveau des collisions et des interactions avec les PnJ)
  • les intérieurs, et les combats sont encore sur la todo list donc rien de nouveau : c'est encore un peu flou, surtout les intérieurs xD)


Les mécaniques
  • concernant les statistiques, avec le peu de place que j'ai sur l'écran, j'ai opté pour un système assez simple : XP, PV, point d'attaque, points de défense
  • les points d'XP sont au centre du moteur, c'est eux qui contrôlent toute l'avancée de votre histoire, faire un arbre de progression de l'XP me semble être une bonne idée. Cette méthode peut être naïve ou peu adaptée. J'ai un seul argument : en jouant finement avec, on peut faire des scénarios à plusieurs fins, et il me semble que les RpG à fin multiples ne courent pas les rues (fin de l'unique argument )


Que dire d'autre ? La sauvegarde sera très probablement sous forme d'une liste. Je sais pas trop ce que je pourrais ajouter, ça peut faire peur peut-être vu de l'extérieur, je pense déjà pouvoir vous dire que le plus dur, ça va être les évènements et la gestion de l'XP. La map c'est du ASCII-Art pur et simple. Une fois que votre progression de l'XP est bien nette, il faut juste écrire un petit : game = Asci() ; game.mainloop() et c'est terminé.
"Ce n'est pas parce que les chose sont dures que nous ne les faisons pas, c'est parce que nous ne les faisons pas qu'elles sont dures." Sénèque

Lephenixnoir Hors ligne Administrateur Points: 20809 Défis: 143 Message

Citer : Posté le 13/08/2021 15:26 | #


cette map devra impérativement être stockée dans une variable world elle-même stockée dans un fichier world_map pour simplifier l'édition, je pense regrouper les fichiers que vous devrez faire en un seul, je vous tient au jus

Question : pourquoi est-ce qu'on ne pourrait pas avoir une fonction asci_init_map() à laquelle on donne la chaîne qu'on veut ? Si tu donnes au moteur le contrôle des fichiers les choses se compliquent un peu dans le sens où on est plus vite limité par le moteur (eg. on est limités à une seule world_map) que si les choses se passent via une API.

concernant les statistiques, avec le peu de place que j'ai sur l'écran, j'ai opté pour un système assez simple : XP, PV, point d'attaque, points de défense

Est-ce que tu ne peux pas avoir un dictionnaire pour les stats et laisser le jeu qui utilise ton moteur décider de leurs noms et rôles ?

En gros, si tu veux, peut-être que ton moteur peut être un peu plus petit et laisser de la marge au jeu ! Ce serait dommage si on ne peut coder que les mécaniques de Asci Opus 1 et 2 non ?
Shadow15510 Hors ligne Administrateur Points: 4981 Défis: 16 Message

Citer : Posté le 13/08/2021 15:35 | #


Pour la map, ça doit pouvoir se faire assez facilement effectivement

Pour les stats, c'est un peu plus délicat, elles sont stockés à côté des coordonnées, j'ai essayé de faire en sorte que ce soit simplement modifiable… mais reste un problème, les combats sont normalement géré par le moteur… après je peux ne pas les gérer ? 'fin si je ne connais a priori pas les stats, je ne peux plus gérer les combats ^^'
"Ce n'est pas parce que les chose sont dures que nous ne les faisons pas, c'est parce que nous ne les faisons pas qu'elles sont dures." Sénèque

Lephenixnoir Hors ligne Administrateur Points: 20809 Défis: 143 Message

Citer : Posté le 13/08/2021 15:53 | #


Pour les combats, tu peux implémenter la GUI mais laisser le jeu spécifier ce que les actions font. Par exemple dans un Pokémon tu as 4 attaques toutes uniques (pas juste attaquer/défendre/etc). Si ça peut t'aider donne-toi quelques idées de jeux (Asci-Opus 1 et 2, Pokémon, Final Fantasy) et essaie de construire chaque partie d'une façon qui permette de coder les 3 à la fois.

Il est absolument certain que plus tu laisses de liberté au jeu, moins tu peux faire de choses toi-même (et donc plus tu laisses de travail au jeu). Toute la subtilité c'est d'identifier quelles sont les parties communes à tous les jeux, ou que les auteurs des jeux n'ont pas envie de coder eux-mêmes, et de mettre ça dans le moteur. À toi de voir où tu veux te placer entre «le moteur code très peu : liberté maximale mais beaucoup de travail» et «le moteur code tout : trivial à utiliser mais très limité» !
Shadow15510 Hors ligne Administrateur Points: 4981 Défis: 16 Message

Citer : Posté le 13/08/2021 16:01 | #


Je vois les limitations

Du coup je vais partir sur une sorte de compromis… d'un côté j'ai l'XP, les PV les coordonnées (dont j'ai besoin) et de l'autre j'ai une variable "stat" laissée vierge

De même pour les combats, la fonction sera vide… peut-être passer en argument la fonction de combat ?

Obsolète
Cliquer pour enrouler
Ajouté le 14/08/2021 à 00:07 :
J'ai pas mal avancé !
Un premier tuto est sur le README :

Utilisation

Vous devez commencer par copier le fichier asci_lib.py dans le répertoire de votre jeu. Créez ensuite un fichier qui va correspondre à votre jeu. Nommez-le comme vous voulez, le nom ne présente pas d'importance pour le moteur.

Ici, notre fichier s'appellera sample.py.

Notre fichier va se découper en plusieurs parties :
- l'importation de la bibliothèque asci_lib.py
- la création de la carte
- la création des dialogues
- la création de la fonction qui correspond à votre jeu et finalisation

La carte est un grand tuple qui est de la forme :

carte_monde = (
<carte_du_monde>,
(<carte_maison_1>, (x_entree1, y_entree1), (x_sortie1, y_sortie1)),
(<carte_maison_2>, (x_entree2, y_entree2), (x_sortie2, y_sortie2)),
...)

la <carte_du_monde> et les <carte_maison_X> sont des r-docstrings. Vous pouvez mettre à peu près n'importe quoi, veillez à bien respecter la légende :
- @ : caractère réservé au joueur (ne pas utiliser)
- ^ : porte de maison
- * : PnJ
- $ : adversaire

Dans le cas des maisons, le premier tuple correspond aux coordonnées de la porte de la maison *dans la carte du monde*. Le second tuple correspond aux coordonnées de la porte de la maison *dans la carte de la maison*.

Sur votre carte fraîchement créée, vous avez mis des PnJ (si ce n'est pas le cas, mettez-en, la suite sera plus intéressante ). L'idée est assez simple, il va falloir créer une fonction qui va prendre en argument l'expérience, les points de vie, l'id de la carte (l'index de la carte dans le tuple des maps, 0 : carte du monde, 1 : première maison etc), les coordonnées du joueur et les stat (une liste qui peut contenir des variables nécessaires aux mécaniques de votre jeu). Nous avons donc déjà :

def dialogue(xp, pv, carte_actuelle, x, y, stat):

Cette fonction va renvoyer un dictionnaire ou une liste.

C'est assez important, car, si vous renvoyez un dictionnaire, le dialogue sera lu en fonction des points d'expériences du joueur. Si vous renvoyez une liste, c'est le dialogue de la liste qui sera lu.

Le seul impératif que vous devez absolument respecter est la forme du dictionnaire et des listes.

Le dictionnaire est de la forme :

dialogues = {
    xp_1: [...],
    xp_2: [...],
    ...
    "base": [...]
}

xp_X correspond au nombre de points d'expérience à avoir pour déclencher ce dialogue.
"base" est le dialogue lancé par défaut si aucun autre cas ne marche.

Les listes sont, elles, de la forme :

[xp_gagne, pv_gagne, "le texte du dialogue", booleen, ...]

xp_gagne correspond aux nombres de points d'expérience gagné lors de la lecture de ce dialogue.
pv_gagne même principe qu'avec l'XP, mais avec les points de vie.
booleen détermine s'il s'agit d'un monologue du PnJ ou si vous pouvez répondre au PnJ.
... correspond à des modificateurs des stats. Vous pouvez tout à fait les oublier en première utilisation

La question maintenant est de savoir comment relier les dialogues au PnJ. Vous êtes libre de mettre en place un système d'ID, Nous vous proposons un système peut-être plus simple : les coordonnées des PnJ. Nous allons ainsi avoir une fonction qui va ressembler à :

def dialogue(xp, pv, carte_actuelle, x, y, stat):
    # Pour des raisons de clareté, on déclare un tuple avec les coordonnées
    coords = (x, y)

    # Si nous sommes en extérieur
    if carte_actuelle == 0:
        if coords == (X1, Y1):
            return {...}
        elif coords == (X2, Y2):
            return {...}

    # Si nous sommes dans la première maison de la liste
    elif carte_actuelle == 1:
        ...

    # Si le PnJ est bien sur la map, mais n'a aucun dialogue d'assigné :
    return [0, 0, "Hmm ?", False]

Vous pouvez également créer des dialogues. Pour cela, il vous suffit de mettre le booleen sur True et de mettre les réponses possibles dans le corps du dialogue, par exemple : [0, 0, "Ceci est une question ? 1. Réponse 1 2. Réponse 2", True]. Le numéro de la réponse correspond au nombre de point d'expérience qu'elle rapporte, cela vous permet de gérer les différents cas dans al suite du dialogue.

Il reste à faire une petite fonction qui va créer un "modèle" de jeu de rôle vierge, il faudra lui donner la carte, la fonction des dialogues et ce sera fini !

La fonction est vraiment triviale :

def mon_jeu():
    rpg_python = Asci(carte_monde, dialogue, 10, [])
    rpg_python.mainloop()

Les deux premiers arguments carte_monde et dialogue ont déjà été vu. Le 10 correspond aux nombres de points d'expérience au bout duquel le programme s'arrête, il s'agit de la fin de la partie si vous voulez. La liste passée en dernier argument correspondent aux stats.

Exemples et astuces

Pour commencer simplement, voici une carte assez banale (ne pas oublier la virgule à la fin !) :

carte_monde = (
r"""
_         ###
/o\__     #####
|  <>\     ###  
|____|     /_\

  *


|==|==|==|==|==|==|==|""",)

Nous n'avons pas de maisons, juste un PnJ

Nous allons faire parler notre PnJ ! Et comme on est chaud, on va directement faire un petit dialogue. Pour bien séparer les réaction à la question du reste, je met un niveau d'indentation supplémentaire, ça ne change rien au comportement du code.

def dialogue(xp, pv, carte_actuelle, x, y, stat):
    coords = (x, y)

    if carte_actuelle == 0:
        if coords == (2, 5): return {
            0: [0, 0, "Coucou ! Comment ca va ? 1. Ca va, et toi ? 2. Bof... 3. Je t'emmerde.", True],
                1: [3, 0, "Je vais bien, merci !", False],
                2: [3, 0, "Ow, desole...", False],
                3: [4, 0, "He, reviens quand tu sera de meilleure humeur !", False],

            4: [2, 0, "Bon et bien, je crois bien que cette premiere carte s'est bien passee !", False],
            5: [1, 0, "Je vais y aller, appelle moi si tu as besoin ;)", False],
            6: [1, 0, "A pluche o/", False],

            "base": [0, 0, "Oui ?", False]
            }

    return [0, 0, "Hmm ?", False]

Pour mettre en place vos dialogues (parce que j'espère que vous aurez un peu plus qu'un seul PnJ) faire un arbre de progression de l'XP peut être une bonne idée je vais essayer de le faire en ASCII-art pour vous montrer, mais avec une feuille et un stylo c'est plus simple.

      1    4
     -=----=---
0   / 2    5   \  6  7
------=----=------=--=
    \ 3            /
     -=------------


Il reste la petite fonction à faire :

def mon_jeu():
    rpg_python = Asci(carte_monde, dialogue, 7, [])
    rpg_python.mainloop()


Nous avons le fichier complet :

from asci_lib import *


carte_monde = (
r"""
_         ###
/o\__     #####
|  <>\     ###  
|____|     /_\

  *


|==|==|==|==|==|==|==|""",)


def dialogue(xp, pv, carte_actuelle, x, y, stat):
    coords = (x, y)

    if carte_actuelle == 0:
        if coords == (2, 5): return {
            0: [0, 0, "Coucou ! Comment ca va ? 1. Ca va, et toi ? 2. Bof... 3. Je t'emmerde.", True],
                1: [3, 0, "Je vais bien, merci !", False],
                2: [3, 0, "Ow, desole...", False],
                3: [4, 0, "He, reviens quand tu sera de meilleure humeur !", False],

            4: [2, 0, "Bon et bien, je crois bien que cette premiere carte s'est bien passee !", False],
            5: [1, 0, "Je vais y aller, appelle moi si tu as besoin ;)", False],
            6: [1, 0, "A pluche o/", False],

            "base": [0, 0, "Oui ?", False]
            }

    return [0, 0, "Hmm ?", False]


def mon_jeu():
    rpg_python = Asci(carte_monde, dialogue, 7, [])
    rpg_python.mainloop()


Ajouté le 14/08/2021 à 23:20 :
Mon précédent message est maintenant complètement obsolète
Je vais éviter de trop surcharger le forum avec le mode d'emploi, mais pour faire vite :
- un tuto d'utilisation et des exemples sont disponibles sur le README du dépôt
- le projet est susceptible d'avoir quelques fonctionnalités qui changent, mais rien de majeur n'est prévu.

Le moteur est téléchargeable ici.
"Ce n'est pas parce que les chose sont dures que nous ne les faisons pas, c'est parce que nous ne les faisons pas qu'elles sont dures." Sénèque

Shadow15510 Hors ligne Administrateur Points: 4981 Défis: 16 Message

Citer : Posté le 26/08/2021 00:22 | # | Fichier joint


Version 1.4.0
J'ai refondu le système d'évènements et de touches pour permettre quelque chose de vraiment généraliste.

Maintenant vous pouvez diviser vos évènements en catégories, chaque catégorie a sa fonction (qui renvoie un dictionnaire d'évènements ou un évènements) et cette fonction est appelée lorsque le joueur atteint un symbole que vous précisez.

De même pour les touches, les touches 4, 6, 7 et 8 ne sont pas utilisées par le moteur, vous pouvez donc les associer à des fonctions et en faire ce que vous voulez

Quels avantages ?
Cette nouvelle version est plus légère que la précédente avec 300 octets de gagné
Plus besoin de déclarer une fonction avec un def ... pass parce que le moteur voulait une fonction dont vous n'avez pas l'utilité pour votre projet : économisez de la place au sein de vos jeux.
Le code est plus aéré : si vous avez beaucoup d'évènements dans des circonstances différentes, vous pouvez séparer les cas plus simplement.
si les noms des fonctions sont bien choisis, le code devient extrêmement clair : evenements : {"*": pnj, "?": point_interet} avec pnj et point_interet des fonctions. De même pour les touches : touches = {7: affichage_stat, 8: inventaire} le mapping apparaît explicitement.
Comme dans la version précédente vous pouvez faire correspondre plusieurs symbole à la même fonction évènementielle. Par exemple si mes PnJ sont des * ou des ?, je peux écrire : evenements = {"*?": pnj}

Toute la documentation a été mise à jour, les exemples aussi
@RDP
"Ce n'est pas parce que les chose sont dures que nous ne les faisons pas, c'est parce que nous ne les faisons pas qu'elles sont dures." Sénèque

Shadow15510 Hors ligne Administrateur Points: 4981 Défis: 16 Message

Citer : Posté le 30/08/2021 15:36 | # | Fichier joint


Version 1.4.2

Petit changelog
Possibilité de modifier les statistiques au sein d'un évènement. Il suffit d'ajouter à la fin de la liste, un (ou plusieurs) tuple(s) de la forme (index, valeur). Cela va ajouter valeur à stat[index].
Les statistiques étaient déjà modifiables par effet de bord, la liste data qui est essentiel au fonctionnement du moteur l'est aussi. Attention toutefois, des modifications un peu hasardeuse du contenu de cette liste peut tout faire planter. (mais c'est de votre faute )
Vous pouvez maintenant vous amuser à faire des maps tordues avec des pièces cachées dans des maisons, le moteur sait faire !

Bon, en terme de poids, on a encore perdu 100 octets…
@RDP

Ajouté le 03/10/2021 à 15:27 :
Une toute petite mise à jour mineure. Elle ne concerne pas directement Asci, mais le script Python qui permet de convertir les fichiers tmx en carte pour Asci le truc chiant était de relever les coordonnées des portes à la main pour ensuite faire les points de passages. C'est maintenant géré par le script de conversion

Pour être plus clair, le script vous retourne un fichier .py avec la carte et les coordonnées des portes trouvées il vous reste plus qu'à remplir les trois derniers paramètres qui sont l'indice de la carte d'arrivée, et les coordonnées d'arrivées.

La syntaxe à donc légèrement changé : il faut préciser le symbole "porte" :
$ python converter ma_carte.tmx ^ ~
(avec ^ et ~ les portes)

Ajouté le 04/10/2021 à 17:19 :
C'est une idée qui m'a traversée l'esprit et pour l'instant j'ai ni vraiment le temps de m'y consacrer, ni beaucoup d'idée pour la commencer, mais peut-être un jour, il sera possible pour le développeur de modifier la map pendant l'exécution du jeu. J'entend par là, avoir un ennemi qui bouge, un PnJ qui suit le joueur, etc…

Pour l'instant j'essaye de voir de quoi j'ai besoin pour mettre ça en place, mais ça pose pas mal de problème ^^' il faudrait que le développeur connaisse les coordonnées à l'avance, ou alors fasse une fonction qui renvoie les coordonnées. Se pose aussi le problème du rafraîchissement. Mettre à jour les coordonnées à chaque fois que le joueur bouge me semble être la seule possibilité… Sans compter qu'a priori, y a pas de raison que le joueur ne puisse pas interagir avec le PnJ / ennemi / whatever qui bouge, donc il faudrait revoir le système qui gère ça et ne plus uniquement se fier aux coordonnées (qui vont changer au cours du temps).

'fin voila, j'ai pas d'idée de comment le faire, ça va un peu foutre la merde dans ce qui existe, et je manque un peu de temps pour faire ça d'un coup, mais ça me semble rigolo d'avoir un PnJ qui suit le joueur x)
"Ce n'est pas parce que les chose sont dures que nous ne les faisons pas, c'est parce que nous ne les faisons pas qu'elles sont dures." Sénèque

Lephenixnoir Hors ligne Administrateur Points: 20809 Défis: 143 Message

Citer : Posté le 04/10/2021 19:04 | #


Effectivement modifier la map au fur et à mesure ça pose quelques soucis, après tu peux toujours ne t'occuper que de l'affichage et déléguer au programme utilisateur la tâche d'assurer que les objets ne se rentrent pas dedans.
Shadow15510 Hors ligne Administrateur Points: 4981 Défis: 16 Message

Citer : Posté le 04/10/2021 20:39 | #


Mouaip, après les collisions, ça me semble pas être le plus dur à gérer… Il suffit juste de faire une vérification, et j'ai tout pour la faire
J'essaye de garder ça dans un coin de ma tête, si j'ai plus d'idée concrète sur la mise en place, je m'y pencherai de manière plus sérieuse
"Ce n'est pas parce que les chose sont dures que nous ne les faisons pas, c'est parce que nous ne les faisons pas qu'elles sont dures." Sénèque


LienAjouter une imageAjouter une vidéoAjouter un lien vers un profilAjouter du codeCiterAjouter un spoiler(texte affichable/masquable par un clic)Ajouter une barre de progressionItaliqueGrasSoulignéAfficher du texte barréCentréJustifiéPlus petitPlus grandPlus de smileys !
Cliquez pour épingler Cliquez pour détacher Cliquez pour fermer
Alignement de l'image: Redimensionnement de l'image (en pixel):
Afficher la liste des membres
:bow: :cool: :good: :love: ^^
:omg: :fusil: :aie: :argh: :mdr:
:boulet2: :thx: :champ: :whistle: :bounce:
valider
 :)  ;)  :D  :p
 :lol:  8)  :(  :@
 0_0  :oops:  :grr:  :E
 :O  :sry:  :mmm:  :waza:
 :'(  :here:  ^^  >:)

Σ π θ ± α β γ δ Δ σ λ
Veuillez donner la réponse en chiffre
Vous devez activer le Javascript dans votre navigateur pour pouvoir valider ce formulaire.

Si vous n'avez pas volontairement désactivé cette fonctionnalité de votre navigateur, il s'agit probablement d'un bug : contactez l'équipe de Planète Casio.

Planète Casio v42 © créé par Neuronix et Muelsaco 2004 - 2021 | Il y a 51 connectés | Nous contacter | Qui sommes-nous ? | Licences et remerciements

Planète Casio est un site communautaire non affilié à Casio. Toute reproduction de Planète Casio, même partielle, est interdite.
Les programmes et autres publications présentes sur Planète Casio restent la propriété de leurs auteurs et peuvent être soumis à des licences ou copyrights.
CASIO est une marque déposée par CASIO Computer Co., Ltd