when the rectangle descends the ramp, the rectangle shakes

Viewed 78

I don't know if there is a better way to implement ramps. First i calculate the points that belong to the hipotenuse and use collidepoint to see if there is a collision between the rectangle and any point that belongs to the hipotenuse, then i update the rectangle based on the point where there was a collision. Being careful when the rectangle is at the top of the ramp. The rectangle ascends the ramp perfectly, but when the rectangle descends the ramp, the rectangle shakes.

import sys
import pygame
from pygame.locals import *

pygame.init()

fps = 60
fpsClock = pygame.time.Clock()

width, height = 640, 480
screen = pygame.display.set_mode((width, height))

def draw_grid():
    for y in range(0,height,32):
        pygame.draw.line(screen,'red',(0,y),(width,y))
    for x in range(0,width,32):
        pygame.draw.line(screen,'red',(x,0),(x,height))

class Ramp(pygame.sprite.Sprite):
    def __init__(self, x, y, width, height, color):
        super().__init__()
        self.image = pygame.Surface((width, height), pygame.SRCALPHA)

        #self.image.fill('green')
        pygame.draw.polygon(self.image, color,
                            points=[(0, 0), (0, height), (width, height)])
        self.rect = self.image.get_rect(topleft=(x, y))


class Player(pygame.sprite.Sprite):
    def __init__(self,x,y):
        super().__init__()
        self.image = pygame.Surface((32, 32))
        self.image.fill('blue')
        self.rect = self.image.get_rect(topleft=(x,y))
        self.speed = 5
        self.direction = pygame.math.Vector2(0,0)
        self.gravity = 0.9
        self.initial_jump = -20
        self.on_ground = True
        
    def apply_gravity(self):
        self.direction.y += self.gravity
        self.rect.y += self.direction.y

    def move(self):
        keys=pygame.key.get_pressed()
        if keys[K_LEFT]:
            self.direction.x = -self.speed
        elif keys[K_RIGHT]:
            self.direction.x = self.speed
        else:
            self.direction.x = 0
        if keys[K_UP] and self.on_ground:
            self.direction.y = self.initial_jump
            self.on_ground = False
        self.rect.x += self.direction.x
             
    
    def check_borders(self):
        if self.rect.x <= 0:
            self.rect.x = 0
        if self.rect.right >= width:
            self.rect.right = width
        if self.rect.bottom >= height:
            self.rect.bottom = height
            self.direction.y = 0
            self.on_ground = True
        if self.rect.colliderect(ramp_rect):
            if self.direction.x > 0 and abs(self.rect.right-ramp_rect.left) <= 5:
                self.rect.right = ramp_rect.left
            # ramp stuff
            for p in hypotenuse_points:
                if self.rect.collidepoint(p):
                    if self.rect.left >= ramp_rect.left:
                        self.rect.bottomleft = p
                    else:
                        self.rect.bottom = ramp_rect.top
                    self.on_ground = True
                    self.direction.y = 0
                    
    def update(self):
        self.move()
        self.apply_gravity()
        self.check_borders()

player = pygame.sprite.GroupSingle(Player(12*32,10*32))
ramp = pygame.sprite.GroupSingle(Ramp(5*32,10*32,7*32,5*32,'red'))
ramp_rect = ramp.sprite.rect

m = (ramp_rect.height)/( ramp_rect.width)
x1,y1 = ramp_rect.topleft
hypotenuse_points = [] 
for x in range(ramp_rect.left,ramp_rect.right):
    hypotenuse_points.append((x,m*(x-x1)+y1)) # Point-slope equation

while True:
    screen.fill('white')
    for event in pygame.event.get():
        if event.type == QUIT:
            pygame.quit()
            sys.exit()    
    ramp.draw(screen)
    player.update()
    player.draw(screen)
    
    #draw_grid()
    pygame.draw.lines(screen,'black',False,hypotenuse_points,3)
    pygame.display.update()
    fpsClock.tick(fps)
1 Answers

There is no problem with your code. Only gravity is too weak. The movement is so fast that gravity is acting too late. Note that instead of moving down the slope, you move to the right and then fall.

Of course there is one problem with your code. Since pygame.Rect is supposed to represent an area on the screen, a pygame.Rect object can only store integral data.

The coordinates for Rect objects are all integers. [...]

The fraction part of the coordinates gets lost when the new position of the object is assigned to the Rect object. If this is done every frame, the position error will accumulate over time.

If you want to store object positions with floating point accuracy, you have to store the location of the object in separate variables respectively attributes and to synchronize the pygame.Rect object. round the coordinates and assign it to the location of the rectangle.

Instead of the list of points I suggest to compute the height of the ramp under the palyer:

if self.rect.colliderect(ramp_rect):
    ratio = ramp_rect.height / ramp_rect.width
    self.rect.bottom = ramp_rect.bottom - (ramp_rect.right - max(self.rect.left, ramp_rect.left)) * ratio
    self.y = self.rect.y

Complete example:

import sys
import pygame
from pygame.locals import *

pygame.init()

fps = 60
fpsClock = pygame.time.Clock()

width, height = 640, 480
screen = pygame.display.set_mode((width, height))

def draw_grid():
    for y in range(0,height,32):
        pygame.draw.line(screen,'red',(0,y),(width,y))
    for x in range(0,width,32):
        pygame.draw.line(screen,'red',(x,0),(x,height))

class Ramp(pygame.sprite.Sprite):
    def __init__(self, x, y, width, height, color):
        super().__init__()
        self.image = pygame.Surface((width, height), pygame.SRCALPHA)

        #self.image.fill('green')
        pygame.draw.polygon(self.image, color,
                            points=[(0, 0), (0, height), (width, height)])
        self.rect = self.image.get_rect(topleft=(x, y))

class Player(pygame.sprite.Sprite):
    def __init__(self,x,y):
        super().__init__()
        self.image = pygame.Surface((32, 32))
        self.image.fill('blue')
        self.rect = self.image.get_rect(topleft=(x,y))
        self.x, self.y = self.rect.topleft
        self.speed = 5
        self.direction = pygame.math.Vector2(0,0)
        self.gravity = 0.9
        self.initial_jump = -20
        self.on_ground = True
        
    def apply_gravity(self):
        self.direction.y += self.gravity
        self.y += self.direction.y
        self.rect.y = round(self.y)

    def move(self):
        keys = pygame.key.get_pressed()
        self.direction.x = (keys[K_RIGHT] - keys[K_LEFT]) * self.speed
        if keys[K_UP] and self.on_ground:
            self.direction.y = self.initial_jump
            self.on_ground = False
        self.x += self.direction.x
        self.rect.x = round(self.x)
              
    def check_borders(self):
        if self.rect.x <= 0:
            self.rect.x = 0
            self.x = self.rect.x
        if self.rect.right >= width:
            self.rect.right = width
            self.x = self.rect.x
        if self.rect.bottom >= height:
            self.rect.bottom = height
            self.direction.y = 0
            self.on_ground = True
            self.y = self.rect.y
        
        if self.rect.colliderect(ramp_rect):
            if self.old_rect.right-1 <= ramp_rect.left:
                self.rect.right = ramp_rect.left
                self.x = self.rect.x
            else:
                ratio = ramp_rect.height / ramp_rect.width
                bottom = ramp_rect.bottom - (ramp_rect.right - max(self.rect.left, ramp_rect.left)) * ratio
                if self.on_ground or self.rect.bottom > bottom:
                    self.rect.bottom = bottom
                    self.y = self.rect.y
                    self.direction.y = 0
                    self.on_ground = True
                    
    def update(self):
        self.old_rect = self.rect.copy()
        self.move()
        self.apply_gravity()
        self.check_borders()

player = pygame.sprite.GroupSingle(Player(12*32,10*32))
ramp = pygame.sprite.GroupSingle(Ramp(5*32,10*32,7*32,5*32,'red'))
ramp_rect = ramp.sprite.rect

m = (ramp_rect.height)/( ramp_rect.width)
x1,y1 = ramp_rect.topleft

while True:
    screen.fill('white')
    for event in pygame.event.get():
        if event.type == QUIT:
            pygame.quit()
            sys.exit()    
    ramp.draw(screen)
    player.update()
    player.draw(screen)
    
    pygame.display.update()
    fpsClock.tick(fps)
Related