In the previous tutorial, we completed a simulation of a particle's trajectory. Unfortunately, once we'd added drag and friction, the simulation came to a halt pretty quickly. In order to try out different starting conditions, we have to edit the code and restart the simulation. It would be much simpler if we could reach into our virtual world and interact with it directly. In this tutorial, we will:
- Add the ability to select particles with a mouse click
- Add the ability to move particles with the mouse
- Add the ability to throw particles with the mouse
As usual the complete code can be found by clicking the Github link at the top of this post. By the end of this tutorial, you too will be able to fling the particles about like this:
Getting the mouse position
Firstly, set the number_of_particles to 3, which will give us a few to choose between without cluttering the screen.
In order to test whether the user has selected a particle, we need to know where they have clicked and we do this by using pygame.mouse.get_pos(). As the name suggests, this function returns the position of the mouse in the Pygame window as an x, y coordinate. For now we only need to know the position of the mouse when the user clicks. We test this by monitoring Pygame events as we did in tutorial 1 using pygame.event.get(). Since we are already calling this function, looking for QUIT events, we might as well search for MOUSEBUTTONDOWN events at the same time.
for event in pygame.event.get(): if event.type == pygame.QUIT: running = False if event.type == pygame.MOUSEBUTTONDOWN: (mouseX, mouseY) = pygame.mouse.get_pos() print mouseX, mouseY
Now, whenever we click in the Pygame window, the position of the mouse is printed to the command line. Note that the MOUSEBUTTONDOWN event is only found once per mouse click; it is not repeatedly found if the mouse is held down.
Rather than print the position of the mouse, we want to test whether the mouse is within the boundary of a particle. Since the particles are circular, this is very straightforward: we test all the particles to see whether the distance from the mouse to particle's centre is less than that particle's size. We can do this with our old friend, math.hypot().
def findParticle(particles, x, y): for p in particles: if math.hypot(p.x-x, p.y-y) <= p.size: return p return None
The return None command is not strictly necessary as Python will return None if it reaches the end of a function without finding a return command. Also note, that if the function finds that the first particle in the array is selected, it will return that particle without bothering to check the other particles.
We can test our findParticle() function by replacing the print function with:
selected_particle = findParticle(my_particles, mouseX, mouseY) if selected_particle: selected_particle.colour = (255,0,0)
If you click on a particle now, its colour should change to red. You can remove this code now if you want.
When we have selected a particle, we want it to stop moving, so change the code that makes particle move to:
for particle in my_particles: if particle != selected_particle: particle.move() particle.bounce() particle.display()
And add selected_particle = None before the while loop starts so the variable exists before a mouse click. We also need to cancel the selection once the mouse button is released, so below the code checking for MOUSEBUTTONDOWN events, add:
elif event.type == pygame.MOUSEBUTTONUP: selected_particle = None
We can now select a particle and stop it, but we want to be able to drag it somewhere. We do this by making the x, y coordinates of a selected particle (if there is one) equal the coordinates of the mouse.
if selected_particle: (mouseX, mouseY) = pygame.mouse.get_pos() selected_particle.x = mouseX selected_particle.y = mouseY
We can now pick up and drag the particles. However, when we let go, the particles drop to the ground in a disappointing sort of way. Instinctively, we expect the particle to be released with a speed equal to the speed the mouse was moving at the time. For this we need to measure the difference between the position of the mouse and the particle and creating a vector that joins them. We can then setting the particle's angle and speed to be this vector. Replace, the above code with the below:
if selected_particle: (mouseX, mouseY) = pygame.mouse.get_pos() dx = mouseX - selected_particle.x dy = mouseY - selected_particle.y selected_particle.angle = math.atan2(dy, dx) + 0.5*math.pi selected_particle.speed = math.hypot(dx, dy) * 0.1
I've found that setting the particle's speed to 0.1 time the actual length of the vector works best. This means it will actually take ten units of time for the particle to catch up with the mouse. Note also that the angle is the arctangent plus half pi. Just trust me it is. I'll try to draw a diagram explaining why later. We now need to ensure the selected particle moves, so change the code back to move all the particle each turn (sorry).
So now we have a simulation that we can interact with. It's quite fun to fling the particles about and I think this simulation could easily be adapted into some sort of game. However, the particles still don't interact with one another. In the next tutorial, I hope to remedy this.