Simple Rain in PICO-8

Hi there!
This is my first tutorial on PICO-8. Today we’re gonna make a simple rain particle system. I will not focus too much on token count but just show you how to make a particle system with a pool that can create a simple rain effect.


Let’s get started.

Start by entering the following. You probably already know this, but _INIT() is for game initialization, _DRAW() is the draw-loop and _UPDATE() is the update-loop.

We clear the screen to a black color at every frame. This is done by CLS(0).

Let’s get started on the particle system.

Create a new function called new_particle_system that takes in a count of elements as the max particles allowed at a time.

Inside the function we will create a local variable called _pool:

_pool={}

Next up we need to create a particle function. Go to a new tab or scroll down and write the following:

function new_particle()
  local _x,_y=0,0
  local _xspeed,_yspeed=0,0
  local _done=true
end

This is the basics of the particle class, we have an y-speed an x-speed and two positions (x,y). Now let’s add a return statement below _done=true:


function new_particle()
  local _x,_y=0,0
  local _xspeed,_yspeed=0,0
  local _done=true
  return {
    launch=function(x,y,xspd,yspd)
      if _done then
        _x,_y,_xspeed,_yspeed,_done=x,y,xspd,yspd,false
        return true
      end
    end,
    update=function()
      if (_done) return
      _x+=_xspeed
      _yspeed+=0.92
      _y+=_yspeed
      if _y>127 then
        _done=true
      end
    end,
    draw=function()
      if (_done) return
      line(_x,_y,_x+_xspeed,_y+_yspeed,7)
    end
  }
end

Above return an object with the following functions:
launch() – for launching the particle with x-position, y-position, x-speed and y-speed.
update() – for updating the position and the current speed of the particle.
draw() – for drawing the particle as a line with both speeds applied.

Let’s take a closer look at the functions:
launch starts by checking if _done is true. This means that we’re allowed to use the particle, so if that is the case we set all the variables to the inputs and set _done to false. We then return true. The return true will hopefully make sense later on.
update also starts by checking if _done and then we dismiss the function if that is the case. No reason to calculate anything if the particle isn’t supposed to show. After that we apply the _xspeed to _x and we apply gravity (0.92) to _yspeed. Then we check if the particle has left the screen (after 127 on y) and set _done to true if that is the case.
draw starts by dismissing if the particle is done. Else it will draw a line from _x,_y to _x+_xspeed, _y+_yspeed.

Cool! That’s all we need for the particle.

Let’s go to the particle system function and work some magic there.
Let’s initialize the particle pool by doing the following:

for i=1,maxparticles do
  _pool[i]=new_particle()
end

LUA’s arrays starts at index 1 so that’s the reason why we do it from i=1 and not i=0.

Now that we have initialized the pool we can return the object with the following functions: rain(), update(), draw().

return {
  rain=function()
  end,
  update=function()
  end
  draw=function()
  end
}

The above code shows how return the object with functions declared inside. Now let’s fill out the blanks:

rain=function()
  local rand=rnd(5)
  for i=1,maxparticles do
    if _pool[i].launch(rnd(127),-rand,0,rand) then
      return
    end
  end
end

The rain function sets a random number between 0 and 5, loops through all particles and dismisses the function in case a particle’s launch function has been called. The call to launch randomly places the particle at an x-position inside the screen and above the screen by -rand (remember PICO-8’s coordinate system is top-left (0,0) to bottom-right (127,127)). We also set the x-speed to 0 and the y-speed to rand.

Now let’s create the update function, it’s pretty straight-forward:

update=function()
  for i=1,maxparticles do
    _pool[i].update()
  end
end

The draw function is more or less the same except that we call draw instead of update:

for i=1,maxparticles do
  _pool[i].draw()
end

And that’s it. Now let’s put it to use…

Go back to your main tab (or where you placed the initial functions and write the following:

function _init()
  particlesys=new_particle_system(512)
end

function _draw()
  cls(0)
  particlesys.draw()
end

function _update()
  particlesys.rain()
  particlesys.update()
end

Go to the console and type in run and see it in action.

Next up is extending the particle systems with sparks!