Forループが他のForループをブロックしている

Nov 29 2020

ミサイルのインスタンスがenemyGroup内の敵のインスタンスと衝突したかどうかを確認するためにミサイルグループをチェックしています。実行すると、最初のループに対して「ヒット」が出力されますが、2番目の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、spriteGroupenemyGroupがスプライトのグループ(enemyGroup <-Enemy(sprite))ではなく、グループ(enemyGroup <-enemyList <-Enemy(sprite))内のスプライトのリストであるため、機能しないと述べました。どうすればアクセスできますか?

アップデート2 @ paxdiabloは、最初のループが反復後にグループを空にする可能性があると述べています。ループの場所を切り替えて、2番目のループが実行されましたが、1番目は実行されませんでした。

更新3完全なコードでは、グループからスプライトを削除する.reset()メソッドが実行さ.kill()れます。最初のループは、2番目のループが衝突を検出できなくなる前にミサイルスプライトを削除するため、次のようになります。

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

回答

Rabbid76 Nov 29 2020 at 21:21

参照pygame.sprite.spritecollide():

別のスプライトと交差するグループ内のすべてのスプライトを含むリストを返します。

したがって、への引数spritecollide()はpygame.sprite.Spriteオブジェクトとオブジェクトでなければなりません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()

スプライトは、それを含むすべてのグループから削除されます

したがってkill()、1番目のループを呼び出すと、スプライトがすべてのグループから削除されるため、2番目のループは機能しません。

あなたは呼んkill()resetメソッド。missile.reset()それぞれeachEnemy.reset()、2番目のループが失敗します。

paxdiablo Nov 29 2020 at 07:26

提供された情報(a)に基づくと、2番目の衝突チェックが失敗する理由は明らかではありません。(たとえば)敵#7とミサイル#3の間に衝突がある場合、ミサイル#3と敵#7の間に衝突あるはずです。

独自の(おそらく非可換)衝突関数を提供するようなエッジケースのものを使用していないので、これを検出するためにスプライト長方形を使用するだけです。

コード内の2つのループの順序を逆にしたときの動作を確認したいと思います。


また、これらのグループ変数のタイプを指定する必要があります。enemyGroupリストではなく枯渇性ジェネレーターのようなものである場合、最初のループによって「空」になり、2番目のループには反復する項目がありません(b)spritecollide呼び出しは各項目をチェックするためにグループを反復しますスプライトに対して)。

これは、バグspritecollide自体を除いて、説明している効果を確認する唯一の方法です。


例として、ジェネレーターを2回反復しようとするコードを次に示します。

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)

出力は、2番目のループが何もしないことを示しています。

A:
  0
  1
  2
B:

最後に、グループの状態を確認する最も確実な方法は、各ループの前に次のコードを配置することです。

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

の適切な値を使用してX、2つのループを区別します。


(a)もちろん、あなたが提供した情報が完全に正確または完全ではない可能性は常にあります(悪意のある意図は想定されていませんが、重要でない詳細と見なされるものを誤ってスキップすることがあり、それは非常に重要になります)。

例:問題の原因となっている2つのループのに何かが起こっている可能性があります。私は人々に疑いの利益を与えることを望みます、しかしあなたはおそらくそれが事実であるかどうか私たちに知らせなければなりません。


(b)実際には、最初のループの最初の反復によって空になるため、おそらく最初のミサイルにのみ一致することがわかります。

Kingsley Nov 29 2020 at 07:51

これは、(PyGame 1.9.6で)この報告された動作が発生しないことを示す簡単な例です。

この例では、2つのスプライトグループを作成し、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()