In this article we’ll be taking a look at the Snowflakes examples from the p5 documentation to understand the basic functions of the library, and improve the underlying code. The example is available here: https://p5js.org/examples/simulate-snowflakes.html
Setup and Draw
These are the 2 main entry points of the p5js library:
The setup function handles the creation of the canvas, and can setup initial parameters
The draw function is called every frame. Draw is a bit of a misnomer as it is responsible for actually drawing things, but also handles logic such as instantiation
If you are familiar with game engines like Unity, setup would be the Start or Awake hook, while draw would be a mixture of Update and of the rendering process.
Looking at the code from the Snowflake example for these 2 functions we see exactly what we just wrote: the setup function creates the canvas and specifies parameters for the drawing (fill for the color of the shapes to be drawn and no stroke to disable the outline), while the draw function ensures the background is redrawn at every step to hide previously drawn points and specific methods are called at every time step for each snowflakes .
functionsetup() {createCanvas(400, 600);fill(240);noStroke();}functiondraw() {background('brown');let t = frameCount /60; // update time// create a random number of snowflakes each framefor (let i =0; i <random(5); i++) { snowflakes.push(newsnowflake()); // append snowflake object }// loop through snowflakes with a for..of loopfor (let flake of snowflakes) { flake.update(t); // update snowflake position flake.display(); // draw snowflake }}
The rendering of the snowflakes is pretty easy: in the code above we call the display method, which is actually the following code in the snowflake class:
P5js is conveniently wrapping the creation of all kind of shapes in simple functions like ellipse, as well as the handling of events. Most of what you’ll need is clearly explained by the documentation here: https://github.com/processing/p5.js/wiki/p5.js-overview
Improving the Example Code
Now that we have introduced the 2 entry points of p5js, we’ll just show a quick improvement on the example.
Resources are limited on a computer so, just like when making games, there are some simple optimizations that can and should be implemented whenever possible.
In this case, the example creates and destroys many snowflakes every frame. This is a waste as these destroyed particles will need to be garbage collected. A much better way to handle this is to instead use a simple pooling pattern which is easy to implement in p5js thanks to the way boundaries are handled:
Create an array in global scope, just like in the example, and add as many snowflakes as you want
Since p5js allows for negative positions (i.e. out of canvas), you can instantiate anywhere you like
Top left corner is the (0, 0) origin, so if the y coordinate is bigger than the height of the canvas, this means it is below the bottom border so we can reuse it. Instead of deleting the particle and creating new ones, we will instead reposition it to the top of the canvas.
Replace this:
if (this.posY > height) {let index = snowflakes.indexOf(this); snowflakes.splice(index, 1);}
by this:
if (this.posY > HEIGHT) {this.posY =this.initialPosY;}
My Example
I used the code above in my own modified Snowflake example, and changed the rotation used for the particles to get an effect closer to what I imagine when I think of snow falling. By using the pooling pattern and inlining intermediary computations, we should avoid memory allocations and garbage collecting, which will make the animation run much more smoothly. Considering mobile devices make up for most of connected devices, reducing computational needs whenever possible is a real improvement!
I hope this quick example got you interested in p5. As I said above, it’s very easy to use, even for non-professional programmers, and is based on Processing, which can be used in Python so you wouldn’t have too many issues using a different language!