For Loop блокирует другой For Loop
Я проверяю missileGroup, чтобы увидеть, столкнулись ли какие-либо экземпляры ракеты с какими-либо экземплярами врага в группе врагов. При запуске он печатает «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- это список спрайтов внутри группы (вражеская группа <- вражеский список <- Враг (спрайт)) вместо группы спрайтов (вражеская группа <- Враг (спрайт)). Как мне получить к нему доступ?
В обновлении 2 @paxdiablo указано, что первый цикл может опустошить группу после итерации. Я поменял местами петли и 2-я петля пробежала, а 1-я - нет.
Обновление 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()
Ответы
Смотрите 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-го цикла.
На основе предоставленной информации (а) нет очевидной причины, по которой вторая проверка столкновения должна завершиться неудачно. Если есть столкновение между (например) противника # 7 и # 3 ракеты, должно также быть столкновение ракеты # 3 и # 7 противника.
Вы не используете какие-либо пограничные элементы, такие как предоставление собственной (возможно, некоммутативной) функции столкновения, поэтому она просто будет использовать прямоугольник спрайта для обнаружения этого.
Мне было бы любопытно увидеть поведение, когда вы меняете порядок двух циклов в коде.
Кроме того, вы должны указать типы этих групповых переменных. Если бы это enemyGroupбыл что-то вроде исчерпываемого генератора, а не списка, он был бы «опустошен» первым циклом, и тогда во втором цикле не было бы элементов для итерации (b) ( spritecollideвызов будет повторяться по группе для проверки каждого элемента против спрайта).
Это почти единственный способ, если не считать 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) Фактически он будет опустошен при первой итерации первого цикла, поэтому вы обнаружите, что он, вероятно, когда-либо будет соответствовать только первой ракете.
Вот быстрый пример, показывающий (в PyGame 1.9.6) такого поведения, о котором сообщалось, не происходит.
В примере создаются две группы спрайтов , а затем они сталкиваются точно так же, как в примере кода 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()