pygame.Surface 和鼠标之间的碰撞检测不工作

Collision detection between pygame.Surface and mouse not working

我正在尝试为像素艺术制作 canvas。

class Canvas:
    def __init__(self):
        self.__blocks = []
        self.__positions = []
        for i in range(1830):
            self.__blocks.append(pygame.Surface((20, 20)).convert())
        for y in range(30):
            y *= 20
            for x in range(61):
                x = x* 20
                self.__positions.append([x, y])
        self.__color = False

    def draw(self, window):
        for i in range(1830):
            self.__color = not self.__color
            if self.__color:
                self.__blocks[i].fill((200, 200, 200))
            else:
                self.__blocks[i].fill((50, 50, 50))
            window.blit(self.__blocks[i], (self.__positions[i][0]
                                , self.__positions[i][1]))

我在这里尝试生成并绘制 1830 个独特的表面,这很有效。然后我尝试在每个块和鼠标之间实现碰撞检测,但失败了。

def collided(self, pos):
      for i in range(1380):
          block = self.__blocks[i].get_rect()
          if block.collidepoint(pos[0], pos[1]):
              print(block.x, block.y)

然后我对它可能失败的原因进行了不同的测试。这是其中之一。我将更改单个块的颜色,在我们的例子中是第 10 个块 self.__blocks[10].fill((255, 0, 0)) 为红色,以便我们知道要单击哪个框。然后我们将尝试检查该特定块的碰撞。

def testBlock(self, pos):
    block = self.__blocks[10].get_rect()
    if block.collidepoint(pos[0], pos[1]):
        print(block.x)

它不起作用,但奇怪的是它对第一个块(在第 0 个索引中)有效,并且无论我测试哪个表面都只对第一个块有效。任何关于如何解决这个问题的想法将不胜感激。以下是复制粘贴代码。

import pygame
pygame.init()

win = pygame.display
D = win.set_mode((1220, 600))

class Canvas:
    def __init__(self):
        self.__blocks = []
        self.__positions = []
        for i in range(1830):
            self.__blocks.append(pygame.Surface((20, 20)).convert())
        for y in range(30):
            y *= 20
            for x in range(61):
                x = x* 20
                self.__positions.append([x, y])
        self.__color = False
        self.testBlock = 10

    def draw(self, window):
        for i in range(1830):
            self.__color = not self.__color
            if self.__color:
                self.__blocks[i].fill((200, 200, 200))
            else:
                self.__blocks[i].fill((50, 50, 50))
            self.__blocks[self.testBlock].fill((255, 0, 0)) # Changing the color for testing 
                                                
            window.blit(self.__blocks[i], (self.__positions[i][0]
                                , self.__positions[i][1]))


    def test(self, pos):
        block = self.__blocks[self.testBlock].get_rect()
        if block.collidepoint(pos[0], pos[1]):
            print(block.x, block.y)


canvas = Canvas()
while True:
    D.fill((0, 0, 0))
    pygame.event.get()
    mousepos = pygame.mouse.get_pos()
    canvas.draw(D)
    canvas.test(mousepos)
    win.flip()

当您在 Surface 上调用 .get_rect() 时,它不知道其当前位置,因为那不是 Surface 信息。所以需要在碰撞检测前将位置赋值给Rect。

使用您当前的代码布局,您可以在构建期间执行此操作。随着 Canvass 块位置现在在 __rects 列表中,__positions 列表变得多余。

class Canvass:
    
    def __init__(self):
        self.__blocks    = []
        self.__rects     = []

        for y in range( 30 ):
            for x in range( 61 ):
                self.__blocks.append(pygame.Surface((20, 20)).convert())
                self.__rects.append( self.__blocks[-1].get_rect() )
                self.__rects[-1].topleft = ( x, y )

        self.__color = False
        self.testBlock = 10

这给你一个简单的测试:

def collided(self, pos):
    hit = False
    for i in range( len( self.__rects ) ):
        if ( self.__rects[i].collidepoint( pos[0], pos[1] ) ):
            print( "Click on block %d" % ( i ) )
            hit = True
            break
    return hit, i
    

.get_rect() 给出具有块大小但位置为 (0, 0)

的矩形

您在 __positions 中有实际职位,您需要

  .get_rect(topleft=self.__positions[self.testBlock])

def test(self, pos):
        block = self.__blocks[self.testBlock].get_rect(topleft=self.__positions[self.testBlock])
        if block.collidepoint(pos[0], pos[1]):
            print(block.x, block.y)

但最好在开始时获取rect并设置其位置,以后不要使用get_rect()

您还可以创建类似于 class Sprite 的 class Pixel,其中 self.image 保持表面,self.rect 保持其大小和位置。然后你可以使用 Group 检查所有像素的碰撞。


编辑:

使用 class pygame.sprite.Sprite to create class Pixel and it keeps all pixels in pygame.sprite.Group

的示例

它还处理事件 (MOUSEBUTTONDOWN) 以在单击时更改任何像素的颜色。

import pygame

# --- classes ---

class Pixel(pygame.sprite.Sprite):
    
    def __init__(self, x, y, color, width=20, height=20):
        super().__init__()
        self.color_original = color
        
        self.color = color

        self.image = pygame.Surface((20, 20)).convert()
        self.image.fill(self.color)

        self.rect = pygame.Rect(x, y, width, height)
        
    def handle_event(self, event):
        if event.type == pygame.MOUSEBUTTONDOWN:

            if self.rect.collidepoint(event.pos):
                if self.color != self.color_original:
                    self.color = self.color_original
                else:
                    self.color = (255,0,0)
                self.image.fill(self.color)
                # event handled
                return True
            
        # event not handled
        return False
         
class Canvas:
    
    def __init__(self):
        # create group for sprites
        self.__blocks = pygame.sprite.Group()

        # create sprites
        self.__color = False
    
        for y in range(30):
            y *= 20
            for x in range(61):
                x *= 20
                self.__color = not self.__color
                if self.__color:
                    color = (200, 200, 200)
                else:
                    color = (50, 50, 50)
                self.__blocks.add(Pixel(x, y, color))
                    
        # changing the color for testing 
        self.testBlock = 10
        
        all_sprites = self.__blocks.sprites()
        block = all_sprites[self.testBlock]
        
        block.image.fill((255, 0, 0))

    def draw(self, window):
        # draw all sprites in group
        self.__blocks.draw(window)

    def test(self, pos):
        # test collision with one sprite
        all_sprites = self.__blocks.sprites()
        block = all_sprites[self.testBlock]
        if block.rect.collidepoint(pos):
            print(block.rect.x, block.rect.y)
            
    def handle_event(self, event):
        for item in self.__blocks:
            if item.handle_event(event):
                # don't check other pixels if event already handled
                return True
            
# --- main ---

pygame.init()

win = pygame.display
D = win.set_mode((1220, 600))

canvas = Canvas()
while True:

    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            pygame.quit()
            exit()
        canvas.handle_event(event)
        
    #mousepos = pygame.mouse.get_pos()
    #canvas.test(mousepos)

    # draws (without updates, etc)
    #D.fill((0, 0, 0)) # no need clean screen if it will draw all elements again
    canvas.draw(D)
    win.flip()