oujood.com

Utilisation des images de sprites et des sons pour améliorer le jeu - Tutoriel

Dans ce tutoriel nous allons compléter le jeu vidéo créé dans les parties 1 et 2, par l'ajout des images de sprites pour améliorer l'apparence, l'ajout des effets sonores et régler la vitesse du jeu.

Images de sprites pour améliorer votre jeu

À la fin de la seconde partie de ce tutoriel nous avons obtenu un jeu, mais soyons honnêtes... Il est plutôt laid. Le joueur et les ennemis ne sont que des blocs blancs sur un fond noir. C'était le top du top à la naissance de Pong, mais ce n'est plus du tout le cas aujourd'hui.
Donc remplaçons tous ces rectangles blancs ennuyeux et moches par des images plus cool qui donneront au jeu l'impression d'être un vrai jeu.

Plus tôt dans nos cours et tutoriels, nous avons appris que les images peuvent être chargées dans une Surface à l'aide du module image.
Dans ce tutoriel, nous avons créé un petit jet avec un joueur et des missiles pour les ennemis. Nous vous invitons à utiliser ces images, à dessiner les vôtres ou à télécharger des images gratuites pour le jeu. Vous pouvez cliquer sur le lien ci-dessous pour télécharger les illustrations utilisées dans ce tutoriel :

Télécharger les illustrations

Modification des constructeurs d'objets

Avant d'utiliser des images pour représenter les sprites du joueur et de l'ennemi, nous devons modifier leurs constructeurs. Le code ci-dessous remplace le code utilisé précédemment :

Exemple :       Copier le code

# Importer pygame.locals pour faciliter l'accès aux coordonnées clés
# Mise à jour pour se conformer aux standards flake8 et black
# from pygame.locals import *
from pygame.locals import (
    RLEACCEL,
    K_UP,
    K_DOWN,
    K_LEFT,
    K_RIGHT,
    K_ESCAPE,
    KEYDOWN,
    QUIT,
)

# Définir des constantes pour la largeur et la hauteur de l'écran
LARGEUR_ECRAN = 800
HAUTEUR_ECRAN = 600


# Définir l'objet Player en étendant pygame.sprite.Sprite
# Au lieu d'une surface, utilisez une image pour un meilleur sprite
class Joueur(pygame.sprite.Sprite):
    def __init__(self):
        super(Joueur, self).__init__()
        self.surf = pygame.image.load("chemin-vers-le-dossier-dans-votre-pc/avion.png").convert()
        self.surf.set_colorkey((255, 255, 255), RLEACCEL)
        self.rect = self.surf.get_rect()

Décortiquons un peu le code:
L'appel à la méthode pygame.image.load() charge une image depuis le disque. Nous lui passons un chemin vers le fichier. Il renvoie une Surface.
Et l'appel à la méthode .convert() optimise la Surface, rendant les futurs appels à .blit() plus rapides.

L'utilisation de .set_colorkey() pour indiquer la couleur que pygame rendra transparente. Dans ce cas, nous choisissons le blanc, car c'est la couleur de fond de l'image du jet. La constante RLEACCEL est un paramètre optionnel qui aide pygame à effectuer un rendu plus rapide sur les écrans non accélérés. Elle est ajoutée à la déclaration d'importation de pygame.locals.
Rien d'autre n'a besoin d'être modifié. L'image est toujours une Surface, sauf que maintenant elle a une image peinte dessus. Vous continuez à l'utiliser de la même manière.
Le code suivat montre des changements similaires pour l'ennemi :

Exemple :       Copier le code

# Définir l'objet ennemi en étendant pygame.sprite.Sprite
# Au lieu d'une surface, utilisez une image pour un meilleur sprite
class Ennemi(pygame.sprite.Sprite):
    def __init__(self):
        super(Ennemi, self).__init__()
        self.surf = pygame.image.load("chemin-vers-le-dossier-dans-votre-pc/missile.png").convert()
        self.surf.set_colorkey((255, 255, 255), RLEACCEL)
        # La position de départ est générée aléatoirement, tout comme la vitesse
        self.rect = self.surf.get_rect(
            center=(
                random.randint(LARGEUR_ECRAN + 20, LARGEUR_ECRAN + 100),
                random.randint(0, HAUTEUR_ECRAN),
            )
        )
        self.vitesse = random.randint(1, 5)

L'exécution du programme devrait montrer qu'il s'agit du même jeu que celui que nous avions auparavant, à ceci près que nous avons ajouté de jolis skins graphiques avec des images. Mais pourquoi se contenter de rendre seulement les sprites du joueur et de l'ennemi agréables à regarder ? Ajoutons aussi quelques nuages qui passent pour donner l'impression d'un jet volant dans le ciel.

Ajout d'images d'arrière-plan

Pour les nuages d'arrière-plan, vous utilisez les mêmes principes que pour le joueur et l'ennemi :

Création de la classe Nuage.
Ajoutons-y l'image d'un nuage.
Créons une méthode .update() qui déplace le nuage vers le côté gauche de l'écran.
Créons aussi un événement et un gestionnaire personnalisés pour créer de nouveaux objets nuage à un intervalle de temps donné. Ajoutons les objets nuages nouvellement créés à un nouveau groupe appelé clouds (nuages).
Mettons à jour et dessinons les nuages dans la boucle du jeu.
Voici à quoi ressemble un nuage :

Exemple :       Copier le code

# Définir l'objet nuage en étendant pygame.sprite.Sprite
# Utiliser une image pour un sprite plus beau
class Cloud(pygame.sprite.Sprite) :
    def __init__(self) :
        super(Cloud, self).__init__()
        self.surf = pygame.image.load("chemin-vers-le-dossier-dans-votre-pc/nuage.png").convert()
        self.surf.set_colorkey((0, 0, 0), RLEACCEL)
        # La position de départ est générée aléatoirement
        self.rect = self.surf.get_rect(
            center=(
                random.randint(LARGEUR_ECRAN  + 20, LARGEUR_ECRAN  + 100),
                random.randint(0, HAUTEUR_ECRAN),
            )
        )

    # Déplacer le nuage en fonction d'une vitesse constante
    # Retire le nuage lorsqu'il passe le bord gauche de l'écran
    def update(self) :
        self.rect.move_ip(-2, 0)
        if self.rect.right < 0 :
            self.kill()

Tout cela devrait nous sembler très familier. C'est à peu près la même chose que pour la classe Ennemi.

Pour faire apparaître les nuages à certains intervalles, nous allons utiliser un code de création d'événement similaire à celui que nous avons utilisé pour créer de nouveaux ennemis. Plaçons-le juste en dessous de l'événement de création d'ennemi :

Exemple :       Copier le code

# Créer des événements personnalisés pour ajouter un nouvel ennemi et un nouvel nuage
ADDENEMY = pygame.USEREVENT + 1
pygame.time.set_timer(ADDENEMY, 250)
ADDCLOUD = pygame.USEREVENT + 2
pygame.time.set_timer(ADDCLOUD, 500)

Cela signifie qu'il faut attendre 500 millisecondes, soit une demi seconde, avant de créer le nuage suivant.
Ensuite, créons un nouveau groupe pour contenir chaque nuage nouvellement créé :

Exemple :       Copier le code

# Créer des groupes pour contenir les sprites ennemis, les sprites de nuages et tous les sprites
# - ennemis est utilisé pour la détection des collisions et les mises à jour de position
# - nuages est utilisé pour les mises à jour de position
# - all_sprites est utilisé pour le rendu
ennemis = pygame.sprite.Group()
all_sprites = pygame.sprite.Group()
nuages = pygame.sprite.Group()
all_sprites = pygame.sprite.Group()
all_sprites.add( joueur)

Ensuite, ajoutez un gestionnaire pour le nouvel événement ADDCLOUD dans le gestionnaire d'événements :

Exemple :       Copier le code

# Boucle principale
while running :
    # Regarde chaque événement dans la file d'attente
    pour event dans pygame.event.get() :
        # L'utilisateur a-t-il appuyé sur une touche ?
        if event.type == KEYDOWN :
            # Était-ce la touche Escape ? Si c'est le cas, arrêtez la boucle.
            if event.key == K_ESCAPE :
                running = False

        # L'utilisateur a-t-il cliqué sur le bouton de fermeture de la fenêtre ? Si c'est le cas, arrêtez la boucle.
        elif event.type == QUIT :
            running = False

        # Ajouter un nouvel ennemi ?
        elif event.type == ADDENEMY :
            # Créer le nouvel ennemi et l'ajouter aux groupes de sprites
            new_enemy = Enemy()
            enemies.add(new_enemy)
            all_sprites.add(new_enemy)

        # Ajouter un nouveau nuage ?
        elif event.type == ADDCLOUD :
            # Créer le nouveau nuage et l'ajouter aux groupes de sprites
            nouveau_nuage = Cloud()
            nuages.add(nouveau_nuage)
            all_sprites.add( nouveau_nuage)

Enfin, assurez-vous que les nuages sont mis à jour à chaque image :

Exemple :       Copier le code

# Mettre à jour la position des ennemis et des nuages
ennemis.update()
nuages.update()

# Remplir l'écran de bleu ciel
screen.fill((135, 206, 250))

On met à jour le screen.fill() original pour remplir l'écran d'une agréable couleur bleu ciel. N'hésitez pas à changer cette couleur pour quelque chose d'autre. Peut-être voulez-vous un monde extraterrestre avec un ciel violet, un terrain vague toxique en vert néon, ou la surface de Mars en rouge !

Notez que chaque nouveau nuage et chaque nouvel ennemi sont ajoutés à all_sprites ainsi qu'aux nuages et aux ennemis. Cela s'explique par le fait que chaque groupe est utilisé dans un but différent :

Le rendu est effectué à l'aide de all_sprites.
Les mises à jour de position sont effectuées à l'aide des nuages et des ennemis.
La détection des collisions se fait à l'aide des ennemis.
Nous créons plusieurs groupes afin de pouvoir modifier la façon dont les sprites se déplacent ou se comportent sans affecter le mouvement ou le comportement des autres sprites.

Régler la vitesse du jeu

En testant le jeu, vous avez peut-être remarqué que les ennemis se déplacent un peu vite. Si ce n'est pas le cas, ce n'est pas grave, car les résultats varieront d'une machine à l'autre.

La raison en est que la boucle du jeu traite les images aussi vite que le processeur et l'environnement le permettent. Comme tous les sprites se déplacent une fois par image, ils peuvent se déplacer des centaines de fois par seconde. Le nombre d'images traitées par seconde s'appelle le taux de rafraîchissement, et un bon réglage fait la différence entre un jeu jouable et un jeu oubliable.

Normalement, vous voulez une fréquence d'images aussi élevée que possible, mais pour ce jeu, vous devez la ralentir un peu pour que le jeu soit jouable. Heureusement, le module time contient une horloge qui est conçue exactement à cette fin.

L'utilisation de Clock pour établir une fréquence d'images jouable ne nécessite que deux lignes de code. La première crée une nouvelle horloge avant que la boucle du jeu ne commence :

Exemple :       Copier le code

# Régler l'horloge pour un framerate adéquat
clock = pygame.time.Clock()

La seconde appelle .tick() pour informer pygame que le programme a atteint la fin de l'image :

Exemple :       Copier le code

# Afficher le tout à l'écran
pygame.display.flip()

# S'assurer que le programme maintient un taux de 30 images par seconde
clock.tick(30)

L'argument transmis à .tick() détermine la fréquence d'images souhaitée. Pour ce faire, .tick() calcule le nombre de millisecondes que chaque image doit prendre, en fonction de la fréquence d'images souhaitée. Il compare ensuite ce nombre au nombre de millisecondes qui se sont écoulées depuis le dernier appel à .tick(). Si le temps écoulé est insuffisant, .tick() retarde le traitement pour s'assurer qu'il ne dépasse jamais la fréquence d'images spécifiée.

Une fréquence d'images plus faible laisse plus de temps à chaque image pour les calculs, tandis qu'une fréquence d'images plus élevée permet un jeu plus fluide (et éventuellement plus rapide) :

N’hésitez pas à changer ce chiffre pour voir ce qui vous convient le mieux !

Effets sonores

Jusqu'à présent, nous nous sommes concentrés sur le gameplay et les aspects visuels de notre jeu. Maintenant, explorons comment ajouter du son à notre jeu. pygame fournit le module de mixage 'mixer' pour gérer toutes les activités liées au son. Nous utiliserons les classes et les méthodes de ce module pour fournir de la musique de fond et des effets sonores pour diverses actions.

Le nom mixer fait référence au fait que le module mélange différents sons en un tout cohérent. Le sous-module Musique (Music) nous permet de diffuser des fichiers sonores individuels dans une variété de formats, y compris MP3, Ogg et Mod. Nous pouvons également utiliser Sound pour enregistrer et jouer un effet sonore unique, au format Ogg ou WAV non compressé. La lecture se fait en arrière-plan, donc lorsque nous jouons un son, la méthode sera renvoyée dès que le son aura été joué.

Note : La documentation de pygame indique que la prise en charge des MP3 est limitée et que les formats non pris en charge peuvent entraîner un plantage du système. Les sons référencés dans cet article ont été testés, et nous vous recommandons de tester tous les sons avant de publier votre jeu.

Comme pour la plupart des choses dans Pygame, l'utilisation du mixeur commence par une étape d'initialisation. Heureusement, cette étape est déjà réalisée par pygame.init(). Nous n'avons besoin d'appeler pygame.mixer.init() que si nous voulons changer les paramètres par défaut :

Exemple :       Copier le code

# Configuration pour les sons. Les valeurs par défaut sont bonnes.
pygame.mixer.init()

# Initialiser pygame
pygame.init()

# Configurer l'horloge pour un framerate décent
clock = pygame.time.Clock()

pygame.mixer.init() accepte un certain nombre d'arguments, mais les valeurs par défaut fonctionnent bien dans la plupart des cas. Notez que si vous voulez changer les valeurs par défaut, vous devez appeler pygame.mixer.init() avant d'appeler pygame.init(). Sinon, les valeurs par défaut resteront inchangées.

Une fois le système initialisé, nous pouvons configurer les sons et la musique d'ambiance :

Voici le code à placer just avant la boucle principale du jeu :

Exemple :       Copier le code

# Chargement et lecture de la musique de fond
# Source du son : http://ccmixter.org/files/Apoxode/59262
# Licence : https://creativecommons.org/licenses/by/3.0/
pygame.mixer.music.load("Apoxode_-_Electric_1.mp3")
pygame.mixer.music.play(loops=-1)

# Chargement de tous les fichiers son
# Sources sonores : Jon Fincher
move_up_sound = pygame.mixer.Sound("Rising_putter.ogg")
move_down_sound = pygame.mixer.Sound("Falling_putter.ogg")
collision_sound = pygame.mixer.Sound("Collision.ogg")

Les lignes :

pygame.mixer.music.load("Apoxode_-_Electric_1.mp3")
pygame.mixer.music.play(loops=-1)

chargent un clip sonore d'arrière-plan et commencent à le jouer. Nous pouvons demander au clip sonore de tourner en boucle et de ne jamais se terminer en définissant le paramètre nommé loops=-1.

Les lignes:

move_up_sound = pygame.mixer.Sound("Rising_putter.ogg")
move_down_sound = pygame.mixer.Sound("Falling_putter.ogg")
collision_sound = pygame.mixer.Sound("Collision.ogg")

Chargent trois sons que nous utiliserons pour divers effets sonores. Les deux premiers sont des sons ascendants et descendants, qui sont joués lorsque le joueur se déplace vers le haut ou vers le bas. Le dernier est le son utilisé lorsqu'il y a une collision. Nous pouvons également ajouter d'autres sons, comme un son pour chaque fois qu'un ennemi est créé, ou un son final pour la fin du jeu.

Comment utiliser les effets sonores ? Nous voulons que chaque son soit joué lorsqu'un certain événement se produit. Par exemple, lorsque le navire se déplace vers le haut, nous voulons jouer le son move_up_sound. Par conséquent, vous ajoutez un appel à .play() chaque fois que nous gérons cet événement. Dans la conception, cela signifie ajouter les appels suivants à .update() pour Player :

Exemple :       Copier le code

# Définir l'objet Player en étendant pygame.sprite.Sprite
# Au lieu d'une surface, utilisez une image pour un meilleur sprite
class Player(pygame.sprite.Sprite) :
    def __init__(self) :
        super(Player, self).__init__()
        self.surf = pygame.image.load("jet.png").convert()
        self.surf.set_colorkey((255, 255, 255), RLEACCEL)
        self.rect = self.surf.get_rect()

    # Déplacer le sprite en fonction des touches pressées
    def update(self, pressed_keys) :
        if pressed_keys[K_UP] :
            self.rect.move_ip(0, -5)
            move_up_sound.play()
        if pressed_keys[K_DOWN] : self.rect.move_ip(0, -5) move_up_sound.play()
            self.rect.move_ip(0, 5)
            move_down_sound.play()

Pour une collision entre le joueur et un ennemi, nous jouons le son correspondant à la détection des collisions :

Exemple :       Copier le code

    # Vérifier si des ennemis ont collisionné avec le joueur
    if pygame.sprite.spritecollideany(joueur, ennemis):
    # Si c'est le cas, supprimer le joueur et arrêter la boucle
        joueur.kill()

    # Arrête les sons de mouvement et joue le son de collision
    move_up_sound.stop()
    move_down_sound.stop()
    collision_sound.play()

    # Arrêter la boucle
    running = False

Ici, nous arrêtons d'abord tous les autres effets sonores, car lors d'une collision, le joueur ne se déplace plus. Ensuite, nous jouons le son de la collision et continuons l'exécution à partir de là.

Enfin, lorsque le jeu est terminé, tous les sons doivent s'arrêter. Ceci est vrai que le jeu se termine à cause d'une collision ou que l'utilisateur quitte le jeu manuellement. Pour ce faire, ajoutons les lignes suivantes à la fin du programme, après la boucle :

Exemple :       Copier le code

# Tout! Arrêter et quitter le mixeur.
pygame.mixer.music.stop()
pygame.mixer.quit()

Techniquement, ces dernières lignes ne sont pas nécessaires, puisque le programme se termine juste après. Cependant, si nous décidons plus tard d'ajouter un écran d'introduction ou un écran de sortie à notre jeu, il se peut qu'il y ait plus de code qui s'exécute après la fin du jeu.

Conclusion
Tout au long de ce tutoriel, nous avons appris comment la programmation de jeux avec pygame diffère de la programmation procédurale standard. Nous avons également appris à :

  • Implémenter des boucles d'événements
  • Dessiner des objets à l'écran
  • Jouer des effets sonores et de la musique
  • Gérer les entrées de l'utilisateur

Pour ce faire, nous avons utilisé un sous-ensemble de modules pygame, notamment les modules display, mixer and music, time, image, event et key. Nous avons également utilisé plusieurs classes pygame, notamment Rect, Surface, Sound et Sprite. Mais cela ne fait qu'effleurer la surface de ce que pygame peut faire ! Consultez la documentation officielle de pygame pour une liste complète des modules et classes disponibles.

Vous pouvez trouver tous les fichiers de code, de graphiques et de sons pour cet article en cliquant sur le lien ci-dessous :
Exemple de code : Télécharger le code source du projet PyGame utilisé dans ce tutoriel.




Voir aussi nos tutoriel :

fonction ltrim

Supprime les espaces (ou d'autres caractères) de d but de chaîne

:hover

Ajoute un style à un élément lorsque vous passez la souris dessus

fonction get_html_translation_table, get_html_translation_table

Retourne la table de traduction des entités utilisée par htmlspecialchars et htmlentities