1
\$\begingroup\$

I made the looks of asteroids using pygame. I still need to implement collisions but since it is running incredibly slow with 'amount' anything above 10. Please rip my code to shreds and explain why it is bad so that I can improve. :)

import pygame
import pygame.locals
import numpy
import random
asteroids = []
paused = False
delta_angle = 0.01
amount = 5 # Anything above 5 makes it run much slower, above 10 makes it run really slow.
maxVelocity = 10
screen = (1600, 1200)
left_down, right_down = False, False
boost = False
pygame.init()
display = pygame.display.set_mode(screen, pygame.RESIZABLE)
background = pygame.Surface(screen)
clock = pygame.time.Clock()
class Boid:
 def __init__(self):
 self.position = numpy.array([0, 0])
 self.velocity = numpy.array([0, 0])
 self.acceleration = numpy.array([0, 0])
 self.angle = 0
 self.size = 1
 def update(self):
 self.position += self.velocity
 self.velocity += self.acceleration
 self.acceleration = 0
def rotation(angle):
 rotation = numpy.array([[numpy.cos(angle), -numpy.sin(angle)], [numpy.sin(angle), numpy.cos(angle)]])
 return rotation
def Ship(angle):
 offset1 = numpy.array([[0.0, -15.0], [-10.0, 15.0], [0.0, 10.0], [10.0, 15.0], [-5.0, 12.5], [0.0, 20.0], [5.0, 12.5]])
 ship = []
 for i in range(len(offset1)):
 offset1[i] = rotation(angle).dot(offset1[i])
 ship.append(offset1[i])
 return ship
def Asteroids(angle):
 offset2 = numpy.array([[-10.0, 0.0], [-8.0, -5.0], [-5.0, -8.0], [0.0, -10.0], [6.0, -8.0], [9.0, -4.0], [4.0, -2.0], [10.0, 0.0], [7.0, 7.5], [2.5, 4.0], [0.0, 10.0], [-6.0, 8.0], [-9.0, 3.0], [-4.0, 0.0]])
 asteroid = []
 for i in range(len(offset2)):
 offset2[i] = rotation(angle).dot(offset2[i])
 asteroid.append(offset2[i])
 return asteroid
def player_creator():
 boid = Boid()
 vec = numpy.random.random_sample(2) - numpy.array([0.5, 0.5])
 boid.position = numpy.array([screen[0] / 2, screen[1] / 2])
 boid.velocity = vec / numpy.linalg.norm(vec) * 5
 boid.acceleration = numpy.array([0.0, 0.0])
 if vec[0] < 0 and vec[1] > 0 or vec[0] < 0 and vec[1] < 0:
 boid.angle = numpy.arctan((vec[1]) / (vec[0])) + numpy.pi
 elif vec[0] > 0 and vec[1] < 0:
 boid.angle = numpy.arctan((vec[1]) / (vec[0])) + 2 * numpy.pi
 else:
 boid.angle = numpy.arctan((vec[1]) / (vec[0]))
 return boid
def drawing_player_creator():
 vertices = player.position + player.velocity + numpy.array([Ship(angle)[0], Ship(angle)[1], Ship(angle)[2], Ship(angle)[3]])
 pygame.draw.polygon(display, (0, 0, 0), (vertices), 0)
 pygame.draw.polygon(display, (255, 255, 255), (vertices), 2)
 player.update()
def drawing_booster_flames():
 vertices = player.position + numpy.array([Ship(angle)[2], Ship(angle)[4], Ship(angle)[5], Ship(angle)[6]])
 pygame.draw.polygon(display, (0, 0, 0), (vertices), 0)
 pygame.draw.polygon(display, (255, 255, 255), (vertices), 2)
def asteroid_creator(amount):
 for asteroid in range(amount):
 asteroid = Boid()
 vec = numpy.random.random_sample(2) - numpy.array([0.5, 0.5])
 asteroid.position = numpy.random.random_sample(2) * screen
 asteroid.velocity = vec / numpy.linalg.norm(vec)
 asteroid.acceleration = numpy.array([0.0, 0.0])
 asteroid.angle = numpy.random.random_sample(1)[0] * 2 * numpy.pi
 asteroid.size = numpy.random.randint(2, 8)
 asteroids.append(asteroid)
def drawing_asteroid_creator(amount):
 for n in range(amount):
 #pygame.draw.circle(display, (255 - (n / amount * 255), 255 - (n / amount * 255), 255 - (n / amount * 255)), (int(asteroids[n].position[0]), int(asteroids[n].position[1])), 21)
 #pygame.draw.circle(display, (n / amount * 255, n / amount * 255, n / amount * 255), (int(asteroids[n].position[0]), int(asteroids[n].position[1])), 19)
 asteroid_angle = asteroids[n].angle
 vertices = asteroids[n].position + asteroids[n].size * numpy.array([(Asteroids(asteroid_angle)[0]), (Asteroids(asteroid_angle)[1]), (Asteroids(asteroid_angle)[2]), (Asteroids(asteroid_angle)[3]), (Asteroids(asteroid_angle)[4]), (Asteroids(asteroid_angle)[5]), (Asteroids(asteroid_angle)[6]), (Asteroids(asteroid_angle)[7]), (Asteroids(asteroid_angle)[8]), (Asteroids(asteroid_angle)[9]), (Asteroids(asteroid_angle)[10]), (Asteroids(asteroid_angle)[11]), (Asteroids(asteroid_angle)[12]), (Asteroids(asteroid_angle)[13])])
 pygame.draw.polygon(display, (0, 0, 0), (vertices), 0)
 pygame.draw.polygon(display, (255, 255, 255), (vertices), 2)
 asteroids[n].update()
asteroid_creator(amount)
player = player_creator()
angle = player.angle + numpy.pi / 2
while True:
 for event in pygame.event.get():
 if event.type == pygame.QUIT:
 pygame.quit()
 exit()
 if event.type == pygame.KEYDOWN:
 if event.key == pygame.K_p:
 if paused == False:
 paused = True
 elif paused == True:
 paused = False
 if event.key == pygame.K_LEFT:
 left_down = True
 if event.key == pygame.K_RIGHT:
 right_down = True
 if event.key == pygame.K_SPACE:
 boost = True
 if event.type == pygame.KEYUP:
 if event.key == pygame.K_LEFT:
 left_down = False
 if event.key == pygame.K_RIGHT:
 right_down = False
 if event.key == pygame.K_SPACE:
 boost = False
 if event.type == pygame.VIDEORESIZE:
 screen = (event.w, event.h)
 display = pygame.display.set_mode((screen), pygame.RESIZABLE)
 background = pygame.Surface(screen)
 if paused == False:
 display.blit(background, (0, 0))
 drawing_asteroid_creator(amount)
 drawing_player_creator()
 if left_down == True:
 angle -= delta_angle * 2 * numpy.pi
 #player.velocity = rotation(-delta_angle * 2 * numpy.pi).dot(player.velocity)
 if right_down == True:
 angle += delta_angle * 2 * numpy.pi
 #player.velocity = rotation(delta_angle * 2 * numpy.pi).dot(player.velocity)
 if boost == True:
 drawing_booster_flames()
 player.velocity += rotation(-numpy.pi / 2).dot(numpy.array([numpy.cos(angle), numpy.sin(angle)]) / 10)
 if maxVelocity < numpy.linalg.norm(player.velocity):
 player.velocity = maxVelocity * player.velocity / numpy.linalg.norm(player.velocity)
 if player.position[0] > screen[0] + 15:
 player.position[0] = 0
 if player.position[0] < 0 - 15:
 player.position[0] = screen[0]
 if player.position[1] > screen[1] + 15:
 player.position[1] = 0 - 15
 if player.position[1] < 0 - 15:
 player.position[1] = screen[1] + 15
 for n in range(amount):
 if asteroids[n].position[0] > screen[0] + asteroids[n].size * 15:
 asteroids[n].position[0] = 0 - asteroids[n].size * 15
 if asteroids[n].position[0] < 0 - asteroids[n].size * 15:
 asteroids[n].position[0] = screen[0] + asteroids[n].size * 15
 if asteroids[n].position[1] > screen[1] + asteroids[n].size * 15:
 asteroids[n].position[1] = 0 - asteroids[n].size * 15
 if asteroids[n].position[1] < 0 - asteroids[n].size * 15:
 asteroids[n].position[1] = screen[1] + asteroids[n].size * 15
 clock.tick(120)
 pygame.display.update()
Stephen Rauch
4,31412 gold badges24 silver badges36 bronze badges
asked Jan 26, 2020 at 18:31
\$\endgroup\$

1 Answer 1

1
\$\begingroup\$

I'll focus mostly on the performance issue and only hint at a few design ones at the end.

For this use-case, I'd say stay away from numpy. numpy has some pretty high overhead, which you tend to amortize by faster computations at a "massive" scale. Which is not the case here.

You use numpy for vector addition and for trigonometric functions. In the latter case, you can see that using numpy is the wrong approach. A quick benchmark:

In [5]: import numpy as np 
In [6]: %timeit np.cos(0.6) 
630 ns ± 19.1 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)
In [7]: import math 
In [8]: %timeit math.cos(0.6) 
70.7 ns ± 1.47 ns per loop (mean ± std. dev. of 7 runs, 10000000 loops each)
In [9]: from math import cos 
In [10]: %timeit cos(0.6) 
44.1 ns ± 0.51 ns per loop (mean ± std. dev. of 7 runs, 10000000 loops each)

numpy is ~10x slower than pure Python, and importing the function locally which gives you an extra bump.

For vectors of length 2, probably the overhead of using it is way higher than the computational wins. That's even more when you apply a trigonometric function to a single value (scalar).

If you still want to go on with numpy you'd most likely want to focus on the rotation function.

def rotation(angle):
 rotation = numpy.array([[numpy.cos(angle), -numpy.sin(angle)], [numpy.sin(angle), numpy.cos(angle)]])
 return rotation

Here you calculate twice each trigonometric function used. you could just calculate each once and reuse it

def rotation_reuse(angle):
 c, s = numpy.cos(angle), numpy.sin(angle)
 return numpy.array(((c,-s), (s, c)))

Again, benchmarking this change

In [17] %timeit rotation(0.6) 
4.3 μs ± 252 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)
In [18]: %timeit rotation_reuse(0.6) 
2.68 μs ± 30.6 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)

You can see that it takes basically half the time, as you could expect by doing half the amount of operations.

And if we continue with what we learned already

In [20]: def new_rotation(angle): 
 ...: c, s = cos(angle), sin(angle) 
 ...: return numpy.array(((c,-s), (s, c))) 
 ...: 
In [21]: %timeit new_rotation(0.6) 
1.22 μs ± 18.7 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)

Another 2x improvement, while keeping a numpy array as return value. This won't break the rest of your operations with the positions and velocities. You'd have to check if doing a manual loop to do those is faster or not than using numpy.

Furthermore, on a more "design" level, you have a lot of duplicated code between the Ship and Asteroid functions. Here, you most likely should try to have a class and then create instances, and thus reducing the amount of duplication.

Also, in Python, people tend to write class definitions with the first letter uppercase and functions all lowercase. You have Ship and Asteroid as functions. This ties with the previous paragraph, maybe you already kinda realized that those things are proper entities that deserve a more substantial modeling.

Moreover, improve the doctrings of the functions. As it is now, it's rather hard for a casual reader to understand what's happening.

answered Jan 27, 2020 at 13:58
\$\endgroup\$
4
  • \$\begingroup\$ Thanks for the feedback! I implemented the single vertices calculator but i think i made a mistake because all the objects now spin like crazy. Do you know why that might be? \$\endgroup\$ Commented Jan 27, 2020 at 14:31
  • \$\begingroup\$ Probably you're applying the rotation action onto the objects at every tick of the game clock now. \$\endgroup\$ Commented Jan 27, 2020 at 14:32
  • \$\begingroup\$ I don't know where i am applying it again and again. :S I can't find it \$\endgroup\$ Commented Jan 27, 2020 at 14:36
  • \$\begingroup\$ That's beyond what I can help with. Undo your changes and start again carefully. \$\endgroup\$ Commented Jan 27, 2020 at 14:53

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

By clicking "Post Your Answer", you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.