Wednesday, 3rd February 2010
Pretty much by definition, all simulations have values which change over time. One value that often changes is the position of an object, in which case the object is moving. In this tutorial, we will:
- Give our particles speed and direction (a velocity vector)
- Use basic trigonometry to convert vectors into movement
- Make the particles move randomly
Our simulation is a discrete time simulation, which means that we split time into individual steps. At each step we update the simulation a bit then display the new situation. We keep updating the simulation until the user exits. We do this by adding code into the infinite
while loop that we've already created. The first thing we do therefore is move the following block of code from before the
while loop to inside it. This will have no effect on how the program runs but allows us to add additional function calls later.
for particle in my_particles: particle.display() pygame.display.flip()
The simplest way to represent movement is to create two attributes: dx and dy. Then during each pass through the loop, add dx to x and dy to y. So, for example, if a particle has dx=2 and dy=1, it will follow a shallow diagonal, from left to right and top to bottom. This s the method I used for a long time - it is simple and fine for many situations. However, when we come to work out interactions between two particles, things become a lot more complex.
My preferred method now is to create attributes to represent speed and direction (i.e. velocity). This requires a bit more work to start with but makes life a lot easier later one. This method is also good for creating objects that have a constant speed, but varying direction. I actually first started using this method when trying to create an ant simulation. The ants have a creepily realistic movement when given a constant speed and randomly changing their a direction every few seconds.
So let's give our particle object a speed and a direction.
self.speed = 0.01 self.angle = 0
The math module
Since we’re going to use some trigonometry, we need to import Python’s math module. Like the random module, this is part of main Python program so there's no need to download anything extra.
We now have access to various mathematical functions, such as sine and cosine, which we'll use shortly.
We now need to add a
move() function to our particle object. This function will change the x, y coordinates of the particle based on its speed and the angle of its movement. The diagram below illustrates how we calculate the change in x and y. I find it simplest to consider an an angle of 0 to be pointing upwards, despite the fact that y-axis actually pointing downwards in computer graphics.
To calculate the change in x and y, we use some secondary school-level trigonometry as shown in the code below. Remember to minus the y to take into account the downward pointing y-axis. Although it doesn't make much difference at the moment you will have to be consistent with your signs later.
def move(self): self.x += math.sin(self.angle) * self.speed self.y -= math.cos(self.angle) * self.speed
Another point to bear in mind is that the angles are all in radians. If you’re used to working with degrees then this might be a little confusing; just remember that 1 radians = 180°/π. Therefore if you want to make the particle move forwards (left to right) along the screen, then its angle should be π/2 (90°). In Python we do this like so:
self.angle = math.pi/2
Now we can we call the particles'
move() function during the loop immediately before calling their
for particle in my_particles: particle.move() particle.display()
If we run this program now, we’ll see the circles moving right, leaving a smear across the screen:
The reason the particles smear is that once Pygame has drawn something on the window it will stay there unless drawn over. The easiest way to clear the circles from the previous time step is to fill the whole screen with the background colour. We can do this by simply moving the
screen.fill(background_colour) command into the loop. The effect of movement is therefore achieved by drawing a particle, then clearing it and drawing a short distance away.
You might have also got a deprecation warning telling you "integer argument expected, got float" followed by the
pygame.draw.circle() function. The reason, as you may have guessed, is that this pygame can only draw circles at integer x, y coordinates and after we move the circles, their coordinates become floating point numbers. Although the Python deals with the problem perfectly well, we should convert the x, y coordinates to integers first:
pygame.draw.circle(screen, self.colour, (int(self.x), int(self.y)), self.size, self.thickness)
If you run this program now, you’ll see all the circles moving rightward at the same speed, so it will look like they are all draw on a single moving surface. We can making things more interesting by giving each particle a random speed and direction. We could do this by defining the speed and angle attributes as random in the Particle class, but I prefer to set these values (the default behaviour) to 0. We can update them once the individual objects have been created, by altering our particle-creating loop:
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)
Notice that we use the
random.random() function to generate a speed between 0 and 1, and the
random.uniform() function to create a random angle between 0 and 2*pi, which covers all angles. If you run the program now, the circles will fly off the screen at random angles and speeds. In the next tutorial I tell you how to keep the particle within the bounds of the window.