Pico-8 Bach Inventions Effects ~ Day 3: Drifting Dust
We are releasing one Bach Invention portrait every day for 15 days, with Chris (Gruber_music) who remade the musics in Pico-8. (I can't prove it yet, but I'm fairly certain he used some form of black magic)

Hi everyone!

In today's Bach portrait, I used a super cheap and satisfying effect that I shamelessly stole from Celeste! (the Pico-8 version) It's the drifting snow/dust!

To be clear, I didn't actually steal Celeste's implementation, I didn't even take a look at it (I couldn't find it), I simply came up with my own implementation to reproduce this effect.

My point is: that floating dust makes anything beneath it look better and today I'm going to tell you about my implementation!

First off, we want dust particles pretty much all over the screen. So we will use two 'for' loops like this:

`<code>for y=0,7 do for x=0,7 do  local x=x*16+rnd(16)  local y=y*16+rnd(16) endend</code>`

Note that declaring the local variables x and y only overrides the x and y from the loops for the scope in which they've been declared. In simpler terms, the local x and y exist only until the first 'end' encountered. You could just as well call these variables something else, I just think it's cool we can do that in lua.

With this snippet of code, we have points that are all over the screen, but also scattered, randomly placed.

But this snippet will go into the _draw() function and rnd(16) will give different values for every frame... unless we call 'srand(1)' right before these loops! That will reset the "Random Number Generation" (RNG) with the "seed" 1 and make the subsequent 'rnd()' calls give out the same thing as the last time you called 'srand(1)'!

If you want to come back to more random numbers afterwards, I would recommend using the line 'srand(time())' just after the code for the dust effect.

Now let's make those dust particles move! We want them to move towards the right in an irregular way! Here's what I've come up with!

`<code>local t=time()srand(1)for y=0,8 do  for x=0,8 do  local x=x*16+rnd(16)+t*30  local y=y*16+rnd(16)   local kt=rnd(1)  x+=3*cos(t+kt)  y+=3*sin(t+kt)*(sgn(rnd(2)-1))   x=x%127  pset(x,y,7) endend</code>`

First, we add the time(*30) to the x coordinate of every particle, so that it moves towards the right.

Next, we add a circular motion to both x and y, using 'cos()' and 'sin()'. We don't want that circular motion to be synced up between all the particles, so instead of using the time as angle for these functions, we use the time + a random value, which will be the same for each particle every frame thanks to our 'srand(1)' call.

To make it look even more random and natural, we make it so there's a chance that the motion added to the y coordinate is inversed. For this, we use this tiny bit of code: '*(sgn(rnd(2)-1))', multiplying by the sign of a random number between 0 and 2, minus 1.

Finally, we use a modulo to make sure our x coordinate loops from the right side of the screen to the left.

We're really close now!

All that's left to do now is to make a few random particles bigger! For this, simply do a number comparison with a random number. For example, I used 'if rnd(10)<2'. If that comparison is true, we use 'rect(x,y,x+1,y+1,7)'. If it's false, we only use 'pset(x,y,7)'.

Once again, calling 'srand(1)' beforehand will make sure that we always get the same result for each particle.

And we're done! Here's the result and then the complete code:

`<code>local t=time()srand(1)for y=0,8 do  for x=0,8 do  local x=x*16+rnd(16)+t*30  local y=y*16+rnd(16)   local kt=rnd(1)  x+=3*cos(t+kt)  y+=3*sin(t+kt)*(sgn(rnd(2)-1))   x=x%127  if rnd(10)<2 then   rect(x,y,x+1,y+1,7)  else   pset(x,y,7)  end endend</code>`