Untuk Loop memblokir lainnya For Loop

Nov 29 2020

Saya memeriksa misilGroup untuk melihat apakah ada misil yang bertabrakan dengan musuh dalam kelompok musuh. Saat dijalankan, ia mencetak "Hit" untuk loop pertama, tetapi mengabaikan loop kedua untuk. Mengapa demikian?

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

Pembaruan : @ Rabbid76 menyatakan itu spritecollidetidak akan berfungsi karena spriteGroup enemyGroupadalah daftar sprite dalam grup (musuhGroup <- musuhList <- Musuh (sprite)) bukan grup sprite (musuhGroup <- Enemy (sprite)). Bagaimana saya mengaksesnya?

Perbarui 2 @paxdiablo menyatakan bahwa loop pertama mungkin mengosongkan grup setelah iterasi. Saya mengganti tempat loop dan loop ke-2 berjalan, sedangkan loop pertama tidak.

Perbarui 3 Dalam kode lengkap, .reset()metode berjalan .kill()yang menghapus sprite dari grup. Karena loop pertama menghilangkan sprite rudal sebelum loop kedua tidak dapat mendeteksi tabrakan apa pun:

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

Jawaban

Rabbid76 Nov 29 2020 at 21:21

Lihat pygame.sprite.spritecollide():

Kembalikan daftar yang berisi semua Sprite dalam Grup yang berpotongan dengan Sprite lain.

Oleh karena itu, argumen spritecollide()harus menjadi pygame.sprite.Spriteobjek dan pygame.sprite.Groupobjek.
Daftar pygame.sprite.Spriteobjek bukannya Grup tidak bekerja.

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

Selanjutnya baca tentang kill()

Sprite dihapus dari semua Grup yang berisi itu.

Oleh karena itu, jika Anda memanggil kill()di loop pertama, loop kedua tidak akan berfungsi, karena sprite dihapus dari semua Grup.

Anda menelepon kill()di resetmetode. missile.reset()masing-masing eachEnemy.reset()menyebabkan loop ke-2 gagal.

paxdiablo Nov 29 2020 at 07:26

Tidak ada alasan yang jelas, berdasarkan informasi yang diberikan (a) , mengapa pemeriksaan tabrakan kedua gagal. Jika ada tabrakan antara (misalnya) musuh # 7 dan rudal # 3, harus juga menjadi tabrakan antara rudal # 3 dan musuh # 7.

Anda tidak menggunakan hal-hal edge-case seperti menyediakan fungsi tabrakan Anda sendiri (mungkin non-komutatif), jadi ini hanya akan menggunakan persegi panjang sprite untuk mendeteksi ini.

Saya akan penasaran untuk melihat perilaku saat Anda membalik urutan dua loop dalam kode.


Selain itu, Anda harus menentukan jenis variabel grup tersebut. Jika enemyGroupada sesuatu seperti generator yang bisa habis daripada daftar, itu akan "dikosongkan" oleh loop pertama dan kemudian loop kedua tidak akan memiliki item untuk diiterasi (b) ( spritecollidepanggilan akan mengulang grup untuk memeriksa setiap item melawan sprite).

Itu tentang satu-satunya cara, selain bug spritecollideitu sendiri, Anda akan melihat efek yang Anda gambarkan.


Sebagai contoh, berikut adalah potongan kode yang mencoba untuk mengulang generator dua kali:

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)

Outputnya menunjukkan bahwa loop kedua tidak melakukan apa-apa:

A:
  0
  1
  2
B:

Terakhir, cara yang pasti untuk memeriksa status grup adalah dengan meletakkan kode berikut sebelum setiap loop:

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

menggunakan nilai yang sesuai Xuntuk membedakan antara dua loop.


(a) Tentu saja, selalu ada kemungkinan informasi yang Anda berikan tidak sepenuhnya akurat atau lengkap (tidak ada niat jahat yang diharapkan tetapi terkadang orang secara tidak sengaja melewatkan apa yang mereka anggap sebagai detail yang tidak penting, yang akhirnya menjadi sangat penting).

Contoh: mungkin ada sesuatu yang terjadi di antara dua loop tersebut yang menyebabkan masalah. Saya lebih suka memberi orang keuntungan dari keraguan tetapi Anda mungkin harus memberi tahu kami jika itu masalahnya.


(b) Ini sebenarnya akan dikosongkan oleh iterasi pertama dari loop pertama sehingga Anda akan menemukan bahwa itu mungkin hanya cocok untuk rudal pertama.

Kingsley Nov 29 2020 at 07:51

Berikut adalah contoh singkat yang menunjukkan (di PyGame 1.9.6) perilaku yang dilaporkan ini tidak terjadi.

Contoh tersebut membuat dua grup sprite , lalu menumbuknya dengan cara yang sama persis seperti kode contoh OP.

Selain pencetakan, sprite juga berubah dari outline -> terisi, tergantung apakah mereka yakin telah berpartisipasi dalam tabrakan. Ada pemetaan 1: 1 dari Musuh yang bertabrakan dengan Rudal, dan sebaliknya.

Mohon maaf atas frame-rate yang buruk ...

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