Eyal Kutz
Eyal Kutz

Reputation: 310

Can you help me fix this bug in my pygame physics simulation?

I wrote this physics simulation in pygame and the collision mechanism is not working properly. It seems to work when I collide the player character with the wall from above the wall or from the left and not work for collisions from the bottom or from the right

I have been trying to find this bug for some time but I just have no clue as to what might cause this. I am using python 3.7.3 and pygame 1.9.5 (latest versions as of date)

I am sorry for pasting an entire file but I just have no Idea where the problem is

import pygame  # import the pygame library to have access to game building tools
import math

# these variables will be used to hold game objects and draw them
rigid_bodies = []
g = 100
BLACK = (0, 0, 0)
WHITE = (255, 255, 255)
PURPLE = (127, 0, 255)
RED = (200, 0, 0)
GREEN = (0, 200, 0)
BLUE = (0, 0, 200)


class RigidBody(pygame.Rect):
    """
    represents a rectangular object that acts according to newton's laws of motion
    """

    def __init__(self, canvas, color, m, u, x, y, w, h):
        """
        called automatically when a new object is created to initialize the object
        :param canvas: the canvas on which to draw the object
        :param color: the color of the object
        :param m: the mass of the object
        :param u: Coefficient of friction
        :param x: the starting position of the object on the x axis
        :param y: the starting position of the object on the y axis
        :param w: the width of the object
        :param h: the height of the object
        """
        super().__init__(x, y, w, h)  # initialize the parent Rect object
        self.canvas = canvas
        self.color = color
        self.m = m
        self.u = u
        self.x_speed = 0  # the speed of the object on the x axis
        self.y_speed = 0  # the speed of the object on the y axis

    def apply_force(self, axis, F, initiator=None):
        """
        used to apply force on the object
        :param axis: the axis of the force
        :param F: the amount of force to apply
        :param initiator: the object that is applying the force
        """
        a = F / self.m  # calculate the acceleration the object should have
        if axis == 'y':
            self.y_speed += a
        elif axis == 'x':
            self.x_speed += a
        if initiator:
            initiator.apply_force(axis, -1 * F)  # apply normal force
            print('colliding')

    def inertia(self):
        """
        shall be run every frame to make the object move according to its speed
        if possible and take the necessary steps if not
        """
        # go:
        self.x += self.x_speed
        self.y += self.y_speed
        for body in rigid_bodies:
            if self.colliderect(body):  # if collide with another object:
                self.x -= self.x_speed  # go back
                self.y -= self.y_speed
                body.apply_force('x', self.m * self.x_speed, self)  # and apply force on that object
                body.apply_force('y', self.m * self.y_speed, self)
                break

    def draw(self):
        """
        shall be run every frame to draw the object on the canvas
        """
        pygame.draw.rect(self.canvas, self.color, (self.x, self.y, self.w, self.h))


class Controller:
    def __init__(self, character, F):
        """
        initialize the controller object
        :param character: the character to control
        :param F: the force to apply to the character for every frame a button is being pressed
        """
        self.character = character
        self.up = 0  # whether to move up or not
        self.down = 0  # whether to move down or not
        self.left = 0  # whether to move left or not
        self.right = 0  # whether to move right or not
        self.F = F

    def stop(self):
        """
        stops applying force on the object
        """
        self.up = 0
        self.down = 0
        self.left = 0
        self.right = 0

    def run(self):
        """
        shall be run every frame to apply force on the character according to user input
        """
        self.character.apply_force('y', -self.F * self.up)
        self.character.apply_force('y', self.F * self.down)
        self.character.apply_force('x', -self.F * self.left)
        self.character.apply_force('x', self.F * self.right)


def main():
    """
    the main function contains the main loop
    that runs repeatedly while the game is running
    """
    crashed = False  # tells if the program crashed or if the window was closed
    pygame.init()  # required to use pygame
    canvas = pygame.display.set_mode((1000, 700))  # define the canvas
    clock = pygame.time.Clock()  # will be used to limit the number of times a loop runs per second
    pygame.display.set_caption('the dot game V2')
    character = RigidBody(canvas, WHITE, 1000, 0.3, 500, 500, 20, 50)  # initialize the character
    player = Controller(character, 500)  # initialize the controller
    rigid_bodies.append(RigidBody(canvas, WHITE, math.inf, 0, 300, 300, 300, 20))  # initialize the wall
    while not crashed:
        # handle inputs:
        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                crashed = True
            elif event.type == pygame.MOUSEBUTTONUP:
                pass
            elif event.type == pygame.KEYDOWN:
                if event.key == pygame.K_UP:
                    player.up = 1
                elif event.key == pygame.K_DOWN:
                    player.down = 1
                elif event.key == pygame.K_LEFT:
                    player.left = 1
                elif event.key == pygame.K_RIGHT:
                    player.right = 1
            elif event.type == pygame.KEYUP:
                player.stop()
        player.run()
        character.inertia()
        canvas.fill(BLACK)
        character.draw()
        for body in rigid_bodies:
            body.draw()
        pygame.display.update()
        clock.tick(60)


if __name__ == '__main__':
    main()

I suspect that the problem is with either the "inertia" or the "apply_force" functions but I just cant figure out WHAT is the problem with those functions

The character should stop moving every time it hits the wall, but when it hits the wall from below or from the right it gets stuck and can only move up or to the left

Upvotes: 2

Views: 280

Answers (1)

Rabbid76
Rabbid76

Reputation: 210958

The issue is caused by casting a floating point value to int and can be solved by:

stored_pos = (self.x, self.y)
self.x += self.x_speed
self.y += self.y_speed
for body in rigid_bodies:
    if self.colliderect(body):  # if collide with another object:
        self.x, self.y = stored_pos

Note, that

self.x -= self.x_speed
self.y -= self.y_speed

is not the inverse operation of

self.x += self.x_speed
self.y += self.y_speed

e.g: a = 2 and b = 0.5

int(a + b) == int(2 + 0.5) == 2
int(a - b) == int(2 - 0.5) == 1

The solution is to store the original values of self.x and self.y

stored_pos = (self.x, self.y)

and to restore it in the case of a collision:

self.x, self.y = stored_pos

Upvotes: 3

Related Questions