For Loop bloqueando otro For Loop
Estoy revisando missileGroup para ver si alguna instancia de misil colisionó con alguna instancia enemiga en enemigoGroup. Cuando se ejecuta, imprime "Hit" para el primer ciclo, pero ignora el segundo ciclo for. ¿Porqué es eso?
#### 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")
Actualización : @ Rabbid76 declaró que spritecollide
no funcionaría porque spriteGroup enemyGroup
es una lista de sprites dentro de un grupo (EnemyGroup <- EnemyList <- Enemy (sprite)) en lugar de un grupo de sprites (EnemyGroup <- Enemy (sprite)). ¿Cómo accedería a eso?
La actualización 2 @paxdiablo indicó que el primer ciclo tal vez vaciará el grupo después de iterar. Cambié los lugares de los bucles y el segundo bucle se ejecutó, mientras que el primero no.
Actualización 3 En el código completo, .reset()
se ejecuta el método .kill()
que elimina el objeto del grupo. Dado que el primer ciclo elimina el sprite del misil antes de que el segundo ciclo no pudiera detectar ninguna colisión:
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()
Respuestas
Ver pygame.sprite.spritecollide():
Devuelve una lista que contiene todos los Sprites de un grupo que se cruzan con otro Sprite.
Por lo tanto, los argumentos de spritecollide()
deben ser un pygame.sprite.Spriteobjeto y un pygame.sprite.Groupobjeto.
Una lista de pygame.sprite.Sprite
objetos en lugar del Grupo no funciona.
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")
Además lea sobre kill()
El Sprite se elimina de todos los Grupos que lo contienen.
Por lo tanto, si llama kill()
en el primer ciclo, el segundo ciclo no funcionará, porque el sprite se elimina de todos los grupos.
Llamas kill()
a los reset
métodos. missile.reset()
respectivamente, eachEnemy.reset()
hace que falle el segundo bucle.
No hay una razón obvia, basada en la información proporcionada (a) , por la que la segunda verificación de colisión deba fallar. Si hay una colisión entre (por ejemplo) enemigo # 7 y # 3 misiles, no debería también ser una colisión entre misil enemigo # 3 y # 7.
No está utilizando ningún elemento de caso de borde como proporcionar su propia función de colisión (posiblemente no conmutativa), por lo que simplemente usará el rectángulo de sprite para detectar esto.
Tendría curiosidad por ver el comportamiento cuando inviertes el orden de los dos bucles en el código.
Además, debe especificar los tipos de esas variables de grupo. Si enemyGroup
fuera algo así como un generador agotable en lugar de una lista, sería "vaciado" por el primer ciclo y luego el segundo ciclo no tendría elementos sobre los que iterar (b) (la spritecollide
llamada iterará sobre el grupo para verificar cada elemento contra el sprite).
Esa es la única forma, salvo un error en spritecollide
sí mismo, de ver los efectos que está describiendo.
A modo de ejemplo, aquí hay un fragmento de código que intenta iterar sobre un generador dos veces:
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)
La salida muestra que el segundo ciclo no hace nada:
A:
0
1
2
B:
Por último, una forma definitiva de comprobar el estado de los grupos es simplemente poner el siguiente código antes de cada bucle:
print("loop X enemy ", len(enemyGroup), enemyGroup)
print("loop X missile", len(missileGroup), missileGroup)
utilizando un valor adecuado de X
para distinguir entre los dos bucles.
(a) Por supuesto, siempre existe la posibilidad de que la información que ha proporcionado no sea del todo precisa o completa (no se supone ninguna intención malintencionada, pero a veces las personas se saltan sin darse cuenta detalles que consideran sin importancia, que terminan siendo muy importantes).
Ejemplo: puede haber algo entre esos dos bucles que está causando el problema. Preferiría darles a las personas el beneficio de la duda, pero probablemente debería informarnos si ese es el caso.
(b) En realidad, se vaciaría con la primera iteración del primer bucle, por lo que probablemente solo coincidiría con el primer misil.
Aquí hay un ejemplo rápido que muestra (en PyGame 1.9.6) que este comportamiento informado no ocurre.
El ejemplo crea dos grupos de sprites , luego los colisiona exactamente de la misma manera que el código de ejemplo del OP.
Además de imprimir, los sprites cambian de contorno -> relleno, dependiendo de si creen que han participado en una colisión. Hay un mapeo 1: 1 de un Enemigo chocando con un Misil, y viceversa.
Disculpas por la baja velocidad de fotogramas ...

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