Проблема с отскакиванием мячей(Python, raylib)

В этом коде по какой-то причине мячи не отскакивают друг от друга, я не понимаю почему. Обработка коллизий происходит в game_scene_process_logic().

import datetime
import random

import pyray
from raylib import colors


class Image:
    def __init__(self, x, y, filename):
        self.image = pyray.load_image(filename)
        self.texture = pyray.load_texture_from_image(self.image)
        self.rect = pyray.Rectangle(x, y, self.texture.width, self.texture.height)
        self.filename = filename

    def draw(self):
        pyray.draw_texture(self.texture, self.rect.x, self.rect.y, None )
class Ball(Image):
    def __init__(self, x, y, filename, shift_x, shift_y):
        Image.__init__(self, x, y, filename)
        self.shift = pyray.Vector2(shift_x, shift_y)
        self.radius = self.texture.width//2
        self.initial = {}
        self.position = pyray.Vector2(x, y)
    def activate(self):
        self.position.x = self.initial["x"]
        self.position.y = self.initial["y"]
        self.shift_x = self.initial["shift_x"]
        self.shift_y = self.initial["shift_y"]
    def step(self):
        self.position.x+=self.shift.x
        self.position.x += self.shift.y
    def collides_with_horizontal_border(self, window_height):
        if self.position.y>window_height or self.position.y<window_height:
            return True
        else:
            return False

    def collides_with_vertical_border(self, window_width):
        if self.position.x>window_width or self.position.x<window_width:
            return True
        else:
            return False
    def bounce_x(self):
        self.shift.x *= -1
    def bounce_y(self):
        self.shift.y *= -1
    def collides_with(self, other_ball):
        if pyray.check_collision_circles(pyray.Vector2(self.position.x, self.position.y), self.radius, pyray.Vector2(other_ball.position.x, other_ball.position.y), other_ball.radius):
            return True
        else:
            return False

    def collide(self, other_ball):
        temp = self.shift
        self.shift = other_ball.shift
        other_ball.shift = temp
    def logic(self, window_width, window_height):
        self.step()
        if self.collides_with_horizontal_border(window_height):
            self.bounce_y()
        if self.collides_with_vertical_border(window_width):
            self.bounce_x()

def main():
    # Инициализация окна
    window_width = 800
    window_height = 600
    pyray.init_window(window_width, window_height, 'Hello, raylib')
    pyray.set_exit_key(pyray.KeyboardKey.KEY_F8)
    pyray.set_target_fps(120)

    # Инициализация глобальных переменных
    scene_index = 0
    scene_changed = True
    background_color = colors.BLACK

    # Инициализация сцены 0 (menu)
    scene_0_button_new_geometry = pyray.Rectangle(window_width / 2 - 100 / 2, window_height / 2 - 10 - 50, 100, 50)
    scene_0_button_exit_geometry = pyray.Rectangle(window_width / 2 - 100 / 2, window_height / 2 + 10, 100, 50)

    motion_seconds = 3
    motion_start = datetime.datetime.now()
    motion_now = datetime.datetime.now()
    percent_completed = 0
    line_color = colors.WHITE

    # Инициализация сцены 1 (game)
    ball_image = pyray.load_image('basketball.png')
    ball_texture = pyray.load_texture_from_image(ball_image)
    pyray.unload_image(ball_image)
    del ball_image

    max_collision_count = 5

    collision_text_format = 'Collisions: {}/' + str(max_collision_count)

    # ball_0_position = pyray.Vector2(10, 10)
    # ball_0_shift = [1, 1]

    ball_0 = Ball(10, 10, "basketball.png", 1, 1)


    # ball_1_position = pyray.Vector2(500, 100)
    # ball_1_shift = [-1, 1]

    ball_1 = Ball(500, 100, "basketball.png", -1, 1)
    # ball_2_position = pyray.Vector2(400, 500)
    # ball_2_shift = [-1, -1]

    ball_2 = Ball(400, 500, "basketball.png", -1, -1)
    balls = [ball_0, ball_1, ball_2]
    collision_count = 0

    # Инициализация сцены 2 (gameover)
    max_wait_seconds = 3
    wait_seconds = 0
    gameover_text_format = 'Game over ({}/{})'.format('{}', max_wait_seconds)
    open_scene_datetime = datetime.datetime.now()

    # Основной цикл программы
    while not pyray.window_should_close():

        # Действия, выполняемые при первом появлении сцены на экране
        if scene_changed:
            scene_changed = False
            if scene_index == 0:  # menu
                motion_start, percent_completed = menu_scene_on_activate(motion_start, percent_completed)
            elif scene_index == 1:  # game
                balls = game_scene_on_activate(balls)
                #Рандомизируем позицию мячей
            elif scene_index == 2:  # gameover
                open_scene_datetime = game_over_scene_on_activate(open_scene_datetime)

        # Обработка событий различных сцен (при каждом кадре)
        if not scene_changed:
            if scene_index == 0:  # menu
                scene_changed, scene_index = menu_scene_process_event(scene_0_button_exit_geometry,
                                                                      scene_0_button_new_geometry, scene_changed,
                                                                      scene_index)
            elif scene_index == 1:  # game
                scene_changed, scene_index = game_scene_process_event(scene_changed, scene_index)
            elif scene_index == 2:  # gameover
                scene_changed, scene_index = game_over_scene_process_event(scene_changed, scene_index)

        # Обработка логики работы сцен (при каждом кадре)
        if not scene_changed:
            if scene_index == 0:  # menu
                percent_completed = menu_scene_process_logic(motion_seconds, motion_start, percent_completed)
            elif scene_index == 1:  # game
                balls, collision_count, scene_changed, scene_index = game_scene_process_logic(balls, ball_texture, collision_count, max_collision_count, scene_changed, scene_index, window_height, window_width)

            elif scene_index == 2:  # gameover
                scene_changed, scene_index, wait_seconds = game_over_scene_process_logic(max_wait_seconds,
                                                                                         open_scene_datetime,
                                                                                         scene_changed, scene_index,
                                                                                         wait_seconds)

        # Обработка отрисовки различных сцен (при каждом кадре)
        if not scene_changed:
            pyray.begin_drawing()
            pyray.clear_background(background_color)

            if scene_index == 0:  # menu
                menu_scene_process_draw(line_color, percent_completed)
            elif scene_index == 1:  # game
                game_scene_process_draw(balls, ball_texture, collision_count, collision_text_format)
            elif scene_index == 2:  # gameover
                game_over_scene_process_draw(gameover_text_format, wait_seconds)
            pyray.end_drawing()

    pyray.unload_texture(ball_texture)
    pyray.close_window()
    exit(0)


def game_over_scene_process_draw(gameover_text_format, wait_seconds):
    pyray.draw_text(gameover_text_format.format(wait_seconds), 100, 250, 78, colors.RED)


def game_scene_process_draw(balls, ball_texture, collision_count, collision_text_format):
    for i in balls:
        pyray.draw_texture_v(ball_texture, i.position, colors.WHITE)
    pyray.draw_text(collision_text_format.format(collision_count), 10, 10, 78, colors.WHITE)


def menu_scene_process_draw(line_color, percent_completed):
    # четыре анимированные линии (две кнопки уже отрисовались)
    pyray.draw_line_ex(pyray.Vector2(100, 100), pyray.Vector2(100 + 600 * percent_completed, 100),
                       4, line_color)
    pyray.draw_line_ex(pyray.Vector2(700, 100), pyray.Vector2(700, 100 + 400 * percent_completed, ),
                       4, line_color)
    pyray.draw_line_ex(pyray.Vector2(700, 500), pyray.Vector2(700 - 600 * percent_completed, 500),
                       4, line_color)
    pyray.draw_line_ex(pyray.Vector2(100, 500), pyray.Vector2(100, 500 - 400 * percent_completed),
                       4, line_color)


def game_over_scene_process_logic(max_wait_seconds, open_scene_datetime, scene_changed, scene_index, wait_seconds):
    now = datetime.datetime.now()
    wait_seconds = (now - open_scene_datetime).seconds
    # Переключение сцен при достижении нужного количества секунд (микросекунд)
    if wait_seconds == max_wait_seconds:
        scene_changed = True
        scene_index = 0
    return scene_changed, scene_index, wait_seconds


def game_scene_process_logic(balls, ball_texture, collision_count, max_collision_count, scene_changed,
                             scene_index, window_height, window_width):
    # Движение мячиков
    for i in balls:
        i.position.x += i.shift[0]
        i.position.y += i.shift[1]

    # Отражение от стенок
    for i in balls:
        if i.position.x < 0 or i.position.x + ball_texture.width > window_width:
            i.shift[0] *= -1
        if i.position.y < 0 or i.position.y + ball_texture.height > window_height:
            i.shift[1] *= -1

    # Обработка коллизий
    for i in range(len(balls)):
        for j in range(len(balls)):
            if balls[i].collides_with(balls[j]) and i != j:
                balls[i].collide(balls[j])
                collision_count+=1
    # Переключение сцен при достижении нужного количества коллизий
    if collision_count == max_collision_count:
        scene_changed = True
        scene_index = 2
    return balls, collision_count, scene_changed, scene_index


def menu_scene_process_logic(motion_seconds, motion_start, percent_completed):
    motion_now = datetime.datetime.now()
    delta = (motion_now - motion_start)
    ms = delta.seconds * 1000000 + delta.microseconds
    percent_completed = min(1.0, ms / (motion_seconds * 1000000))
    return percent_completed


def game_over_scene_process_event(scene_changed, scene_index):
    if pyray.is_key_down(pyray.KeyboardKey.KEY_ESCAPE):
        scene_changed = True
        scene_index = 0
    return scene_changed, scene_index


def game_scene_process_event(scene_changed, scene_index):
    if pyray.is_key_down(pyray.KeyboardKey.KEY_ESCAPE):
        scene_changed = True
        scene_index = 0
    return scene_changed, scene_index


def menu_scene_process_event(scene_0_button_exit_geometry, scene_0_button_new_geometry, scene_changed, scene_index):
    if pyray.gui_button(scene_0_button_new_geometry, 'New game'):
        scene_changed = True
        scene_index = 1
    if pyray.gui_button(scene_0_button_exit_geometry, 'Exit'):
        pyray.close_window()
        exit(0)
    return scene_changed, scene_index


def game_over_scene_on_activate(open_scene_datetime):
    open_scene_datetime = datetime.datetime.now()
    return open_scene_datetime


def game_scene_on_activate(balls):
    for i in balls:
        i.shift = [random.choice([-1, 1]), random.choice([-1, 1])]
        i.position = pyray.Vector2(random.randint(10, 500), random.randint(10, 500))
    return balls


def menu_scene_on_activate(motion_start, percent_completed):
    motion_start = datetime.datetime.now()
    motion_now = datetime.datetime.now()
    percent_completed = 0
    return motion_start, percent_completed


if __name__ == '__main__':
    main()

Ответы (0 шт):