Well, I took the plunge and set:
Code: Select all
KERNEL_OPTIONS += -DSPRITES_AUTO_PROCESS=0
in my Makefile, and dealt with the flood of compile errors, because tons of things that I relied on were now gone.
No more:
- sprites[] array (saving me 96 bytes of precious RAM!)
- MapSprite2() function
- MoveSprite() function
- ProcessSprites() function
I just wanted to get it back to a state where it would compile, so the first thing I did was make my own empty functions called MapSprite2 and MoveSprite. I've play tested this so much that I can play it with an invisible unicorn, that doesn't matter for now. Then all of the places where I filled in the sprites[] array, I replaced with a call to BlitSprite, and I commented out the call to ProcessSprites() since it wasn't needed anymore. And it compiled! And when I ran it, all of the enemies moved around, and I could move my invisible unicorn all around the level collecting things and dying, albeit invisibly.
Then I had to work on writing a better replacement for MapSprite2 and MoveSprite. I always thought it was silly they were two separate functions, so I first decided to combine them and replace the sprite assignment with a call to BlitSprite. Then I realized I had 3 unused parameters, so I deleted them and rearranged the rest so it has the same function signature as BlitSprite, except instead of passing a tileIndex as the second parameter, you pass a pointer to a mega sprite map.
Sometimes a rectangular sprite map will contain an 8x8 section that is blank. This is
super wasteful, because it both costs CPU time to blit them, AND they waste precious RAM tiles for absolutely nothing at all. There are two of them in the walk/run animations of my gassy unicorn:

- blank-tiles-in-map.png (11.26 KiB) Viewed 678 times
I make sure that the first tile in my spritemap is all transparent, and I checked for that in my new BlitSpriteMap function, so I can skip the blit if possible.
Code: Select all
#define SPRITE_BLANK 0
void BlitSpriteMap(uint8_t spriteFlags, const char *map, uint8_t map_x, uint8_t map_y)
{
uint8_t mapWidth = pgm_read_byte(&map[0]);
uint8_t mapHeight = pgm_read_byte(&map[1]);
int8_t x, y, dx, dy, t;
if (spriteFlags & SPRITE_FLIP_X) {
x = mapWidth - 1;
dx = -1;
} else {
x = 0;
dx = 1;
}
if (spriteFlags & SPRITE_FLIP_Y) {
y = mapHeight - 1;
dy = -1;
} else {
y = 0;
dy = 1;
}
for (uint8_t cy = 0; cy < mapHeight; cy++) {
for( uint8_t cx = 0; cx < mapWidth; cx++) {
t = pgm_read_byte(&map[y * mapWidth + x + 2]);
if (t != SPRITE_BLANK)
BlitSprite(spriteFlags,
t,
map_x + (TILE_WIDTH * cx),
map_y + (TILE_HEIGHT * cy));
x += dx;
}
y += dy;
x = (spriteFlags & SPRITE_FLIP_X) ? mapWidth - 1 : 0;
}
}
After replacing all of the calls to MapSprite2/MoveSprite with calls to this new BlitSpriteMap function, it compiled and runs!!!
The totally crazy thing about this now is there are no more sprites, only blits, so all of my code that made sure I wasn't exceeding MAX_SPRITES is gone now, and all the places where I had to set the sprite flags so the sprites turned off, gone! So much code just vanished. I had to add an extra call to RestoreBackground in the beginning of my popup menu, and move the call to AnimateCollectables so it happens before any of the blitting starts.
I don't want to just gloss over that last point, because that is one of the really important things you need to follow when setting SPRITES_AUTO_PROCESS=0 and switching over from having a sprites[] array to just blitting:
Once you start blitting to VRAM, you can't change VRAM until you call RestoreBackground() again!
I was very fortunate that the only thing I needed to do was to move the call to AnimateCollectables up a bit.
I also made one other function that I call after RestoreBackground before blitting any sprites. That sets the same variables that the old ProcessSprites() did before it called BlitSprite:
Code: Select all
// Call this after calling RestoreBackground, but before calling BlitSprite
static inline void ResetUserRamTilesCountAndFreeTileIndex(void) {
extern uint8_t user_ram_tiles_c;
extern uint8_t user_ram_tiles_c_tmp;
extern uint8_t free_tile_index;
user_ram_tiles_c = user_ram_tiles_c_tmp;
free_tile_index = user_ram_tiles_c;
}
Now if I want to change the Z-order, I can just call my p.render() function again after I blit all of the entities, though that does waste clock cycles, so I'm still investigating whether or not a modified version of BlitSprite that only copies the background tile and reserves RAM tiles is the best solution.
This is quite awesome, and I am so grateful that Jubatian rewrote mode 3 to make this all possible. I'm just sorry it took me this long to find the time to start wrapping my head around these fancy enhancements. The code size and memory usage both dropped quite dramatically, and this should allow for some fancy effects once I realize its full potential. Blitting single pixels on top of my sprites comes to mind. I always wanted to make the unicorn stick its tongue out a bit while eating the lollipops, but didn't want to waste PROGMEM space for that.