For Loop blokuje inną pętlę For

Nov 29 2020

Sprawdzam grupę pocisków, aby zobaczyć, czy jakiekolwiek wystąpienia pocisku nie zderzyły się z jakąkolwiek instancją wroga w grupie przeciwników. Po uruchomieniu drukuje "Hit" dla pierwszej pętli, ale ignoruje drugą pętlę for. Dlaczego?

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

Aktualizacja : @ Rabbid76 stwierdził, że spritecollideto nie zadziała, ponieważ spriteGroup enemyGroupto lista sprite'ów w grupie (wrogiej grupie <- wrogaLista <- Wróg (sprite)) zamiast grupy duszków (wrogaGroup <- Wróg (sprite)). Jak miałbym do tego uzyskać dostęp?

Update 2 @paxdiablo stwierdził, że pierwsza pętla może opróżnić grupę po iteracji. Zamieniłem miejsca pętli i biegła druga pętla, podczas gdy pierwsza nie.

Aktualizacja 3 W pełnym kodzie zostaje .reset()uruchomiona metoda, .kill()która usuwa duszka z grupy. Ponieważ pierwsza pętla usuwa duszkę pocisku, zanim druga pętla nie wykryje kolizji:

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

Odpowiedzi

Rabbid76 Nov 29 2020 at 21:21

Zobacz pygame.sprite.spritecollide():

Zwróć listę zawierającą wszystkie duszki w grupie, które przecinają się z innym duchem.

Dlatego argumenty spritecollide()muszą być pygame.sprite.Spriteprzedmiotem i pygame.sprite.Groupprzedmiotem.
Lista pygame.sprite.Spriteobiektów zamiast grupy nie działa.

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

Przeczytaj również o kill()

Sprite jest usuwany ze wszystkich grup, które go zawierają.

Dlatego jeśli wywołasz kill()w pierwszej pętli, druga pętla nie zadziała, ponieważ duszek zostanie usunięty ze wszystkich grup.

Dzwonisz kill()w resetmetodach. missile.reset()odpowiednio eachEnemy.reset()powoduje niepowodzenie drugiej pętli.

paxdiablo Nov 29 2020 at 07:26

Na podstawie dostarczonych informacji (a) nie ma oczywistego powodu, dla którego druga kontrola kolizji miałaby się nie powieść. W przypadku kolizji (na przykład) przeciwnika nr 7 i pocisku nr 3, powinno również dojść do zderzenia pocisku nr 3 z wrogiem nr 7.

Nie używasz żadnych skrajnych rzeczy, takich jak dostarczanie własnej (prawdopodobnie nieprzemiennej) funkcji kolizji, więc po prostu użyje prostokąta sprite, aby to wykryć.

Byłbym ciekawy, jak zachowuje się odwrócenie kolejności dwóch pętli w kodzie.


Powinieneś także określić typy tych zmiennych grupowych. Gdyby enemyGroupbył czymś w rodzaju wyczerpującego się generatora, a nie listy, zostałby "opróżniony" przez pierwszą pętlę, a następnie druga pętla nie miałaby elementów do iteracji po (b) ( spritecollidewywołanie będzie iterować po grupie, aby sprawdzić każdy element przeciwko duszkowi).

To prawie jedyny sposób, pomijając błąd spritecollidesam w sobie, aby zobaczyć opisywane efekty.


Na przykład, oto fragment kodu, który dwukrotnie próbuje iterować po generatorze:

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)

Wynik pokazuje, że druga pętla nic nie robi:

A:
  0
  1
  2
B:

Ostatnim sposobem sprawdzenia stanu grup jest po prostu umieszczenie następującego kodu przed każdą pętlą:

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

używając odpowiedniej wartości Xdo rozróżnienia między dwiema pętlami.


(a) Oczywiście zawsze istnieje możliwość , że podane przez Ciebie informacje nie są w pełni dokładne lub kompletne (nie zakłada się złego zamiaru, ale czasami ludzie nieumyślnie pomijają to, co uważają za nieważne szczegóły, które ostatecznie okazują się bardzo ważne).

Przykład: coś może się dziać między tymi dwoma pętlami, co jest przyczyną problemu. Wolałbym dać ludziom korzyść w postaci wątpliwości, ale prawdopodobnie powinieneś nas o tym powiadomić.


(b) W rzeczywistości zostałby opróżniony przez pierwszą iterację pierwszej pętli, więc okaże się, że prawdopodobnie pasowałby tylko do pierwszego pocisku.

Kingsley Nov 29 2020 at 07:51

Oto krótki przykład pokazujący (w PyGame 1.9.6) to zgłoszone zachowanie nie występuje.

Przykład tworzy dwie grupy sprite , a następnie zderza je w dokładnie taki sam sposób jak przykład kodem OP.

Oprócz drukowania, duszki zmieniają się z konturów -> wypełnione, w zależności od tego, czy uważają, że uczestniczyły w kolizji. Istnieje mapowanie 1: 1 wroga zderzającego się z pociskiem i odwrotnie.

Przepraszamy za kiepską liczbę klatek na sekundę ...

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