First post here! Hello everyone! I've started playing around with pico-8 recently and am still kind of noob-levelish. So I'm looking at some suggestions and advice.
This is a little test I'm working before integrating it into a game I'm building with a friend. I need to create a circle that will follow a character. The "circle" will change the color of the sprites it overlaps. I started originally by calculating distance from the player to N number of x,y coordinates every frame which caused a ton of CPU load and didn't perform well. So I saved that data to a table and load that on _init. Then on each frame I just have to focus on looping through the table and calculating the drawing logic. I also saved a little more info for each "pixel" in the table like the ring level of the circle and an ON state I can use for other things I plan to do later.
Currently when I run the test the mem usage seems to be around 251(KB?) which seems pretty high from other little experiments I've done so far. Does pico-8 have a limit for mem usage or is there any threshold where I should start worrying? Is there a better way to store this info?
The only thing I can think of is to use LOCAL.
I read somewhere that local variables are faster in Lua, I don't know how this affects pico8. I haven't done extensive testing myself so hopefully someone else has a better answer
Thanks gcentauri, that's a good tip! I switched a few to local variables in places I could and that saved about 4KB. I think my table of tables is the biggest source of memory so currently trying to see other ways for optimizing that. Again I'm not sure if I should be concerned about using now 249-250KB of memory or not?
If you need just compare distance with something - there is no point in calculating sqrt. It will be much faster to calculate distance square and compare it with (radius-6)*(radius-6). (it won't save your memory, but will greatly optimize cpu cost).
"Currently when I run the test the mem usage seems to be around 251(KB?) which seems pretty high from other little experiments I've done so far. Does pico-8 have a limit for mem usage or is there any threshold where I should start worrying?"
Yes, the limit is 512K. Using 250 may or may not be worrisome, it just depends on how much the rest of your game needs, but overall it seems fine.
Not long ago, the limit USED to be 256K, but zep doubled it because it was just too easy to hit that threshold. It's still pretty easy to hit 512, but most of PICO-8's constraints are fairly easy to reach, so 512 feels about right for it. Fortunately, since it's a fantasy console and not real hardware, zep can upgrade and tweak the "hardware" at any time :P
Memory limit is 1024k since v0.1.6 if I am not mistaken.
@TastyEnchilada you are correct, sir. In fact I don't think it was ever 256; it's the 512 -> 1024 change that I was remembering. So yeah, that makes using 250K even MORE insignificant - it's not even 25% of the allotment. I know one of my WiP games was peaking at like 360K at one point and I was worried about it, but that was before 1.6 was released (I should really get back to that project, ran out of tokens not even halfway through and kinda put it on hold to avoid the hassle of token optimizing... that was probably 6 or 7 months ago, lol).
@Shogal, thanks! So I think CPU usage was what was causing the sluggishness I saw with my method above. I was running around .85. So I tried switching my distance comparison to something like what you mention but can't get it to give me values that look right. This is a snippet of what I have. And it's printing out values that are in the thousands.
function sqr(a) return a*a end function build_circle(startx,starty,radius) local prevx = startx-radius local prevy = starty-radius for i=0,(radius*2) do for i=0,(radius*2) do local my_dist = sqr(prevx-player.x)+sqr(prevy-player.y) printh('distance is: '..my_dist) end end |
My old code gave me what looked like correct distance values and for reference was:
function distance_calculator(x,y,xx,yy) return sqrt(((x-xx)*(x-xx))+(((y-yy)*(y-yy)))) end function build_circle(startx,starty,radius) local prevx = startx-radius local prevy = starty-radius for i=0,(radius*2) do for i=0,(radius*2) do local my_dist = distance_calculator(prevx,prevy,player.x,player.y) printh(my_dist) end end |
The point is if you simply need to compare distances, the squared distance calculation will still give you what is farther away. If you need the actual distance then you'd still need to square root. Like the if clauses in build circle just need to change to have the radius - 6 part squared. the idea is that distance calculation uses aa + bb = cc right? But if cc is greater than another c*c we don't need to do the square root to know it is larger.
Oh and if you don't need to store every pixel for your spotlight circles you can save a lot of both CPU and memory. Might want to have like a default circle with tables of highlight spots or something?
Thanks for the help. So posting more of the game so people have a better idea of the problem.
I played around with changing the distance equation but ultimately I do this in the _init so it doesn't impact game performance. I also did some work on reducing the data I store in the table containing the spotlight's coordinates and other state info. The current memory usage is down to 70-80KB but I think drawing the spotlight every frame is really impacting the framerate overall.
You can see this by pressing X to toggle the lantern off/on and see the difference in walking animation smoothness. It seems the work in the draw_circle() function is too much to handle every frame?
At first glance it looks like maybe there is some logic in _draw that should be in update? Maybe that is what is slowing down the draw. Like all the movement and animation stuff should probably be in _update preparing the objects for drawing functions
The artwork is beautiful btw, can't wait to see what you guys make !
@littlebluebro—yes, drawing your lantern spotlight using many calls to pset() will be way too slow. (Think about how many pixels it has to touch to do it—something like 3600?) It will be faster to use the sprite drawing functions in conjunction with palette swapping.
There's an example here, submitted by slembcke: https://www.lexaloffle.com/bbs/?pid=33799#p33799
It's a neat trick: you draw the screen without the highlighted area, copy the tiles of the screen that will be highlighted into the sprite sheet using memcpy(), swap the palette to use the brighter version of the normal colors with pal(), then draw the tiles back to the screen using spr().
Ah thanks @musurca! I saw that post earlier but didn't quite grok the memcpy() > pal() > spr() approach. I'll try looking at slembcke's example code. It definitely looks like it could improve performance.
slembcke's method is still probably best, but you could also do something like this if you don't mind a noisier look to the lantern:
You just precalculate a large set of random coordinates in a normal distribution, then draw a subset of them every frame (1000 in this case).
EDIT: here's an alternate version of the effect, with fewer, chunkier points drawn:
littlebluebro: at a glance there's a big region around the character that will always be full lit.
find the map cells that are inside the circle, draw them with pal()&spr(). then handle those that intersect with another method (maybe yours would be enough)
@musurca, that's a clever approach! Unfortunately not the look that we're going for. It's interesting seeing how different people solve these problems though. I think you're right, slembcke's method is the one we're gonna want to try, once we figure out how to do it :)
@ultrabrite, i tried playing around with drawing tiles from a map and palette swapping. Still learning this:
[Please log in to post a comment]