86113
PythonのライブラリPygameの当たり判定についての質問です。  
クラスBlockで画面上に壁を出現させて、矢印キーで自由に動かせるplayerの障害物にするという目的で書いたコードなのですが、なぜかL字型の壁の、  
■ ↓  
■ ■  
矢印の壁に接触すると  
■  
■ ■ □←  
矢印の位置にワープしてしまいます。  
これが壁一つでは問題なく動作します。つまりはL字型の壁に配置した時だけ、特定の壁が予期しない挙動を見せます。原因解明をお願いします。

```

    import pygame
    
    pygame.init()
    clock = pygame.time.Clock()
    FPS = 30
    
    screen = pygame.display.set_mode((320, 320))
    pygame.display.set_caption('World_Collision')
    
    
    class Player(pygame.sprite.Sprite):
    
        def __init__(self):
            super().__init__()
            self.image = pygame.Surface((32, 32))
            self.image.fill((0, 0, 255))
            self.rect = self.image.get_rect(center=(160, 160))
            self.speed = 5
    
        def move(self):
            dx = dy = 0
            if move_left:
                dx -= self.speed
            if move_right:
                dx += self.speed
            if move_up:
                dy -= self.speed
            if move_down:
                dy += self.speed
            self.rect.x += dx
            for block in block_group.sprites():
                block.collide_x()
            self.rect.y += dy
            for block in block_group.sprites():
                block.collide_y()
    
        def update(self):
            self.move()
    
    
    player_group = pygame.sprite.Group()
    player = Player()
    player_group.add(player)
    
    move_left = False
    move_right = False
    move_up = False
    move_down = False
    
    
    class Block(pygame.sprite.Sprite):
    
        def __init__(self, x, y):
            super().__init__()
            self.image = pygame.Surface((32, 32))
            self.image.fill((255, 0, 0))
            self.rect = self.image.get_rect(topleft=(x, y))
    
        @staticmethod
        def create_block():
            for row_index, row in enumerate(Level):
                for column_index, column in enumerate(row):
                    if column == 1:
                        block_group.add(Block(column_index * 32, row_index * 32))
    
        def collide_x(self):
            if pygame.sprite.spritecollideany(player, block_group):
                if self.rect.left < player.rect.left < self.rect.right:
                    player.rect.left = self.rect.right
                if self.rect.left < player.rect.right < self.rect.right:
                    player.rect.right = self.rect.left
    
        def collide_y(self):
            if pygame.sprite.spritecollideany(player, block_group):
                if self.rect.top < player.rect.top < self.rect.bottom:
                    player.rect.top = self.rect.bottom
                if self.rect.top < player.rect.bottom < self.rect.bottom:
                    player.rect.bottom = self.rect.top
    
    
    block_group = pygame.sprite.Group()
    Level = [
        [0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
        [0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
        [0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
        [0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
        [0, 0, 0, 0, 0, 0, 0, 1, 0, 0],
        [0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
        [0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
        [0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
        [0, 0, 0, 0, 1, 0, 0, 0, 0, 0],
        [0, 0, 0, 0, 1, 1, 0, 0, 0, 0],
    ]
    Block.create_block()
    
    while True:
    
        screen.fill((0, 0, 0))
    
        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                pygame.quit()
    
            if event.type == pygame.KEYDOWN:
                if event.key == pygame.K_LEFT:
                    move_left = True
                if event.key == pygame.K_RIGHT:
                    move_right = True
                if event.key == pygame.K_UP:
                    move_up = True
                if event.key == pygame.K_DOWN:
                    move_down = True
            if event.type == pygame.KEYUP:
                if event.key == pygame.K_LEFT:
                    move_left = False
                if event.key == pygame.K_RIGHT:
                    move_right = False
                if event.key == pygame.K_UP:
                    move_up = False
                if event.key == pygame.K_DOWN:
                    move_down = False
    
        player_group.draw(screen)
        player_group.update()
    
        block_group.draw(screen)
        block_group.update()
    
        pygame.display.flip()
        clock.tick(FPS)
    
```
CC BY-SA 4.0
2022-02-02T16:54:12.097
86105
2022-02-03T07:59:31.727
51078
question
python pygame
Python Pygame 当たり判定について
`collide_x` と `collide_y` の定義に問題があります。

`pygame.sprite.spritecollideany` はどれかのブロックと衝突している場合に真になりますが、Y 方向に衝突している場合でも
`collide_x` 中で真になるので、そのまま `collide_x` の中の、どのブロックにめりこもうとしているのかという部分に進み、L
字型に右に飛び出した部分に X 方向的に左側からめり込んでいるものと判断され、右に飛び出したブロックの右端まで押し戻されます。

その後、Y 方向の移動処理が引き続き行われ、下に進んでいくわけです。

今回の質問に即した応急処置的には、`pygame.sprite.spritecollideany` で真になった場合、さらに、
**どのブロックと衝突しているのか？** を `pygame`
の命令を利用して行います。[`pygame.sprite.spritecollide`](https://www.pygame.org/docs/ref/sprite.html#pygame.sprite.spritecollide)
がそれですので、例えば次のようになります：

```

    def collide_x(self):
        if pygame.sprite.spritecollideany(player, block_group):
            colliders = pygame.sprite.spritecollide(player, block_group, False)
            if colliders[0].rect.left < player.rect.left < colliders[0].rect.right:
                player.rect.left = colliders[0].rect.right
            elif colliders[0].rect.left < player.rect.right < colliders[0].rect.right:
                player.rect.right = colliders[0].rect.left
    
    def collide_y(self):
        if pygame.sprite.spritecollideany(player, block_group):
            colliders = pygame.sprite.spritecollide(player, block_group, False)
            if colliders[0].rect.top < player.rect.top < colliders[0].rect.bottom:
                player.rect.top = colliders[0].rect.bottom
            elif colliders[0].rect.top < player.rect.bottom < colliders[0].rect.bottom:
                player.rect.bottom = colliders[0].rect.top
    
```

このコードはあくまでも今回の問題に即した解決策ですので、これが最良ではありません。`pygame.sprite.spritecollideany` と
`pygame.sprite.spritecollide` を使えば、そもそも下のように全ブロックを for
ループでチェックすること自体が冗長になります。が、今回の問題の主旨からは外れますので、詳しい説明は割愛しますが、

```

    self.rect.x += dx
    for block in block_group.sprites():
        block.collide_x()
    self.rect.y += dy
    for block in block_group.sprites():
        block.collide_y()
    
```

この部分を次のようにしてブロックグループ全体で 1 回ずつ実行します：

```

    self.rect.x += dx
    Block.collide_x()
    self.rect.y += dy
    Block.collide_y()
    
```

`Block#collide_x` と `Block#collide_y` は、スタティックメソッドに書き換えます。

```

    @staticmethod
    def collide_x():
        # 略
    
    @staticmethod
    def collide_y():
        # 略
    
```
CC BY-SA 4.0
2022-02-03T05:08:39.140
86113
2022-02-03T07:59:31.727
2022-02-03T07:59:31.727
7290
7290
86105
answer