pythonpygamekeydownkeyup

Pygame sometimes fails to detect KEYUP or KEYDOWN


I'm learning pygame with this mummy games, but the movement of the player is not responding as wanted. Sometimes, it work, sometimes, it continues to move even if the key has been released. Why pygame does not detect all the times when I release or push my arrow keys ? Thanks for any help. The way the arrow keys are used is described in "game", end of the "update" methode.

Main :

"""
Created on Mon Jun 14 09:54:56 2021

@author: Graven
"""

import pygame as pg
import math
from game import Game


pg.init()  # charger les composants de pygame

clock = pg.time.Clock()           

# Générer la fenêtre du jeu
screenSize = (1080, 720)
pg.display.set_caption('Comet fall game')  #(title, icontitle)  Définir le titre de la fenêtre
screen = pg.display.set_mode(screenSize)  # (size, flag...) taille de la fenêtre

# Importer l'arrière plan
background = pg.image.load('assets/bg.jpg')

# Importer la bannière
banner = pg.image.load('assets/banner.png')
banner = pg.transform.scale(banner, (500, 500))
banner_rect = banner.get_rect()
banner_rect.x = math.ceil(screen.get_width() / 4)
# importerle bouton pour lancer la partie
play_button = pg.image.load('assets/button.png')
play_button = pg.transform.scale(play_button, (400, 150))
play_button_rect = play_button.get_rect()
play_button_rect.x = math.ceil(screen.get_width() / 3.33)
play_button_rect.y = math.ceil(screen.get_height() / 2)


# Charger le jeu
game = Game()
running = True  # jeu en cours d'execution

# Boucle tant que running est vrai
while running :
    # pg.time.delay(10)
    # Appliquer l'arrière plan du jeu
    screen.blit(background, (-1000, -200))  # injecter l'image sur l'écran


    # Vérifier si notre jeu a commencé ou non
    if game.is_playing:
        # Déclencher les instructions de la partie
        game.update(screen, screenSize) 
          
    # Vérifier si le jeu n'a pas encore commencé 
    else :
        # Ajouter l'écran de bienvenue
        screen.blit(play_button, play_button_rect)
        screen.blit(banner,banner_rect)

            
    # mettre à jour l'écran
    pg.display.flip() 
    # clock.tick(1000)
    
    # si le joueur ferme cette fenetre ou click sur start
    for event in pg.event.get():  # reprendre tous les évenements possibles
        # vérifier que l'evenement est fermeture de fenetre
        if event.type == pg.QUIT :
            running = False
            pg.quit()
        elif event.type == pg.MOUSEBUTTONDOWN:
            # Vérification pour savoir si le curseur est en collision avec le boutton
            if play_button_rect.collidepoint(event.pos):
                # mettre le jeu en mode "lancé"
                game.start()
                
                
              

Game :

"""
Created on Mon Jun 14 10:40:05 2021

@author: Graven
"""
import pygame as pg
from player import Player
from monster import Monster
clock = pg.time.Clock()  
# Créer une seconde classe qui représente le jeu
class Game :
    def __init__(self):
        # Définir si le jeu a commencé ou non
        self.is_playing = False
        # Générer notre joueur
        self.all_players = pg.sprite.Group()  # Uniquement pour la fonction collision qui vérifie un sprite avec un groupe
        self.player = Player(self)
        self.all_players.add(self.player)
        # Enregistrer toutes les touches actionnées   
        self.pressed = {}
        # Groupe de monstre
        self.all_monsters = pg.sprite.Group()
        self.k=0
    
    def start(self):
        # lors d'une nouvelle partie, respawn des monstres
        self.is_playing = True
        # self.spawn_monster()
        # self.spawn_monster()
    
    def game_over(self):
        # Remettre le jeu à neuf, retirer les monstres, remettre 100 pv et jeu en attente
        self.all_monsters = pg.sprite.Group()
        self.player.health = self.player.max_health
        self.is_playing = False
        
    def update(self, screen, screenSize):
        # Appliquer l'image du joueur
        screen.blit(self.player.image, self.player.rect)
        # Visualiser la barre de vie du joueur
        self.player.update_health_bar(screen, self.player.chargeState)
        # Récupérer les projectiles du joueur
        for projectile in self.player.all_projectile:
            projectile.move()
        # Récupérer les monstres du jeu
        for monster in self.all_monsters:
            monster.forward()
            monster.update_health_bar(screen)
            
            
        # Appliquer l'ensemble des images de mon groupe de projectiles
        self.player.all_projectile.draw(screen)
        # Appliquer l'ensemble des images de mon groupe de monstre
        self.all_monsters.draw(screen)
        # Charge du projectile
        if self.player.charge_projectile:
            self.player.chargeState += 1
        else:
            self.player.chargeState = 0

        
        # Vérifier si le joueur souhaite se déplacer
        if self.pressed.get(pg.K_RIGHT) and self.player.rect.x < screenSize[0] - self.player.rect.width:
            self.player.move_right()
            
        elif self.pressed.get(pg.K_LEFT) and self.player.rect.x > 0:
            self.player.move_left()
            
 
        # si le joueur réalise un évenement
        for event in pg.event.get():  # reprendre tous les évenements possibles
            # vérifier que l'evenement est fermeture de fenetre
            if event.type == pg.QUIT :
                pg.quit()  
            # Détecter si un joueur lache une touche du clavier
            elif event.type == pg.KEYDOWN:
                self.pressed[event.key] = True                
                # Détecter si espace est enclenché pour lancer le projectile
                if event.key == pg.K_SPACE:
                    self.player.charge_projectile = True
            elif event.type == pg.KEYUP and self.player.charge_projectile :
                self.player.launch_projectile()
                self.player.charge_projectile = False
                self.pressed[event.key] = False
            elif event.type == pg.KEYUP :
                self.pressed[event.key] = False
        # self.k += 1
        # print(self.k, self.pressed)

    
    def check_collision(self, sprite, group):
        return pg.sprite.spritecollide(sprite, group, False, pg.sprite.collide_mask)
    
    def spawn_monster(self):
        monster = Monster(self)
        self.all_monsters.add(monster)

Player :

"""
Created on Mon Jun 14 10:39:16 2021

@author: Graven
"""
import pygame as pg
from projectile import Projectile


# Créer une première classe qui représente le joueur
class Player(pg.sprite.Sprite) :
    def __init__(self, game):
        super().__init__()
        self.game = game
        self.health = 100
        self.max_health = 100
        self.chargeState = 0
        self.attack =  0.5
        self.max_charge = 100
        self.velocity = 3  
        self.image = pg.image.load('assets/player.png')
        self.rect =  self.image.get_rect()
        self.rect.x = 450
        self.rect.y = 500
        self.all_projectile = pg.sprite.Group()
        self.charge_projectile = False
        
        
    def damage(self, amout):
        self.health -= amout
        if self.health <= 0 :
            self.game.game_over()
        
    def move_right(self):
        # si le joueur n'est pas en collision avec un monstre
        if not self.game.check_collision(self, self.game.all_monsters):
            self.rect.x += self.velocity
    def move_left(self):
        self.rect.x -= self.velocity
        
    def launch_projectile(self):
        # Créer une nouvelle instance de la classe projectile
        self.all_projectile.add(Projectile(self))
        
    
    def update_health_bar(self, surface, max_charge):
        # Définir une couleur pour la jauge de vie(vert) et son arrière plan (gris)
        if max_charge <  self.max_charge :
            bar_color = (111, 210, 46)
        else:
            bar_color = (255, 128, 0)
        back_bar_color = (60, 63,60)
        # Définir la position de la jauge de vie et sa largeur/épaisseur
        bar_position = [self.rect.x +50, self.rect.y + 20, self.health, 7]        # Définir la position de l'arrière plan dela jauge
        back_bar_position = [self.rect.x + 50, self.rect.y + 20, self.max_health, 7]
        # Dessiner la barre de vie
        pg.draw.rect(surface, back_bar_color, back_bar_position)
        pg.draw.rect(surface, bar_color, bar_position)

Monster

# -*- coding: utf-8 -*-
"""
Created on Mon Jun 14 18:48:46 2021

@author: Graven
"""

import pygame as pg
import random


# Création d'une classe qui gère la notion de monstre
class Monster(pg.sprite.Sprite):
    def __init__(self, game):
        self.game = game
        super().__init__()
        self.max_health = 60 + random.randint(0,60) 
        self.health = self.max_health
        self.attack = 0.3
        self.image = pg.image.load('assets/mummy.png')
        self.rect = self.image.get_rect()
        self.rect.x = 1080 + random.randint(0, 300)
        self.rect.y = 540
        self.velocity = random.randint(1,3)
    
    def damage(self, amount):
        self.health -= amount
        if self.health <= 0 :
            # Réapparaitre comme un nouveau monstre
            self.rect.x = 1080 + random.randint(0, 300)
            self.max_health = 60 + random.randint(0,60) 
            self.health = self.max_health
            self.velocity = random.randint(1,3)
        
    def update_health_bar(self, surface):
        # Définir une couleur pour la jauge de vie(vert) et son arrière plan (gris)
        bar_color, back_bar_color = (111, 210, 46), (60, 63,60)
        # Définir la position de la jauge de vie et sa largeur/épaisseur
        bar_position = [self.rect.x + 10, self.rect.y - 20, self.health, 5]
        # Définir la position de l'arrière plan dela jauge
        back_bar_position = [self.rect.x + 10, self.rect.y - 20, self.max_health, 5]

        # Dessiner la barre de vie
        pg.draw.rect(surface, back_bar_color, back_bar_position)
        pg.draw.rect(surface, bar_color, bar_position)
        
        
    def forward(self):
        # si le monster n'est pas en collision avec le joueur
        if not self.game.check_collision(self, self.game.all_players):
            self.rect.x -= self.velocity
        else:
            # Si le monstre est en collision avec le joueur, inflige des degats
            self.game.player.damage(self.attack)
        

Projectile :

# -*- coding: utf-8 -*-
"""
Created on Mon Jun 14 11:41:52 2021

@author: Graven
"""

import pygame as pg
# définir la classe qui gère le projectile de notre joueur
class Projectile(pg.sprite.Sprite):
    
    #définir le constructeur
    def __init__(self, player):
        super().__init__()
        self.velocity = 5
        self.player = player
        self.image = pg.image.load('assets/projectile.png')
        self.rect = self.image.get_rect()
        self.damage = int(self.player.attack * min(self.player.chargeState, self.player.max_charge)**1.1)
        self.image = pg.transform.scale(self.image, (self.damage, self.damage))
        self.rect.x = player.rect.x + 95
        self.rect.y = player.rect.y + 115 - self.damage/2
        self.origin_image = self.image
        self.angle = 0
        
        
    def rotate(self):
        # Tourner le projectile
        self.angle += 2
        self.image = pg.transform.rotozoom(self.origin_image, self.angle, 1)
        self.rect = self.image.get_rect(center = self.rect.center)

        
    def remove(self):
        self.player.all_projectile.remove(self)
        
    def move(self):
        self.rect.x += self.velocity
#        self.rotate()
#        centre_proj = self.rect.center
#        print('centre_proj',centre_proj)
        
        # Vérifier su le projectile entre en collision avec un monstre
        for monstre_rencontre in  self.player.game.check_collision(self, self.player.game.all_monsters):
            # Attribuer les dégats
            monstre_rencontre.damage(self.damage)

            
            #supprimer le projectile
            self.remove()
        # Vérifier si le projectie n'est plus présent sur l'écran
        if self.rect.x > 1080:
            # Supprimer le projectile
            self.remove()
            
            

Solution

  • pygame.event.get() get all the messages and remove them from the queue. See the documentation:

    This will get all the messages and remove them from the queue. [...]

    If pygame.event.get() is called in multiple event loops, only one loop receives the events, but never all loops receive all events. As a result, some events appear to be missed.

    Get the events once per frame and use them in multiple loops or pass the list of events to functions and methods where they are handled:

    class Game:
        # [...]
      
        def update(self, screen, screenSize, eventList):
            # [...]
    
            for event in eventList:
                # [...]
    
    while running :
        eventList = pg.event.get()
    
        # [...]
    
        if game.is_playing:
            # Déclencher les instructions de la partie
            game.update(screen, screenSize, eventList) 
    
        # [...]
    
        for event in eventList:
            # [...]