From 2ce177af39c3ccd03a028f1ba9dd8bf63bfb1948 Mon Sep 17 00:00:00 2001 From: Danny Staple Date: Mon, 25 Nov 2024 16:14:14 +0000 Subject: [PATCH 1/3] Most of the way to an article --- ...g-particle-systems-for-fun-and-robotics.md | 350 ++++++++++++++++++ .../one-dot.png | Bin 0 -> 2139 bytes .../pygame-coordinates.png | Bin 0 -> 15013 bytes .../pygame-coordinates.svg | 119 ++++++ .../raindrops.png | Bin 0 -> 46494 bytes 5 files changed, 469 insertions(+) create mode 100644 content/2024/11/24-making-particle-systems-for-fun-and-robotics/24-making-particle-systems-for-fun-and-robotics.md create mode 100644 content/2024/11/24-making-particle-systems-for-fun-and-robotics/one-dot.png create mode 100644 content/2024/11/24-making-particle-systems-for-fun-and-robotics/pygame-coordinates.png create mode 100644 content/2024/11/24-making-particle-systems-for-fun-and-robotics/pygame-coordinates.svg create mode 100644 content/2024/11/24-making-particle-systems-for-fun-and-robotics/raindrops.png diff --git a/content/2024/11/24-making-particle-systems-for-fun-and-robotics/24-making-particle-systems-for-fun-and-robotics.md b/content/2024/11/24-making-particle-systems-for-fun-and-robotics/24-making-particle-systems-for-fun-and-robotics.md new file mode 100644 index 00000000..8357c37d --- /dev/null +++ b/content/2024/11/24-making-particle-systems-for-fun-and-robotics/24-making-particle-systems-for-fun-and-robotics.md @@ -0,0 +1,350 @@ +--- +title: How to make particle systems for fun and robotics +date: 2024-11-24 +tags: ["robotics", "programming", "python", "particle systems"," robotics at home"] +thumbnail: content/2024/11/24-making-particle-systems-for-fun-and-robotics/raindrops.png +--- +I have a lifelong fascination with Particle systems, complementing my robotics. However, it was only a few years ago that I found out how to bring the two together in a Monte Carlo Localization particle filter. + +Today I am going to express my love of particle systems in Python. This might be a long ride, with a few programs in a series of posts. I hope you'll join me, with some demonstrations in how much fun these can be. IT would be helpful if you've done a little Python before, but this aims to be a beginner-friendly course. + +This is going to be a visual course in programming. We'll write it using PyGame letting us get a lot on the screen. Beyond being handy in robotics, this is fun for some simple visual effects, games and simulations. + +## Getting prepared + +Depending on your experience, you will need an environment to run this in. + +For absolute beginners, I recommend starting in the Mu editor, as this comes with a python environment and most of what you'll need to follow along. However, if you are more experienced, you can use your own IDE. + +If you are using your own IDE you will need to ensure you have at least Python 3.8 with PyGame installed. You can install PyGame using pip or poetry: + +```bash +pip install pygame +``` + +## One dot + +The simplest particle system is one dot. We'll use this to get up and running. This is the least interesting particle system, but we can build on it to something more fun. + +```python +import pygame + +WIDTH = 800 +HEIGHT = 800 +FRAME_RATE = 60 +BG_COLOUR = pygame.Color("darkslategray1") +DOT_COLOUR = pygame.Color("deepskyblue4") +DOT_SIZE = 2 + +one_dot = [400, 400] + +def draw(surface): + pygame.draw.circle(surface, DOT_COLOUR, one_dot, DOT_SIZE) + + +pygame.init() +screen = pygame.display.set_mode((WIDTH, HEIGHT)) +clock = pygame.time.Clock() + +running = True +while running: + for event in pygame.event.get(): + if event.type == pygame.QUIT: + running = False + + screen.fill(BG_COLOUR) + draw(screen) + pygame.display.flip() + clock.tick(FRAME_RATE) +pygame.quit() +``` + +Run it and you should see this: + +![One dot on the screen](/2024/11/24-making-particle-systems-for-fun-and-robotics/one-dot.png) + +We start with importing [PyGame](https://www.pygame.org/), a python gaming library for drawing 2D games. + +We then set up some parameters for our program. We define the display size with `WIDTH` and `HEIGHT`. We use FRAME_RATE so the program runs at a consistent speed. + +We then set up some colours. A background colour, and a colour for our dot, followed by a dot size. putting these things in parameters makes them easy to change later. + +We then define a list with two numbers, the x and y position of the dot. This is our particle, followed by a function to draw our particle, a circle on the screen. In PyGame, the top left corner is 0,0, so the bottom of the screen is HEIGHT. + +![PyGame Coordinate System](/2024/11/24-making-particle-systems-for-fun-and-robotics/pygame-coordinates.png) + +After this we initialise pygame, with a screen and a clock and enter a main loop. The main loop starts with a running variable, so the system can be told when to exit. The code looks for a QUIT event, triggered when you close the window to ensure it shuts down. This is a common pattern in PyGame. + +The next part of the loop fills the screen with background colour. We then use the `draw` function to draw the dot. We must call `pygame.display.flip` as pygame draws everything on a hidden buffer, which we swap with the visible screen. + +Finally we tick the clock to keep the framerate the same. The last line of the program is `pygame.quit` to ensure everything is cleaned up. + +This is drawn at 400, 400 which is the middle of the screen. I suggest you try a few values between 0 and the WIDTH to see how it changes. I guarantee you won't like my colour choices, so you can also try different colour names, or even RGB values (like `(255, 0, 0)` for red). + +## Making it a bit random + +A key concept in a particle system is randomness. We can make the one dot less boring by making it random. + +At the top of the file, lets import the random module above pygame: + +```python +import random +import pygame +``` + +Now we can make it show the dot at a random place every time we run it. Update the one_dot line to be: + +```python +one_dot = [random.randint(0, WIDTH), random.randint(0, HEIGHT)] +``` + +## Movement + +We have a particle, but particles have a lifecycle: + +- They are created +- They update and change over time +- They may also die + +We can add a little movement to our particle. Let's introduce a speed constant and update our particle with it. + +In the constants add the following: + +```python +SPEED = 2 +``` + +We can add an update function, which can be called in the main loop: + +```python +def update(): + one_dot[1] += SPEED + if one_dot[1] >= HEIGHT: + one_dot[1] = 0 +``` + +This will add the speed to the second element (the Y coordinate) of the one_dot list. If the dot reaches the bottom of the screen, we reset it to the top. + +We then call this in the main loop before we start drawing things: + +```python +running = True +while running: + for event in pygame.event.get(): + if event.type == pygame.QUIT: + running = False + + update() + screen.fill(BG_COLOUR) + draw(screen) + pygame.display.flip() + clock.tick(FRAME_RATE) +``` + +Run this. You can adjust the speed by changing the SPEED constant, or increasing the FRAME_RATE, however, note that above a certain frame rate value we may be being slowed by the speed of the program. With high SPEED values, you may see the dot jump on the screen. + +This dot has a lifecycle: + +- It is created at a random position +- It moves down the screen +- When it reaches the bottom, it is moved back to the top + +## Multiple dots + +We can make this more interesting again by having multiple dots, like a rain storm. We can do this by having a list of dots. + +Swap our one dot for this: + +```python +POPULATION_SIZE = 200 + +raindrops = [] + +def populate(): + for n in range(POPULATION_SIZE): + raindrops.append( + [ + random.randint(0, WIDTH), + random.randint(0, HEIGHT), + ] + ) +``` + +We then need to update the draw function to draw all the dots: + +```python +def draw(surface): + for raindrop in raindrops: + pygame.draw.circle(surface, DOT_COLOUR, raindrop, DOT_SIZE) +``` + +And modify the update function to move all the dots: + +```python +def update(): + for raindrop in raindrops: + raindrop[1] += SPEED + if raindrop[1] >= HEIGHT: + raindrop[1] = 0 +``` + +Finally, we need to call the populate function to create the dots, while we initialise the program: + +```python +pygame.init() +screen = pygame.display.set_mode((WIDTH, HEIGHT)) +clock = pygame.time.Clock() +populate() +``` + +Run this, and you should now see dots falling down the screen. I've also used some more rain-like colours. + +![Raindrops on the screen](/2024/11/24-making-particle-systems-for-fun-and-robotics/raindrops.png) + +The colour names I've used are `darkslategray1` for the background and `deepskyblue4` for the dots. You can find a list of colour names in the [PyGame documentation](https://www.pygame.org/docs/ref/color.html). + +You can modify the number of dots, but be aware that too large a number might make it slower. You can still adjust the speed and size of the dots. + +These dot's all have the same lifecycle as our one dot! + +## Adjusting the lifecycle + +We can change this particle system in a few interesting ways. You might have noticed the raindrops loop around in a repeating pattern. + +We can fix this by changing the lifecycle. Instead of just wrapping the raindrop, we can pretend this raindrop has reached the end of the lifecycle and that we are creating a new one. However, we can do something sneaky and reset the x to a random value when we do this. + +We only need to modify the `update` function: + +```python +def update(): + for raindrop in raindrops: + raindrop[1] += SPEED + if raindrop[1] >= HEIGHT: + raindrop[1] = 0 + raindrop[0] = random.randint(0, WIDTH) +``` + +You shouldn't be able to see a repeating pattern any more. + +## Random speeds + +We can add a little depth by adding a further parameter to our raindrops. For this we will change two parts of the lifecycle - the add function and the update function. +We'll also adjust the parameters above. + +```python +POPULATION_SIZE = 200 +MIN_SPEED = 2 +MAX_SPEED = 6 +``` + +In the populate function, lets make a random speed: + +```python +def populate(): + for n in range(POPULATION_SIZE): + raindrops.append( + [ + random.randint(0, WIDTH), + random.randint(0, HEIGHT), + random.randint(MIN_SPEED, MAX_SPEED), + ] + ) +``` + +We can then update using this stored speed: + +```python +def update(): + for raindrop in raindrops: + raindrop[1] += raindrop[2] + if raindrop[1] >= HEIGHT: + raindrop[1] = 0 + raindrop[0] = random.randint(0, WIDTH) +``` + +However, we made an assumption in draw that the raindrop was only 2 numbers - the coordinates of the drop. With 3, we need to filter them: + +```python +def draw(surface): + for raindrop in raindrops: + pygame.draw.circle(surface, DOT_COLOUR, raindrop[:2], DOT_SIZE) +``` + +If you run this, you can now see raindrops falling at different speeds. + +## Checkpoint - raindrops + +We've built a small particle system, transforming a single static dot into a rainstorm with raindrops at different speeds. Here's the full code: + +```python +import random +import pygame + +WIDTH = 800 +HEIGHT = 800 +FRAME_RATE = 60 +BG_COLOUR = pygame.Color("darkslategray1") +DOT_COLOUR = pygame.Color("deepskyblue4") +DOT_SIZE = 2 +SPEED = 2 +POPULATION_SIZE = 200 +MIN_SPEED = 2 +MAX_SPEED = 6 + +raindrops = [] + +def populate(): + for n in range(POPULATION_SIZE): + raindrops.append( + [ + random.randint(0, WIDTH), + random.randint(0, HEIGHT), + random.randint(MIN_SPEED, MAX_SPEED), + ] + ) + +def draw(surface): + for raindrop in raindrops: + pygame.draw.circle(surface, DOT_COLOUR, raindrop[:2], DOT_SIZE) + +def update(): + for raindrop in raindrops: + raindrop[1] += raindrop[2] + if raindrop[1] >= HEIGHT: + raindrop[1] = 0 + raindrop[0] = random.randint(0, WIDTH) + +pygame.init() +screen = pygame.display.set_mode((WIDTH, HEIGHT)) +clock = pygame.time.Clock() +populate() + +running = True +while running: + for event in pygame.event.get(): + if event.type == pygame.QUIT: + running = False + + update() + screen.fill(BG_COLOUR) + draw(screen) + pygame.display.flip() + clock.tick(FRAME_RATE) +pygame.quit() +``` + +## Other ideas + +With this system, you could make the raindrops different sizes. You could add wind factors and other elements. + +You can add backgrounds or other embellishments. + +## Summary + +You've built a simple particle system, raindrops, using Python and PyGame. You've used the random number generator to position them on the screen, and for other aspects of the particles. + +You've also seen how particles have a lifecycle. + +Over the coming for posts, we can explore what other ways we can use particle systems, some variations on this theme, and some quite different. + +I've built this inspired by the Kingston University Coder Dojo where I mentor python, and will have other particle systems inspired by research I've done for my books. diff --git a/content/2024/11/24-making-particle-systems-for-fun-and-robotics/one-dot.png b/content/2024/11/24-making-particle-systems-for-fun-and-robotics/one-dot.png new file mode 100644 index 0000000000000000000000000000000000000000..95359405d7df67d1509e125b20388adb04d3bfc0 GIT binary patch literal 2139 zcmeAS@N?(olHy`uVBq!ia0y~yV0;6_mpRygB2U@0C4dxTage(c!@6@aFM%AEbVpxD z28NCO+M1MG6B0`v*@K$B>G+x3@P2-gRJb4Xl;1YEwHA z+iAPx$ht$m45k-YeJ5CND-OD`RV3h0-0L|r&2PmYoGbchQ||5G5)2zQtbWYNpyRWF zQNdvpkA}f$8W@l?@WANXKKaJYJ0y4C>%TpJ;~z z@#p=sv%l)z{JydM@%QV=`;Xr{)?iwx%BNmrZI*xa^Us^FZ`}RJa_z}f3kDVrvpFaJ z9C%as=IQHM4q2*zQODu vTLVM=VITowjN;KS7)=9%E)6`*z0GdIE;EOFo>(EULBQbY>gTe~DWM4fWyGWm literal 0 HcmV?d00001 diff --git a/content/2024/11/24-making-particle-systems-for-fun-and-robotics/pygame-coordinates.png b/content/2024/11/24-making-particle-systems-for-fun-and-robotics/pygame-coordinates.png new file mode 100644 index 0000000000000000000000000000000000000000..6f689adad6dfb1b81628b30d74b41c4e2e5eb137 GIT binary patch literal 15013 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

)@vOJ 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 QJ|VC(9>3R|4*Bc<02`cuD*ylh literal 0 HcmV?d00001 diff --git a/content/2024/11/24-making-particle-systems-for-fun-and-robotics/pygame-coordinates.svg b/content/2024/11/24-making-particle-systems-for-fun-and-robotics/pygame-coordinates.svg new file mode 100644 index 00000000..99d418a1 --- /dev/null +++ b/content/2024/11/24-making-particle-systems-for-fun-and-robotics/pygame-coordinates.svg @@ -0,0 +1,119 @@ + + + + + + + + + 0,0 + WIDTH, HEIGHT + WIDTH, 0 + 0, HEIGHT + + x, y + + + diff --git a/content/2024/11/24-making-particle-systems-for-fun-and-robotics/raindrops.png b/content/2024/11/24-making-particle-systems-for-fun-and-robotics/raindrops.png new file mode 100644 index 0000000000000000000000000000000000000000..82296856c49a098a6ff92e9afb61e37ba1da9d50 GIT binary patch literal 46494 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 literal 0 HcmV?d00001 From eba7479713613ec3db18ddd0d602fa8c27ba6e17 Mon Sep 17 00:00:00 2001 From: Danny Staple Date: Mon, 25 Nov 2024 16:46:08 +0000 Subject: [PATCH 2/3] Improve --- .../24-making-particle-systems-for-fun-and-robotics.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/content/2024/11/24-making-particle-systems-for-fun-and-robotics/24-making-particle-systems-for-fun-and-robotics.md b/content/2024/11/24-making-particle-systems-for-fun-and-robotics/24-making-particle-systems-for-fun-and-robotics.md index 8357c37d..cfbdb08b 100644 --- a/content/2024/11/24-making-particle-systems-for-fun-and-robotics/24-making-particle-systems-for-fun-and-robotics.md +++ b/content/2024/11/24-making-particle-systems-for-fun-and-robotics/24-making-particle-systems-for-fun-and-robotics.md @@ -6,15 +6,15 @@ thumbnail: content/2024/11/24-making-particle-systems-for-fun-and-robotics/raind --- I have a lifelong fascination with Particle systems, complementing my robotics. However, it was only a few years ago that I found out how to bring the two together in a Monte Carlo Localization particle filter. -Today I am going to express my love of particle systems in Python. This might be a long ride, with a few programs in a series of posts. I hope you'll join me, with some demonstrations in how much fun these can be. IT would be helpful if you've done a little Python before, but this aims to be a beginner-friendly course. +Today I am going to express my love of particle systems in Python. This might be a long ride, with a few programs in a series of posts. I hope you'll join me, with some demonstrations in how much fun these can be. It would be helpful if you've done a little Python before, but this aims to be a beginner-friendly course. -This is going to be a visual course in programming. We'll write it using PyGame letting us get a lot on the screen. Beyond being handy in robotics, this is fun for some simple visual effects, games and simulations. +This series will focus on programming with lots of visuals. We'll write it using PyGame letting us get a lot on the screen. Beyond being handy in robotics, this is fun for some simple visual effects, games and simulations. ## Getting prepared Depending on your experience, you will need an environment to run this in. -For absolute beginners, I recommend starting in the Mu editor, as this comes with a python environment and most of what you'll need to follow along. However, if you are more experienced, you can use your own IDE. +For absolute beginners, I recommend starting in the Mu editor, as this comes with a Python environment and most of what you'll need to follow along. However, if you are more experienced, you can use your own IDE. If you are using your own IDE you will need to ensure you have at least Python 3.8 with PyGame installed. You can install PyGame using pip or poetry: @@ -63,7 +63,7 @@ Run it and you should see this: ![One dot on the screen](/2024/11/24-making-particle-systems-for-fun-and-robotics/one-dot.png) -We start with importing [PyGame](https://www.pygame.org/), a python gaming library for drawing 2D games. +We start with importing [PyGame](https://www.pygame.org/), a Python gaming library for drawing 2D games. We then set up some parameters for our program. We define the display size with `WIDTH` and `HEIGHT`. We use FRAME_RATE so the program runs at a consistent speed. @@ -347,4 +347,4 @@ You've also seen how particles have a lifecycle. Over the coming for posts, we can explore what other ways we can use particle systems, some variations on this theme, and some quite different. -I've built this inspired by the Kingston University Coder Dojo where I mentor python, and will have other particle systems inspired by research I've done for my books. +I've built this inspired by the Kingston University Coder Dojo where I mentor Python, and will have other particle systems inspired by research I've done for my books. From c571f42f8151316b6d7c874603969f191cb77637 Mon Sep 17 00:00:00 2001 From: Danny Staple Date: Mon, 25 Nov 2024 16:55:18 +0000 Subject: [PATCH 3/3] Improve --- ...g-particle-systems-for-fun-and-robotics.md | 66 +++++++++---------- 1 file changed, 32 insertions(+), 34 deletions(-) diff --git a/content/2024/11/24-making-particle-systems-for-fun-and-robotics/24-making-particle-systems-for-fun-and-robotics.md b/content/2024/11/24-making-particle-systems-for-fun-and-robotics/24-making-particle-systems-for-fun-and-robotics.md index cfbdb08b..0af02fab 100644 --- a/content/2024/11/24-making-particle-systems-for-fun-and-robotics/24-making-particle-systems-for-fun-and-robotics.md +++ b/content/2024/11/24-making-particle-systems-for-fun-and-robotics/24-making-particle-systems-for-fun-and-robotics.md @@ -8,7 +8,7 @@ I have a lifelong fascination with Particle systems, complementing my robotics. Today I am going to express my love of particle systems in Python. This might be a long ride, with a few programs in a series of posts. I hope you'll join me, with some demonstrations in how much fun these can be. It would be helpful if you've done a little Python before, but this aims to be a beginner-friendly course. -This series will focus on programming with lots of visuals. We'll write it using PyGame letting us get a lot on the screen. Beyond being handy in robotics, this is fun for some simple visual effects, games and simulations. +This series will focus on programming with lots of visuals. You'll write it using PyGame letting us get a lot on the screen. Beyond being handy in robotics, this is fun for some simple visual effects, games and simulations. ## Getting prepared @@ -24,7 +24,7 @@ pip install pygame ## One dot -The simplest particle system is one dot. We'll use this to get up and running. This is the least interesting particle system, but we can build on it to something more fun. +The simplest particle system is one dot. You'll use this to get up and running. This is the least interesting particle system, but you can build on it to something more fun. ```python import pygame @@ -63,27 +63,25 @@ Run it and you should see this: ![One dot on the screen](/2024/11/24-making-particle-systems-for-fun-and-robotics/one-dot.png) -We start with importing [PyGame](https://www.pygame.org/), a Python gaming library for drawing 2D games. +The code starts with importing [PyGame](https://www.pygame.org/), a Python gaming library for drawing 2D games. It then sets up some parameters for your program. It defines the display size with `WIDTH` and `HEIGHT`, and uses `FRAME_RATE` so the program runs at a consistent speed. -We then set up some parameters for our program. We define the display size with `WIDTH` and `HEIGHT`. We use FRAME_RATE so the program runs at a consistent speed. +You then set up some colours. A background colour, and a colour for your dot, followed by a dot size. putting these things in parameters makes them easy to change later. -We then set up some colours. A background colour, and a colour for our dot, followed by a dot size. putting these things in parameters makes them easy to change later. - -We then define a list with two numbers, the x and y position of the dot. This is our particle, followed by a function to draw our particle, a circle on the screen. In PyGame, the top left corner is 0,0, so the bottom of the screen is HEIGHT. +Your code then defines a list with two numbers, the x and y position of the dot. This is your particle, followed by a function to draw our particle, a circle on the screen. In PyGame, the top left corner is 0,0, so the bottom of the screen is HEIGHT. ![PyGame Coordinate System](/2024/11/24-making-particle-systems-for-fun-and-robotics/pygame-coordinates.png) -After this we initialise pygame, with a screen and a clock and enter a main loop. The main loop starts with a running variable, so the system can be told when to exit. The code looks for a QUIT event, triggered when you close the window to ensure it shuts down. This is a common pattern in PyGame. +After this you initialise pygame, with a screen and a clock and enter a main loop. The main loop starts with a running variable, so the system can be told when to exit. The code looks for a QUIT event, triggered when you close the window to ensure it shuts down. This is a common pattern in PyGame. -The next part of the loop fills the screen with background colour. We then use the `draw` function to draw the dot. We must call `pygame.display.flip` as pygame draws everything on a hidden buffer, which we swap with the visible screen. +The next part of the loop fills the screen with background colour. You then use the `draw` function to draw the dot. You must call `pygame.display.flip` as pygame draws everything on a hidden buffer, which you swap with the visible screen. -Finally we tick the clock to keep the framerate the same. The last line of the program is `pygame.quit` to ensure everything is cleaned up. +Finally you tick the clock to keep the framerate the same. The last line of the program is `pygame.quit` to ensure everything is cleaned up. This is drawn at 400, 400 which is the middle of the screen. I suggest you try a few values between 0 and the WIDTH to see how it changes. I guarantee you won't like my colour choices, so you can also try different colour names, or even RGB values (like `(255, 0, 0)` for red). ## Making it a bit random -A key concept in a particle system is randomness. We can make the one dot less boring by making it random. +A key concept in a particle system is randomness. You can make the one dot less boring by making it random. At the top of the file, lets import the random module above pygame: @@ -92,7 +90,7 @@ import random import pygame ``` -Now we can make it show the dot at a random place every time we run it. Update the one_dot line to be: +Now you can make it show the dot at a random place every time you run it. Update the one_dot line to be: ```python one_dot = [random.randint(0, WIDTH), random.randint(0, HEIGHT)] @@ -100,21 +98,21 @@ one_dot = [random.randint(0, WIDTH), random.randint(0, HEIGHT)] ## Movement -We have a particle, but particles have a lifecycle: +You have a particle, but it's not doing much. You can add a little movement to our particle. + +Particles have a lifecycle: - They are created - They update and change over time - They may also die -We can add a little movement to our particle. Let's introduce a speed constant and update our particle with it. - -In the constants add the following: +Let's introduce a speed constant and update our particle with it. In the constants add the following: ```python SPEED = 2 ``` -We can add an update function, which can be called in the main loop: +You then add an `update` function, which can be called in the main loop: ```python def update(): @@ -123,9 +121,9 @@ def update(): one_dot[1] = 0 ``` -This will add the speed to the second element (the Y coordinate) of the one_dot list. If the dot reaches the bottom of the screen, we reset it to the top. +This will add the speed to the second element (the Y coordinate) of the one_dot list. If the dot reaches the bottom of the screen, you reset it to the top. -We then call this in the main loop before we start drawing things: +You then call `update` in the main loop before you start drawing things: ```python running = True @@ -141,7 +139,7 @@ while running: clock.tick(FRAME_RATE) ``` -Run this. You can adjust the speed by changing the SPEED constant, or increasing the FRAME_RATE, however, note that above a certain frame rate value we may be being slowed by the speed of the program. With high SPEED values, you may see the dot jump on the screen. +Run this. You can adjust the speed by changing the SPEED constant, or increasing the FRAME_RATE, however, note that above a certain frame rate value your frames may be being slowed by the speed of the program. With high SPEED values, you may see the dot jump on the screen. This dot has a lifecycle: @@ -149,9 +147,11 @@ This dot has a lifecycle: - It moves down the screen - When it reaches the bottom, it is moved back to the top +What happens if you make the SPEED constant negative? + ## Multiple dots -We can make this more interesting again by having multiple dots, like a rain storm. We can do this by having a list of dots. +You can make this more interesting again by having multiple dots, like a rain storm. You can do this by having a list of dots. Swap our one dot for this: @@ -170,7 +170,7 @@ def populate(): ) ``` -We then need to update the draw function to draw all the dots: +You then need to update the draw function to draw all the dots: ```python def draw(surface): @@ -188,7 +188,7 @@ def update(): raindrop[1] = 0 ``` -Finally, we need to call the populate function to create the dots, while we initialise the program: +Finally, you need to call the populate function to create the dots while you initialise the program: ```python pygame.init() @@ -209,11 +209,9 @@ These dot's all have the same lifecycle as our one dot! ## Adjusting the lifecycle -We can change this particle system in a few interesting ways. You might have noticed the raindrops loop around in a repeating pattern. - -We can fix this by changing the lifecycle. Instead of just wrapping the raindrop, we can pretend this raindrop has reached the end of the lifecycle and that we are creating a new one. However, we can do something sneaky and reset the x to a random value when we do this. +You can change this particle system in a few interesting ways. You might have noticed the raindrops loop around in a repeating pattern. -We only need to modify the `update` function: +You can fix this by changing the lifecycle. Instead of just wrapping the raindrop, you can pretend this raindrop has reached the end of the lifecycle and that your are creating a new one. However, you can do something sneaky and reset the x to a random value when you do this. You only need to modify the `update` function: ```python def update(): @@ -228,8 +226,8 @@ You shouldn't be able to see a repeating pattern any more. ## Random speeds -We can add a little depth by adding a further parameter to our raindrops. For this we will change two parts of the lifecycle - the add function and the update function. -We'll also adjust the parameters above. +You can add a little depth by adding a further parameter to our raindrops. For this you will change two parts of the lifecycle - the add function and the update function. +You'll also adjust the parameters above. Extend the constants after the POPULATION_SIZE: ```python POPULATION_SIZE = 200 @@ -237,7 +235,7 @@ MIN_SPEED = 2 MAX_SPEED = 6 ``` -In the populate function, lets make a random speed: +You can then modify the populate function to generate a random speed: ```python def populate(): @@ -251,7 +249,7 @@ def populate(): ) ``` -We can then update using this stored speed: +You can then update using this stored speed: ```python def update(): @@ -262,7 +260,7 @@ def update(): raindrop[0] = random.randint(0, WIDTH) ``` -However, we made an assumption in draw that the raindrop was only 2 numbers - the coordinates of the drop. With 3, we need to filter them: +However, the code made an assumption in draw that the raindrop was only 2 numbers - the coordinates of the drop. With 3, you need to filter them: ```python def draw(surface): @@ -274,7 +272,7 @@ If you run this, you can now see raindrops falling at different speeds. ## Checkpoint - raindrops -We've built a small particle system, transforming a single static dot into a rainstorm with raindrops at different speeds. Here's the full code: +You've built a small particle system, transforming a single static dot into a rainstorm with raindrops at different speeds. Here's the full code: ```python import random @@ -345,6 +343,6 @@ You've built a simple particle system, raindrops, using Python and PyGame. You'v You've also seen how particles have a lifecycle. -Over the coming for posts, we can explore what other ways we can use particle systems, some variations on this theme, and some quite different. +Over the coming for posts, we can explore what other ways you can use particle systems, some variations on this theme, and some quite different. I've built this inspired by the Kingston University Coder Dojo where I mentor Python, and will have other particle systems inspired by research I've done for my books.