简体   繁体   中英

For Loop blocking other For Loop

I'm checking missileGroup to see if any instances of missile collided with any instances enemy in enemyGroup. When run, it prints "Hit" for the first loop, but it ignores the second for loop. Why is that?

 #### 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 stated that spritecollide wouldn't work because the spriteGroup enemyGroup is a list of sprites within a group(enemyGroup <- enemyList <- Enemy(sprite)) instead of a group of sprites(enemyGroup <- Enemy(sprite)). How would I access that?

Update 2 @paxdiablo stated that the first loop maybe emptying the group after iterating. I switched the places of the loops and 2nd loop ran, while the 1st did not.

Update 3 In the full code, .reset() method runs .kill() which removes the sprite from the group. Since the first loop removes the missile sprite before the second loop couldn't detect any 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()

There's no obvious reason, based on the information provided (a) , why the second collision check should fail. If there's a collision between (for example) enemy #7 and missile #3, there should also be a collision between missile #3 and enemy #7.

You're not using any edge-case stuff like providing your own (possibly non-commutative) collision function, so it will simply use the sprite rectangle to detect this.

I'd be curious to see the behaviour when you reverse the order of the two loops in the code.


Also, you should specify the types of those group variables. If enemyGroup were something like an exhaustible generator rather than a list, it would be "emptied" by the first loop and then the second loop would have no items to iterate over (b) (the spritecollide call will be iterating over the group to check each item against the sprite).

That's about the only way, short of a bug in spritecollide itself, that you would see the effects you're describing.


By way of example, here's a piece of code that tries to iterate over a generator twice:

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)

The output shows that the second loop does nothing:

A:
  0
  1
  2
B:

Lastly, a definitive way to check the state of the groups is to simply put the following code before each loop:

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

using a suitable value of X to distinguish between the two loops.


(a) Of course, there's always the possibility the information you've given is not fully accurate or complete (no malicious intent is supposed but sometimes people inadvertently skip what they consider to be unimportant details, that ends up being very important).

Example: there may be something happening between those two loops which is causing the issue. I'd prefer to give people the benefit of the doubt but you should probably let us know if that's the case.


(b) It would actually be emptied by the first iteration of the first loop so you'd find that it would probably only ever match for the first missile.

Here's a quick example that shows (in PyGame 1.9.6) this reported behaviour does not happen.

The example creates twosprite groups , then collides them in the exact same way as the OP's example code.

As well as printing, the sprites change from outline -> filled, depending on whether they believe they have participated in a collision. There's a 1:1 mapping of an Enemy colliding with a Missile, and vice-versa.

Apologies for the poor frame-rate... 碰撞演示

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

See pygame.sprite.spritecollide() :

Return a list containing all Sprites in a Group that intersect with another Sprite.

Therefore the arguments to spritecollide() must be a pygame.sprite.Sprite object and apygame.sprite.Group object.
A list of pygame.sprite.Sprite objects instead of the Group does not work.

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

Furthermore read about kill()

The Sprite is removed from all the Groups that contain it.

Hence if you call kill() in the 1st loop, the 2nd loop won't work, because the sprite is removed from all Groups.

You call kill() in the reset methods. missile.reset() respectively eachEnemy.reset() causes the 2nd loop to fail.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM