Movement

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()

Representing movement

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.

import math

We now have access to various mathematical functions, such as sine and cosine, which we'll use shortly.

Movement vectors

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.

The mathematics of movement

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 display() function.

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:

Smeared circles

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)

Random movement

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.

AttachmentSize
particle_tutorial_4.txt1.34 KB

Comments

hi again:

a small error found:

in code snippet about 'Random movement', line 33 lacks the seq parenthesis so:

33   particle = Particle(x, y, size)

should be:

33   particle = Particle((x, y), size)

Thanks, I've changed it. It's nice to know that someone's reading this tutorial.

Iirc, your conversion from degree to radians is the wrong way around. 1 degree = pi/180 radians, and 1 radian = 180/pi degrees. You say that 1 degree = 180/pi radians, which would make 1 degree 57.3 radians.

Thanks Joe, you're absolutely right. I've corrected the text.

Hi Peter, I'm  a complete beginner with a recently acquired raspberry pi and trying to learn python & pygame. I'm still getting used to the RPi so at the moment I am using a VirtualBoxVM (on a Win XP host) to run a Linux Debian OS with Pygame installed. Your tuts are really great and very easy to follow - so many thanks! I have encountered one issue though on Tut 4, Movement. The code runs fine and does everything it is supposed to with one exception. The console display will only update when my mouse pointer is placed over the console window and moved. In the case of Tut No. 4, the console display initially shows the screen with 10 static circles, then as I move my mouse (without any buttons pressed) over the console window, the circles begin to move. Stop moving the mouse and the circles stop. Its as though the program is somehow linked to detecting a mouse movement and only steps through the move() and display() functions when it detects this. I have tried various forums and google to see if this is a commom problem but nothing found - any ideas? 

Hi Colin, I think I've had that issue before. The problem is probably with the indentation. Ensure that screen.fill() and the for particle in my_particles loop are outside of the loop searching for events. Otherwise they will only be called if there is an event, such as the mouse moving over the screen.

Many thanks Peter - perfect diagnosis! I had indeed got the screen.fill and for particle in my_particles indented under the for event loop. Two quick changes to my code and I now have circles floating without the aid of any mouse support. Amazing how obvious things are once they are pointed out (note to self: don't just check you have the same code as the example but check it is in the same place!) With that sorted, I'm off to crack gravity. Thanks again, really enjoying this set of tutorials, are you planning to do any more?

Well, I was only able to diagnose the problem because I've had it myself more than once. It is the one problem I have with Python's indentation - confusing bugs can occur when copy-and-pasting sections of code. It's even more of a problem if the code switches from spaces to tabs as then indentation can be wrong even if it looks right.

I'm not sure about making more tutorials in this series. I don't know what else to add. I have a couple of ideas, I might try. I could move it into the third dimension which would require a complete rewrite (something I've started), or maybe add joints to create a sort of skeleton to create stick figures that can move in a fluid way.

Good luck with the rest of the tutorials and beyond.

Hi, for the line particle = Particle((x,y), size)

I get an error telling me name 'Particle' is not defined. Any ideas why this is?

Thanks,matt

Hi Matt,

It's difficult to say for sure without seeing your code. Do you definitely have the Particle class above that line and it's definitely spelt correct? The only other thing I can think of is that there might be some problem with the indentation somewhere so the Particle class is out of scope.

hey peter thanks for these tutorials! Can you make a tutorial on rotating objects??

HI, i really enjoy these tutorials. Thanks

I don't think it matters much, but shouldn't the line 22 "self.x += math.sin(self.angle) * self.speed" use cos instead of sin? since sin is o/h and once we mutiply it by the speed(the hyptenuse)  it would give as the chage in y, not the change in x. Similarly line 23 should have used sin instead of cos, using the same reasoning.

 

Hi,

First of all, thank you for creating this tutorial site. It helps me see python programming in different light.  My I.Q has increased by one point.

Anyway, I wanted to share something with you. I improved your code a bit to slow down the screen update. Without the modification, the circles would run off the screen before I had a chance to see what happen. I am using a fast computer with Python 3.3 which is why.

So I added a timer to slow things down a bit.

running = True
clock = pygame.time.Clock() <-- add this

while running:
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            running = False

    screen.fill(background_colour)

    for particle in my_particles:
        particle.move()
        particle.display()
       
    clock.tick(60)  <-- add this
    pygame.display.flip()

pygame.quit() <-- optional but useful

Don't set the clock.tick( ) more than 60, you won't be able to see what happen. Too fast for your eyes to see. You can set it to 20 for slowness.

Post new comment

The content of this field is kept private and will not be shown publicly.