For the next couple of weeks I though we might explore the rudiments of physics simulators and I was inspired by the excellent tutorials from Peter Collingridge on the topic. We have covered all the basic python tools we need already, so I think we can leap in with a fairly functional demo which will allow you to fling some 2D circular shapes (we’re calling them particles) around using the mouse. The particle implementation is also an excellent use of the concept of Classes we discussed a few weeks ago. In the code below you can see how we group together all the data and behaviour (the functions or methods) of a particle in one piece of code that defines a Particle class and then create lots of ‘instances’ of particles – each has the same structure (making all the management code much easier) but they all maintain own specific data values - positions, speeds and directions etc. This is perhaps the simplest (but still very powerful) benefit of using classes – we will come onto other benefits in due course.
Take a look at the code below. At the bottom you should by now recognise the absolutely standard game loop – with the usual input->update->draw cycle. I hope you will see it is surprisingly short and very readable! This is a good indication of the well structured code above. In the middle of the code, after we define a couple of helper functions, is the definition of the class (make sure you get your indenting right here – the class’ functions must be indented inside the class level. The interesting aspects of the maths I will cover in tonight’s session – but it all comes down to some very simple sin, cos, tan usage and good ol’ Pythagoras.
import pygameimport randomimport mathbackground_colour = (255,255,255)(width, height) = (400, 400)drag = 0.999elasticity = 0.75gravity = (math.pi, 0.2)def addVectors(vector1, vector2):angle1 = vector1length1 = vector1angle2 = vector2length2 = vector2x = math.sin(angle1) * length1 + math.sin(angle2) * length2y = math.cos(angle1) * length1 + math.cos(angle2) * length2angle = 0.5 * math.pi - math.atan2(y, x)length = math.hypot(x, y)return (angle, length)def findParticle(particles, x, y):for p in particles:if math.hypot(p.x-x, p.y-y) <= p.size:return preturn Noneclass Particle():def __init__(self, x_y, size):self.x = x_yself.y = x_yself.size = sizeself.colour = (0, 0, 255)self.thickness = 1self.speed = 0self.angle = 0def display(self):pygame.draw.circle(screen, self.colour, (int(self.x), int(self.y)), self.size, self.thickness)def move(self):(self.angle, self.speed) = addVectors((self.angle, self.speed), gravity)self.x += math.sin(self.angle) * self.speedself.y -= math.cos(self.angle) * self.speedself.speed *= dragdef bounce(self):if self.x > width - self.size:self.x = 2*(width - self.size) - self.xself.angle = - self.angleself.speed *= elasticityelif self.x < self.size:self.x = 2*self.size - self.xself.angle = - self.angleself.speed *= elasticityif self.y > height - self.size:self.y = 2*(height - self.size) - self.yself.angle = math.pi - self.angleself.speed *= elasticityelif self.y < self.size:self.y = 2*self.size - self.yself.angle = math.pi - self.angleself.speed *= elasticityscreen = pygame.display.set_mode((width, height))pygame.display.set_caption('Angry Circles')number_of_particles = 3my_particles = for n in range(number_of_particles):size = random.randint(10, 20)x = random.randint(size, width-size)y = random.randint(size, height-size)particle = Particle((x, y), size)particle.speed = random.random()particle.angle = random.uniform(0, math.pi*2)my_particles.append(particle)clock = pygame.time.Clock()selected_particle = Nonerunning = Truewhile running:clock.tick(20)for event in pygame.event.get():if event.type == pygame.QUIT:running = Falseelif event.type == pygame.MOUSEBUTTONDOWN:(mouseX, mouseY) = pygame.mouse.get_pos()selected_particle = findParticle(my_particles, mouseX, mouseY)elif event.type == pygame.MOUSEBUTTONUP:selected_particle = Noneif selected_particle:(mouseX, mouseY) = pygame.mouse.get_pos()dx = mouseX - selected_particle.xdy = mouseY - selected_particle.yselected_particle.angle = 0.5*math.pi + math.atan2(dy, dx)selected_particle.speed = math.hypot(dx, dy) * 0.1screen.fill(background_colour)for particle in my_particles:particle.move()particle.bounce()particle.display()pygame.display.flip()