Bienvenue sur IndexError.

Ici vous pouvez poser des questions sur Python et le Framework Django.

Mais aussi sur les technos front comme React, Angular, Typescript et Javascript en général.

Consultez la FAQ pour améliorer vos chances d'avoir des réponses à vos questions.

pygame : le groupe de sprite LayeredDirty ne renvoie pas les bons rects de refresh

+3 votes

Mes dirty sprites ne semblent pas se rafraîchir correctement quand ils sont dans un groupe LayeredDirty.


Code avec des sprites normaux, qui fonctionne.

# -*- coding: utf-8 -*-

from __future__ import (unicode_literals, absolute_import,
                        print_function, division)
import pygame
import pygame.locals

SCREEN_RECT = pygame.Rect(0, 0, 400, 300)

class SpriteCarreRouge(pygame.sprite.DirtySprite):

    def __init__(self):
        super(self.__class__,self).__init__()
        img_carre_rouge = pygame.Surface((30, 30)).convert()
        img_carre_rouge.fill((200, 0, 0))
        self.image = img_carre_rouge
        self.rect = pygame.Rect(0, 100, 0, 0)
        self.counter = 0

    def update(self):
        # Le sprite bouge vers la droite.
        self.counter += 1
        self.rect.right = self.counter * 40


def main():
    print("coucou !!! ")
    pygame.init()
    screen = pygame.display.set_mode(SCREEN_RECT.size)

    # ---- spécial dirty
    # Le groupe RenderUpdates gère les différents rectangles de l'aire de jeu
    # qui doivent être rafraîchi.
    all_sprites = pygame.sprite.RenderUpdates()
    # ---- fin spécial dirty

    # Image de fond : un gros rectangle vert de la taille de l'écran.
    img_background = pygame.Surface(SCREEN_RECT.size).convert()
    img_background.fill((0, 100, 0))
    screen.blit(img_background, (0, 0))
    pygame.display.flip()

    sprite_carre_rouge = SpriteCarreRouge()
    all_sprites.add(sprite_carre_rouge)

    clock = pygame.time.Clock()

    while 1:

        # 4 frame par secondes.
        clock.tick(4)

        # On efface tous les sprites existants,
        # en redessinant dessus la surface img_background.
        # Cette fonction est censée mettre à jour une liste interne de zones
        # à rafraîchir.
        all_sprites.clear(screen, img_background)

        # Fin du programme quand on ferme la fenêtre.
        for event in pygame.event.get():
            if event.type == pygame.locals.QUIT:
                return

        # Cette fonction exécute la fonction update() de tous les sprites
        # contenus dans le groupe.
        all_sprites.update()

        # Dessine les sprites. Cette fonction complète les zones à rafraîchir,
        # puis les renvoie sous forme d'une liste de Rect.
        list_rect_to_repaint = all_sprites.draw(screen)
        print("dirty.  rects_to_repaint:", list_rect_to_repaint)

        pygame.display.update(list_rect_to_repaint)

    pygame.quit()


if __name__ == '__main__':
    main()

À l'écran, je vois bien mon carré rouge en entier, qui se déplace vers la droite.

Le log donne ceci :

coucou !!!
normal. rects_to_repaint: [<rect(40, 100, 30, 30)>]
normal. rects_to_repaint: [<rect(80, 100, 30, 30)>, <rect(40, 100, 30, 30)>]
normal. rects_to_repaint: [<rect(120, 100, 30, 30)>, <rect(80, 100, 30, 30)>]

Après la première frame d'initialisation, j'ai à chaque fois deux zones à rafraîchir à l'écran.
C'est normal, il y a la zone où se trouvait le carré avant (il faut la repeindre avec l'image de fond), et la zone où se trouve le rectangle maintenant.
Ces deux zones ont une taille de 30*30, c'est à dire la taille de mon sprite. Tout va bien.


Code avec des dirty sprites.

Les passages modifiés sont indiqués par des commentaires "spécial dirty".

# -*- coding: utf-8 -*-

from __future__ import (unicode_literals, absolute_import,
                        print_function, division)
import pygame
import pygame.locals

SCREEN_RECT = pygame.Rect(0, 0, 400, 300)

class SpriteCarreRouge(pygame.sprite.DirtySprite):

    def __init__(self):
        super(self.__class__,self).__init__()
        img_carre_rouge = pygame.Surface((30, 30)).convert()
        img_carre_rouge.fill((200, 0, 0))
        self.image = img_carre_rouge
        self.rect = pygame.Rect(0, 100, 0, 0)
        self.counter = 0
        # ---- spécial dirty
        # On met 2, ça veut dire que le sprite va se réafficher à chaque cycle.
        # Le but c'est d'avoir un fonctionnement le plus proche
        # du mode "pas-dirty".
        self.dirty = 2
        # ---- fin spécial dirty

    def update(self):
        # Le sprite bouge vers la droite.
        self.counter += 1
        self.rect.right = self.counter * 40


def main():
    print("coucou !!! ")
    pygame.init()
    screen = pygame.display.set_mode(SCREEN_RECT.size)

    # ---- spécial dirty
    # Le groupe LayeredDirty est censé gérer les dirty Sprites, ainsi que
    # les différents rectangles de l'aire de jeu qui doivent être rafraîchi.
    all_sprites = pygame.sprite.LayeredDirty()
    # ---- fin spécial dirty

    # Image de fond : un gros rectangle vert de la taille de l'écran.
    img_background = pygame.Surface(SCREEN_RECT.size).convert()
    img_background.fill((0, 100, 0))
    screen.blit(img_background, (0, 0))
    pygame.display.flip()

    sprite_carre_rouge = SpriteCarreRouge()
    all_sprites.add(sprite_carre_rouge)

    clock = pygame.time.Clock()

    while 1:

        # 4 frame par secondes.
        clock.tick(4)

        # On efface tous les sprites existants,
        # en redessinant dessus la surface img_background.
        # Cette fonction est censée mettre à jour une liste interne de zones
        # à rafraîchir.
        all_sprites.clear(screen, img_background)

        # Fin du programme quand on ferme la fenêtre.
        for event in pygame.event.get():
            if event.type == pygame.locals.QUIT:
                return

        # Cette fonction exécute la fonction update() de tous les sprites
        # contenus dans le groupe.
        all_sprites.update()

        # Dessine les sprites. Cette fonction complète les zones à rafraîchir,
        # puis les renvoie sous forme d'une liste de Rect.
        list_rect_to_repaint = all_sprites.draw(screen)
        print("dirty.  rects_to_repaint:", list_rect_to_repaint)

        # ---- spécial dirty
        # Si on ne passe pas de paramètre, tout l'écran est rafraîchi.
        # Dans ce cas, on n'a pas le bug.
        #pygame.display.update()
        # ---- fin spécial dirty
        pygame.display.update(list_rect_to_repaint)

    pygame.quit()


if __name__ == '__main__':
    main()

À l'écran, je vois mon carré rouge une première fois au début, et c'est tout.

Le log donne ceci :

coucou !!!
dirty.  rects_to_repaint: [<rect(0, 0, 400, 300)>]
dirty.  rects_to_repaint: [<rect(80, 100, 0, 0)>, <rect(40, 100, 30, 30)>]
dirty.  rects_to_repaint: [<rect(120, 100, 0, 0)>, <rect(80, 100, 30, 30)>]

La première frame d'initialisation rafraîchit tout l'écran. Bon pourquoi pas.
Ensuite, j'ai à chaque fois deux zones à rafraîchir, sauf que celle correspondant à la position actuelle du sprite, a une taille de 0*0.

Pourquoi ?

C'est moi qui n'ait pas compris comment fonctionne les DirtySprite et le groupe LayeredDirty, ou bien il y a un bug dans pygame ?


Mes versions :

Python 2.7.9
pygame.version.ver : '1.9.1release'

demandé 4-Jan-2015 par recher (380 points)

1 Réponse

+3 votes
 
Meilleure réponse

La propriété rect de la classe DirtySprite est utilisé pour calculer la zone à rafraîchir (si besoin évidement). Cette propriété contient la position et la taille du sprite. Si on veut, on peut donc afficher seulement une partie d'une image sans devoir la découper.

Dans ton exemple, la position de sprite_carre_rouge est à (0, 100) et sa taille à (0, 0). C'est donc pour cette raison que list_rect_to_repaint contient des Rect avec une taille nulle.

Donc pour corriger ça, il suffit de changer la ligne

self.rect = pygame.Rect(0, 100, 0, 0)

par

self.rect = pygame.Rect(0, 100, 30, 30)

ou si tu préfères utiliser directement l'image

self.rect = img_carre_rouge.get_rect() # recupère la taille de l'image
self.rect.topleft = 0, 100 # positionne l'objet en (0, 100)
répondu 14-Jan-2015 par Hashlime (150 points)
sélectionné 15-Jan-2015 par recher

Ah oui ça marche !
C'est bizarre que le RenderUpdates utilise la taille de l'image, et que le LayeredDirty utilise la taille du rect. Mais pourquoi pas. Il suffit de le savoir.
Merci beaucoup !

En effet, je n'ai malheureusement rien trouvé dans la documentation, j'ai du aller voir dans le code d'un programme fonctionnel

...