For Loop blockiert andere For Loop

Nov 29 2020

Ich überprüfe missileGroup, um festzustellen, ob Raketeninstanzen mit feindlichen Instanzen in der feindlichen Gruppe kollidierten. Beim Ausführen wird "Hit" für die erste Schleife ausgegeben, die zweite for-Schleife wird jedoch ignoriert. Warum ist das so?

 #### 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")

Update : @ Rabbid76 gab an, dass spritecollidedies nicht funktionieren würde, da die SpriteGroup enemyGroupeine Liste von Sprites innerhalb einer Gruppe (feindliche Gruppe <- feindliche Liste <- Feind (Sprite)) anstelle einer Gruppe von Sprites (feindliche Gruppe <- Feind (Sprite)) ist. Wie würde ich darauf zugreifen?

Update 2 @paxdiablo gab an, dass die erste Schleife die Gruppe möglicherweise nach dem Iterieren leert. Ich habe die Plätze der Schleifen gewechselt und die 2. Schleife lief, während die 1. nicht lief.

Update 3 Im vollständigen Code wird eine .reset()Methode ausgeführt, mit .kill()der das Sprite aus der Gruppe entfernt wird. Da die erste Schleife das Raketensprite entfernt, bevor die zweite Schleife keine Kollisionen erkennen konnte:

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()

Antworten

Rabbid76 Nov 29 2020 at 21:21

Siehe pygame.sprite.spritecollide():

Gibt eine Liste zurück, die alle Sprites in einer Gruppe enthält, die sich mit einem anderen Sprite überschneiden.

Daher müssen die Argumente spritecollide()ein pygame.sprite.SpriteObjekt und ein pygame.sprite.GroupObjekt sein.
Eine Liste von pygame.sprite.SpriteObjekten anstelle der Gruppe funktioniert nicht.

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")

Lesen Sie weiter über kill()

Das Sprite wird aus allen Gruppen entfernt, die es enthalten.

Wenn Sie also kill()die 1. Schleife aufrufen , funktioniert die 2. Schleife nicht, da das Sprite aus allen Gruppen entfernt wird.

Sie rufen kill()die resetMethoden auf. bewirkt missile.reset()jeweils eachEnemy.reset()einen Ausfall der 2. Schleife.

paxdiablo Nov 29 2020 at 07:26

Aufgrund der bereitgestellten Informationen (a) gibt es keinen offensichtlichen Grund, warum die zweite Kollisionsprüfung fehlschlagen sollte. Wenn es eine Kollision zwischen (zum Beispiel) Feind Nr. 7 und Rakete Nr. 3 gibt, sollte es auch eine Kollision zwischen Rakete Nr. 3 und Feind Nr. 7 geben.

Sie verwenden keine Edge-Case-Funktionen wie die Bereitstellung einer eigenen (möglicherweise nicht kommutativen) Kollisionsfunktion. Daher wird einfach das Sprite-Rechteck verwendet, um dies zu erkennen.

Ich wäre gespannt auf das Verhalten, wenn Sie die Reihenfolge der beiden Schleifen im Code umkehren .


Außerdem sollten Sie die Typen dieser Gruppenvariablen angeben. Wenn enemyGroupso etwas wie ein erschöpfbarer Generator statt einer Liste wäre, würde er von der ersten Schleife "geleert", und dann hätte die zweite Schleife keine Elemente, über die man iterieren könnte (b) (der spritecollideAufruf wird über die Gruppe iteriert, um jedes Element zu überprüfen gegen das Sprite).

Das ist ungefähr die einzige Möglichkeit, abgesehen von einem Fehler an spritecollidesich, die von Ihnen beschriebenen Effekte zu sehen.


Beispiel: Hier ist ein Code, der versucht, einen Generator zweimal zu durchlaufen:

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)

Die Ausgabe zeigt, dass die zweite Schleife nichts tut:

A:
  0
  1
  2
B:

Eine endgültige Möglichkeit, den Status der Gruppen zu überprüfen, besteht darin, einfach den folgenden Code vor jede Schleife zu setzen:

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

Verwenden eines geeigneten Werts von X, um zwischen den beiden Schleifen zu unterscheiden.


(a) Natürlich besteht immer die Möglichkeit, dass die von Ihnen angegebenen Informationen nicht vollständig korrekt oder vollständig sind (es wird keine böswillige Absicht angenommen, aber manchmal überspringen Personen versehentlich, was sie als unwichtige Details betrachten, was letztendlich sehr wichtig ist).

Beispiel: Möglicherweise passiert zwischen diesen beiden Schleifen etwas, das das Problem verursacht. Ich würde es vorziehen, den Menschen den Vorteil des Zweifels zu geben, aber Sie sollten uns wahrscheinlich wissen lassen, ob dies der Fall ist.


(b) Es würde tatsächlich durch die erste Iteration der ersten Schleife geleert, so dass Sie feststellen würden, dass es wahrscheinlich immer nur für die erste Rakete passen würde.

Kingsley Nov 29 2020 at 07:51

Hier ist ein kurzes Beispiel, das zeigt (in PyGame 1.9.6), dass dieses gemeldete Verhalten nicht auftritt.

Das Beispiel erstellt zwei Sprite - Gruppen , dann kollidiert sie in der exakt gleichen Art und Weise wie das Beispiel - Code des OP.

Neben dem Drucken ändern sich die Sprites vom Umriss -> gefüllt, je nachdem, ob sie glauben, an einer Kollision teilgenommen zu haben. Es gibt eine 1: 1-Kartierung eines Feindes, der mit einer Rakete kollidiert, und umgekehrt.

Entschuldigung für die schlechte Bildrate ...

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()