PICO-8 Tweetcart Studies

Back to main menu


falling sand

Author: 2DArray (@2DArray)
Link: https://twitter.com/2DArray/status/1047932855526076418

falling sand

Summary

This is a fairly small, simple falling-sand effect. And it looks really good! There’s a lot of ways to do effects like this, and this cart takes the approach of: for each pixel of sand, see whether the space right below it, below to the left, or below to the right is free, and if it is then move there. It’s a short, simple approach, but it ends up working well for this.

Interestingly, cls() is never called during the render loop. But it doesn’t need to be, since every time a moving pixel of sand is drawn with pset(), the code writes over that position with the dark-blue background colour on the next tick.

Exercise for the reader, try changing the effect so that the pile of sand can be less or more steep! Or take a look at this tweak which allows mouse control of the drop point!

Pictures

Replacing rnd()<.1 near the end with rnd()<.01 makes the sand 'set' slower.

Tweet code

s={}cls(1)::_::
for i=0,5 do
add(s,{rnd(10)+59,rnd(3),10+flr(rnd(2))*5})
end
for p in all(s) do
pset(p[1],p[2],1)
m=0
for j=1,3 do
u=j%3-1
if(pget(p[1]+u,p[2]+1)==1)p[1]+=u p[2]+=1 m=1
end
pset(p[1],p[2],p[3])
if(m<1 and rnd()<.1)del(s,p)pset(p[1],p[2],9)
end
flip()goto _

Breakdown

-- pieces of sand
s={}

-- set bg to blue
cls(1)

-- start rendering loop
::_::

-- add 5 new pieces of sand each tick
for i=0,5 do
  -- spawn in a small range at the top.
  -- colour can either be 10 or 15.
  add(s,{rnd(10)+59,rnd(3),10+flr(rnd(2))*5})
end

-- process each piece of sand, from oldest to newest
for p in all(s) do
  -- clear where this piece of sand currently is
  pset(p[1],p[2],1)
  -- m ~= 'is there a clear space under me'
  m=0

  -- check three pixels underneath where this piece of
  --  sand is - below+left, below, below+right.
  for j=1,3 do
    -- now this is interesting! u is the x offset that
    --  it's checking underneath this pixel. so if u=1,
    --  we're checking the pixel below+right, etc.
    -- j goes 1->3, but the %3 turns it to 1->2->0, and
    --  with the -1 that means that u goes 0->1->-1, so
    --  we're initially testing the pixel directly below
    --  us first rather than the below+left pixel. cool!
    u=j%3-1

    -- this pixel is empty, so let's move there
    if (pget(p[1]+u,p[2]+1)==1) then
      p[1]+=u
      p[2]+=1
      m=1
    end
  end

  -- sand is now here, draw it here
  pset(p[1],p[2],p[3])

  -- if there's no empty space below us, lock this sand
  --  into place (by removing it from the table) *maybe*
  if (m<1 and rnd()<.1) then
    del(s,p)
    pset(p[1],p[2],9)
  end
end

-- write new frame to the screen and wait for next tick
flip()
goto _