Python Arcade: Performance issue when trying to test collision between hundreds Sprites and thousands cell

88 Views Asked by At

I'm trying to build a simulation on python.

I want a grid of 50x50 cells of different colors And balls that spawn on the grid and move until they collid a cell of another color.

When a ball collide a cell of another color, the cell change her color and the ball disapear.

The basics of the simulation is working but i'm facing a huge performance issue.

Below ~40 balls, i'm able to keep a 60FPS simulation. but when i have more (and i would like to handle thousands balls without problem) the FPS are impacted.

How could i optimize this collision detection please? here is part of the code:

GRID_SIZE = 50
CELL_SIZE = 10
self.balls = arcade.SpriteList()
self.cells = arcade.SpriteList(use_spatial_hash=True, spatial_hash_cell_size=CELL_SIZE)

def on_update(self, delta_time):
    self.balls.update()

class Cell(arcade.SpriteSolidColor):
    def __init__(self, x, y, color, grid_app):
        super().__init__(CELL_SIZE, CELL_SIZE, arcade.color.WHITE)
        self.gridApp = grid_app
        self.center_x = x
        self.center_y = y
        self.update_color(color)

    def update_color(self, color):
        self.color = color


class Ball(arcade.Sprite):
    def __init__(self, x, y, velocity, angle, spriteConf, grid_app, scale=1):
        super().__init__(SPRITE_CONF[spriteConf]["imagePath"], scale=scale)
        self.center_x = x
        self.center_y = y
        self.my_color=SPRITE_CONF[spriteConf]["color"]
        self.velocity = velocity
        self.my_angle = angle
        self.gridApp = grid_app

    def update(self):
        self.center_x += self.velocity * REFRESH_RATE * math.cos(math.radians(self.my_angle))
        self.center_y += self.velocity * REFRESH_RATE * math.sin(math.radians(self.my_angle))

        # Check for collision with grid sides
        if self.center_x < 0 or self.center_x > WINDOW_WIDTH:
            self.my_angle = 180 - self.my_angle
        if self.center_y < 0 or self.center_y > WINDOW_HEIGHT:
            self.my_angle = 360 - self.my_angle

        ################################################################################
        #                        HERE THE SLOW CODE                                    #
        ################################################################################
        collidedCells = arcade.check_for_collision_with_lists(
            self,
            [
                self.gridApp.cells
            ]
        )
        hasToBeDelete = False
        for cell in collidedCells:
            if cell.color != self.my_color:
                hasToBeDelete = True
                cell.update_color(self.my_color)
        if hasToBeDelete:
            self.gridApp.removeBall(self)
        ################################################################################
        #                        HERE THE SLOW CODE                                    #
        ################################################################################

This is what the game looks like at the max balls before starting having FPS issues: Game situation before performance issue

1

There are 1 best solutions below

0
Alderven On

You can define cells and balls as pixels so you won't needed to calculate shapes collisions which significantly increases performance.

In the following example I've created 100*100 grid and generated 1000 "balls-pixels". You can see that the FPS (displayed in the left upper corner) is always around 60. For this example I've used pyxel lib which is optimized for pixel drawing.

enter image description here

Code:

import pyxel
import random

class Ball(object):
    def __init__(self):
        self.alive = True
        self.x = pyxel.width/2
        self.y = pyxel.height/2

    def draw(self):
        if pyxel.pget(self.x, self.y) == pyxel.COLOR_BLACK:
            self.x += random.choice([-1, 0, 1])
            self.y += random.choice([-1, 0, 1])
        else:
            pyxel.pset(self.x, self.y, pyxel.COLOR_BLACK)
            self.alive = False

class App:
    def __init__(self):
        pyxel.init(100, 100, fps=60)
        pyxel.rect(0, 0, pyxel.width/2, pyxel.height/2, pyxel.COLOR_RED)
        pyxel.rect(pyxel.width/2, 0, pyxel.width/2, pyxel.height/2, pyxel.COLOR_DARK_BLUE)
        pyxel.rect(0, pyxel.height/2, pyxel.width/2, pyxel.height/2, pyxel.COLOR_LIME)
        pyxel.rect(pyxel.width/2, pyxel.height/2, pyxel.width/2, pyxel.height/2, pyxel.COLOR_YELLOW)
        self.balls = [Ball() for i in range(1000)]
        pyxel.run(self.update, self.draw)

    def update(self):
        self.balls = [b for b in self.balls if b.alive]

    def draw(self):
        [b.draw() for b in self.balls]

App()