PICO-8 Devlog 4: Refactoring
I'm falling into a rhythm in which I alternate between the two strands of my project, which basically can be summed up as 1) function and 2) form. My last stretch was on the latter, figuring out a graphical style and adding some effects, so it was back to nuts and bolts for this block. Technical debt, basically.
Objective
My movement system is basically the tile-based approach from Lazy Devs Academy's Porklike tutorial. I had a working prototype but the code was very duplicative and ad hoc, so I knew I'd need to sort that out. The other complication was was that instead of just moving one space at a time, my player sprite needs to know in advance how many empty tiles there are in any given direction before it meets an obstruction. So I have two separate mechanisms for which I need general solutions, and they need to work together.
Process
I watched and rewatched Lazy Devs Academy videos, trying to extract the relevant information. It was tough because the order in which he deals with things is not the same as mine, and his use-case is a bit different.
I eventually reached a point where I roughly knew what I wanted to do, but there were too many moving parts to make it happen all in one go. So I made two new carts to spike out the movement and range-finding functions separately, and when I had both working I set about integrating them.
Even using the PICO-8 tabs and organising the code in sensible ways, I'm still struggling to track down why things aren't working. And then, when I do track them down, they're almost always some variant on tables being 1-indexed 🙄
I was going to say "I'll spare you the details", but then I remembered that I'm probably the only person who'll ever read this back, and if I do it'll be because I want to remember the details, so I'll try and get them down here in a relatively light-touch way.
Range-finding
Previously I was assigning empty tile counts to key:value pairs in a table —
empty = {
n: [num],
s: [num],
e: [num],
w: [num],
}
— and doing a whole if btnp(0) then px+=empty.w thing. That's been abstracted away:
for i=0,3 do -- poll for inputs
if btnp(i) then
dx=dirx[i+1] -- -1|0|1
dy=diry[i+1] -- -1|0|1
local distx=dx*empty[i+1] -- 'empty' is an array of how far can be moved in any direction
local disty=dy*empty[i+1] -- either dx or dy will always be zero, so 1-axis movement only
if not (distx+disty==0) then -- a 0 total means there are no free tiles in that direction
px+=distx -- again, player position is set on both axes but one of them is alway zero
py+=disty
get_moves(px,py)
pox,poy=distx*8,disty*8
sfx(sound.launch)
_upd=upd_pturn
else
shake=1 -- do: extract this!
sfx(sound.blocked)
end
end
end
And I managed to get this whole business:
function update_pturn()
if pox>0 then
pox-=8
end
if pox<0 then
pox+=8
end
if poy>0 then
poy-=8
end
if poy<0 then
poy+=8
end
Down to this:
function upd_pturn()
local speed=8 -- *|/ 8 only
pox-=speed*dx
poy-=speed*dy
Cool. This is what I set out to do!
I'm not going to say that I think this implementation is perfect or optimal or extensible to whatever use cases I might have down the line, but it's appropriately elegant for my current purposes; there's nothing I'd consider technical debt nagging away at my conscience.
Next steps
Back to the FX. I want to get those block particle effects working, shockwaves, crunchy interactions, all that biz.
END