UrWorld, le retour !

Version Alpha 0.0.1 ! Sortie pour le nouvel an !

a marqué ce sujet comme résolu.

Folaefolc, je vais 'apprendre un truc super utile: os.path.join, va voir sur la doc, ça devrait t'éviter de mettre des os.sep partout. Et puis la plupart des fonctions des librairies bien faites acceptent un chemin avec des / et reconvertissent si nécessaire. Regarde la doc de Python/du module avant d'en utiliser une fonction…

Edit: @Mizugola -> FUUU j'avais oublié de tourner la page (>.<')

+2 -0

@Phigger : Haha, quel con xD J'ai toujours été naïf :'

Eh ben du coup, je vais essayer quand même d'installer PIL avec wine (ça restera dans mon dossier personnel .wine, ça ne va donc pas "cradosser" mon système).

+0 -0

À vrai dire, je me prends un peu la tête pour réussir à faire fonctionner PIL. Mais j'ai trouvé quelque chose d'intéressant : Pillow. C'est un fork de PIL qui est à jour et surtout actif (il n'y a qu'à voir la dernière date de modification).
Et même pas besoin de changer le nom d'importation : import PIL as PILImage (pour reprendre ton code).

Pour l'installer sur GNU/Linux (et pour Windows aussi à-priori), pas plus compliqué que ça (aucun téléchargement à faire soi-même) :

1
pip3 install pillow

Sinon, tout est expliqué .

EDIT : J'ai pas encore réussi à faire fonctionner UrWorld. J'en suis à :

1
AttributeError: 'module' object has no attribute 'windll'

Ton code n'est pas portable, je vois que tu utilises des fonctions/objets spécifiques à Windows. […] Si ça t'intéresse, télécharge la version pygame-1.9.2a0.win32-py3.2.msi.
EDIT : Tu utilises Pygame >_< Du coup, il reste juste à remplacer les lignes qui posent problème par du code portable.

+0 -0

Remplacer la ligne par ma résolution ? La ligne qui pose problème est celle-là :

1
ctypes.windll.user32.GetSystemMetrics(0)

Je ne sais pas trop par quoi la remplacer.

Je suis allé voir ton code et tu utilises effectivement pygame. Mais dans ce cas, tu ne devrais pas avoir besoin de ctypes pour récupérer la résolution ?

EDIT : j'avais oublié de préciser que cette ligne (ligne 12 dans ombrage_bloc.py) ne fonctionnait pas :

1
level = [[' ' for 1 in range(width)] for 1 in range(height)]

Il suffit de remplacer les 1 par n'importe quel nom de variable, comme x ou y.

EDIT 2 : J'ai cherché un peu et j'en arrive à cette conclusion : il y a un problème d'organisation. ombrage_bloc.py est un fichier qui est importé par un(d') autre(s), il ne devrait donc pas chercher lui-même la résolution de l'écran. Elle devrait lui être donné en paramètre, par exemple.
Les variables globales width et height sont maladroites. On ne devrait mettre en global que des constantes avec une valeur directement (comme HEIGHT = 30). Les constantes HEIGHT et WIDTH ne devraient pas être définies dans ombrage_bloc.py mais plutôt dans constantes.py. Ton fichier ombrage_bloc.py est lui-même maladroit, j'aurais plutôt fait un module effets, avec dedans un fichier qui contient la classe pour gérer les ombres. Et plus tard, peut-être une autre classe pour gérer des effets miroirs, par exemple.
Sinon, j'ai du mal à comprendre à quoi sert tes deux lignes x = sorted(…). Avec une combinaison de max/min, tu n'arriverais pas au même résultat ? Ou tout simplement avec deux ternaires combinées :

1
var = x if truc > machin else (y if bidule > machin else chouette)

Parce que là tu construis une liste triée uniquement pour récupérer l'élément du milieu. C'est un peu comme si je construisait une dénoyauteuse automatique pour ne l'utiliser qu'une seule fois et ne récupérer que les noyaux (bah, oui, ça peut servir, les noyaux d'olives) ^^

+1 -0

EDIT : bonne lecture !

Je me permets d'écrire un autre message à la suite du mien :-°

J'ai un peu regardé ton fichier jeu.py.

D'abord, je tombe sur ta série de listes/dictionnaires qui te servent de constantes. C'est pas propre du tout de les déclarer dans la fonction qui exécute le jeu. Et leur sémantique est assez obscure (on ne sait pas qu'est-ce qui représente quoi), je préfèrerais utiliser des classes qui gèrent tout ça.
Et surtout ta fonction jeu_boubouille est énoooooooooooorme :waw:
Je vois que tu déclares des fonctions dedans. Dans ce cas, autant créer une classe Jeu qui contient la méthode sauvegarder, par exemple, et heu… c'est quoi ces noms de fonctions ? J'ai pas le temps d'essayer de comprendre tout le code, tes noms de fonctions doivent être explicites (= pas besoin de regarder le code pour comprendre ce qu'elles font). De plus, j'ai l'impression que certaines sont inutiles et devraient être regroupées en une seule, ou d'autres qui en font trop. Et elles ont presque toutes trop de paramètres.

Ta boucle principale de gestion des évènements est extrêmement trop énorme. Il y a un gros travail à faire là-dessus. Tu ne dois surtout pas avoir de logique dans la gestion d'un évènement. Quand tel ou tel évènement est vérifié, il délègue le travail : c'est pas le sien de déplacer le joueur, mettre à jour le bloc actuel etc.... Parce que là, tu répète des morceaux de code dans presque tout tes évènements et tu as une sémantique complètement mélangé.

J'ai mis un peu de temps avant de trouver à quoi correspondait le paramètre s envoyé un peu partout. Et au final, j'ai pas compris ce que contenait _S_ (EDIT: ah oui c'est vrai, le zmarkdown interprète les '_') (je sais juste que tu le charge de "s.sav"). Ton code est assez mystérieux.

Ce n'est qu'un aperçu de toutes les critiques que je pourrais faire, y a énormément de travail à faire !

Arrivé là, le plus efficace est de TOUT RÉÉCRIRE de zéro (et en profiter pour faire de l'objet, comme tu as commencé à le faire ;) ). Certains morceaux de codes seront à reprendre, peut-être.
Oui, je sais, ça va te prendre beaucoup de temps. Mais après, ce sera bien plus simple d'ajouter quelque chose et surtout de pouvoir t'aider. Là c'est vraiment le gros bazar.

Et c'est pas grave si tu n'ajoutes pas de fonctionnalité avant 1 mois ! C'est très important de nettoyer un code dès qu'on a terminé d'en ajouter une. Quand je dis nettoyer, c'est prendre du recul, regarder ce qui ne va pas. Et si besoin, ré-organiser certaines choses (parfois tout, comme c'est le cas ici, mais ça ne devrais pas arriver si tu as bien structuré dès le départ), enlever des choses inutiles, abandonner une fonctionnalité ingérable (c'est plutôt rare), factoriser des morceaux de codes, encapsuler des données dans une classe etc…

Pour te mettre sur une bonne voie et te donner une idée, je vais te montrer ma façon de fonctionner.
D'abord, je commence par écrire la logique globale du jeu dans du pseudo-code :

1
2
3
4
5
6
7
def run_game :
    initialiser jeu
    tant que le joueur ne quitte pas :
        délai de 20 ms # ou faire un calcul en fonction des FPS
        gestion évènements
        affichage
    quitter jeu

Normalement je détaille bien plus, mais je préfère résumer, mon message prend déjà beaucoup de place.

Tout de suite, je me dis "tiens, une classe Jeu qui se charge d'initialiser, afficher et quitter le jeu, ça serait pratique". Et ben c'est parti, j'imagine qu'elle existe déjà et je m'en sert (et je fais de même pour toute fonction/classe qui me semble utile) :

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
def run_game():
    jeu = Jeu(RESOLUTION) # Constante définie dans constantes.py
    joueur = jeu.creer_joueur() # Le jeu gardera une référence du joueur.
    fin = false
    while not fin:
        # Sur cette ligne, un délai de X ms en fonction des FPS.
        # Puis la gestion des évènements.
        for e in pygame.event.get():
            if event.type == QUIT:
                fin = true
            if event.type == JOYAXISMOTION:
                if event.axis == 0 and event.value > 0:
                    joueur.deplacer_a_droite()
                if event.axis == 0 and event.value < 0:
                    joueur.deplacer_a_gauche()
                if event.axis == 1 and event.value > 0:
                    joueur.accroupir()
                if event.axis == 1 and event.value < 0:
                    joueur.sauter()
            if event.type == JOYBUTTONDOWN:
                if event.button == 0:
                    joueur.taper()
                if event.button == 1:
                    joueur.poser_bloc()
            if event.type == KEYDOWN:
                if event.key == K_ESCAPE:
                    fin = jeu.menu() # Vu que le menu entrera dans sa propre boucle de jeu, ça mettra automatiquement le jeu en pause. Renvoie si le joueur décide de quitter.

        # Mettra à jour automatiquement le joueur via un joueur.mettre_a_jour() grâce à la référence interne dans jeu.
        jeu.mettre_a_jour() # Ou jeu.update() si tu préfères ^^
        jeu.afficher()

    jeu.quitter()

Si tu regardes le code, pas besoin de voir ce qu'il y a dans les classes Jeu et Joueur pour savoir ce qu'il se passe.
D'ailleurs, il peut y avoir un peu ce qu'on veut comme code à l'intérieur. Ce qui montre que si on souhaite changer une logique du jeu, voir le changer radicalement, les modifications se feront discrètes, faciles et proprement (ça demandera quand même du travail, bien sûr ;) ).

Ça donne une bonne idée d'un jeu structuré, où chaque objet/méthode/fonction ne fait pas plus qu'une chose à la fois. run_game fait un peu exception : elle charge le jeu, exécute la boucle principal et quitte le jeu (d'ailleurs, on peut facilement le "résoudre"). Mais chaque méthode de joueur ne fait bien qu'une seule chose à la fois, ainsi que les méthodes de l'objet jeu.
Chaque méthode mettre_a_jour() dans objet.mettre_a_jour() met à jour les attributs de l'objet (et rien d'autres) en fonction son état et de son environnement. Ensuite, jeu.afficher() ne fait QUE afficher (à-priori des séries de objet.afficher(), comme carte.afficher(), joueur.afficher(), barre_inventaire.afficher() etc…). joueur.sauter() ou joueur.accroupir() ne font que modifier l'état du joueur mais ne modifient pas les coordonnées (mais peut-être la vitesse) de l'objet. Elles n'affichent rien non plus. Ces objets auront logiquement chacun leurs coordonnées et éventuellement vitesses.

Maintenant, on peut définir les fonctions/classes que j'ai utilisé qui n'existe pas encore. Et j'utilise la même logique : plutôt que d'écrire tout le code du menu dans jeu.menu(), j'invente des fonctions/méthodes/classes qui me permettront d'avoir un code clair, propre et structuré (comme une classe Bouton, une classe Options, une méthode jeu.sauvegarder(), une méthode jeu.charger_options(nouvelles_options) par exemple). Et je fais ça à chaque fois, jusqu'à définir le code "bas niveau". Concrètement, on arrivera aux pygame.display.blit(), self.x += self.vx et self.objet_actuel = objet_selectionne dans la classe Joueur par exemple.

En gros, je définie le cahier des charges des fonctions/classes en écrivant la logique générale d'abord, puis en allant de plus en plus vers le bas niveau du code. Pour ce genre de jeu où l'organisation n'est pas très difficile à faire, ça marche très bien (enfin, avec moi en tout cas :) ).
Mais pour certains logiciels à développer, c'est loin d'être aussi simple. Parfois, il faut réfléchir pendant plusieurs heures avec un crayon et du papier pour savoir comment résoudre un problème et garder un code évolutif et modulable. Dans ces cas-là, il existe probablement un design pattern (patron de conception) qui répond au problème.

+3 -0

@louk: C'est moi qui ai écrit la fonction ombrage_blocs. La ligne avec les sorted sont en fait un clamp en one-liner, pas clair je te l'accorde :°

Cela permet de faire en sorte que le get_fov_tiles ne retourne pas des blocs à l'autre bout de la map (index négatifs) si le joueur s'y trouve à une extrémité.

Le level = [[' ' for 1 in range(width)] for 1 in range(height)] c'est parce que j'ai donné le code à Folaefolc par Skype, qui pense que les underscores pour mettre en italique (dernière version). La ligne originelle était:

level = [[' ' for _2 in range(width)] for _1 in range(height)]

Mais en fait un simple level = [list(' '*width) for _ in range(height)]. De toute façon cette ligne était juste pour montrer que mon code était valable avec les variables suivant un modèle, ici une ligne imbriquée, Fola la remplacera par le niveau lui même :) .

Je suis totalement d'accord avec ce que tu dis, Fola devrait recommencer le code, mais penses-tu ! Ça doit être difficile au bout de plus de 6 mois maintenant sur le projet…

+2 -0

Si vous voulez je peux vous passer mon architecture POO World.py spécialement faite pour les jeux 2D avec des tiles. Avec des fonctions comme get, set, getSquare (obtenir un chunk pour l'optimisation), gestion de la priorité des updates des blocs, gestion des collisions et tout. Pas sûr que ce soit super bien codé mais si ça peut aider..

@Mizugola: Je veux bien que tu me l'envoie ! J'ai vu quel pattern on pourrait utiliser pour UrWorld et donc je suis en train de créer la structure du jeu. Je pense utiliser le pattern MVC (c'est le seul que je connais :° , merci Dan737), je pourrais peut être voir ton code pour "fusionner" les deux :)

Ce sujet est verrouillé.