Boucle For bloquant une autre boucle For

Nov 29 2020

Je vérifie missileGroup pour voir si des instances de missile sont entrées en collision avec des instances ennemies dans l'ennemiGroup. Lorsqu'il est exécuté, il imprime "Hit" pour la première boucle, mais il ignore la deuxième boucle for. Pourquoi donc?

 #### Imagine this is in a game loop ####
    for missile in missileGroup:
        if pygame.sprite.spritecollide(missile, enemyGroup, False) :
            print("Hit")

    
    for enemy in enemyGroup:
        if pygame.sprite.spritecollide(enemy, missileGroup, False):
            print("HI")

Mise à jour : @ Rabbid76 a déclaré que spritecollidecela ne fonctionnerait pas car le spriteGroup enemyGroupest une liste de sprites au sein d'un groupe (ennemiGroup <- ennemiList <- Enemy (sprite)) au lieu d'un groupe de sprites (ennemiGroup <- Enemy (sprite)). Comment pourrais-je y accéder?

La mise à jour 2 @paxdiablo a déclaré que la première boucle vidait peut-être le groupe après l'itération. J'ai changé les emplacements des boucles et la 2ème boucle s'est déroulée, alors que la 1ère ne l'a pas fait.

Mise à jour 3 Dans le code complet, la .reset()méthode s'exécute .kill()qui supprime le sprite du groupe. Puisque la première boucle supprime le sprite de missile avant que la deuxième boucle ne puisse détecter de collisions:

for missile in missileGroup:
    if pygame.sprite.spritecollide(missile, enemyGroup, False) :
        missile.reset()

for eachEnemy in enemyGroup: 
        if pygame.sprite.spritecollide(eachEnemy, missileGroup, False):
            eachEnemy.reset()

Réponses

Rabbid76 Nov 29 2020 at 21:21

Voir pygame.sprite.spritecollide():

Renvoie une liste contenant tous les Sprites d'un groupe qui se croisent avec un autre Sprite.

Par conséquent, les arguments spritecollide()doivent être un pygame.sprite.Spriteobjet et un pygame.sprite.Groupobjet.
Une liste d' pygame.sprite.Spriteobjets au lieu du groupe ne fonctionne pas.

missileGroup = pygame.sprite.Group()
enemyGroup = pygame.sprite.Group()
for missile in missileGroup:
    if pygame.sprite.spritecollide(missile, enemyGroup, False):
        print("Hit")

for enemy in enemyGroup:
    if pygame.sprite.spritecollide(enemy, missileGroup, False):
        print("HI")

En outre, lisez sur kill()

Le Sprite est supprimé de tous les groupes qui le contiennent.

Par conséquent, si vous appelez kill()la première boucle, la deuxième boucle ne fonctionnera pas, car le sprite est supprimé de tous les groupes.

Vous appelez kill()les resetméthodes. provoque missile.reset()respectivement eachEnemy.reset()l'échec de la 2ème boucle.

paxdiablo Nov 29 2020 at 07:26

Il n'y a aucune raison évidente, basée sur les informations fournies (a) , pour laquelle la deuxième vérification de collision échoue. S'il y a une collision entre (par exemple) l'ennemi n ° 7 et le missile n ° 3, il devrait également y avoir une collision entre le missile n ° 3 et l'ennemi n ° 7.

Vous n'utilisez pas de trucs de cas de bord comme fournir votre propre fonction de collision (éventuellement non commutative), donc il utilisera simplement le rectangle de sprite pour détecter cela.

Je serais curieux de voir le comportement lorsque vous inversez l'ordre des deux boucles dans le code.


Vous devez également spécifier les types de ces variables de groupe. S'il y enemyGroupavait quelque chose comme un générateur épuisable plutôt qu'une liste, il serait «vidé» par la première boucle, puis la deuxième boucle n'aurait aucun élément à parcourir (b) (l' spritecollideappel sera itéré sur le groupe pour vérifier chaque élément contre le sprite).

C'est à peu près le seul moyen, à moins d'un bogue en spritecollidesoi, que vous puissiez voir les effets que vous décrivez.


À titre d'exemple, voici un morceau de code qui essaie d'itérer deux fois sur un générateur:

class gen3(object):
    def __init__(self): self._num = 0
    def __iter__(self): return self
    def __next__(self):
        if self._num == 3: raise StopIteration()
        self._num += 1
        return self._num - 1

gen = gen3()
print("A: ")
for i in gen: print(" ", i)
print("B: ")
for i in gen: print(" ", i)

La sortie montre que la deuxième boucle ne fait rien:

A:
  0
  1
  2
B:

Enfin, un moyen définitif de vérifier l'état des groupes est de simplement mettre le code suivant avant chaque boucle:

print("loop X enemy  ", len(enemyGroup),   enemyGroup)
print("loop X missile", len(missileGroup), missileGroup)

en utilisant une valeur appropriée de Xpour distinguer les deux boucles.


(a) Bien sûr, il est toujours possible que les informations que vous avez fournies ne soient pas totalement exactes ou complètes (aucune intention malveillante n'est supposée, mais parfois les gens sautent par inadvertance ce qu'ils considèrent comme des détails sans importance, cela finit par être très important).

Exemple: il se peut qu'il se passe quelque chose entre ces deux boucles, ce qui cause le problème. Je préférerais donner aux gens le bénéfice du doute, mais vous devriez probablement nous le faire savoir si c'est le cas.


(b) Il serait en fait vidé par la première itération de la première boucle, de sorte que vous constateriez qu'il ne correspondrait probablement que pour le premier missile.

Kingsley Nov 29 2020 at 07:51

Voici un exemple rapide qui montre (dans PyGame 1.9.6) ce comportement signalé ne se produit pas.

L'exemple crée deux groupes de sprites , puis les met en collision exactement de la même manière que l'exemple de code de l'OP.

En plus de l'impression, les sprites changent de contour -> rempli, selon qu'ils croient avoir participé à une collision. Il existe une cartographie 1: 1 d'un ennemi entrant en collision avec un missile, et vice-versa.

Toutes mes excuses pour la faible fréquence d'images ...

import pygame
import random

# Window size
WINDOW_WIDTH    = 800
WINDOW_HEIGHT   = 800

DARK_BLUE = (   3,   5,  54 )
RED       = ( 200,   0,   0 )
YELLOW    = ( 240, 250,   0 )
BLACK     = (   0,   0,   0 )
GREY      = ( 200, 200, 200 )
GREEN     = ( 250,   0,   0 )
TRANSPARENT=( 0,0,0,0 )


class Shape(pygame.sprite.Sprite):
    def __init__(self, width=48, height=48):
        pygame.sprite.Sprite.__init__(self)
        self.image = pygame.Surface( ( width, height ), pygame.SRCALPHA)
        self.rect  = self.image.get_rect()
        # Start position is randomly across the screen, and a little off the top
        self.rect.center = ( random.randrange( 0, WINDOW_WIDTH ), random.randrange( 0, WINDOW_HEIGHT ) )
        # Movement
        self.dx = random.randrange( -2, 2 )
        self.dy = random.randrange( -2, 2 )
        # Looks like
        self.filled = 2
        self.last_fill = -1
        self.render()

    def setFilled( self, value ):
        if ( value == True ):
            self.filled = 0
        else:
            self.filled = 2

    def update( self ):
        if ( self.last_fill != self.filled ):
            self.last_fill = self.filled
            self.render()

        self.rect.move_ip( self.dx, self.dy )

        if ( self.rect.left > WINDOW_WIDTH ):
            self.rect.x = -self.rect.width
        elif ( self.rect.right < 0 ):
            self.rect.left = WINDOW_WIDTH
        if ( self.rect.y > WINDOW_HEIGHT ):
            self.rect.y = 0
        elif ( self.rect.y < 0 ):
            self.rect.y = WINDOW_HEIGHT


class Square( Shape ):
    def render( self ):
        # Something to draw

        if ( self.filled == 0 ):
            self.image.fill( RED )
        else:
            border=3
            x, y = border, border
            width = self.rect.width - border -1
            height = self.rect.height - border -1
            self.image.fill( TRANSPARENT )
            pygame.draw.rect( self.image, RED, (x,y,width,height), self.filled )

class Circle( Shape ):
    def render( self ):
        self.image.fill( TRANSPARENT )
        pygame.draw.circle( self.image, YELLOW, (self.rect.width//2, self.rect.height//2), self.rect.width//2, self.filled )




### initialisation
pygame.init()
window = pygame.display.set_mode( ( WINDOW_WIDTH, WINDOW_HEIGHT ) )

### Some sprite groups
missileGroup = pygame.sprite.Group()
for i in range( 3 ):
    new_missile = Circle()
    new_missile.render()
    missileGroup.add( Circle() )

enemyGroup = pygame.sprite.Group()
for i in range( 12 ):
    new_enemy = Square()
    new_enemy.render()
    enemyGroup.add( Square() )


### Main Loop
clock = pygame.time.Clock()
done = False
while not done:

    # Handle user-input
    for event in pygame.event.get():
        if ( event.type == pygame.QUIT ):
            done = True
        elif ( event.type == pygame.MOUSEBUTTONUP ):
            # On mouse-click
            pass
    # Move * collide the sprites
    missileGroup.update()
    enemyGroup.update()

    # Test Collisions
    for missile in missileGroup:
        if pygame.sprite.spritecollide(missile, enemyGroup, False) :
            print("Missile " + str(missile) + " Hits Enemy")
            missile.setFilled( True )
        else:
            missile.setFilled( False )

    for enemy in enemyGroup:
        if pygame.sprite.spritecollide(enemy, missileGroup, False):
            print("Enemy  " + str(enemy) + " Hits Missile")
            enemy.setFilled( True )
        else:
            enemy.setFilled( False )


    # Paint the window, but not more than 60fps
    window.fill( DARK_BLUE )
    enemyGroup.draw( window )
    missileGroup.draw( window )
    pygame.display.flip()


    # Clamp FPS
    clock.tick(60)


pygame.quit()