Problème collisions pygame

Résolu
_rUBEN__ Messages postés 3 Date d'inscription samedi 2 mars 2024 Statut Membre Dernière intervention 4 mars 2024 - 2 mars 2024 à 12:14
_rUBEN__ Messages postés 3 Date d'inscription samedi 2 mars 2024 Statut Membre Dernière intervention 4 mars 2024 - 4 mars 2024 à 20:24

Bonjour, j'ai commencé à coder un petit jeu de plateforme, mais je rencontre un problème avec la détection de collisions, je n'arrive pas à faire tomber le joueur quand il n'est plus sur une plateforme: 

il y a la map et comment elle est dessinée :

map1 = [
    [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, 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, 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, 1],
    [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0],
    [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 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, 0, 0, 0, 0],
]
for lineIndex in range(len(map1)):
            for blockIndex in range(len(map1[lineIndex])):
                blockValue = map1[lineIndex][blockIndex]
                x = blockIndex * blockSize
                y = lineIndex * blockSize
                rect = pygame.Rect(x,y,blockSize,blockSize)
 
                if blockValue == 0:
                    pass
                elif blockValue == 1:
                    pygame.draw.rect(screen, (0, 200, 0), (x, y, blockSize, blockSize))

et les collisions avec le joueur : 

if not blockValue == 0 :
                    if (
                        player.x + player.w > x
                        and player.x < x + blockSize
                        and player.y + player.h > y
                        and player.y + player.h < y + 20
                        and player.jump == False
                    ):
                        player.fall = False
                        player.velocity = 0
                        player.y = y - player.h
                     
                    else:
                        player.fall =True
 
                     
                    if (
                        player.x + player.w > x
                        and player.x < x + blockSize
                        and player.y < y + blockSize
                        and player.y > y + blockSize - 20
                    ):
                        player.y = y + blockSize
                        player.jump=False
                        player.fall=True
                        player.velocity = 0
                     
                    elif (
                        player.x + player.w > x
                        and player.x + player.w < x + player.speed + 2
                        and player.y + player.h > y
                        and player.y < y + blockSize
                    ):
                        player.x -= player.speed
 
                    elif (
                        player.x < x + blockSize
                        and player.x > x + blockSize - player.speed - 2
                        and player.y + player.h > y
                        and player.y < y + blockSize
                    ):
                        player.x += player.speed

C'est la ligne 

else:
    player.fall =True

qui pose problème car le joueur ne peux plus sauter du tout.

Voila voila

Merci si qqun peut m'aider

:)

Windows / Chrome 122.0.0.0

3 réponses

Salut,

Il n'y a pas de méthodes magiques, c'est à toi de gérer ta collision en fonction de l'état et position du joueur par rapport aux éléments.

Un exemple très simplifié de la gestion du joueur en fonction de s'il est en mouvement, en chute,  test de la « collision » avec les plateformes.

Si ça peut te donner une idée de comment faire ça avec des sprites.

import pygame

SCREEN_WIDTH = 800
SCREEN_HEIGHT = 600

FPS = 60
ACCELERATION = 0.458

SCREEN_COLOR = pygame.Color(10, 10, 155, 255)
GROUND_COLOR = pygame.Color(101, 67, 33, 255)
PLATFORM_COLOR = pygame.Color('#ff0000')
PLAYER_BG_COLOR = pygame.Color('lightGreen')
PLAYER_FG_COLOR = pygame.Color('limeGreen')


def sprites_clear(surface, rects):
    surface.fill(SCREEN_COLOR, rects)


def get_sprite_below(top_sprite, sprites):
    '''
    Reourne le sprite parmi "sprites" se situant en dessous
    de top_sprite ou None si aucun n'y est.
    '''
    rect = pygame.Rect(
        top_sprite.rect.x,
        top_sprite.rect.bottom + 1,
        top_sprite.rect.w,
        1,
    )
    for sprite in sprites:
        if rect.colliderect(sprite.rect):
            return sprite


class Ground(pygame.sprite.Sprite):
    def __init__(self, width, height):
        super().__init__()
        self.image = pygame.Surface((width, height)).convert()
        self.image.fill(GROUND_COLOR)
        self.rect = self.image.get_rect()


class Player(pygame.sprite.Sprite):
    def __init__(self):
        super().__init__()
        self.image = pygame.Surface(
            (24, 24), pygame.SRCALPHA
        ).convert_alpha()
        self.image.fill(PLAYER_BG_COLOR)
        self.rect = self.image.get_rect()
        # Yeux
        eye_size = self.rect.w // 6
        for x in (eye_size, self.rect.right - eye_size * 2):
            pygame.draw.rect(
                self.image, PLAYER_FG_COLOR, (x, 6, 4, 4)
            )
        # Bouche
        mouth_rect = pygame.Rect(
            eye_size,
            self.rect.bottom - eye_size * 2,
            self.rect.w - eye_size * 2,
            4,
        )
        pygame.draw.rect(self.image, PLAYER_FG_COLOR, mouth_rect)

        self.jumping = False
        self.falling = False
        self.horizontal_step = 6
        self.vertical_step = 30
        self.acceleration = ACCELERATION

    def move(self, amount):
        self.rect.x += amount

    def move_left(self):
        self.move(-self.horizontal_step)

    def move_right(self):
        self.move(self.horizontal_step)

    def jump(self):
        if not self.jumping and not self.falling:
            self.jumping_step = self.vertical_step
            self.jumping = True

    def update(self):
        if self.jumping:
            self.rect.y -= self.jumping_step * self.acceleration
            self.jumping_step -= 1
            if self.jumping_step == 0:
                self.jumping = False
                self.falling = True
                self.falling_step = 1
        elif self.falling:
            self.rect.y += self.falling_step * self.acceleration
            self.falling_step = min(
                self.vertical_step, self.falling_step + 1
            )


class Platform(pygame.sprite.Sprite):
    def __init__(self, x, y, width, height=16):
        super().__init__()
        self.image = pygame.Surface(
            (width, height), pygame.SRCALPHA
        ).convert_alpha()
        radius = height // 2
        pygame.draw.circle(
            self.image, PLATFORM_COLOR, (radius, radius), radius
        )
        pygame.draw.circle(
            self.image,
            PLATFORM_COLOR,
            (width - radius, radius),
            radius,
        )
        pygame.draw.rect(
            self.image,
            PLATFORM_COLOR,
            (radius, 0, width - radius * 2, height),
        )
        self.rect = self.image.get_rect()
        self.rect.topleft = x, y


class Game:
    def __init__(self, title):
        self.screen = pygame.display.set_mode(
            (SCREEN_WIDTH, SCREEN_HEIGHT)
        )
        pygame.display.set_caption(title)
        self.screen.fill(SCREEN_COLOR)
        pygame.display.update()
        # Rect de la fenêtre
        self.rect = self.screen.get_rect()

        # Groupes de sprites
        self.groups = dict(
            # Groupe de sprites sur lesquels le joueur peut se balader
            walkable=pygame.sprite.Group(),
            # Sprites à afficher à l'écran
            display=pygame.sprite.RenderUpdates(),
        )
        self._init_sprites()
        # Liaisons mouvements latéraux => fonctions joueur
        self._player_motions = {
            pygame.K_LEFT: self.player.move_left,
            pygame.K_RIGHT: self.player.move_right,
        }

    def _init_sprites(self):
        # Inialisations des éléments du jeu
        ground = Ground(self.rect.w, 50)
        ground.rect.bottom = self.rect.bottom
        self.groups['display'].add(ground)
        self.groups['walkable'].add(ground)
        for x, y, width in (
            (150, ground.rect.top - 150, 100),
            (300, ground.rect.top - 300, 100),
            (450, ground.rect.top - 500, 80),
            (400, 320, 65),
            (500, 305, 65),
            (600, 290, 65),
        ):
            platform = Platform(x, y, width)
            self.groups['walkable'].add(platform)
            self.groups['display'].add(platform)
        self.player = Player()
        self.groups['display'].add(self.player)
        # Position initiale du joueur
        self.player.rect.bottomleft = 100, ground.rect.top

    def _manage_player(self, pressed_keys):
        for key in self._player_motions:
            if pressed_keys[key]:
                # Appel de la fonction de déplacement
                self._player_motions[key]()
                # Repositionnement joueur si hors écran
                if self.player.rect.x < 0:
                    self.player.rect.x = 0
                elif self.player.rect.right > self.rect.right:
                    self.player.rect.right = self.rect.right

                if not self.player.falling:
                    # On regarde s'il y a une surface sous ses pieds
                    below_sprite = get_sprite_below(
                        self.player, self.groups['walkable']
                    )
                    if not below_sprite:
                        self.player.falling = True
                break
        if self.player.falling:
            below_sprite = get_sprite_below(
                self.player, self.groups['walkable']
            )
            # le joueur est arrivé sur une surface
            if below_sprite:
                self.player.falling = False
                self.player.rect.bottom = below_sprite.rect.top

    def run(self):
        clock = pygame.time.Clock()
        running = True

        while running:
            clock.tick(FPS)
            for event in pygame.event.get():
                if event.type == pygame.QUIT:
                    running = False
                elif event.type == pygame.KEYDOWN:
                    if event.key == pygame.K_UP:
                        self.player.jump()
                    elif event.key == pygame.K_ESCAPE:
                        running = False

            keys = pygame.key.get_pressed()
            self._manage_player(keys)

            self.groups['display'].clear(self.screen, sprites_clear)
            # Appel de la méthode update de tous les sprites du groupe display
            self.groups['display'].update()
            # Affichage des sprites du groupes
            rects = self.groups['display'].draw(self.screen)
            # Mise à jour des rectangles ayant eu un changement
            pygame.display.update(rects)


game = Game('Hop hop hop')
game.run()
pygame.quit()

La gestion des positions s'effectue donc avec les rect des sprites.

Là, cela fonctionne pas trop mal, mais ce n'est pas géré super bien, il suffit d'accélérer la vitesse de chute ou de réduire l'épaisseur des plateformes pour que le joueur lors de sa chute passe à travers, l'idée pour y pallier serait alors de faire comme en général dans les jeux, avoir un rect de « hitbox » un peu plus grand que la taille effective de l'image de façon à détecter une collision plus facilement.

On pourrait utiliser pour ce faire spritecollide qui permet de passer sa propre fonction de test via le paramètre collided.

https://www.pygame.org/docs/ref/sprite.html#pygame.sprite.spritecollide

Ou alors avec une méthode dans la classe Player (ou Plateform) afin d'utiliser le Rect de la hitbox au lieu du Rect de l'image.

Bonne continuation.

1
_rUBEN__ Messages postés 3 Date d'inscription samedi 2 mars 2024 Statut Membre Dernière intervention 4 mars 2024 1
4 mars 2024 à 20:24

Du coup au final j'ai réussi merci beaucoup 

:)

1

Salut, ça me semble mal embarqué.

Les collisions dans un jeu, ce n'est pas ce qu'il y a de plus facile à gérer, encore moins lorsqu'il y a un mécanisme de saut et chute libre.
Pygame fournit des méthodes et fonctions pour tester les collisions, dans la classe Rect et le module sprite.

Faire un jeu, même des plus simples, sans se servir de sprites, de groupe de sprites, de rect, est tout de même dommage, cela simplifie des tas de choses.

Comment ton mécanisme de chute est-il implémenté, chute auto du joueur tant que joueur.fall est à True ?

Comment point de vue algo gèrerais-tu déplacement, saut et chute du joueur ?

0
_rUBEN__ Messages postés 3 Date d'inscription samedi 2 mars 2024 Statut Membre Dernière intervention 4 mars 2024 1
3 mars 2024 à 13:46

Bonjour, 

Voici la partie du code de la classe Player : 
 

class Player:
    def __init__(self,x,y):
        self.x = x 
        self.y = y 
        self.w = 100
        self.h = 140
        self.speed = 5
        self.velocity = 0
        self.maxVelocity = 15
        self.jumpSpeed, self.fallSpeed = 1, 1
        self.jump = False
        self.fall = False
        self.rect = pygame.Rect(self.x, self.y, self.w, self.h)
        self.collisionRight, self.collisionLeft = False, False
        self.canMove = False
        self.show = False 
        self.position = "idle"
        self.direction = "right"
        self.rowX = 0
        self.rowY = 0
        self.timeR = 0
        self.timeL = 0
        self.maxTime = 10

    def events(self, keyPressed):
        if self.show:

            self.rect = pygame.Rect(self.x, self.y, self.w, self.h)
            screen.blit(playeR, (self.x, self.y), (self.rowX * self.w, self.rowY * self.h, self.w, self.h))

            if self.direction == "right":
                self.timeR += 1
                if self.timeR >= self.maxTime: 
                    self.timeR = 0
                    self.rowX += 1

                if self.position == "idle":
                    self.rowY = 0
                    if self.rowX >= 9:
                        self.rowX = 0

                elif self.position=="walk":
                    self.rowY = 1
                    if self.rowX >= 5:
                        self.rowX = 0


            elif self.direction == "left":
                self.timeL += 1
                if self.timeL >= self.maxTime: 
                    self.timeL = 0
                    self.rowX += 1

                if self.position == "idle":
                    self.rowY = 2
                    if self.rowX >= 9:
                        self.rowX = 0

                elif self.position == "walk":
                    self.rowY = 3
                    if self.rowX >= 5:
                        self.rowX = 0


            if self.canMove:

                self.y -= self.velocity

                if keyPressed[K_d] and not self.collisionRight:
                    self.x += self.speed
                    self.direction="right"
                    self.position="walk"

                elif keyPressed[K_q] and not self.collisionLeft:
                    self.x -= self.speed
                    self.direction="left"
                    self.position="walk"

                else:self.position="idle"

                if keyPressed[K_z] and not self.jump and not self.fall:
                    self.jump = True
                    self.position="idle"

                if self.jump:
                    if self.velocity < self.maxVelocity:
                        self.velocity += self.jumpSpeed
                    else:
                        self.velocity = 0
                        self.jump=False
                        self.fall=True

                elif self.fall:
                    self.velocity -= self.fallSpeed



Et pour les méthodes de pygame, je n'ai pas trouvé comment détecter si le joueur touche la droite, le haut ou la gauche d'un bloc, je n'ai que "rect1.colliderect(rect2)".

0