For循环阻塞其他For循环

For Loop blocking other For Loop

我正在检查 missileGroup 以查看是否有任何导弹实例与 enemyGroup 中的任何敌人实例相撞。当 运行 时,它会为第一个循环打印“Hit”,但会忽略第二个 for 循环。这是为什么?

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

更新:@Rabbid76 表示 spritecollide 不起作用,因为 spriteGroup enemyGroup 是一个组中的精灵列表(enemyGroup <- enemyList <- Enemy(sprite)) 而不是一组精灵(enemyGroup <- Enemy(sprite))。我将如何访问它?

更新 2 @paxdiablo 表示第一个循环可能会在迭代后清空组。我切换了循环和第二个循环的位置 运行,而第一个没有。

更新 3 在完整代码中,.reset() 方法 运行s .kill() 从组中删除精灵。由于第一个循环在第二个循环无法检测到任何碰撞之前移除了导弹精灵:

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

根据所提供的信息(a),没有明显的理由证明第二次碰撞检查失败。如果(例如)敌人 #7 和导弹 #3 发生碰撞,则导弹 #3 和敌人 #7 之间也应该发生碰撞。

您没有使用任何边缘情况,例如提供您自己的(可能是非交换的)碰撞函数,因此它只会使用 sprite 矩形来检测它。

当您颠倒代码中两个循环的顺序时,我很想看看这种行为。


此外,您应该指定这些组变量的类型。如果 enemyGroup 类似于一个可耗尽的生成器而不是一个列表,它将被第一个循环“清空”,然后第二个循环将没有要迭代的项目(b)spritecollide 调用将遍历组以根据精灵检查每个项目)。

这是唯一的方法,除了 spritecollide 本身的错误,你会看到你描述的效果。


例如,这里有一段代码尝试对生成器进行两次迭代:

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)

输出显示第二个循环什么都不做:

A:
  0
  1
  2
B:

最后,检查组状态的最终方法是在每个循环之前简单地放置以下代码:

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

使用合适的值 X 来区分两个循环。


(a)当然,总是有可能您提供的信息不完全准确或完整(无恶意意图是应该的,但有时人们会不经意地跳过他们认为不重要的细节,最终变得非常重要)。

示例:这两个循环之间可能发生了导致问题的事情。我宁愿让人们从怀疑中获益,但如果是这种情况,您可能应该让我们知道。


(b) 它实际上会被第一个循环的第一个 迭代 清空,所以你会发现它可能会只匹配第一枚导弹。

这是一个简单的示例,显示(在 PyGame 1.9.6 中)这种报告的行为不会发生。

该示例创建两个 sprite groups,然后以与 OP 的示例代码相同的方式 精确 碰撞它们。

与打印一样,精灵也会从轮廓变为填充 -> 填充,这取决于它们是否认为自己参与了碰撞。有一个敌人与导弹相撞的 1:1 映射,反之亦然。

对于糟糕的帧率表示歉意...

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

pygame.sprite.spritecollide():

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

因此 spritecollide() 的参数必须是 pygame.sprite.Sprite object and a pygame.sprite.Group 对象。
pygame.sprite.Sprite 对象列表而不是 不起作用。

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

进一步了解 kill()

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

因此,如果您在第一个循环中调用 kill(),第二个循环将不起作用,因为精灵已从 all 个组中移除。

您在 reset 方法中调用 kill()missile.reset() 分别 eachEnemy.reset() 导致第二个循环失败。