Chặn vòng lặp For khác For Loop

Nov 29 2020

Tôi đang kiểm tra tên lửa Group để xem có trường hợp tên lửa nào va chạm với bất kỳ trường hợp nào của kẻ thù trong nhóm đối phương hay không. Khi chạy, nó in "Lượt truy cập" cho vòng lặp đầu tiên, nhưng nó bỏ qua vòng lặp for thứ hai. Tại sao vậy?

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

Cập nhật : @ Rabbid76 đã nói rằng điều đó spritecollidesẽ không hoạt động vì spriteGroup enemyGrouplà danh sách các sprite trong một nhóm (địchGroup <- địchList <- Enemy (sprite)) thay vì một nhóm sprite (địchGroup <- Enemy (sprite)). Làm cách nào để truy cập vào đó?

Cập nhật 2 @paxdiablo cho biết rằng vòng lặp đầu tiên có thể làm trống nhóm sau khi lặp lại. Tôi đã chuyển đổi vị trí của các vòng lặp và vòng lặp thứ 2 chạy, trong khi vòng lặp thứ nhất thì không.

Cập nhật 3 Trong mã đầy đủ, .reset()phương thức chạy .kill()sẽ xóa sprite khỏi nhóm. Vì vòng lặp đầu tiên loại bỏ mô hình tên lửa trước khi vòng lặp thứ hai không thể phát hiện bất kỳ va chạm nào:

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

Trả lời

Rabbid76 Nov 29 2020 at 21:21

Xem pygame.sprite.spritecollide():

Trả về danh sách chứa tất cả các Sprite trong một Nhóm giao nhau với một Sprite khác.

Do đó, các đối số spritecollide()phải là một pygame.sprite.Spriteđối tượng và một pygame.sprite.Groupđối tượng.
Danh sách các pygame.sprite.Spriteđối tượng thay vì Nhóm không hoạt động.

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

Hơn nữa đọc về kill()

Sprite bị xóa khỏi tất cả các Nhóm chứa nó.

Do đó, nếu bạn gọi kill()trong vòng lặp thứ nhất, thì vòng lặp thứ hai sẽ không hoạt động, vì sprite bị xóa khỏi tất cả các Nhóm.

Bạn gọi kill()các resetphương thức. missile.reset()tương ứng eachEnemy.reset()làm cho vòng lặp thứ 2 bị lỗi.

paxdiablo Nov 29 2020 at 07:26

Không có lý do rõ ràng nào, dựa trên thông tin được cung cấp (a) , tại sao việc kiểm tra va chạm thứ hai không thành công. Nếu có một vụ va chạm giữa (ví dụ) kẻ thù số 7 và tên lửa số 3, thì cũng sẽ có một vụ va chạm giữa tên lửa số 3 và kẻ thù số 7.

Bạn không sử dụng bất kỳ thứ gì có dạng cạnh như cung cấp hàm va chạm (có thể không giao hoán) của riêng bạn, vì vậy nó sẽ chỉ sử dụng hình chữ nhật sprite để phát hiện điều này.

Tôi rất tò mò muốn xem hành vi khi bạn đảo ngược thứ tự của hai vòng lặp trong mã.


Ngoài ra, bạn nên chỉ định loại của các biến nhóm đó. Nếu enemyGrouplà một cái gì đó giống như một trình tạo có thể sử dụng thay vì một danh sách, nó sẽ bị "làm trống" bởi vòng lặp đầu tiên và sau đó vòng lặp thứ hai sẽ không có mục nào để lặp qua (b) (lệnh spritecollidegọi sẽ lặp lại nhóm để kiểm tra từng mục chống lại sprite).

Đó là cách duy nhất, chỉ thiếu một lỗi spritecollide, mà bạn sẽ thấy các hiệu ứng mà bạn đang mô tả.


Ví dụ, đây là một đoạn mã cố gắng lặp lại hai lần trên một trình tạo:

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)

Kết quả đầu ra cho thấy rằng vòng lặp thứ hai không làm gì cả:

A:
  0
  1
  2
B:

Cuối cùng, một cách rõ ràng để kiểm tra trạng thái của các nhóm là chỉ cần đặt đoạn mã sau trước mỗi vòng lặp:

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

sử dụng giá trị phù hợp Xđể phân biệt giữa hai vòng lặp.


(a) Tất nhiên, luôn có khả năng thông tin bạn đã cung cấp không hoàn toàn chính xác hoặc không đầy đủ (không có ý định xấu nhưng đôi khi mọi người vô tình bỏ qua những gì họ coi là chi tiết không quan trọng, nhưng cuối cùng lại rất quan trọng).

Ví dụ: có thể có điều gì đó xảy ra giữa hai vòng lặp đó gây ra sự cố. Tôi muốn cung cấp cho mọi người lợi ích của sự nghi ngờ nhưng bạn có thể nên cho chúng tôi biết nếu đó là trường hợp.


(b) Nó thực sự sẽ được làm trống bởi lần lặp đầu tiên của vòng lặp đầu tiên, vì vậy bạn sẽ thấy rằng nó có thể sẽ chỉ khớp với tên lửa đầu tiên.

Kingsley Nov 29 2020 at 07:51

Dưới đây là một ví dụ nhanh cho thấy (trong PyGame 1.9.6) hành vi được báo cáo này không xảy ra.

Ví dụ tạo ra hai nhóm ma , sau đó va chạm chúng trong chính xác cùng một cách như mã ví dụ của OP.

Cũng như khi in, các sprites thay đổi từ phác thảo -> lấp đầy, tùy thuộc vào việc chúng có tin rằng chúng đã tham gia vào một vụ va chạm hay không. Có một bản đồ 1: 1 về Kẻ thù va chạm với Tên lửa và ngược lại.

Xin lỗi vì tốc độ khung hình kém ...

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