Search code examples
pythonpygame

Object moves a little bit weird with pygame vector2


I have an issue with pygame module. I have a small program where user can click on any place in a window and a ball will spawn there and start moving up-left with 45 degrees angle (135 actually) and everything seems pretty fine but if you spawn many of this orbs so they move like a beam you'll notice that they reflect not actually 90 degrees everytime. I mean, reflection isn't random, it's calculated, but direction of 45 degrees is obviously closer to 90 than it should be. You'll see what I mean if you start the program. So here is the code:

import math
import pygame

NE = 45
NW = 135
SW = 225
SE = 315
V = 5
TICK = 60
RADIUS = 10


def convert_degrees_to_radians(angle_degrees):
    return math.radians(angle_degrees)


class Circle:

    def __init__(self, center_pos, direction_angle):
        self.center_pos = center_pos
        self.movement_angle, self.movement_direction = None, None
        self.change_direction(direction_angle)

    def change_direction(self, new_angle):
        self.movement_angle = new_angle
        self.movement_direction = pygame.math.Vector2(math.cos(convert_degrees_to_radians(new_angle)),
                                                      math.sin(convert_degrees_to_radians(new_angle)))

    def change_position(self, new_position):
        self.center_pos = new_position


def draw_circle(position):
    pygame.draw.circle(screen, pygame.Color('white'), position, RADIUS)


if __name__ == '__main__':
    pygame.init()
    pygame.display.set_caption('Balls')
    size = width, height = 1000, 600
    screen = pygame.display.set_mode(size)

    running = True
    circles = []
    clock = pygame.time.Clock()
    while running:
        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                running = False
            if event.type == pygame.MOUSEBUTTONUP and event.button == 1:
                draw_circle(event.pos)
                circles.append(Circle(event.pos, NW))
        screen.fill(pygame.Color('black'))
        clock.tick(TICK)
        for circle in circles:
            new_center_pos = [int(circle.center_pos[0] + V * circle.movement_direction.x),
                              int(circle.center_pos[1] - V * circle.movement_direction.y)]
            circle.change_position(tuple(new_center_pos))
            draw_circle(circle.center_pos)
            if circle.center_pos[0] <= RADIUS:
                if circle.movement_angle == SW:
                    circle.change_direction(SE)
                if circle.movement_angle == NW:
                    circle.change_direction(NE)
            if circle.center_pos[0] >= width - RADIUS:
                if circle.movement_angle == SE:
                    circle.change_direction(SW)
                if circle.movement_angle == NE:
                    circle.change_direction(NW)
            if circle.center_pos[1] <= RADIUS:
                if circle.movement_angle == NW:
                    circle.change_direction(SW)
                if circle.movement_angle == NE:
                    circle.change_direction(SE)
            if circle.center_pos[1] >= height - RADIUS:
                if circle.movement_angle == SW:
                    circle.change_direction(NW)
                if circle.movement_angle == SE:
                    circle.change_direction(NE)
        pygame.display.flip()
    pygame.quit()

Does someone have an idea how to solve it? I tried, but really can't see any mistake.

I tried to calculate directions by myself instead of using degrees, but result was the same, so maybe there is a problem with calculations of new_center_pos but I can't see it.


Solution

  • The problem is here:

    new_center_pos = [int(circle.center_pos[0] + V * circle.movement_direction.x),
                      int(circle.center_pos[1] - V * circle.movement_direction.y)]
    

    You calculate everything correctly with floating point coordinates, but unfortunately you convert the coordinates to int before assigning them to the new position. Since the new position is used for the calculation in the next frame, the movement is inaccurate.

    Do not convert to int

    new_center_pos = [circle.center_pos[0] + V * circle.movement_direction.x,
                      circle.center_pos[1] - V * circle.movement_direction.y]
    

    round only the position for drawing the circle

    pygame.draw.circle(screen, pygame.Color('white'), (round(position[0]), round(position[1])), RADIUS)