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!
Instead, JeyB -
Avoid using tons of code in the Player's
draw
event 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
then
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!