Redrawing a portion of a sprite

Hi! I'm currently working on a game using dynamically-generated backgrounds and sprites on top; and due to limitations in the sprite system I'm also using the background as a sprite. However, I've encountered an issue where drawing on any portion of the background (using playdate.graphics.pushContext and playdate.graphics.popContext) causes the entire sprite contents to be redrawn. This behavior makes sense for small sprites (where any change to the image is likely to result in a near-full redraw anyways) but seems wasteful on display-sized sprites. Is it possible to redraw modified portions of the image without having to manually keep track of modified areas after every single call to a drawing function?

I'm aware of playdate.graphics.sprite.setBackgroundDrawingCallback but it doesn't feel suited to this use-case, because the background is actually larger than the screen (~800x800) and I want it to scroll alongside the sprites when using playdate.graphics.setDrawOffset. It also feels annoying to have to reimplement drawing dirty areas of the background image when the sprites on top move which is otherwise automatically done. Plus, it looks like changes to the underlying background image are expected to be handled by just calling redrawBackground which redraws the entire area as with updating normal sprites.

I don’t have a definitive answer for you, but I have a few questions that might help. First, you mention that the backgrounds are dynamically generated. Is that a one-time event, or are they constantly changing? If they aren’t changing, are you rendering them into a playdate.graphics.image at initialization and then just drawing that image from then on? That should definitely be more efficient. You can do so by creating a new image of the appropriate size, passing a reference to the image to pushContext, and doing that rendering all up front. Then use the saved image to draw your sprite.

I’d think the background drawing callback could still work for you if you set the offset the image should be drawn at accordingly. I’m using it (although with a screen-size image) in my game and it properly redraws the regions which need to as sprites move atop it without causing a full screen update.

There’s also playdate.graphics.sprite.addDirtyRect to flag particular regions for redraw, but per the docs that shouldn’t be necessary to call yourself.

1 Like

Ebs is on the right track.

For reference though, the answer to your original question is to set this flag then manually add dirty rects:

playdate.graphics.sprite:setRedrawsOnImageChange(flag)
By default, sprites are automatically marked for redraw when their image is changed via playdate.graphics.sprite:setImage(). If disabled by calling this function with a falseargument, playdate.graphics.sprite.addDirtyRect() can be used to mark the (potentially smaller) area of the screen that needs to be redrawn.

1 Like

The main game board is one-shot generated where the redraw is not an issue, but I was planning on having some form of dynamic animated background to add some visual texture.

The docs claim that the playdate.graphics drawing routines automatically call playdate.graphics.sprite.addDirtyRect, but from my testing that only seems to happen when drawing to the screen and doesn't happen when drawing on an image—I guess because it doesn't know where/if that image is being rendered to add an appropriate dirty rect. So if I do go for animated background sprites I'd probably use playdate.graphics.sprite:setRedrawsOnImageChange per TheMediocritist's suggestion and then do quite a bit of manual bookkeeping when calling any drawing functions.

However, now I'm reconsidering the animated background altogether after some consideration, because with the Playdate's screen size and the amount of “busyness” planned the rest of the game the animation probably isn't necessary to keep it visually interesting and would substantially simplify the whole background generation and handling.

It would be nice if the drawing routines added dirty rects to the new context created by pushContext (relative to the image's coordinates) that you could access and then do whatever with, like converting to screen coordinates and calling addDirtyRect or whatever.