Search code examples
pythonpygame

How does one get the sprite to stop accelerating after colliding with the wall?


Making a platform game and expect the sprite to collide with the wall and be aligned with the top of the wall and not accelerate through the wall but the moment it collides with the wall it slowly sinks through the wall, after testing it has shown that during the sinking part it does not actually collide with the wall. At the moment I've just been focusing on gravity and the y axis.

import os
import pygame
import time
import random
vec = pygame.math.Vector2

class player(pygame.sprite.Sprite):
    def __init__(self):
        super().__init__()
        self.image=pygame.image.load("rectmo.png").convert_alpha()
        self.image=pygame.transform.scale(self.image, (25,100))
        self.rect = self.image.get_rect()
        self.rect.center = (width/2,height/2)
        self.pos = vec(width/2,height/2)
        self.vel = vec(0,0)
        self.acc = vec(0,0)

    def update(self):

        user_input=pygame.key.get_pressed()

        if user_input[pygame.K_d]:
            self.acc.x=player_acc
        if user_input[pygame.K_a]:
            self.acc.x=-player_acc

        for wall in walls:
            if self.rect.colliderect(wall.rect)==True:
                if self.acc.x > 0:
                    self.rect.right=wall.rect.left
                    self.vel.x=0
                if self.acc.x < 0:
                    self.rect.left=wall.rect.right                    
                    self.vel.x=0
                if self.acc.y > 0:
                    self.rect.bottom=wall.rect.top
                    self.acc.y=0
                    self.vel.y=0
                if self.pos.y < 0:
                    self.rect.top=wall.rect.bottom
                    self.acc.y=0
            if self.rect.colliderect(wall.rect)==False:
                #gravity
                self.acc = vec(0,0.5)


        #adds Friction    
        self.acc.x += self.vel.x * player_friction
        #applying accelerating equation
        self.vel += self.acc
        self.pos += self.vel + 0.5 * self.acc
        self.rect.center = self.pos

I can't tell if the way I made the wall is the problem so I'll just leave it here.

class Wall(object):
    def __init__(self,wx,wy):
        walls.append(self)
        self.rect= pygame.Rect(wx,wy,30,30)
    def reset_wall(self):
        self.active = False

walls=[]

levels= [['WWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWW',
   'W  E                                         W',
   'W                                            W',
   'W                                            W',
   'W                                            W',
   'W                                            W',
   'W                                            W',
   'W                                            W',
   'W                                            W',
   'W                                            W',
   'W             W                              W',
   'W             W                              W',
   'W             W                              W',
   'W             W                 W            W',
   'W             W                 W            W',
   'W             W                 W            W',
   'W             W                 W            W',
   'W             WWWWWWWWWWWWWWWWWWW            W',
   'W                                            W',
   'W                                            W',
   'W                                            W',
   'W                                            W',
   'W                                            W',
   'W                                            W',
   'W                                            W',
   'WWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWW',
 ]]

x=y=0
level=random.choice(levels)
for row in level:
    for col in row:
        if col=='W':
            Wall(x,y)
        if col=='E':
            end_rect=pygame.Rect(x,y,30,30)
        x += 30
    y+=30
    x=0

Here's the the whole code for testing:

import os
import pygame
import time
import random
vec = pygame.math.Vector2

class player(pygame.sprite.Sprite):
    def __init__(self):
        super().__init__()
        self.image=pygame.image.load("rectmo.png").convert_alpha()
        self.image=pygame.transform.scale(self.image, (25,100))
        self.rect = self.image.get_rect()
        self.rect.center = (width/2,height/2)
        self.pos = vec(width/2,height/2)
        self.vel = vec(0,0)
        self.acc = vec(0,0)

    def update(self):

        user_input=pygame.key.get_pressed()

        if user_input[pygame.K_d]:
            self.acc.x=player_acc
        if user_input[pygame.K_a]:
            self.acc.x=-player_acc

        for wall in walls:
            if self.rect.colliderect(wall.rect)==True:
                if self.acc.x > 0:
                    self.rect.right=wall.rect.left
                    self.vel.x=0
                if self.acc.x < 0:
                    self.rect.left=wall.rect.right
                    self.vel.x=0
                if self.acc.y > 0:
                    self.rect.bottom=wall.rect.top
                    self.vel.y=0
                if self.pos.y < 0:
                    self.rect.top=wall.rect.bottom
                    self.vel.y=0
            if self.rect.colliderect(wall.rect)==False:
                self.acc = vec(0,0.5)


        #adds Friction    
        self.acc.x += self.vel.x * player_friction
        #applying accelerating equation
        self.vel += self.acc
        self.pos += self.vel + 0.5 * self.acc
        self.rect.center = self.pos




class Wall(object):
    def __init__(self,wx,wy):
        walls.append(self)
        self.rect= pygame.Rect(wx,wy,30,30)
    def reset_wall(self):
        self.active = False


os.environ['SDL_VIDEO_CENTERED'] = '1'
pygame.init()

pygame.display.set_caption('A Game')
width = 1366
height= 768
screen=pygame.display.set_mode((width,height))

clock = pygame.time.Clock()
walls=[]
player_acc=0.5
player_friction=-0.05
rectmo=player()

rectmo.rect.x=500
rectmo.rect.y=400
main_colour=(0,0,0)
colour=main_colour
wall_colour=(255,255,255)
current_score=0

levels= [['WWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWW',
   'W  E                                         W',
   'W                                            W',
   'W                                            W',
   'W                                            W',
   'W                                            W',
   'W                                            W',
   'W                                            W',
   'W                                            W',
   'W                                            W',
   'W             W                              W',
   'W             W                              W',
   'W             W                              W',
   'W             W                 W            W',
   'W             W                 W            W',
   'W             W                 W            W',
   'W             W                 W            W',
   'W             WWWWWWWWWWWWWWWWWWW            W',
   'W                                            W',
   'W                                            W',
   'W                                            W',
   'W                                            W',
   'W                                            W',
   'W                                            W',
   'W                                            W',
   'WWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWW',
 ]]

x=y=0
level=random.choice(levels)
for row in level:
    for col in row:
        if col=='W':
            Wall(x,y)
        if col=='E':
            end_rect=pygame.Rect(x,y,30,30)
        x += 30
    y+=30
    x=0

running=True

while running==True:
    clock.tick(60)
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            running = False
    rectmo.update()










    if running==True:
        screen.fill(main_colour)
        for wall in walls:
            pygame.draw.rect(screen, wall_colour,wall.rect)
        pygame.draw.rect(screen,(255,0,0),end_rect)
        pygame.draw.rect(screen,colour,rectmo.rect)

        all_sprites_list = pygame.sprite.Group()
        all_sprites_list.add(rectmo)
        all_sprites_list.draw(screen)
        pygame.display.flip()


Solution

  • Chang the position of the player, before evaluation the collisions. If the player collides, then you've to update self.rect and you have to synchronize self.pos to self.rect.center. For instnace:

    if self.val.x > 0:
        self.rect.right=wall.rect.left
        self.vel.x=0
        self.pos = vec(self.rect.center)
    

    Evaluate if the player stays on the ground, by testing if the player would intersect the wall if he would be located 1 pixel below:

    test_rect = pygame.Rect(self.rect.x, self.rect.y+1, self.rect.width, self.rect.height) 
    if test_rect.colliderect(wall.rect):
       on_ground = True
    

    Set the acceleration in y direction dependent on the state on_ground:

    if on_ground:
        self.acc.y = 0
    elif self.acc.y == 0:
        self.acc.y = 0.5
    

    Method update:

    class player(pygame.sprite.Sprite):
        # [...]
    
        def update(self):
    
            user_input=pygame.key.get_pressed()
    
            self.acc.x = 0
            if user_input[pygame.K_d]:
                self.acc.x=player_acc
            if user_input[pygame.K_a]:
                self.acc.x=-player_acc
    
            self.vel.x = self.vel.x * 0.95 + self.acc.x
            self.vel.y += self.acc.y
            self.pos += self.vel
            self.rect.center = self.pos
    
            on_ground = False
            for wall in walls:
                if self.rect.colliderect(wall.rect)==True:
                    if self.vel.x > 0:
                        self.rect.right=wall.rect.left
                        self.vel.x=0
                        self.pos = vec(self.rect.center)
                    if self.vel.x < 0:
                        self.rect.left=wall.rect.right
                        self.vel.x=0
                        self.pos = vec(self.rect.center)
                    if self.vel.y > 0:
                        self.rect.bottom=wall.rect.top
                        self.vel.y=0
                        self.pos = vec(self.rect.center)
                    if self.vel.y < 0:
                        self.rect.top=wall.rect.bottom
                        self.vel.y=0
                        self.pos = vec(self.rect.center)
    
                test_rect = pygame.Rect(self.rect.x, self.rect.y+1, self.rect.width, self.rect.height) 
                if test_rect.colliderect(wall.rect):
                    on_ground = True
    
            if on_ground:
                self.acc.y = 0
            elif self.acc.y == 0:
                self.acc.y = 0.5