user44557
user44557

Reputation: 99

Friction and gravity in pygame

I want player1 to have a constant downward acceleration acting on him. I tried to do it a couple ways, but it didn't really work how I wanted it to. When I give player1 velocity in the up direction, he keeps going higher and higher. I tried changing the velocity and gravity values and it looks a little better, but I'm not sure if it's right. I also want there to be a friction force acting on player1 when he's on the bottom of the screen, but because friction depends on normal force, which depends on gravity (Correct me if I'm wrong), I can't really do that yet, so I just have a constant friction acting on player1, but that acts kind of "weird" too. I want it to stop player1 when the velocity reaches 0, not reverse the acceleration.

import sys
import pygame

pygame.init()

screen_width = 640
screen_height = 480
screen = pygame.display.set_mode((screen_width, screen_height))
screen_rect = screen.get_rect()

clock = pygame.time.Clock()

fps = 30


class Character(object):
    def __init__(self, surface, velo, accel, gravity, friction):
        self.surface = surface
        self.velo = velo
        self.accel = accel
        self.gravity = gravity
        self.friction = friction
        self.timestep = 1/fps
        self.vel = (0, 0)
        self.pos = ((screen_width / 2), (screen_height / 2))
        self.size = (10, 10)

    def velocity_right(self):
        self.pos = (self.pos[0] + self.velo, self.pos[1])

    def velocity_left(self):
        self.pos = (self.pos[0] - self.velo, self.pos[1])

    def velocity_up(self):
        self.pos = (self.pos[0], self.pos[1] - self.velo)

    def velocity_down(self):
        self.pos = (self.pos[0], self.pos[1] + self.velo)

    def velocity(self):
        keys = pygame.key.get_pressed()
        if keys[pygame.K_i]:
            self.velocity_up()
        if keys[pygame.K_j]:
            self.velocity_left()
        if keys[pygame.K_k]:
            self.velocity_down()
        if keys[pygame.K_l]:
            self.velocity_right()

    def accelerate_right(self):
        self.vel = (self.vel[0] + self.accel, self.vel[1])
        self.pos = (self.pos[0] + self.vel[0], self.pos[1])

    def accelerate_left(self):
        self.vel = (self.vel[0] - self.accel, self.vel[1])
        self.pos = (self.pos[0] + self.vel[0], self.pos[1])

    def accelerate_up(self):
        self.vel = (self.vel[0], self.vel[1] - self.accel)
        self.pos = (self.pos[0], self.pos[1] + self.vel[1])

    def accelerate_down(self):
        self.vel = (self.vel[0], self.vel[1] + self.accel)
        self.pos = (self.pos[0], self.pos[1] + self.vel[1])

    def accelerate(self):
        keys = pygame.key.get_pressed()
        if keys[pygame.K_w]:
            self.accelerate_up()
        if keys[pygame.K_a]:
            self.accelerate_left()
        if keys[pygame.K_s]:
            self.accelerate_down()
        if keys[pygame.K_d]:
            self.accelerate_right()

        if self.pos[0] <= 0 or self.pos[0] >= screen_width:
            self.vel = (self.vel[0] * -1, self.vel[1])

        if self.pos[1] <= 0 or self.pos[1] >= screen_height:
            self.vel = (self.vel[0], self.vel[1] * -1)

        self.character = pygame.Rect((self.pos[0], self.pos[1]), self.size)
        self.character.clamp_ip(screen_rect)

    def display(self):
        pygame.draw.rect(self.surface, (255, 255, 255), self.character)

    def reset(self):
        (x_pos, y_pos) = pygame.mouse.get_pos()
        self.pos = (x_pos, self.pos[1])
        self.pos = (self.pos[0], y_pos)
        self.vel = (0, 0)

    def apply_gravity(self):
        self.vel = (self.vel[0], self.vel[1] + self.accel)
        self.pos = (self.pos[0], self.pos[1] + self.vel[1])
        # self.vel = (self.vel[0], self.vel[1] + self.gravity * self.timestep)
        # self.pos = (self.pos[0], self.pos[1] + self.vel[1] * self.timestep + 0.5 * self.gravity * self.timestep**2)

    def apply_friction(self):
        keys = pygame.key.get_pressed()
        if keys[pygame.K_w] or keys[pygame.K_i]:
            self.vel = (self.vel[0], self.vel[1] + self.friction)
            self.pos = (self.pos[0], self.pos[1] + self.vel[1])
        if keys[pygame.K_a] or keys[pygame.K_j]:
            self.vel = (self.vel[0] + self.friction, self.vel[1])
            self.pos = (self.pos[0] + self.vel[0], self.pos[1])
        if keys[pygame.K_s] or keys[pygame.K_k]:
            self.vel = (self.vel[0], self.vel[1] - self.friction)
            self.pos = (self.pos[0], self.pos[1] + self.vel[1])
        if keys[pygame.K_d] or keys[pygame.K_l]:
            self.vel = (self.vel[0] - self.friction, self.vel[1])
            self.pos = (self.pos[0] + self.vel[0], self.pos[1])


def main():
    player1 = Character(screen, 10, 1, .5, .4)
    while True:
        for event in pygame.event.get():
                if event.type == pygame.QUIT:
                    pygame.quit()
                    sys.exit()
                elif event.type == pygame.MOUSEBUTTONDOWN:
                    player1.reset()

        player1.apply_gravity()
        player1.apply_friction()

        player1.velocity()
        player1.accelerate()

        screen.fill((0, 0, 0))

        player1.display()

        pygame.display.update(screen_rect)
        clock.tick(fps)

if __name__ == "__main__":
    main()

Upvotes: 0

Views: 2301

Answers (1)

Alvaro Fuentes
Alvaro Fuentes

Reputation: 17455

I think you can model your velocity AND acceleration as vectors this works for me (just an example with timestep = 1 fps, simplified equations):

import sys
import pygame

pygame.init()

screen_width = 640
screen_height = 480
screen = pygame.display.set_mode((screen_width, screen_height))
screen_rect = screen.get_rect()

clock = pygame.time.Clock()

fps = 30
FRICTION = 0.8

import numpy as np

class Character(object):
    def __init__(self, surface, accel, gravity):
        self.surface = surface
        self.accel = np.array(accel)
        self.gravity = np.array([0.0,float(gravity)])
        self.vel = np.array([0.0,0.0])
        self.pos = np.array([(screen_width / 2.0), (screen_height / 2.0)])
        self.size = (10, 10)

    def move_right(self):
        self.vel += np.array([1,0])
        self.accel += np.array([1,0])

    def move_left(self):
        self.vel += np.array([-1,0])
        self.accel += np.array([-1,0])

    def move_up(self):
        self.vel += np.array([0,-1])
        self.accel += np.array([0,-1])       

    def move_down(self):
        self.vel += np.array([0,1])
        self.accel += np.array([0,1])        

    def move(self):
        keys = pygame.key.get_pressed()

        if keys[pygame.K_w]:
            self.move_up()
        if keys[pygame.K_a]:
            self.move_left()
        if keys[pygame.K_s]:
            self.move_down()
        if keys[pygame.K_d]:
            self.move_right()

        if self.pos[0] <= 0 or self.pos[0] >= screen_width:
            self.vel[0] *= -FRICTION            
            self.accel[0] *= -FRICTION
            if self.pos[0] <= 0:
                self.pos[0] = 0
            elif self.pos[0] >= screen_width:
                self.pos[0] = screen_width
        elif self.pos[1] <= 0 or self.pos[1] >= screen_height:
            self.vel[1] *= -FRICTION
            self.accel[1] *= -FRICTION
            if self.pos[1] <= 0:
                self.pos[1] = 0
            elif self.pos[1] >= screen_height:
                self.pos[1] = screen_height
        else:
            self.vel = self.vel + (self.gravity+self.accel)
            if self.vel.dot(self.vel) < 1e-5:
                self.vel = np.zeros(2)
            self.accel *=  FRICTION
            if self.accel.dot(self.accel) < 1e-5:
                self.accel = np.zeros(2)

        self.pos = self.pos + self.vel + 0.5*self.gravity+self.accel

        #print self.pos, self.vel, self.accel

        self.character = pygame.Rect((self.pos[0], self.pos[1]), self.size)
        self.character.clamp_ip(screen_rect)

    def display(self):
        pygame.draw.rect(self.surface, (255, 255, 255), self.character)

    def reset(self):
        self.pos = np.array(pygame.mouse.get_pos())
        self.vel = np.array([0,0]);


def main():
    player1 = Character(screen, [0.0, 0.0], .5)
    while True:
        for event in pygame.event.get():
                if event.type == pygame.QUIT:
                    pygame.quit()
                    sys.exit()
                elif event.type == pygame.MOUSEBUTTONDOWN:
                    player1.reset()       

        player1.move()

        screen.fill((0, 0, 0))

        player1.display()

        pygame.display.update(screen_rect)
        clock.tick(fps)

if __name__ == "__main__":
    main()

Upvotes: 1

Related Questions