For Loop blocca altri For Loop
Sto controllando missileGroup per vedere se qualche istanza di missile è entrato in collisione con qualsiasi istanza nemica in nemicoGroup. Quando viene eseguito, stampa "Hit" per il primo ciclo, ma ignora il secondo ciclo for. Perché?
#### 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")
Aggiornamento : @ Rabbid76 ha affermato che spritecollidenon funzionerebbe perché spriteGroup enemyGroupè un elenco di sprite all'interno di un gruppo (nemicoGroup <- nemicoList <- Enemy (sprite)) invece di un gruppo di sprite (nemicoGroup <- Enemy (sprite)). Come potrei accedervi?
L'aggiornamento 2 @paxdiablo ha affermato che il primo ciclo potrebbe svuotare il gruppo dopo l'iterazione. Ho cambiato le posizioni dei loop e il 2 ° loop è stato eseguito, mentre il 1 ° no.
Aggiornamento 3 Nel codice completo, .reset()viene eseguito il metodo .kill()che rimuove lo sprite dal gruppo. Poiché il primo loop rimuove lo sprite del missile prima che il secondo loop non sia stato in grado di rilevare alcuna collisione:
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()
Risposte
Vedi pygame.sprite.spritecollide():
Restituisce un elenco contenente tutti gli sprite in un gruppo che si intersecano con un altro sprite.
Pertanto gli argomenti per spritecollide()devono essere un pygame.sprite.Spriteoggetto e un pygame.sprite.Groupoggetto.
Un elenco di pygame.sprite.Spriteoggetti al posto del gruppo non funziona.
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")
Inoltre leggi kill()
Lo Sprite viene rimosso da tutti i gruppi che lo contengono.
Quindi se chiami kill()nel primo ciclo, il secondo ciclo non funzionerà, perché lo sprite viene rimosso da tutti i gruppi.
Chiami kill()i resetmetodi. missile.reset()rispettivamente eachEnemy.reset()fa fallire il secondo ciclo.
Non c'è alcun motivo ovvio, in base alle informazioni fornite (a) , per cui il secondo controllo di collisione dovrebbe fallire. Se c'è una collisione tra (ad esempio) il nemico n. 7 e il missile n. 3, dovrebbe esserci anche una collisione tra il missile n. 3 e il nemico n. 7.
Non stai usando alcun elemento edge-case come fornire la tua funzione di collisione (possibilmente non commutativa), quindi userà semplicemente il rettangolo dello sprite per rilevarlo.
Sarei curioso di vedere il comportamento quando si inverte l'ordine dei due cicli nel codice.
Inoltre, è necessario specificare i tipi di tali variabili di gruppo. Se enemyGroupfosse qualcosa come un generatore esauribile piuttosto che un elenco, verrebbe "svuotato" dal primo ciclo e poi il secondo non avrebbe elementi da iterare su (b) (la spritecollidechiamata itererà sul gruppo per controllare ogni elemento contro lo sprite).
Questo è l'unico modo, a parte un bug in spritecollidesé, per vedere gli effetti che stai descrivendo.
A titolo di esempio, ecco un pezzo di codice che cerca di iterare due volte su un generatore:
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)
L'output mostra che il secondo ciclo non fa nulla:
A:
0
1
2
B:
Infine, un modo definitivo per controllare lo stato dei gruppi è semplicemente inserire il seguente codice prima di ogni ciclo:
print("loop X enemy ", len(enemyGroup), enemyGroup)
print("loop X missile", len(missileGroup), missileGroup)
utilizzando un valore appropriato di Xper distinguere tra i due loop.
(a) Naturalmente, c'è sempre la possibilità che le informazioni che hai fornito non siano completamente accurate o complete (non si suppone alcun intento dannoso, ma a volte le persone saltano inavvertitamente quelli che considerano dettagli non importanti, che finisce per essere molto importante).
Esempio: potrebbe succedere qualcosa tra questi due loop che sta causando il problema. Preferirei concedere alle persone il beneficio del dubbio, ma probabilmente dovresti farci sapere se è così.
(b) Verrebbe effettivamente svuotato dalla prima iterazione del primo ciclo, quindi scopriresti che probabilmente corrisponderebbe sempre e solo al primo missile.
Ecco un rapido esempio che mostra (in PyGame 1.9.6) questo comportamento segnalato non si verifica.
L'esempio crea due gruppi di sprite , quindi li fa collidere esattamente allo stesso modo del codice di esempio dell'OP.
Oltre alla stampa, gli sprite cambiano da contorno -> pieno, a seconda che credano di aver partecipato a una collisione. C'è una mappatura 1: 1 di un nemico in collisione con un missile e viceversa.
Mi scuso per lo scarso 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()