There were a lot of discussions on this topic in different forums and on Discord, so i decided to summarize here and add my own two cents.
TL:DR - You can implement a fun limited sight mechanic
Here's the pdx and Pulp json for anyone who wants to take a look:
Player_lighting_tricks.zip (51.8 KB)
I was chatting with Guv Bubbs about his pulp light game that he's developing that has dark stages that are illuminated by the player's lantern. His prototype looked fantastic, but unfortunately suffers from performance issues when running on the Playdate Hardware. He was trying to use the
fill function, which as Shaun and Neven are concstantly reminding us needs to be used very sparingly
What he was trying to accomplish reminded me of the mechanic that JeyB and Laura Hall used in their excellent game Your Turn Little Bot that they made for the Pulp game jam. In addition to the great game, they were also kind enough to provide their code, so i'd definitely recommend that you check it out!
Anyway, i thought I'd share the approach that the used that they came up with and i put together in a demo based on everyone's recommendations. I guess the best way to talk about it is the lessons learned in a series of bullet points:
Avoid copious use of
fill. It's very process intensive on the hardware and should be used sparingly!
Avoid using tons of code in the Player's
drawevent handler, since it's called every frame that the game is running.
To quote Pulp Guru Shaun: "A good rule of thumb with PulpScript is "do less, less often."
A better way to approach this would be to play to Pulp’s strengths: tiles. For single frame tiles, switch between a light and dark frame as they pass in and out of the ring of light. For animated tiles, swap between light and dark versions. These tiles will only be redrawn when they change. Also, making this tile-based allows you to add highlights to dark frames, eg. to provide landmarks in a dark room."
The solution that everyone came up with is to manipulate the frames of the tiles that we want to swap between "light" and "dark". We make frame 0 (the first frame) the normal, "light" version, and the frame 1 (the second frame) the dark version, which can either be total black, or something shadowy to catch the player's eye.
(setting the fps to 0 is necessary for us to be able to disable auto-animation and let us set the frame manually in our PulpScript)
Now for any room that we want to darken, and then illuminate around the player, we can use this PulpScript:
on enter do room_illuminated = 0 tell event.game to call "darken_room" call "illuminate_tiles" end end
The first function, as the name suggests, simply iterates over all of the tiles and sets the frame to 1 (the frame with the tile's "dark" version). The second function will then look at where the player is, and illuminate the tiles in a radius around him by changing the tiles' frame back to 0.
This is a good amount of operations, but it should be ok since it's just a one-off operation that we're doing when the player enters a dark room.
But since we're using the tiles' frames to manage all this, we can't use the frames to have our tiles be animated
... unless we come up with a clever workaround
In our case, we can create 2 different versions of the tile. In our
illuminate_tiles function, we're setting the frame to 0 and also calling the tile's "brighten_tile" function:
// illuminate tiles around player location by swapping to frame 0 while _x<=x_max do while _y<=y_max do tell _x,_y to frame 0 // tell tile to swap to its "bright" version if it has one call "brighten_tile" end _y++ end _x++ _y = y_min end
If the tile we're illuminating doesn't have a "brighten_tile" function then nothing happens. But if it does have it, it'll be called and we can do something. In our case we want the tile to swap out for the "bright" version that can have a fun animation to show. Take a look at this bat sprite:
Here we can see the "light" version. Its PulpScript will swap it out for the dark version either when the player enters the room, or when the
darken_tile function is called, for example when the player walks away and we want that part of the stage to go dark again
on darken_tile do swap "bat_dark" end on enter do swap "bat_dark" end
If we take a look at the dark version, we can see that it has the "brighten_tile" function that we can call to swap to the light version. Note that neither of these tiles have the fps set to 0, so they'll just play their animations normally. Even if our code is telling it to set the frame to 1 or 0, it'll ignore this command since
fps isn't set to zero.
Now let's see it in action!
OK, now the critical part of all of this: How we handle this in the Player's PulpScript to avoid performance problems. JeyB and Laura figured out while they were making their game that updating all of these tiles was pretty computationally costly for the Hardware, so the trick is to only update the first and last column (or row) where the player is walking.
We only wan to call this when the player moves (not every frame), so in the player's
update event handler we have:
// if the room isn't flagged as illuminated then update the tiles that we're going to illuminate if room_illuminated==0 then call "update_illuminated_tiles" end
on update_illuminated_tiles do // since we're calling this more often, we'll only update the tiles that are changing when the playe rmoves x_min = event.px x_min -= illumination_radius x_max = event.px x_max += illumination_radius y_min = event.py y_min -= illumination_radius y_max = event.py y_max += illumination_radius // make sure we're not trying to access tiles off-screen while x_min<0 do x_min++ end while x_max>24 do x_max-- end while y_min<0 do y_min++ end while y_max>14 do y_max-- end _x = x_min _y = y_min // illuminate tiles where player is walking and re-darken tiles now outside of the illumination radius // NOTE: to make this as few operations as possible, we're only going to update the tiles at the edge of the radius that need to be changed if event.dx>0 then // player is moving right, so re-darken tiles to the left darken_x = x_min if darken_x>0 then darken_x-- end while _y<=y_max do // illuminate tiles ahead of player tell x_max,_y to frame 0 // frame zero is "bright" version of tile // tell tile to run its animation if it has one call "brighten_tile" end // re-darken tiles now outside of the illumination radius tell darken_x,_y to call "darken_tile" // if defined in tile, will swap back to the darked version frame 1 // frame 1 is "dark" version of the tile end _y++ end elseif event.dx<0 then // player is moving left darken_x = x_max if darken_x<24 then darken_x++ end while _y<=y_max do // illuminate tiles ahead of player tell x_min,_y to frame 0 // frame zero is "bright" version of tile call "brighten_tile" // if defined in sprite, will swap to bright version end // re-darken tiles now outside of the illumination radius tell darken_x,_y to call "darken_tile" // if defined in the sprite, will swap back to the darked version frame 1 // frame 1 is "dark" version of the tile end _y++ end elseif event.dy<0 then // player is moving up darken_y = y_max if darken_y<14 then darken_y++ end while _x<=x_max do // illuminate tiles ahead of player tell _x,y_min to frame 0 // frame zero is "bright" version of tile call "brighten_tile" end // re-darken tiles now outside of the illumination radius tell _x,darken_y to call "darken_tile" // if defined in tile, will swap back to the darked version frame 1 // frame 1 is "dark" version of the tile end _x++ end elseif event.dy>0 then // player is moving down darken_y = y_min if darken_y>0 then darken_y-- end while _x<=x_max do // illuminate tiles ahead of player tell _x,y_max to frame 0 // frame zero is "bright" version of tile call "brighten_tile" end // re-darken tiles now outside of the illumination radius tell _x,darken_y to call "darken_tile" // if defined in tile, will swap back to the darked version frame 1 // frame 1 is "dark" version of the tile end _x++ end end end
And there you have it! Take a look at the attached code for more nitty gritty details, and hopefully you found this post illuminating!