Une introduction au framework de jeu Python – Real Python

By | janvier 15, 2020

Cours Python en ligne

Les jeux informatiques sont un excellent moyen d'initier les gens au codage et à l'informatique. Depuis que j'étais un joueur dans ma jeunesse, l'attrait d'écrire des jeux vidéo a été la raison pour laquelle j'ai appris à coder. Bien sûr, quand j'ai appris Python, mon premier réflexe a été d'écrire un jeu Python.

Alors que Python rend l'apprentissage du code plus accessible à tous, les choix pour l'écriture de jeux vidéo peuvent être limités, surtout si vous voulez écrire jeux d'arcade avec de superbes graphismes et des effets sonores accrocheurs. Pendant de nombreuses années, les programmeurs de jeux Python se sont limités à pygame cadre. Maintenant, il y a un autre choix.

le arcade La bibliothèque est un framework Python moderne pour créer des jeux avec des graphismes et des sons convaincants. Orienté objet et construit pour Python 3.6 et supérieur, arcade fournit au programmeur un ensemble moderne d'outils pour créer de grandes expériences de jeu Python.

Dans ce didacticiel, vous apprendrez à:

  • Installer le arcade bibliothèque
  • Dessiner éléments à l'écran
  • Travail avec le arcade Boucle de jeu Python
  • Gérer éléments graphiques à l'écran
  • Manipuler entrée utilisateur
  • Jouer effets sonores et musique
  • Décris comment la programmation de jeux Python avec arcade diffère de pygame

Ce tutoriel suppose que vous avez une compréhension de l'écriture de programmes Python. Puisque arcade est une bibliothèque orientée objet, vous devez également être familiarisé avec la programmation orientée objet. Tous les codes, images et sons de ce didacticiel sont disponibles en téléchargement sur le lien ci-dessous:

Contexte et configuration

le arcade bibliothèque a été écrite par Paul Vincent Craven, professeur d'informatique au Simpson College dans l'Iowa, aux États-Unis. Comme il est construit au-dessus du pyglet fenêtrage et médiathèque, arcade propose diverses améliorations, modernisations et améliorations par rapport à pygame:

  • Présente des graphismes OpenGL modernes
  • Prend en charge les indications de type Python 3
  • A un meilleur support pour les sprites animés
  • Intègre des noms de commandes, de fonctions et de paramètres cohérents
  • Encourage la séparation de la logique du jeu du code d'affichage
  • Nécessite moins de code passe-partout
  • Maintient plus de documentation, y compris des exemples de jeux Python complets
  • Dispose d'un moteur physique intégré pour les jeux de plateforme

À installer arcade et ses dépendances, utilisez les pépin commander:

$ python -m pip install arcade

Sur Mac, vous devez également installer PyObjC:

$ python -m pip installe l'arcade PyObjC

Des instructions d'installation complètes basées sur votre plate-forme sont disponibles pour Windows, Mac, Linux et même Raspberry Pi. Vous pouvez même installer arcade directement depuis la source si vous préférez.

Ce didacticiel suppose que vous utilisez arcade 2.1 et Python 3.7 partout.

De base arcade Programme

Avant de creuser, examinons un arcade programme qui ouvrira une fenêtre, la remplira de blanc et dessinera un cercle bleu au milieu:

    1 # Programme d'arcade de base
    2 # Affiche une fenêtre blanche avec un cercle bleu au milieu
    3 
    4 # Importations
    5 importation arcade
    6 
    sept # Constantes
    8 SCREEN_WIDTH = 600
    9 SCREEN_HEIGHT = 800
dix SCREEN_TITLE = "Bienvenue dans Arcade"
11 RAYON = 150
12 
13 # Ouvrez la fenêtre
14 arcade.fenêtre ouverte(SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_TITLE)
15 
16 # Définissez la couleur d'arrière-plan
17 arcade.set_background_color(arcade.Couleur.BLANC)
18 
19 # Videz l'écran et commencez à dessiner
20 arcade.start_render()
21 
22 # Dessinez un cercle bleu
23 arcade.draw_circle_filled(
24     SCREEN_WIDTH / 2, SCREEN_HEIGHT / 2, RAYON, arcade.Couleur.BLEU
25 )
26 
27 # Terminer le dessin
28 arcade.finish_render()
29 
30 # Tout afficher
31 arcade.courir()

Lorsque vous exécutez ce programme, vous verrez une fenêtre qui ressemble à ceci:

Un programme de base utilisant la bibliothèque d'arcade.

Décomposons cette ligne par ligne:

  • Ligne 5 importe le arcade bibliothèque. Sans cela, rien d'autre ne fonctionne.
  • Lignes 8 à 11 définissez certaines constantes que vous utiliserez un peu plus tard, pour plus de clarté.
  • Ligne 14 ouvre la fenêtre principale. Vous fournissez la largeur, la hauteur et le texte de la barre de titre, et arcade fait le reste.
  • Ligne 17 définit la couleur d'arrière-plan à l'aide d'une constante de la arcade.color paquet. Vous pouvez également spécifier une couleur RVB à l'aide d'une liste ou d'un tuple.
  • Ligne 20 ensembles arcade en mode dessin. Tout ce que vous dessinez après cette ligne sera affiché à l'écran.
  • Lignes 23 à 25 dessinez le cercle en fournissant les coordonnées centrales X et Y, le rayon et la couleur à utiliser.
  • Ligne 28 termine le mode dessin.
  • Ligne 31 affiche votre fenêtre pour que vous puissiez la voir.

Si vous connaissez pygame, vous remarquerez que certaines choses sont différentes:

  • Il n'y a pas pygame.init (). Toute l'initialisation est gérée lorsque vous exécutez importation d'arcade.
  • Il n'y a pas de boucle d'affichage explicitement définie. Il est géré en arcade.run ().
  • Il n'y a pas de boucle d'événement ici non plus. Encore, arcade.run () gère les événements et fournit certains comportements par défaut, tels que la possibilité de fermer la fenêtre.
  • Vous pouvez utiliser des couleurs prédéfinies pour le dessin plutôt que de les définir vous-même.
  • Vous devez commencer et terminer le dessin dans l'arcade en utilisant start_render () et finish_render ().

Examinons de près les principes fondamentaux arcade concepts derrière ce programme.

arcade Concepts

Comme pygame, arcade le code s'exécute sur presque toutes les plateformes prenant en charge Python. Cela nécessite arcade pour traiter les abstractions pour diverses différences matérielles sur ces plates-formes. Comprendre ces concepts et abstractions vous aidera à concevoir et à développer vos propres jeux tout en comprenant comment arcade diffère de pygame vous aidera à vous adapter à son point de vue unique.

Initialisation

Puisqu'il traite d'une variété de plateformes, arcade doit effectuer une étape d'initialisation avant de pouvoir l'utiliser. Cette étape est automatique et se produit chaque fois que vous importez arcade, il n'y a donc pas de code supplémentaire à écrire. Lorsque vous l'importez, arcade fait ce qui suit:

  • Vérifier que vous exécutez sur Python 3.6 ou supérieur.
  • Importation le pyglet_ffmeg2 bibliothèque pour la gestion du son, si elle est disponible.
  • Importation le pyglet bibliothèque pour la gestion des fenêtres et du multimédia.
  • Installer constantes pour les couleurs et les mappages de touches.
  • Importation le reste arcade bibliothèque.

Comparez cela avec pygame, ce qui nécessite une étape d'initialisation distincte pour chaque module.

Fenêtres et coordonnées

Tout en arcade se passe dans une fenêtre, avec vous créez en utilisant fenêtre ouverte(). Actuellement, arcade ne prend en charge qu'une seule fenêtre d'affichage. Vous pouvez rendre la fenêtre redimensionnable lorsque vous l'ouvrez.

arcade utilise le même système de coordonnées cartésiennes que vous avez peut-être appris en classe d'algèbre. La fenêtre vit dans le quadrant I, avec le point d'origine (0, 0) situé dans le coin inférieur gauche de l'écran. La coordonnée x augmente lorsque vous vous déplacez vers la droite et la coordonnée y augmente lorsque vous vous déplacez vers le haut:

La disposition d'une fenêtre d'arcade.

Il est important de noter que ce comportement est le contraire de pygame et de nombreux autres frameworks de jeu Python. Il vous faudra peut-être un certain temps pour vous adapter à cette différence.

Dessin

Hors de la boîte, arcade a des fonctions pour dessiner diverses formes géométriques, notamment:

  • Arcs
  • Cercles
  • Ellipses
  • Lignes
  • Paraboles
  • Points
  • Des polygones
  • Rectangles
  • Triangles

Toutes les fonctions de dessin commencent par dessiner_ et suivez un modèle de nom et de paramètres cohérent. Il existe différentes fonctions pour dessiner des formes remplies et délimitées:

Quelques exemples de formes dessinées avec arcade

Les rectangles étant courants, il existe trois fonctions distinctes pour les dessiner de différentes manières:

  • draw_rectangle () attend les coordonnées x et y du centre du rectangle, la largeur et la hauteur.
  • draw_lrtb_rectangle () attend les coordonnées x gauche et droite, suivies des coordonnées y supérieure et inférieure.
  • draw_xywh_rectangle () utilise les coordonnées x et y du coin inférieur gauche, suivies de la largeur et de la hauteur.

Notez que chaque fonction nécessite quatre paramètres. Vous pouvez également dessiner chaque forme à l'aide des fonctions de dessin tamponnées, qui utilisent des tampons de sommet pour tout pousser directement sur la carte graphique pour des améliorations de performances incroyables. Toutes les fonctions de dessin tamponnées commencent par créer_ et suivre des modèles de noms et de paramètres cohérents.

Conception orientée objet

En son coeur, arcade est une bibliothèque orientée objet. Comme pygame, tu peux écrire arcade code de procédure, comme vous l'avez fait dans l'exemple ci-dessus. Cependant, le vrai pouvoir de arcade s'affiche lorsque vous créez complètement programmes orientés objet.

Quand tu as appelé arcade.open_window () dans l'exemple ci-dessus, le code crée un arcade.Window objet dans les coulisses pour gérer cette fenêtre. Plus tard, vous créerez votre propre classe basée sur arcade.Window pour écrire un jeu Python complet.

Tout d'abord, jetez un œil à l'exemple de code d'origine, qui utilise désormais des concepts orientés objet, pour mettre en évidence les principales différences:

    1 # Programme d'arcade de base utilisant des objets
    2 # Affiche une fenêtre blanche avec un cercle bleu au milieu
    3 
    4 # Importations
    5 importation arcade
    6 
    sept # Constantes
    8 SCREEN_WIDTH = 600
    9 SCREEN_HEIGHT = 800
dix SCREEN_TITLE = "Bienvenue dans Arcade"
11 RAYON = 150
12 
13 # Des classes
14 classe Bienvenue(arcade.Fenêtre):
15     "" "Fenêtre d'accueil principale
16                 "" "
17     def __init__(soi):
18         "" "Initialiser la fenêtre
19                                 "" "
20 
21         # Appelle le constructeur de la classe parent
22         super().__init__(SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_TITLE)
23 
24         # Définissez la fenêtre d'arrière-plan
25         arcade.set_background_color(arcade.Couleur.BLANC)
26 
27     def on_draw(soi):
28         "" "Appelé chaque fois que vous avez besoin de dessiner votre fenêtre
29                                 "" "
30 
31         # Videz l'écran et commencez à dessiner
32         arcade.start_render()
33 
34         # Dessinez un cercle bleu
35         arcade.draw_circle_filled(
36             SCREEN_WIDTH / 2, SCREEN_HEIGHT / 2, RAYON, arcade.Couleur.BLEU
37         )
38 
39 # Point d'entrée du code principal
40 si __Nom__ == "__principale__":
41     app = Bienvenue()
42     arcade.courir()

Jetons un œil à ce code ligne par ligne:

  • Lignes 1 à 11 sont les mêmes que l'exemple procédural précédent.

  • Ligne 15 c'est là que les différences commencent. Vous définissez une classe appelée Bienvenue basé sur la classe parent arcade.Window. Cela vous permet de remplacer les méthodes de la classe parente si nécessaire.

  • Lignes 18 à 26 définir les .__ init __ () méthode. Après avoir appelé le parent .__ init __ () méthode utilisant super() pour configurer la fenêtre, vous définissez sa couleur d'arrière-plan, comme vous l'avez fait auparavant.

  • Lignes 28 à 38 définir .on_draw (). Ceci est l'un des nombreux Fenêtre méthodes que vous pouvez remplacer pour personnaliser le comportement de votre arcade programme. Cette méthode est appelée à chaque fois arcade veut dessiner sur la fenêtre. Cela commence par un appel à arcade.start_render (), suivi de tout votre code de dessin. Vous n'avez pas besoin d'appeler arcade.finish_render (), cependant, comme arcade appellera cela implicitement quand .on_draw () prend fin.

  • Lignes 41 à 43 sont le principal point d’entrée de votre code. Après avoir créé un nouveau Bienvenue objet appelé app, tu appelles arcade.run () pour afficher la fenêtre.

Cet exemple orienté objet est la clé pour tirer le meilleur parti de arcade. Vous avez peut-être remarqué une description de .on_draw (). arcade l'appellera chaque fois qu'il souhaite dessiner sur la fenêtre. Alors, comment arcade savoir quand dessiner quelque chose? Voyons les implications de cela.

Boucle de jeu

Toute l'action dans à peu près tous les jeux se déroule dans un boucle de jeu. Vous pouvez même voir des exemples de boucles de jeu dans des jeux physiques comme les dames, Old Maid ou le baseball. La boucle de jeu commence après la configuration et l'initialisation du jeu et se termine lorsque le jeu se termine. Plusieurs choses se produisent séquentiellement à l'intérieur de cette boucle. Au minimum, une boucle de jeu effectue les quatre actions suivantes:

  1. Le programme détermine si le jeu est terminé. Si c'est le cas, la boucle se termine.
  2. L'entrée utilisateur est traité.
  3. Les États des objets de jeu sont mis à jour en fonction de facteurs tels que l'entrée de l'utilisateur ou le temps.
  4. Le jeu affiche des visuels et joue des effets sonores en fonction du nouvel état.

Dans pygame, vous devez configurer et contrôler explicitement cette boucle. Dans arcade, la boucle de jeu Python vous est fournie, encapsulée dans le arcade.run () appel.

Pendant la boucle de jeu intégrée, arcade appelle un ensemble de Fenêtre méthodes pour implémenter toutes les fonctionnalités énumérées ci-dessus. Les noms de ces méthodes commencent tous par sur_ et peut être considéré comme gestionnaires de tâches ou d'événements. Quand le arcade la boucle de jeu doit mettre à jour l'état de tous les objets de jeu Python, elle appelle .on_update (). Lorsqu'il doit vérifier le mouvement de la souris, il appelle .on_mouse_motion ().

Par défaut, aucune de ces méthodes ne fait quoi que ce soit d'utile. Lorsque vous créez votre propre classe basée sur arcade.Window, vous les remplacez si nécessaire pour fournir vos propres fonctionnalités de jeu. Certaines des méthodes fournies sont les suivantes:

  • La saisie au clavier: .on_key_press (), .on_key_release ()
  • Entrée souris: .on_mouse_press (), .on_mouse_release (), .on_mouse_motion ()
  • Mise à jour de l'objet de jeu: .on_update ()
  • Dessin: .on_draw ()

Vous n'avez pas besoin de remplacer toutes ces méthodes, uniquement celles pour lesquelles vous souhaitez fournir un comportement différent. Vous n'avez pas non plus à vous soucier quand on les appelle, juste Que faire quand ils sont appelés. Ensuite, vous découvrirez comment regrouper tous ces concepts pour créer un jeu.

Fondamentaux de la conception de jeux Python

Avant de commencer à écrire du code, c'est toujours une bonne idée d'avoir un design en place. Étant donné que vous allez créer un jeu Python dans ce didacticiel, vous allez également concevoir un gameplay pour celui-ci:

  • Le jeu est un jeu d'évitement ennemi à défilement horizontal.
    • Le joueur commence sur le côté gauche de l'écran.
    • Les ennemies entrez à intervalles réguliers et à des emplacements aléatoires sur la droite.
    • Les ennemies déplacez-vous vers la gauche en ligne droite jusqu'à ce qu'ils soient hors de l'écran.
  • Le joueur peut se déplacer vers la gauche, la droite, le haut ou le bas pour éviter les ennemis.
  • Le joueur ne peut pas quitter l'écran.
  • Le jeu se termine lorsque le joueur est touché par un ennemi ou que l'utilisateur ferme la fenêtre.

Lorsqu'il décrivait des projets logiciels, un de mes anciens collègues a dit un jour: "Vous ne savez pas ce que vous faites tant que vous ne savez pas ce que vous ne faites pas." Dans cet esprit, voici certaines choses que vous ne couvrirez pas. dans ce tutoriel:

  • Pas de vies multiples
  • Pas de tenue de score
  • Aucune capacité d'attaque des joueurs
  • Aucun niveau avancé
  • Pas de personnages «boss»

Vous êtes libre d’essayer d’ajouter ces fonctionnalités et d’autres à votre propre programme.

Importations et constantes

Comme avec tout arcade programme, vous allez commencer par importer la bibliothèque:

    1 # Tireur d'arcade de base
    2 
    3 # Importations
    4 importation arcade
    5 importation Aléatoire
    6 
    sept # Constantes
    8 SCREEN_WIDTH = 800
    9 SCREEN_HEIGHT = 600
dix SCREEN_TITLE = "Arcade Space Shooter"
11 MISE À L'ÉCHELLE = 2.0

En plus de arcade, vous importez également Aléatoire, car vous utiliserez des nombres aléatoires plus tard. Les constantes définissent la taille et le titre de la fenêtre, mais MISE À L'ÉCHELLE? Cette constante est utilisée pour agrandir la fenêtre et les objets du jeu afin de compenser les écrans à haute résolution. Vous le verrez utilisé à deux endroits au fur et à mesure que le didacticiel se poursuit. Vous pouvez modifier cette valeur pour l'adapter à la taille de votre écran.

Classe de fenêtre

Pour profiter pleinement de la arcade Boucle de jeu Python et gestionnaires d'événements, créez une nouvelle classe basée sur arcade.Window:

35 classe SpaceShooter(arcade.Fenêtre):
36     "" "Jeu de défilement latéral Space Shooter
37                 Le joueur démarre à gauche, les ennemis apparaissent à droite
38                 Le joueur peut se déplacer n'importe où, mais pas hors de l'écran
39                 Les ennemis volent vers la gauche à vitesse variable
40                 Les collisions mettent fin au jeu
41                 "" "
42 
43     def __init__(soi, largeur, la taille, Titre):
44         "" "Initialiser le jeu
45                                 "" "
46         super().__init__(largeur, la taille, Titre)
47 
48         # Configurer les listes de sprites vides
49         soi.ennemis_list = arcade.SpriteList()
50         soi.clouds_list = arcade.SpriteList()
51         soi.all_sprites = arcade.SpriteList()

Votre nouvelle classe démarre exactement comme l'exemple orienté objet ci-dessus. À la ligne 43, vous définissez votre constructeur, qui prend la largeur, la hauteur et le titre de la fenêtre de jeu, et utilisez super() de les transmettre au parent. Ensuite, vous initialisez des listes de sprites vides sur les lignes 49 à 51. Dans la section suivante, vous en apprendrez plus sur les sprites et les listes de sprites.

Sprites et listes de sprites

La conception de votre jeu Python nécessite un seul joueur qui commence à gauche et peut se déplacer librement autour de la fenêtre. Il appelle également des ennemis (en d'autres termes, plus d'un) qui apparaissent au hasard sur la droite et se déplacent vers la gauche. Bien que vous puissiez utiliser le dessiner_ commandes pour attirer le joueur et chaque ennemi, il deviendrait rapidement difficile de garder tout droit.

Au lieu de cela, la plupart des jeux modernes utilisent des sprites pour représenter des objets à l'écran. Essentiellement, un lutin est une image en deux dimensions d'un objet de jeu avec une taille définie qui est dessinée à une position spécifique sur l'écran. Dans arcade, les sprites sont des objets de classe arcade.Sprite, et vous les utiliserez pour représenter votre joueur ainsi que les ennemis. Vous allez même ajouter quelques nuages ​​pour rendre l'arrière-plan plus intéressant.

La gestion de tous ces sprites peut être un défi. Vous allez créer un sprite solo, mais vous créerez également de nombreux ennemis et sprites cloud. Garder une trace de tous est un travail pour une liste de sprites. Si vous comprenez comment fonctionnent les listes Python, vous avez les outils à utiliser arcadeListes de sprites. Listes de sprites faites plus que simplement vous accrocher à tous les sprites. Ils permettent trois comportements importants:

  1. Vous pouvez mise à jour tous les sprites de la liste avec un seul appel à SpriteList.update ().
  2. Vous pouvez dessiner tous les sprites de la liste avec un seul appel à SpriteList.draw ().
  3. Vous pouvez vérifier si une seule image-objet est entrée en collision avec une image-objet de la liste.

Vous vous demandez peut-être pourquoi vous avez besoin de trois listes de sprites différentes si vous avez seulement besoin de gérer plusieurs ennemis et nuages. La raison en est que chacune des trois listes de sprites différentes existe parce que vous les utilisez à trois fins différentes:

  1. Tu utilises .enemies_list pour mettre à jour les positions ennemies et vérifier les collisions.
  2. Tu utilises .clouds_list pour mettre à jour les positions du cloud.
  3. Enfin, vous utilisez .all_sprites pour tout dessiner.

Maintenant, une liste n'est plus utile que les données qu'elle contient. Voici comment remplir vos listes de sprites:

53 def installer(soi):
54     "" "Préparez le jeu à jouer
55                 "" "
56 
57     # Définissez la couleur d'arrière-plan
58     arcade.set_background_color(arcade.Couleur.BLEU CIEL)
59 
60     # Configurer le lecteur
61     soi.joueur = arcade.Lutin("images / jet.png", MISE À L'ÉCHELLE)
62     soi.joueur.center_y = soi.la taille / 2
63     soi.joueur.la gauche = dix
64     soi.all_sprites.ajouter(soi.joueur)

Vous définissez .installer() pour initialiser le jeu à un point de départ connu. Bien que vous puissiez le faire dans .__ init __ (), ayant un .installer() est utile.

Imaginez que vous souhaitiez que votre jeu Python ait plusieurs niveaux ou que votre joueur ait plusieurs vies. Plutôt que de redémarrer tout le jeu en appelant .__ init __ (), tu appelles .installer() à la place, pour réinitialiser le jeu à un point de départ connu ou configurer un nouveau niveau. Même si ce jeu Python ne possède pas ces fonctionnalités, la configuration de la structure permet de les ajouter plus rapidement.

Après avoir défini la couleur d'arrière-plan sur la ligne 58, vous définissez ensuite l'image-objet du joueur:

  • Ligne 61 crée un nouveau arcade.Sprite en spécifiant l'image à afficher et le facteur d'échelle. C'est une bonne idée d'organiser vos images dans un seul sous-dossier, en particulier sur des projets plus importants.

  • Ligne 62 définit la position y de l'image-objet sur la moitié de la hauteur de la fenêtre.

  • Ligne 63 définit la position x de l'image-objet en plaçant le bord gauche à quelques pixels du bord gauche de la fenêtre.

  • Ligne 64 utilise enfin .ajouter() pour ajouter le sprite à la .all_sprites liste que vous utiliserez pour dessiner.

Les lignes 62 et 63 montrent deux façons différentes de positionner votre sprite. Examinons de plus près toutes les options de positionnement de sprite disponibles.

Positionnement des sprites

Tous les sprites dans arcade avoir une taille et une position spécifiques dans la fenêtre:

  • La taille, spécifié par Sprite.width et Sprite.height, est déterminé par le graphique utilisé lors de la création de l'image-objet.
  • La position est initialement défini pour avoir le centre du sprite, spécifié par Sprite.center_x et Sprite.center_y, à (0,0) dans la fenêtre.

Une fois la .center_x et .center_y les coordonnées sont connues, arcade peut utiliser la taille pour calculer la Sprite.left, Sprite.right, Sprite.top, et Sprite.bottom bords aussi.

Cela fonctionne également en sens inverse. Par exemple, si vous définissez Sprite.left à une valeur donnée, puis arcade recalculera également les attributs de position restants. Vous pouvez utiliser n'importe lequel d'entre eux pour localiser le sprite ou le déplacer dans la fenêtre. C’est une caractéristique extrêmement utile et puissante arcade sprites. Si vous les utilisez, votre jeu Python nécessitera moins de code que pygame:

Jeu tutoriel d'arcade avec juste un joueur

Maintenant que vous avez défini l'image-objet du joueur, vous pouvez travailler sur les images-objets ennemies. La conception vous demande de faire apparaître les sprites ennemis à intervalles réguliers. Comment peux-tu faire ça?

Fonctions de planification

arcade.schedule () est conçu exactement à cet effet. Il faut deux arguments:

  1. Le nom de la fonction à appeler
  2. L'intervalle de temps attendre entre chaque appel, en quelques secondes

Puisque vous voulez que les ennemis et les nuages ​​apparaissent tout au long du jeu, vous configurez une fonction planifiée pour créer de nouveaux ennemis et une seconde pour créer de nouveaux nuages. Ce code entre dans .installer(). Voici à quoi ressemble ce code:

66 # Générez un nouvel ennemi toutes les 0,25 seconde
67 arcade.programme(soi.add_enemy, 0,25)
68 
69 # Créez un nouveau nuage à chaque seconde
70 arcade.programme(soi.add_cloud, 1.0)

Il ne vous reste plus qu'à définir self.add_enemy () et self.add_cloud ().

Ajout d'ennemis

De la conception de votre jeu Python, les ennemis ont trois propriétés clés:

  1. Ils apparaissent à des endroits aléatoires sur le côté droit de la fenêtre.
  2. Ils se déplacent vers la gauche en ligne droite.
  3. Ils disparaissent quand ils sortent de l'écran.

Le code pour créer un sprite ennemi est très similaire au code pour créer le sprite du joueur:

    93 def add_enemy(soi, delta_time: flotte):
    94     "" "Ajoute un nouvel ennemi à l'écran
    95 
    96                 Arguments:
    97                                 delta_time float - Combien de temps s'est écoulé depuis le dernier appel
    98                 "" "
    99 
100     # Tout d'abord, créez le nouveau sprite ennemi
101     ennemi = arcade.Lutin("images / missile.png", MISE À L'ÉCHELLE)
102 
103     # Réglez sa position sur une hauteur aléatoire et hors écran à droite
104     ennemi.la gauche = Aléatoire.randint(soi.largeur, soi.largeur + 80)
105     ennemi.Haut = Aléatoire.randint(dix, soi.la taille - dix)

.add_enemy () prend un seul paramètre, delta_time, qui représente le temps écoulé depuis le dernier appel. Ceci est requis par arcade.schedule ()et bien que vous ne l'utilisiez pas ici, il peut être utile pour les applications qui nécessitent une synchronisation avancée.

Comme avec le sprite du joueur, vous créez d'abord un nouveau arcade.Sprite avec une image et un facteur d'échelle. Vous définissez la position à l'aide .la gauche et .Haut à une position aléatoire quelque part sur l'écran vers la droite:

Plusieurs ennemis apparaissant à l'écran

Cela permet à l'ennemi de se déplacer sur l'écran en douceur, plutôt que d'apparaître simplement sur l'écran. Maintenant, comment faites-vous bouger?

Déplacement des Sprites

Pour déplacer un sprite, vous devez changer sa position pendant la phase de mise à jour de la boucle de jeu. Bien que vous puissiez le faire vous-même, arcade a des fonctionnalités intégrées pour réduire votre charge de travail. Chaque arcade.Sprite a non seulement un ensemble d'attributs de position, mais il a également un ensemble de attributs de mouvement. Chaque fois que le sprite est mis à jour, arcade utilisera les attributs de mouvement pour mettre à jour la position, conférant un mouvement relatif au sprite.

le Sprite.velocity L'attribut est un tuple composé du changement des positions x et y. Vous pouvez également accéder Sprite.change_x et Sprite.change_y directement. Comme mentionné ci-dessus, chaque fois que le sprite est mis à jour, son .position est modifié en fonction de la .rapidité. Tout ce que vous devez faire .add_enemy () est réglé la vitesse:

107 # Réglez sa vitesse sur une vitesse aléatoire vers la gauche
108 ennemi.rapidité = (Aléatoire.randint(-20, -5), 0)
109 
110 # Ajoutez-le à la liste des ennemis
111 soi.ennemis_list.ajouter(ennemi)
112 soi.all_sprites.ajouter(ennemi)

Après avoir défini la vitesse sur une vitesse aléatoire se déplaçant vers la gauche sur la ligne 108, vous ajoutez le nouvel ennemi aux listes appropriées. Lorsque vous appellerez plus tard sprite.update (), arcade s'occupera du reste:

Ennemis volant dans le jeu de tutoriel

Dans la conception de votre jeu Python, les ennemis se déplacent en ligne droite de droite à gauche. Étant donné que vos ennemis se déplacent toujours vers la gauche, une fois qu'ils sont hors de l'écran, ils ne reviennent pas. Ce serait bien si vous pouviez vous débarrasser d'un sprite ennemi hors écran pour libérer de la mémoire et accélérer les mises à jour. Heureusement, arcade vous a couvert.

Suppression des sprites

Parce que vos ennemis se déplacent toujours vers la gauche, leurs positions x deviennent toujours plus petites et leurs positions y sont toujours constantes. Par conséquent, vous pouvez déterminer qu'un ennemi est hors écran lorsque ennemi. droit est plus petit que zéro, qui est le bord gauche de la fenêtre. Une fois que vous avez déterminé que l'ennemi est hors écran, vous appelez emy.remove_from_sprite_lists () de le supprimer de toutes les listes auxquelles il appartient et libérer cet objet de la mémoire:

si ennemi.droite < 0:
    ennemi.remove_from_sprite_lists()

Mais quand effectuez-vous cette vérification? Normalement, cela se produit juste après le déplacement du sprite. Cependant, souvenez-vous de ce qui a été dit .all_enemies liste de sprites:

Tu utilises .enemies_list pour mettre à jour les positions ennemies et vérifier les collisions.

Cela signifie que dans SpaceShooter.on_update (), vous appellerez ennemis_list.update () pour gérer automatiquement le mouvement ennemi, ce qui fait essentiellement ce qui suit:

pour ennemi dans ennemis_list:
    ennemi.mise à jour()

Ce serait bien si vous pouviez ajouter la vérification hors écran directement au emy.update () appelez, et vous pouvez! Rappelles toi, arcade est une bibliothèque orientée objet. Cela signifie que vous pouvez créer vos propres classes en fonction de arcade et remplacez les méthodes que vous souhaitez modifier. Dans ce cas, vous créez une nouvelle classe basée sur arcade.Sprite et remplacer .mise à jour() seulement:

17 classe FlyingSprite(arcade.Lutin):
18     "" "Classe de base pour tous les sprites volants
19                 Les sprites volants incluent les ennemis et les nuages
20                 "" "
21 
22     def mise à jour(soi):
23         "" "Mettre à jour la position du sprite
24                                 Lorsqu'il se déplace hors de l'écran vers la gauche, retirez-le
25                                 "" "
26 
27         # Déplacez le sprite
28         super().mise à jour()
29 
30         # Supprimer si hors de l'écran
31         si soi.droite < 0:
32             soi.remove_from_sprite_lists()

Vous définissez FlyingSprite comme tout ce qui volera dans votre jeu, comme les ennemis et les nuages. Vous remplacez ensuite .mise à jour(), premier appel super (). update () pour traiter la motion correctement. Ensuite, vous effectuez la vérification hors écran.

Étant donné que vous avez une nouvelle classe, vous devrez également apporter une petite modification à .add_enemy ():

def add_enemy(soi, delta_time: flotte):
    "" "Ajoute un nouvel ennemi à l'écran

                Arguments:
                                delta_time float - Combien de temps s'est écoulé depuis le dernier appel
                "" "

    # Tout d'abord, créez le nouveau sprite ennemi
    ennemi = FlyingSprite("images / missile.png", MISE À L'ÉCHELLE)

Plutôt que de créer un nouveau Lutin, vous créez un nouveau FlyingSprite pour profiter de la nouvelle .mise à jour().

Ajout de nuages

Pour rendre votre jeu Python plus attrayant visuellement, vous pouvez ajouter des nuages ​​au ciel. Les nuages ​​volent dans le ciel, tout comme vos ennemis, vous pouvez donc les créer et les déplacer de la même manière.

.add_cloud () suit le même schéma que .add_enemy (), bien que la vitesse aléatoire soit plus lente:

def add_cloud(soi, delta_time: flotte):
    "" "Ajoute un nouveau cloud à l'écran

                Arguments:
                                delta_time float - Combien de temps s'est écoulé depuis le dernier appel
                "" "

    # Tout d'abord, créez le nouveau sprite cloud
    nuage = FlyingSprite("images / cloud.png", MISE À L'ÉCHELLE)

    # Réglez sa position sur une hauteur aléatoire et hors écran à droite
    nuage.la gauche = Aléatoire.randint(soi.largeur, soi.largeur + 80)
    nuage.Haut = Aléatoire.randint(dix, soi.la taille - dix)

    # Réglez sa vitesse sur une vitesse aléatoire vers la gauche
    nuage.rapidité = (Aléatoire.randint(-5, -2), 0)

    # Ajoutez-le à la liste des ennemis
    soi.clouds_list.ajouter(nuage)
    soi.all_sprites.ajouter(nuage)

Les nuages ​​se déplacent plus lentement que les ennemis, vous calculez donc une vitesse aléatoire inférieure sur la ligne 129.

Maintenant, votre jeu Python semble un peu plus complet:

Nuages ​​volant sur la fenêtre de jeu

Vos ennemis et nuages ​​sont créés et se déplacent d'eux-mêmes maintenant. Il est temps de faire bouger le joueur aussi en utilisant le clavier.

La saisie au clavier

le arcade.Window La classe a deux fonctions pour traiter la saisie au clavier. Votre jeu Python appellera .on_key_press () chaque fois qu'une touche est enfoncée et .on_key_release () chaque fois qu'une touche est relâchée. Les deux fonctions acceptent deux paramètres entiers:

  1. symbole représente la touche réelle qui a été enfoncée ou relâchée.
  2. modificateurs indique quels modificateurs étaient en panne. Il s'agit notamment de la Décalage, Ctrl, et Alt clés.

Heureusement, vous n'avez pas besoin de savoir quels entiers représentent quelles clés. le arcade.key Le module contient toutes les constantes clavier que vous pouvez utiliser. Traditionnellement, déplacer un joueur avec le clavier utilise un ou plusieurs des trois jeux de touches différents:

  1. Les quatre touches fléchées pour En haut, Vers le bas, La gauche, et Droite
  2. Les clés je, J, K, et L, qui correspondent à Haut, Gauche, Bas et Droite
  3. Pour la commande à gauche, les touches W, UNE, S, et , qui mappent également vers le haut, la gauche, le bas et la droite

Pour ce jeu, vous utiliserez les flèches et je/J/K/L. Chaque fois que l'utilisateur appuie sur une touche de mouvement, le sprite du joueur se déplace dans cette direction. Lorsque l'utilisateur relâche une touche de mouvement, le sprite cesse de se déplacer dans cette direction. Vous fournissez également un moyen de quitter le jeu en utilisant Qet un moyen de mettre le jeu en pause à l'aide de P. Pour ce faire, vous devez répondre aux pressions de touches et aux déclenchements:

  • Lorsqu'une touche est enfoncée, appel .on_key_press (). Dans cette méthode, vous vérifiez quelle touche a été enfoncée:
    • Si c'est Q, puis vous quittez simplement le jeu.
    • Si c'est P, puis vous définissez un indicateur pour indiquer que le jeu est en pause.
    • S'il s'agit d'une clé de mouvement, vous définissez le .change_x ou .change_y en conséquence.
    • S'il s'agit d'une autre clé, vous l'ignorez.
  • Lorsqu'une clé est relâchée, appel .on_key_release (). Encore une fois, vous vérifiez quelle clé a été libérée:
    • S'il s'agit d'une clé de mouvement, vous définissez le .change_x ou .change_y à 0 en conséquence.
    • S'il s'agit d'une autre clé, vous l'ignorez.

Voici à quoi ressemble le code:

134 def on_key_press(soi, symbole, modificateurs):
135     "" "Gérer la saisie au clavier utilisateur
136                 Q: Quittez le jeu
137                 P: Suspendre / Annuler le jeu
138                 I / J / K / L: Déplacer vers le haut, gauche, bas, droite
139                 Flèches: Déplacer vers le haut, gauche, bas, droite
140 
141                 Arguments:
142                                 symbol int - Quelle touche a été enfoncée
143                                 modifiers int - Quels modificateurs ont été pressés
144                 "" "
145     si symbole == arcade.clé.Q:
146         # Quittez immédiatement
147         arcade.Fermer la fenêtre()
148 
149     si symbole == arcade.clé.P:
150         soi.en pause = ne pas soi.en pause
151 
152     si symbole == arcade.clé.je ou symbole == arcade.clé.UP:
153         soi.joueur.change_y = 5
154 
155     si symbole == arcade.clé.K ou symbole == arcade.clé.VERS LE BAS:
156         soi.joueur.change_y = -5
157 
158     si symbole == arcade.clé.J ou symbole == arcade.clé.LA GAUCHE:
159         soi.joueur.change_x = -5
160 
161     si symbole == arcade.clé.L ou symbole == arcade.clé.DROITE:
162         soi.joueur.change_x = 5
163 
164 def on_key_release(soi, symbole: int, modificateurs: int):
165     "" "Annuler les vecteurs de mouvement lorsque les touches de mouvement sont relâchées
166 
167                 Arguments:
168                                 symbol int - Quelle touche a été enfoncée
169                                 modifiers int - Quels modificateurs ont été pressés
170                 "" "
171     si (
172         symbole == arcade.clé.je
173         ou symbole == arcade.clé.K
174         ou symbole == arcade.clé.UP
175         ou symbole == arcade.clé.VERS LE BAS
176     ):
177         soi.joueur.change_y = 0
178 
179     si (
180         symbole == arcade.clé.J
181         ou symbole == arcade.clé.L
182         ou symbole == arcade.clé.LA GAUCHE
183         ou symbole == arcade.clé.DROITE
184     ):
185         soi.joueur.change_x = 0

Dans .on_key_release (), vous ne vérifiez que les clés qui auront un impact sur le mouvement de votre sprite de joueur. Il n'est pas nécessaire de vérifier si les touches Pause ou Quit ont été relâchées.

Vous pouvez maintenant vous déplacer sur l'écran et quitter le jeu immédiatement:

Déplacement du lecteur sur l'écran

Vous vous demandez peut-être comment fonctionne la fonctionnalité de pause. Pour voir cela en action, vous devez d'abord apprendre à mettre à jour tous vos objets de jeu Python.

Mise à jour des objets du jeu

Ce n'est pas parce que vous avez défini une vitesse sur tous vos sprites qu'ils bougeront. Pour les faire bouger, il faut mise à jour les maintes et maintes fois dans la boucle de jeu.

Puisque arcade contrôle la boucle de jeu Python, il contrôle également quand des mises à jour sont nécessaires en appelant .on_update (). Vous pouvez remplacer cette méthode pour fournir le comportement approprié pour votre jeu, y compris le mouvement du jeu et d'autres comportements. Pour ce jeu, vous devez faire quelques choses pour tout mettre à jour correctement:

  1. Tu vérifies si le jeu est en pause. Si c'est le cas, vous pouvez simplement quitter, afin qu'aucune autre mise à jour ne se produise.
  2. Vous mettez à jour tous vos sprites pour les faire bouger.
  3. Tu vérifies si l'image-objet du joueur a quitté l'écran. Si c'est le cas, déplacez-les simplement à l'écran.

C'est tout pour le moment. Voici à quoi ressemble ce code:

189 def on_update(soi, delta_time: flotte):
190     "" "Mettre à jour les positions et les statuts de tous les objets du jeu
191                 En cas de pause, ne faites rien
192 
193                 Arguments:
194                                 delta_time float - Temps depuis la dernière mise à jour
195                 "" "
196 
197     # En cas de pause, ne mettez rien à jour
198     si soi.en pause:
199         revenir
200 
201     # Tout mettre à jour
202     soi.all_sprites.mise à jour()
203 
204     # Gardez le lecteur à l'écran
205     si soi.joueur.Haut > soi.la taille:
206         soi.joueur.Haut = soi.la taille
207     si soi.joueur.droite > soi.largeur:
208         soi.joueur.droite = soi.largeur
209     si soi.joueur.bas < 0:
210         soi.joueur.bas = 0
211     si soi.joueur.la gauche < 0:
212         soi.joueur.la gauche = 0

La ligne 198 est l'endroit où vous vérifiez si le jeu est en pause et retournez simplement si c'est le cas. Cela saute tout le code restant, donc il n'y aura aucun mouvement. Tout mouvement de sprite est géré par la ligne 202. Cette ligne unique fonctionne pour trois raisons:

  1. Chaque sprite est membre du self.all_sprites liste.
  2. L'appel à self.all_sprites.update () entraîne l'appel .mise à jour() sur chaque sprite de la liste.
  3. Chaque sprite dans la liste a .rapidité (composé de .change_x et .change_y attributs) et traitera son propre mouvement lorsque .mise à jour() est appelé.

Enfin, vous vérifiez si l'image-objet du joueur est hors écran dans les lignes 205 à 212 en comparant les bords des images-objets avec les bords de la fenêtre. Par exemple, aux lignes 205 et 206, si self.player.top est au-delà du haut de l'écran, vous réinitialisez self.player.top en haut de l'écran. Maintenant que tout est mis à jour, vous pouvez tout dessiner.

Dessin sur la fenêtre

Depuis les mises à jour des objets de jeu se produisent dans .on_update (), il semble approprié que le dessin des objets du jeu se fasse selon une méthode appelée .on_draw (). Parce que vous avez tout organisé en listes de sprites, votre code pour cette méthode est très court:

231 def on_draw(soi):
232     "" "Dessine tous les objets du jeu
233                 "" "
234     arcade.start_render()
235     soi.all_sprites.dessiner()

Tout dessin commence par l'appel à arcade.start_render () à la ligne 234. Tout comme la mise à jour, vous pouvez dessiner tous vos sprites à la fois en appelant simplement self.all_sprites.draw () à la ligne 235. Maintenant, il n'y a qu'une dernière partie de votre jeu Python sur laquelle travailler, et c'est la toute dernière partie de la conception initiale:

Lorsque le joueur est touché par un obstacle, ou si l'utilisateur ferme la fenêtre, le jeu se termine.

Ceci est la partie réelle du jeu! En ce moment, les ennemis voleront à travers votre sprite de joueur sans rien faire. Voyons comment vous pouvez ajouter cette fonctionnalité.

Détection de collision

Les jeux sont tous des collisions sous une forme ou une autre, même dans les jeux non informatiques. Without real or virtual collisions, there would be no slap-shot hockey goals, no double-sixes in backgammon, and no way in chess to capture your opponent’s queen on the end of a knight fork.

Collision detection in computer games requires the programmer to detect if two game objects are partially occupying the same space on the screen. You use collision detection to shoot enemies, limit player movement with walls and floors, and provide obstacles to avoid. Depending on the game objects involved and the desired behavior, collision detection logic can require potentially complicated math.

However, you don’t have to write your own collision detection code with arcade. You can use one of three different Sprite methods to detect collisions quickly:

  1. Sprite.collides_with_point((x,y)) Retour Vrai if the given point (x,y) is within the boundary of the current sprite, and Faux otherwise.
  2. Sprite.collides_with_sprite(Sprite) Retour Vrai if the given sprite overlaps with the current sprite, and Faux otherwise.
  3. Sprite.collides_with_list(SpriteList) returns a list containing all the sprites in the SpriteList that overlap with the current sprite. If there are no overlapping sprites, then the list will be empty, meaning it will have a length of zero.

Since you’re interested in whether or not the single-player sprite has collided with any of the enemy sprites, the last method is exactly what you need. You call self.player.collides_with_list(self.enemies_list) and check if the list it returns contains any sprites. If so, then you end the game.

So, where do you make this call? The best place is in .on_update(), just before you update the positions of everything:

189 def on_update(soi, delta_time: float):
190     """Update the positions and statuses of all game objects
191                 If paused, do nothing
192 
193                 Arguments:
194                                 delta_time float -- Time since the last update
195                 """
196 
197     # If paused, don't update anything
198     si soi.paused:
199         revenir
200 
201     # Did you hit anything? If so, end the game
202     si soi.player.collides_with_list(soi.enemies_list):
203         arcade.close_window()
204 
205     # Update everything
206     soi.all_sprites.mise à jour()

Lines 202 and 203 check for a collision between the player and any sprite in .enemies_list. If the returned list contains any sprites, then that indicates a collision, and you can end the game. Now, why would you check avant updating the positions of everything? Remember the sequence of action in the Python game loop:

  1. You update the states of the game objects. You do this in .on_update().
  2. You draw all the game objects in their new positions. You do this in .on_draw().

If you check for collisions after you update everything in .on_update(), then any new positions won’t be drawn if a collision is detected. You’re actually checking for a collision based on sprite positions that haven’t been shown to the user yet. It may appear to the player as though the game ended before there was an actual collision! When you check first, you ensure that what’s visible to the player is the same as the game state you’re checking.

Now you have a Python game that looks good and provides a challenge! Now you can add some extra features to help make your Python game stand out.

There are many more features you can add to your Python game to make it stand out. In addition to the features the game design called out that you didn’t implement, you may have others in mind as well. This section will cover two features that will give your Python game some added impact by adding sound effects and controlling the game speed.

Du son

Sound is an important part of any computer game. From explosions to enemy taunts to background music, your Python game is a little flat without sound. Out of the box, arcade provides support for WAV files. If the ffmpeg library is installed and available, then arcade also supports Ogg and MP3 format files. You’ll add three different sound effects and some background music:

  1. The first sound effect plays as the player moves up.
  2. The second sound effect plays when the player moves down.
  3. The third sound effect plays when there is a collision.
  4. The background music is the last thing you’ll add.

You’ll start with the sound effects.

Sound Effects

Before you can play any of these sounds, you have to load them. You do so in .setup():

66 # Spawn a new enemy every 0.25 seconds
67 arcade.programme(soi.add_enemy, 0,25)
68 
69 # Spawn a new cloud every second
70 arcade.programme(soi.add_cloud, 1.0)
71 
72 # Load your sounds
73 # Sound sources: Jon Fincher
74 soi.collision_sound = arcade.load_sound("sounds/Collision.wav")
75 soi.move_up_sound = arcade.load_sound("sounds/Rising_putter.wav")
76 soi.move_down_sound = arcade.load_sound("sounds/Falling_putter.wav")

Like your sprite images, it’s good practice to place all your sounds in a single sub-folder.

With the sounds loaded, you can play them at the appropriate time. Pour .move_up_sound et .move_down_sound, this happens during the .on_key_press() handler:

134 def on_key_press(soi, symbol, modifiers):
135     """Handle user keyboard input
136                 Q: Quit the game
137                 P: Pause the game
138                 I/J/K/L: Move Up, Left, Down, Right
139                 Arrows: Move Up, Left, Down, Right
140 
141                 Arguments:
142                                 symbol int -- Which key was pressed
143                                 modifiers int -- Which modifiers were pressed
144                 """
145     si symbol == arcade.key.Q:
146         # Quit immediately
147         arcade.close_window()
148 
149     si symbol == arcade.key.P:
150         soi.paused = ne pas soi.paused
151 
152     si symbol == arcade.key.je ou symbol == arcade.key.UP:
153         soi.player.change_y = 5
154         arcade.play_sound(soi.move_up_sound)
155 
156     si symbol == arcade.key.K ou symbol == arcade.key.DOWN:
157         soi.player.change_y = -5
158         arcade.play_sound(soi.move_down_sound)

Now, whenever the player moves up or down, your Python game will play a sound.

The collision sound will play whenever .on_update() detects a collision:

def on_update(soi, delta_time: float):
    """Update the positions and statuses of all game objects
                If paused, do nothing

                Arguments:
                                delta_time float -- Time since the last update
                """

    # If paused, don't update anything
    si soi.paused:
        revenir

    # Did you hit anything? If so, end the game
    si len(soi.player.collides_with_list(soi.enemies_list)) > 0:
        arcade.play_sound(soi.collision_sound)
        arcade.close_window()

    # Update everything
    soi.all_sprites.mise à jour()

Just before the window closes, a collision sound will play.

Background Music

Adding background music follows the same pattern as adding sound effects. The only difference is when it starts to play. For background music, you normally start it when the level starts, so load and start the sound in .setup():

66 # Spawn a new enemy every 0.25 seconds
67 arcade.programme(soi.add_enemy, 0,25)
68 
69 # Spawn a new cloud every second
70 arcade.programme(soi.add_cloud, 1.0)
71 
72 # Load your background music
73 # Sound source: http://ccmixter.org/files/Apoxode/59262
74 # License: https://creativecommons.org/licenses/by/3.0/
75 soi.background_music = arcade.load_sound(
76     "sounds/Apoxode_-_Electric_1.wav"
77 )
78 
79 # Load your sounds
80 # Sound sources: Jon Fincher
81 soi.collision_sound = arcade.load_sound("sounds/Collision.wav")
82 soi.move_up_sound = arcade.load_sound("sounds/Rising_putter.wav")
83 soi.move_down_sound = arcade.load_sound("sounds/Falling_putter.wav")
84 
85 # Start the background music
86 arcade.play_sound(soi.background_music)

Now, you not only have sound effects, but also some nifty background music as well!

Sound Limitations

There are some limitations on what arcade can currently do with sound:

  1. There is no volume control on any sounds.
  2. There is no way to repeat a sound, such as looping background music.
  3. There is no way to tell if a sound is currently playing before you try to stop it.
  4. Without ffmpeg, you are limited to WAV sounds, which can be large.

Despite these limitations, it’s well worth the effort to add sound to your arcade Python game.

Python Game Speed

The speed of any game is dictated by its fréquence d'images, which is the frequency at which the graphics on the screen are updated. Higher frame rates normally result in smoother gameplay, while lower frame rates give you more time to perform complex calculations.

The frame rate of an arcade Python game is managed by the game loop in arcade.run(). The Python game loop calls .on_update() et .on_draw() roughly 60 times per second. Therefore, the game has a frame rate of 60 frames per second or 60 FPS.

Notice the description above says that the frame rate is roughly 60 FPS. This frame rate is not guaranteed to be exact. It may fluctuate up or down based on many factors, such as load on the machine or longer-than-normal update times. As a Python game programmer, you want to ensure your Python game acts the same, whether it’s running at 60 FPS, 30 FPS, or any other rate. Donc comment fais-tu cela?

Time-Based Movement

Imagine an object moving in space at 60 kilometers per minute. You can calculate how far the object will travel in any length of time by multiplying that time by the object’s speed:

Calculating distance based on speed and time.

The object moves 120 kilometers in 2 minutes and 30 kilometers in half a minute.

You can use this same calculation to move your sprites at a constant speed no matter what the frame rate. If you specify the sprite’s speed in terms of pixels per second, then you can calculate how many pixels it moves every frame if you know how much time has passed since the last frame appeared. How do you know that?

Recall that .on_update() takes a single parameter, delta_time. This is the amount of time in seconds that have passed since the last time .on_update() was called. For a game running at 60 FPS, delta_time will be 1/60 of a second or roughly 0.0167 seconds. If you multiply the time that’s passed by the amount a sprite will move, then you’ll ensure sprite movement is based on the time elapsed and not the frame rate.

Updating Sprite Movement

There’s just one problem—neither Sprite.on_update() nor SpriteList.on_update() accept the delta_time parameter. This means there’s no way to pass this on to your sprites to handle automatically. Therefore, to implement this feature, you need to update your sprite positions manually. Replace the call to self.all_sprites.update() dans .on_update() with the following code:

def on_update(soi, delta_time: float):
    """Update the positions and statuses of all game objects
                If paused, do nothing

                Arguments:
                                delta_time float -- Time since the last update
                """

    # If paused, don't update anything
    si soi.paused:
        revenir

    # Did you hit anything? If so, end the game
    si len(soi.player.collides_with_list(soi.enemies_list)) > 0:
        arcade.play_sound(soi.collision_sound)
        arcade.close_window()

    # Update everything
    pour sprite dans soi.all_sprites:
        sprite.center_x = int(
            sprite.center_x + sprite.change_x * delta_time
        )
        sprite.center_y = int(
            sprite.center_y + sprite.change_y * delta_time
        )

In this new code, you modify the position of each sprite manually, multiplying .change_x et .change_y par delta_time. This ensures that the sprite moves a constant distance every second, rather than a constant distance every frame, which can smooth out gameplay.

Updating Sprite Parameters

Of course, this also means you should re-evaluate and adjust the initial position and speed of all your sprites as well. Recall the position and .velocity your enemy sprites are given when they’re created:

    93 def add_enemy(soi, delta_time: float):
    94     """Adds a new enemy to the screen
    95 
    96                 Arguments:
    97                                 delta_time float -- How much time as passed since the last call
    98                 """
    99 
100     # First, create the new enemy sprite
101     ennemi = FlyingSprite("images/missile.png", SCALING)
102 
103     # Set its position to a random height and off screen right
104     ennemi.la gauche = Aléatoire.randint(soi.width, soi.width + 80)
105     ennemi.Haut = Aléatoire.randint(dix, soi.la taille - dix)
106 
107     # Set its speed to a random speed heading left
108     ennemi.velocity = (Aléatoire.randint(-20, -5), 0)

With the new movement calculations based on time, your enemies will now move at a maximum speed of 20 pixels every second. This means that on an 800-pixel-wide window, the fastest enemy will take forty seconds to fly across the screen. Further, if the enemy starts eighty pixels to the right of the window, then the fastest will take four full seconds just to appear!

Adjusting the position and velocity is part of making your Python game interesting and playable. Start by adjusting each by a factor of ten, and readjust from there. The same reevaluation and adjustments should be done with the clouds, as well as the movement velocity of the player.

Tweaks and Enhancements

During your Python game design, there were several features that you didn’t add. To add to that list, here are some additional enhancements and tweaks that you may have noticed during Python gameplay and testing:

  1. When the game is paused, enemies and clouds are still generated by the scheduled functions. This means that, when the game is unpaused, a huge wave of them are waiting for you. How do you prevent that from happening?
  2. As mentioned above, due to some limitations of the arcade sound engine, the background music does not repeat itself. How do you work around that issue?
  3. When the player collides with an enemy, the game ends abruptly without playing the collision sound. How do you keep the game open for a second or two before it closes the window?

There may be other tweaks you could add. Try to implement some of them as an exercise, and share your results down in the comments!

A Note on Sources

You may have noticed a comment when the background music was loaded, listing the source of the music and a link to the Creative Commons license. This was done because the creator of that sound requires it. The license requirements state that, in order to use the sound, both proper attribution and a link to the license must be provided.

Here are some sources for music, sound, and art that you can search for useful content:

As you make your games and use downloaded content such as art, music, or code from other sources, please be sure that you’re complying with the licensing terms of those sources.

Conclusion

Computer games are a great introduction to coding, and the arcade library is a great first step. Designed as a modern Python framework for crafting games, you can create compelling Python game experiences with great graphics and sound.

Throughout this tutorial, you learned how to:

  • Install the arcade library
  • Draw items on the screen
  • Work with the arcade Python game loop
  • Manage on-screen graphic elements
  • Handle user input
  • Play sound effects and music
  • Describe how Python game programming in arcade differs from pygame

I hope you give arcade un essai. If you do, then please leave a comment below, and Happy Pythoning! You can download all the materials used in this tutorial at the link below: