【问题标题】:For Loop blocking other For LoopFor 循环阻塞其他 For 循环
【发布时间】:2020-11-28 23:31:19
【问题描述】:

我正在检查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

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

更新 3 在完整代码中,.reset() 方法运行 .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()

【问题讨论】:

  • 第一个循环中没有任何内容可以阻止第二个循环运行。 enemyGroup 为空,或者 spritecollide 函数总是返回 false。
  • 您为该问题删除的两个循环之间是否有代码?如果导弹与enemyGroup中的某物相撞,则必须有敌人与missileGroup相撞。
  • 你用的是什么版本的 PyGame?
  • @Rabbid76 是的,missileGroup 是一堆精灵,而enemyGroup 是列表中的一堆精灵(不确定是否重要)
  • @Kingsley 3.8.5

标签: python loops pygame collision-detection


【解决方案1】:

pygame.sprite.spritecollide():

返回一个列表,其中包含一个组中与另一个 Sprite 相交的所有 Sprite。

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

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

Sprite 将从包含它的所有组中删除。

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

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

【讨论】:

【解决方案2】:

根据所提供的信息(a),没有明显的原因导致第二次碰撞检查失败。如果(例如)敌人 #7 和导弹 #3 之间发生碰撞,那么导弹 #3 和敌人 #7 之间发生碰撞。

你没有使用任何边缘情况的东西,比如提供你自己的(可能是非交换的)碰撞函数,所以它只会使用精灵矩形来检测这个。

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


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

除了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) 它实际上会被第一个循环的第一个迭代清空,所以你会发现它可能只匹配第一个导弹。

【讨论】:

  • 当第一个循环被注释掉或者循环被切换时,第二个循环(enemyGroup)将运行。正如你所说,之前它可能被第一个循环的第一次迭代清空,我怎么知道?
  • 有没有办法运行一个 for 循环来检查两个列表或组中的所有项目?我想在 missileenemy 上运行一个名为 .reset 的方法,但只在发生碰撞的项目上运行,而不是在所有项目上运行。
【解决方案3】:

这是一个简单的示例,显示(在 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()

【讨论】:

  • @paxdiablo 提到它可能已经被第一个循环的第一次迭代清空了,我怎么知道?
  • @DaeshaunMorrison - 只有当您使用“kill”参数从组中删除碰撞项目时,它才会被清空。您的代码将此指定为 False,因此此处不会发生。
  • 原来我是。 .reset() 运行 kill()
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-08-16
  • 1970-01-01
  • 2012-04-02
  • 1970-01-01
相关资源
最近更新 更多