Welcome back for another art adventure in TIC-80, the fantasy computer that provides an all-in-one system for making games, animations, and music. Last time, with this code, we made a field of stars that slowly scrolled down the screen.
stars = {}
function makeNewStar(top,bottom)
local newStar = {}
newStar.x = math.random(0,239)
newStar.y = math.random(top,bottom)
newStar.r = math.random(0,1)
newStar.t = math.random(0,240)
newStar.vy = math.random()/2 + 0.1
table.insert(stars,newStar)
end
for i=1,500 do
makeNewStar(0,136)
end
function TIC()
cls(0)
for i,s in ipairs(stars) do
circ(s.x,s.y,s.r,12)
s.y = s.y + s.vy
if s.t == 0 then
s.r = (s.r + 1)%2
s.t = 240
else
s.t = s.t-1
end
if s.y > 136 then
table.remove(stars,i)
makeNewStar(-20,-5)
end
end
end
Now we’re going to build on this code but change the setting to a little city scene with rainfall.
As always, start by making a new program with new lua at the TIC-80 command line and hitting ESC to enter the editor. I’ll go quickly over the parts that are like our last project then get to the fun differences that let us make something new.
First, start by clearing out all the existing code and making it look like the following, which is going to set up our code so that there’s rain falling down on the screen.
raindrops = {}
function makeDrop(x,y)
vy = math.random()*0.5+0.4
table.insert(raindrops,{x=x,y=y,vy=vy})
end
for i=1,200 do
makeDrop(math.random(-40,280),
math.random(-100,100))
end
function TIC()
cls(8)
for i,r in ipairs(raindrops) do
r.y = r.y + r.vy
line(r.x,r.y,r.x,r.y+3,12)
if r.y > 135
then
table.remove(raindrops,i)
makeDrop(math.random(-40,280),
math.random(-10,-5))
end
end
end
The only real difference between this and the starfield is that we’re drawing a line not a circle, but otherwise the rest is basically the same.
Now, we’re going to lay down a sidewalk. We’ll do this in a slightly modular way where we can easily play with the height and width of the rectangular segments of the sidewalk:
raindrops = {}
sidewalkHeight = 105
numSegments = 6
function makeDrop(x,y)
vy = math.random()*0.5+0.4
table.insert(raindrops,{x=x,y=y,vy=vy})
end
for i=1,200 do
makeDrop(math.random(-40,280),
math.random(-100,100))
end
function TIC()
cls(8)
--drawing the sidewalk
for i=1,numSegments do
local xSeg = 240 / numSegments
rect((i-1)*xSeg,sidewalkHeight,
xSeg,136-sidewalkHeight,14)
rectb((i-1)*xSeg,sidewalkHeight,
xSeg,136-sidewalkHeight,0)
end
for i,r in ipairs(raindrops) do
r.y = r.y + r.vy
line(r.x,r.y,r.x,r.y+3,12)
if r.y > 135
then
table.remove(raindrops,i)
makeDrop(math.random(-40,280),
math.random(-10,-5))
end
end
end
We put in the sidewalk drawing code before we draw the raindrops so that we see the rain is on top of the sidewalk and not covered up by it.
The next little feature we’re going to introduce is changing the line if r.y > 135 to if r.y > sidewalkHeight and math.random( ) < 0.15 so the rain starts disappearing randomly when it’s overlapped with the sidewalk. This gives the illusion that it’s falling onto different parts of the sidewalk, an easy way to convey depth. If we wanted to we could make this more sophisticated by making the “background” rain (the rain that’s falling slower) land further “back” on the sidewalk and let the “foreground” rain fall closer to the bottom of the screen.
Next, we’ll add puddles. The puddles will reflect the sky in a fun effect.
First, we’ll add the code to add puddles to another list, then we’ll add the code that makes some puddles on the sidewalk. Finally, we’ll do the clever part where we have the puddles reflect the sky. For space, I’ll cut the code we’ve seen before and put a comment that says –cut instead.
The clever trick here is that we split the drawing of the puddles into two pieces: a part before we draw the rain and a part after we draw the rain.
For the part before the rain, we draw circles with the data in the puddles list and draw them, for convenience, the same color as the sky. In the part after the rain, we use some tricks involving the pix function, which in TIC-80 lets you get the color of a pixel at a point if you give it two arguments, and set the color of a pixel at a point if you give it three arguments.
The logic is that we look for any points that are the color of the sky in the sidewalk, since those have to be puddles, and then we fill in those pixels with the color of the pixel that’s mirrored above the sidewalk.
raindrops = {}
puddles = {}
numSegments = 6
sidewalkHeight = 105
function makePuddle(x,y,r)
table.insert(puddles,{x=x,y=y,r=r})
end
makePuddle(30,110,15)
makePuddle(40,120,10)
makePuddle(179,118,5)
makePuddle(182,117,5)
makePuddle(181,114,4)
makePuddle(150,110,6)
--cut
function TIC()
-- cut
for _,p in ipairs(puddles) do
circ(p.x,p.y,p.r,8)
end
-- cut
for i=0,239 do
for j=sidewalkHeight,136 do
if pix(i,j) == 8 then
local yAbove = 2*sidewalkHeight - j
local cAbove = pix(i,yAbove)
pix(i,j,cAbove)
end
end
end
end
The last part is adding wind. It’s pretty straightforward, but it does mean we need a bit of trigonometry. The line of the rain must point in the direction the rain is moving, so we first need to calculate how much of the falling is to the side and how much is down. From there, we can figure out the slope of the line. Our final code looks like
t=0
raindrops = {}
puddles = {}
windV = 0.2
numSegments = 6
sidewalkHeight = 105
function makePuddle(x,y,r)
table.insert(puddles,{x=x,y=y,r=r})
end
makePuddle(30,110,15)
makePuddle(40,120,10)
makePuddle(179,118,5)
makePuddle(182,117,5)
makePuddle(181,114,4)
makePuddle(150,110,6)
function makeDrop(x,y)
vy = math.random()*0.5+0.4
table.insert(raindrops,{x=x,y=y,vy=vy})
end
for i=1,200 do
makeDrop(math.random(-40,280),
math.random(-100,100))
end
function TIC()
windV = 0.3*math.sin(t/500)
cls(8)
-- this is where we draw the sidewalk
for i=1,numSegments do
local xSeg = 240 / numSegments
rect((i-1)*xSeg,sidewalkHeight,
xSeg,136-sidewalkHeight,14)
rectb((i-1)*xSeg,sidewalkHeight,
xSeg,136-sidewalkHeight,0)
end
for _,p in ipairs(puddles) do
circ(p.x,p.y,p.r,8)
end
for I,r in ipairs(raindrops) do
r.y = r.y + r.vy
r.x = r.x + windV
local totalV = math.sqrt(windV^2 + r.vy^2)
line(r.x,r.y,r.x+3*windV/totalV,r.y+3*r.vy/totalV,12)
if r.y > sidewalkHeight and math.random() < 0.15
then
table.remove(raindrops,i)
makeDrop(math.random(-40,280),
math.random(-10,-5))
end
end
for i=0,239 do
for j=sidewalkHeight,136 do
if pix(i,j) == 8 then
local yAbove = 2*sidewalkHeight - j
local cAbove = pix(i,yAbove)
pix(i,j,cAbove)
end
end
end
t=t+1
end
Next time, we’ll get into some visual effects and music composition to make your programs full A/V art pieces.
Learn More
Learning TIC80
TIC80
Learning to Program in TIC80
https://www.carnaghan.com/learning-to-program-in-tic-80-a-fantasy-console/