How does markDirty work?

Hello!

Me and my friend are trying to make a simple game using the C API on windows.

Currently we have a sprite which we move left and right at the bottom of the screen using moveBy() and draw it by calling updateAndDrawSprites().

However, we have a problem, where it doesn't redraw correctly.

We have called markDirty on the sprite, but the dirty area is fixed on the screen and doesn't follow the sprite when moving.

play_date_error

So in stead we wrote this:

pd->sprite->addDirtyRect(LCDMakeRect(oldBounds.x, oldBounds.y, oldBounds.width, oldBounds.height));
pd->sprite->addDirtyRect(LCDMakeRect(newBounds.x, newBounds.y, newBounds.width, newBounds.height));

This works, however, we would like to know if this is the correct way to deal with moving sprites, or is it a better way?

1 Like

Are you sure you need to mess with all that dirty'ness? :wink:

The docs for addDirtyRect say:

playdate.graphics drawing functions now call this automatically, adding their drawn areas to the sprite’s dirty list, so there’s likely no need to call this manually any more.

I'm just a beginner on all this stuff, but I've done some basic stuff with sprites so far and haven't needed to mess around calling 'dirty' on anything.

(caveat: I've been using Lua, in case the APIs are very different.... but it doesn't seem so)

You need to call updateAndDrawSprites in your update callback. Or do that from the lua side.

They don’t automatically redraw, it’s just that any modifications to a sprite automatically marks it eligible for redraw.

Yes, we saw that, that's why we asked on the forum, we've probably doing something wrong...

I think I wasn't clear enough, sorry.

We call

pd->sprite->updateAndDrawSprites();

every frame in our update function.

First nothing happens when pressing the controllers:
no_mark_dirty

After adding markDirty():

mark_dirty

The area to be redrawn stays the same, even though the sprite changes position...

Very curious, would you be willing to post some code to reproduce the issue? Even minimally so, it might be helpful to see the context.

Okay, so I think I understand what the confusion might be.

Using markDirty on a specific sprite marks the sprites bounds as dirty.
In this specific example you will notice that the bounds are just the paddles size.

In order to allow the paddle to move across the entire width of the bottom row, you would actually want to not dirty the sprite, but instead, dirty the rect of where you expect the sprite to move through.

Here is an example that allows arbitrary movement:

#include <stdio.h>
#include <stdlib.h>

#include "pd_api.h"

PlaydateAPI *pd;

static const LCDRect screenRect = {0, 400, 0, 240};
LCDSprite *playerSprite;

int updateCallback(void* userdata) {
    PDButtons current;
    pd->system->getButtonState(&current, NULL, NULL);

    if (current) {
        pd->sprite->moveBy(
                playerSprite,
                (current & kButtonLeft || current & kButtonRight) ? (current & kButtonLeft ? -5 : 5) : 0,
                (current & kButtonUp || current & kButtonDown) ? (current & kButtonUp ? -5 : 5) : 0);
        pd->sprite->addDirtyRect(screenRect);
    }

    pd->sprite->updateAndDrawSprites();

    return 1;
}

#ifdef _WINDLL
__declspec(dllexport)
#endif
int eventHandler(PlaydateAPI* playdate, PDSystemEvent event, uint32_t arg)
{
	(void)arg;
    pd = playdate;

    LCDBitmap *playerImage = pd->graphics->loadBitmap("images/player", NULL);
    playerSprite = pd->sprite->newSprite();

    int width;
    int height;
    pd->graphics->getBitmapData(playerImage, &width, &height, NULL, NULL, NULL);
    pd->sprite->setImage(playerSprite, playerImage, kBitmapUnflipped);
    pd->sprite->moveTo(playerSprite, width, height);
    pd->sprite->addSprite(playerSprite);

	if ( event == kEventInit ) {
        pd->system->setUpdateCallback(updateCallback, NULL);
	}
	
	return 0;
}

Dirtying the entire screen is likely the easiest thing to do for now and you might be able to optimize later.

Yes, that's what we found out in the end as well, thanks for the help!

1 Like

I hadn't dug into this yet but in another bug I just discovered that markDirty() doesn't actually flag the sprite for redraw, only marks the drawn bounds dirty, as you say. I believe that fix will also make the original code here work as expected. I still don't have a good grasp on where this is a problem.. It seems like it'd be causing more trouble than it apparently is. :thinking:

2 Likes