Fireworks

18th April 2021

Link (seems better for mobile)

Some fairly simple particle fireworks just for fun.

Press X to switch the screen effect on/off. For more about that see here: fading stars

Every sixth of a second, 50-100 Particles are emitted at a random point on the screen with random angle/velocity determined by a tiny bit of trig. They're given colours in the top half of the PICO-8 palette (7+) and as the particles get old the colour changes according to the same mapping that the screen fade effect uses to a darker colour.

If they run out of life or become black then the particles are deleted.

There are two possible update functions for each particle - one with a wiggle :)

Silent CG fireworks are pet friendly

-- update: tidied code a bit, updated fade effect.

-- update 2: added a flash when the fireworks detonate. Other tweaks. Happy Fifth of November :)


function _update60()
	if btnp(❎) then
		g_fade=not g_fade
	end
	
	-- update particle positions and dispose of the dead
	for part=#g_parts,1,-1 do
		if not g_parts[part]:upd() then -- if the update returns false kill the firework
			deli(g_parts,part)
		end
	end
	
	if p%rint(5,18)==0 then
		-- add a new firework
		local col=rint(9,7)
		cls(g_scpal_map[g_scpal_map[col]])
		add_parts({vel,wiggle},rint(50,50),rnd'128',rnd'128',rnd'5',rnd'5',3,5,120,col,rnd(15-col+1))
	end
	
	p=(p+1)%192
end

------------------------------------------------------------------------------------------
--returns random integer (vs float from rnd)
function rint(...)
	return rndr(...)&-1
end

------------------------------------------------------------------------------------------
-- generates a random direction
function rnd_dir(m)
	local angle,d=rnd(),rnd(m)
	return d*sin(angle),d*cos(angle)
end

------------------------------------------------------------------------------------------
-- rndr(t,r)==rnd(t)+r
function rndr(t,r)
	return rnd(t)+(r and r or 0)
end

-- particle update function
function vel(t)
	t.x+=t.vx*0.9 -- slow the particles a bit "friction"
	t.y+=t.vy*0.9
	t.vy+=0.04 -- "gravity"
	t.l-=1
	if t.l%20==19 and t.l<60 then -- older fireworks use darker colours
		t.c=g_scpal_map[t.c]
	end
	return t.y<128 and t.c>0 -- if firework is too old or black then signal to kill it
end

-- alternative particle update function
function wiggle(t)
	t.x+=t.vx*0.7+sin(t.l/20)*0.3*t.vy -- slow the particles a bit "friction"
	t.y+=t.vy*0.7+cos(t.l/20)*0.3*t.vx
	t.vy+=0.04 -- "gravity"
	t.l-=1
	if t.l%20==19 and t.l<60 then -- older fireworks use darker colours
		t.c=g_scpal_map[t.c]
	end
	return t.y<128 and t.c>0 -- if firework is too old or black then signal to kill it
end

------------------------------------------------------------------------------------------
-- particles are single pixel fx only
function add_parts(
	upd,n,-- update function
	x,y,-- position
	vx,-- x vel with random component
	vy,vr,-- y vel
	l,lr,-- lifetime
	c,cr) -- colour
	for i=1,n do
		local vrx,vry=rnd_dir(vr)
		add(g_parts,{x=x,y=y,vx=vrx,vy=vry,l=rint(l,lr),c=rint(cr,c),upd=rnd(upd)})
	end
end

function _draw()
	
	if g_fade then
		scr_fade(p&3)
	else
		cls()
	end
	
	-- draw particles
	for p in all(g_parts) do
		if p.circ then
			circ(p.x,p.y,100-p.l*3,p.c)
		else
			pset(p.x,p.y,p.c)
		end
	end
end

------------------------------------------------------------------------------------------
-- fades a quarter of the screen at a time
-- scan line by scan line, left pixel then right pixel byte by byte
-- which line, side of pair is dictated by p
function scr_fade(p)
	-- local tables seem to be faster.
	-- change start line based on oddness value
	local d,m=0x6000+p%2*64,p&2==0 and g_8bit_map0 or g_8bit_map1
	
	-- for half of the 128 lines on the screen
	for j=0,0x1f80,128 do
		-- for every 4bytes of this line
		for a=d+j,j+d+60,4 do
			
			-- map every pair of pixels to a mapped pair
			-- 4 bytes at a time
			-- shorter and quicker
			poke(a,m[@a],m[@(a+1)],m[@(a+2)],m[@(a+3)])
		end
	end
end

pal(15,135,1) -- light yellow
g_scpal_map={[0]=0,unpack(split'0,1,1,2,1,13,6,2,4,9,3,13,5,4,10')} --g_scpal_map
g_8bit_map0,g_8bit_map1={},{}
for i=0,255 do
	g_8bit_map0[i]=(i&0xf0)+g_scpal_map[i&0xf]
	g_8bit_map1[i]=g_scpal_map[i\16&0xf]*16+i%16
end

g_parts={}
p=0
g_fade=1