By the end of this tutorial we will have a complete simulation of a classical problem in physics: the trajectory of a projectile. In this tutorial, we will:

  • Create a function to add vectors
  • Create a vector to represent gravity
  • Add drag and elasticity

And after that we should have a simulation that looks a bit like this:

As usual the code is available at the bottom of this article.

Gravity works by exerting an constant downward force to each of the particles in the simulation. If we had stored the particles' movement as two positions, dx and dy (as discussed here), then we could simply add a constant to the dy value. However, since we’re using vectors, it’s a bit more complex because we need to add two vectors. Once we have a function to add vectors (which took me a while to work out, but is probably the most useful thing in these tutorials), everything else will be a lot easier.

Adding Vectors

The addVectors() function takes two vectors (each an angle and a length), and returns single, combined a vector. First we move along one vector, then along the other to find the x,y coordinates for where the particle would end up (labelled (x, y) on the diagram below).

def addVectors((angle1, length1), (angle2, length2)):
    x  = math.sin(angle1) * length1 + math.sin(angle2) * length2
    y  = math.cos(angle1) * length1 + math.cos(angle2) * length2

We then calculate the vector that gets there directly. To do this we construct a right-angle triangle as shown in the image below. Then we use good old trigonometry and a couple of handy functions from Python’s math module, which I’ve only recently discovered. The new vector length (speed of the particle) is equal to the hypotenuse of the triangle, which can be calculated using math.hypot(). This takes an x,y coordinate and calculates its distance from the origin (0,0). Note that while the position of our particle on the screen is not (0,0), all the vectors are relative to the particle's position, so can be considered to begin at 0,0.

Vector addition

The angle of the new vector is slightly more complex to calculate. First, we find the angle in the triangle, by calculating the arctangent of y/x. We could do this using the math.atan() function but then we would then need to deal with the case of x=0 and work out the sign of angle. However, Python provides us with a handy function math.atan2(), which takes the x, y coordinates, works out the sign of the angle for us and behaves correctly when x=0. Once we have the angle of the triangle, we subtract it from pi/2 to calculate the angle of the vector.

length = math.hypot(x, y)
angle = 0.5 * math.pi - math.atan2(y, x)
return (angle, length)


Now we can create a vector for gravity: the angle is pi, which is downward and I chose a magnitude of 0.002 purely by experimentation. Feel free to change it:

gravity = (math.pi, 0.002)

Then, in the Particle's move() function, we add the gravity vector to the particle’s vector:

(self.angle, self.speed) = addVectors((self.angle, self.speed), gravity)

Now when we run the simulation, we see the particles bouncing about on the ground endlessly. You may wish to set the number_of_particles to 1 to make it easier to follow a particle.


To complete the trajectory simulation and stop the particles from bouncing forever we need to add two more physical phenomena: drag and elasticity.

Drag represents the loss of speed particles experience as they move through the air - the faster a particle is moving, the more speed is lost. I find it simpler (and computationally quicker) to define a drag variable that represents the inverse of drag. We multiple a particle's speed by this value at each time unit, thus the smaller the value, the more speed is lost. Elasticity represents the loss of speed a particle experiences when it hits a boundary.

You can play with the values to see what looks reasonable, though both should be between 0 and 1. I found that 0.999 and 0.7 respectively work quite well.

drag = 0.999
elasticity = 0.75

To the Particle's move() function add:

self.speed *= drag

Another option would be to multiple by a factor that varied inversely with the particle's size (e.g. self.speed *= (1-self.size/10000.0)), but I don't think that's necessary.

After each of the four boundary conditions, add:

self.speed *= elasticity
self.speed *= elasticity
self.speed *= elasticity
self.speed *= elasticity

And there you have it: a complete simulation of a projectile's trajectory. And we haven't had to explicitly solve any of the equations of motion. Try running the simulation several times to see what happens. You might find it easier to set the y coordinate to 20, so the particle always starts at the top of the simulation. In the next tutorial, we add some user interaction so you can pick up, drop and throw the ball (particle).

particle_tutorial_6.txt2.51 KB



Nice tutorials. I learned a lot from them. But one thing. The window doesn't close nicely so what i usually do is that I import pygame like this:

import sys, pygame                                                                                                                                     from pygame.locals import *

and then make the main loop like this:

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

Now the window shuts nicely when you press the windows closing button.                 (Sorry about the bad english)                                                                    

I've never had a problem with the window not closing properly. I think the reason you might is that you're using an integrated development environment (IDE). If your code fixes the problem then that's great. I guess it's probably best to call pygame.quit() and sys.exit() just to be on the safe side anyway.

Ok. I am using IDE, so that's the reason then. I'm really new in writing python so I'm not familiar with other options there are available yet.


Apologies, I just submitted a comment but there were a couple of typo errors. I referred to Tutorial 5 but I meant Tutorial 6 and my code misspelt 'length2' and should have read;

def addVectors(vector1, vector2):
    angle1, length1 = vector1
    angle2, length2 = vector2

Hope this all makes sense.





first of all, thank you very much for this tutorial, it's great!

i have a little problem with the behavior of the particles. over time, if not using drag or elasticity, the "energy" of the system is not conserved. i noticed that because i measured the average speeds over time in the beginning and in the end, and it was significally different - its getting higher over time. what can be the source of that?

i tried to deal with this in several ways, but none of them was elegant and accurate enough for my final goal, which is a simulation of a ideal gas in a tank with gravity.

Hi Yigal,

The equation used here is a big simplification and does tend to lead to increased momentum over time. I've you skip to the tutorial on mass, I use a much better equation which should conserve momentum (although there can still be problems because we have to use discrete units of time).

I believe it would be easier if you measured the angle from the positive x-axis counter clockwise. Then you wouldn't have to subtract the result of atan2(y, x) from pi/2.


These are truly fantastic tutorials that you put together!!! I was wondering how you are choosing the numbers for gravity and so on. Do you have guideline values that can be deducted from the real values (e.g. g = 9.81 etc.)?

Just wondering as I can't see a pattern. Thanks a lot (also for the tutorials)

Thanks. I just picked values that seemed to give a nice result. If you were going to pick real values then you'd have to decide on a scale, so 1 pixel = 1m or something and then you could work out the values from there. Even so, it might be hard to find accurate values or elasticity or resistance in the form that I have it.

Post new comment

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