Page 3 of 4

Re: Mode 3 Scrolling Game Engine Demo (WIP)

Posted: Tue Jan 24, 2023 4:12 am
by Artcfox
I tried adding back my ram time estimation system to see if I can blit the unicorn late in the process, just before we run out of RAM tiles, and it worked most of the time, except when other entities overlapped and didn't need any more ram tiles to render, then the z-order was wrong.

It used very little CPU, but it wasn't perfect, so I just went whole hog and copied and pasted the BlitSprite() function into my code and added a boolean variable "doblit" that only calls BlitSpritePart() if true.

Now if I want to totally max out my RAM tile usage so the sprite flickering doesn't happen until I truly run out of RAM tiles, but still be able to perfectly control the Z-order of my main character's 2x2, 3x2, or 1x3 megasprite, I can just call MyBlitSprite() with "doblit" set to false to reserve RAM tiles up front without doing any unnecessary blitting. Then I can call the normal BlitSprite() function to draw all of my entities, not stopping or limiting it in any way to take full advantage of free overlapping RAM tiles, and finally I can call MyBlitSprite() with "doblit" set to true to render the main character into the reserved RAM tiles with the highest Z-order when I want to.

That meets all of my goals:
  • Every 8x8 sprite that the main character is composed of must be drawn every frame without flickering
  • Every other visible entity should be blitted without regard for RAM tile usage, so overlapping sprites can happen "for free"
  • Sprite flickering should cost close to nothing, and only happen when absolutely necessary
  • I should have perfect and absolute control of the Z-order of the main character, without wasting any additional CPU
It takes around 6000-10000 clock cycles to double blit, so this seems like a total win. I love it when a plan comes together!

And because I can't help myself... The only thing that might make this better is if the RAM tile allocation wasn't sequential so if sprite flickering does need to happen, it doesn't always happen on entire 8x8 sprites at once, it might only be 1/2 of a sprite if it overlapped 2 RAM tiles, or 1/4 of a sprite if it was overlapping 4 RAM tiles. From what I've seen with my frame-by-frame analysis so far I think flickering just a few pixels of a distributed number of sprites would look way better than losing an entire 8x8 sprite every 1/60th of a second. And it could be combined with the array swizzling technique that I already use to distribute the flicker across all entities. We shall see!

Re: Mode 3 Scrolling Game Engine Demo (WIP)

Posted: Tue Jan 24, 2023 5:10 am
by Artcfox
Ok, so this I would really like some feedback on. I implemented RAM tile striping in the blitter, so instead of just using the next free ram tile, there is a lookup table that is striped:

Code: Select all

for (uint8_t i = 0, j = 0; j < 32; i = (i + 7) % 32, ++j)
    printf("%d, ", i);
The MyBlitSprite function in its entirety now looks like:

Code: Select all

extern void BlitSpritePart(u8 ramtileno, u16 flidx, u16 xy, u16 dxdy);
void MyBlitSprite(u8 flags, u8 sprindex, u8 xpos, u8 ypos, bool doblit)
  uint8_t RamTileLUT[] = { 0, 7, 14, 21, 28, 3, 10, 17, 24, 31, 6, 13, 20, 27, 2, 9, 16, 23, 30, 5, 12, 19, 26, 1, 8, 15, 22, 29, 4, 11, 18, 25 };

  extern uint8_t user_ram_tiles_c;
  extern uint8_t free_tile_index;
  extern struct BgRestoreStruct ram_tiles_restore[];
  u8  bx;
  u8  by;
  u8  dx;
  u8  dy;
  u8  bt;
  u8  x;
  u8  y;
  u8  tx;
  u8  ty;
  u8  wx;
  u8  wy;
  u16 ramPtr;
  u8  ssx;
#if (SCROLLING != 0)
  u16 ssy;
  u8  ssy;

  /* if sprite is off, then don't draw it */

  if ((flags & SPRITE_OFF) != 0U){ return; }

  /* get tile's screen section offsets */

#if (SCROLLING != 0)
  ssx = xpos + Screen.scrollX;
  ssy = ypos + Screen.scrollY;
  if (ypos > (u8)((Screen.scrollHeight << 3) - 1U)){
    ssy += 0xFF00U; /* Sprite should clip on top */
  ssx = xpos;
  ssy = ypos;

  tx = 1U;
  ty = 1U;

  /* get the BG tiles that are overlapped by the sprite,
  ** supporting wrapping (so sprites located just below zero X
  ** or Y would clip on the left). In a scrolling config. only
  ** TILE_WIDTH = 8 is really supported due to the "weird" VRAM
  ** layout, VRAM_TILES_H is also fixed 32 this case. */

#if ((SCROLLING == 0) && (SCREEN_TILES_H < 32))
  bx = ((u8)((ssx + TILE_WIDTH) & 0xFFU) / TILE_WIDTH) - 1U;
  bx = ssx / TILE_WIDTH;
  dx = ssx % TILE_WIDTH;
  if (dx != 0U){ tx++; }

#if (SCROLLING == 0)
  by = ((u8)((ssy + TILE_HEIGHT) & 0xFFU) / TILE_HEIGHT) - 1U;
  by = ssy / TILE_HEIGHT;
  dy = ssy % TILE_HEIGHT;
  if (dy != 0U){ ty++; }

  /* Output sprite tiles */

  for (y = 0U; y < ty; y++){

    wy = by + y;
#if (SCROLLING == 0)
    if (wy < VRAM_TILES_V){
    if ( (Screen.scrollHeight != 0U) &&
         ((u8)((ypos + 7U + (y << 3) - dy) & 0xFFU) < (u8)((Screen.scrollHeight << 3) - 1U)) ){

      while (wy >= Screen.scrollHeight){
        wy -= Screen.scrollHeight;

      for (x = 0U; x < tx; x++){

        wx = bx + x;

#if (SCROLLING == 0)
        if (wx < VRAM_TILES_H){
        wx = wx % VRAM_TILES_H;
#if (SCREEN_TILES_H < 32U)
        if ((u8)((xpos + 7U + (x << 3) - dx) & 0xFFU) < (((SCREEN_TILES_H + 1U) << 3) - 1U)){

#if (SCROLLING == 0)
          ramPtr = (wy * VRAM_TILES_H) +
          ramPtr = ((u16)(wy >> 3) * 256U) +
                   (u8)(wx * 8U) + (u8)(wy & 0x07U);

          bt = vram[ramPtr];

          if ( ( (bt >= RAM_TILES_COUNT) |
                 (bt < user_ram_tiles_c)) &&
               (free_tile_index < RAM_TILES_COUNT) ){ /* if no ram free ignore tile */

            if (bt >= RAM_TILES_COUNT){
              /* tile is mapped to flash. Copy it to next free RAM tile. */
              CopyFlashTile(bt - RAM_TILES_COUNT, RamTileLUT[free_tile_index]);
            }else if (bt < user_ram_tiles_c){
              /* tile is a user ram tile. Copy it to next free RAM tile. */
              CopyRamTile(bt, RamTileLUT[free_tile_index]);
#if (RTLIST_ENABLE != 0)
            ram_tiles_restore[free_tile_index].addr = (&vram[ramPtr]);
            ram_tiles_restore[free_tile_index].tileIndex = bt;
            vram[ramPtr] = RamTileLUT[free_tile_index];
            bt = RamTileLUT[free_tile_index];


          if (doblit && (bt < RAM_TILES_COUNT) &&
               (bt >= user_ram_tiles_c) ){
                           ((u16)(flags) << 8) + sprindex,
                           ((u16)(y)     << 8) + x,
                           ((u16)(dy)    << 8) + dx);


      } /* end for X */


  } /* end for Y */

You can see where I used the LUT by searching on RamTileLUT, and you can watch it in action in the memory view of cuzebox.
rtlut.png (74.31 KiB) Viewed 701 times
This, plus a double circular mixing of my entity array every frame that we needed to sprite flicker:

Code: Select all

    if (l->numSpawned >= 4) {
      ENTITY tmp;

      uint8_t llo = 0;
      uint8_t lhi = (l->numSpawned - 1) / 2;
      tmp = l->entity[lhi];
      memmove(&l->entity[llo + 1], &l->entity[llo], sizeof(ENTITY) * (lhi - llo));
      l->entity[llo] = tmp;

      uint8_t rlo = lhi + 1;
      uint8_t rhi = l->numSpawned - 1;
      tmp = l->entity[rlo];
      memmove(&l->entity[rlo], &l->entity[rlo + 1], sizeof(ENTITY) * (rhi - rlo));
      l->entity[rhi] = tmp;
causes smaller portions of sprite flicker to be distributed among all entities. I haven't tried different striping patterns yet, but this one looks very promising. Once I settle on a good one, I'll ditch the array, and just use the math.

Please, please please try the attached .uze file out and let me know what you think.

Edit: WOW! I just tried it on real hardware with a super high quality LCD, and I the only place I can see any sprite flickering is a just a little bit in the turtle if I get the most things on the screen at once (falling spike, turtle, butterfly, all fireballs, misaligned unicorn, jumping and farting). This makes me wish I had a CRT to try this on!

Re: Mode 3 Scrolling Game Engine Demo (WIP)

Posted: Thu Jan 26, 2023 2:33 am
by Artcfox
Sometimes I feel like I'm just talking to myself here, but documenting things as I'm learning them is certainly easier, and if seeing me go through this process helps someone else out, then all the better.

When I ditched the Mode 3 sprite system, it reminded me of the "There is no spoon" scene from The Matrix. The sprites[] array is only there as a temporary holding spot for the information needed for blitting, and that under the hood it all just boils down to blitting stuff into RAM tiles each frame, and keeping track of what was in VRAM before the blit so it can be restored after the pixels are output. "There is no sprite."

So this got me thinking that the blits for a character don't have to be in a tiled grid pattern, they can be disjointed, or on top of each other with transparency, and in the back of my mind I remembered the Partitioned Sprites that Lee had written about many years ago on our wiki. I had never tried that before, and it seemed like a lot of work to implement in the old system, but with blits, it should be really easy, and I would get the benefit of being able to blit things that are different sizes and shapes that share the same origin point, so I no longer needed to translate each individual object that I needed to draw based on how big it is, or which way it is facing, because tile maps always have the origin point set in the top left corner of the megasprite.

So I came up with my own map format, and I've been calling them BlitMaps to differentiate them from the TileMaps from before, and I set about converting all of my tile maps to blit maps, paying attention to minimizing the bounding boxes (and thus RAM tiles needed) as described in Lee's document, and thinking about how I can reuse the same blit for multiple megablits. For instance, now I only have a single head tile that I can use for all of the poses, and I can just blit that at whatever offset it should be drawn at for that pose.

Here is an example of how I carved up the first walking pose:
tilemap_converted_to_blits.png (12.67 KiB) Viewed 680 times
and here is what its BlitMap looks like with the origin point set to 16 pixels above where the front leg starts:

Code: Select all

// This is the starting tile number for the BlitMaps
#define S (20)

const int8_t blitmap_walk0[] PROGMEM =
   3,    // Number of blits (if the high bit is set, all blits in the map should be flipped on the X axis)
   S,    // tileIndex
   -2,   // X offset of blit
   0,    // Y offset of blit
   S + 1,

   S + 2,
and I just continued to carve up each of my poses (testing as I went along, because it's easy to mess up!) until everything was now stored as a BlitMap. The function for blitting these BlitMaps is pretty simple, though I wanted to bake x-mirroring into the BlitMap, to save on having to have duplicate tiles that are just mirrored images of each other, and so I don't have to account for mirroring things in the code later. For that I hijacked the top bit of the first element in the BlitMap, the length field since you would run out of CPU if you tried to blit 255 things. If I need y-mirroring later I can just use the second highest bit for that.

And then I wrote the function to churn through a BlitMap:

Code: Select all

void BlitMyBlitMap(uint8_t spriteFlags, const int8_t *blitmap, uint8_t map_x, uint8_t map_y, bool doblit)
  uint8_t xflip_len = (uint8_t)pgm_read_byte(&blitmap[0]);
  int8_t len = xflip_len & 0x7F;
  if (xflip_len & 0x80) { // If the high bit of len is set, flip the entire sprite on X
    if (spriteFlags & SPRITE_FLIP_X)
      spriteFlags &= ~SPRITE_FLIP_X;
      spriteFlags |= SPRITE_FLIP_X;

  int8_t xdir;
  int8_t xoff;
  if (spriteFlags & SPRITE_FLIP_X) {
    xdir = -1;
    xoff = TILE_WIDTH * (PLAYER_TILE_WIDTH - 1);
  } else {
    xdir = 1;
    xoff = 0;

  int8_t ydir;
  int8_t yoff;
  if (spriteFlags & SPRITE_FLIP_Y) {
    ydir = -1;
  } else {
    ydir = 1;
    yoff = 0;

  for (int8_t i = 0, pos = 1; i < len; ++i, pos += 3)
                 map_x + xoff + xdir * (int8_t)pgm_read_byte(&blitmap[pos + 1]),
                 map_y + yoff + ydir * (int8_t)pgm_read_byte(&blitmap[pos + 2]),
PLAYER_TILE_WIDTH and PLAYER_TILE_HEIGHT are #defines of 1 and 2 respectively, and doblit is a boolean that tells the MyBlitSprite() function if we are going to blit the sprite (true) or if we just want to reserve the RAM tiles without doing the blit (false).

This ended up reducing my flash usage by having way fewer sprite tiles, it reduced the CPU time needed for blitting, because there are fewer blits per "megablit", and it reduced the number of RAM tiles needed to display the same sized character unaligned on average. So a triple win!

It also made me think about what can happen next now that I'm using these BlitMaps...

I only have a single unicorn head tile now, what if I add two parameters to the BlitMyBlitMap() function: replaceTile and withTile. Then as it blits the map, any time it sees the head tile (#20) I can replace it with a different head, maybe one where the horn is glowing red because you're using magic, or the tongue is sticking out because you just ate a lollipop, or the eyes blink because you are idle. What if I instead allocate a user RAM tile for this, and now at runtime I can change the way the head looks with special effects, or if it takes damage I can tweak individual pixels.

Or maybe certain powerups can change the look (add wings that get blitted on top in any pose?) Or maybe it can carry objects around the level and have them float nearby with its magic. All I would need to do would be add an extra call to the blitter, I wouldn't need to mess around with trying to find the proper sprite number so I get the z-order correct, and then have to deal with the sprite numbers for everything else getting messed up. Nope, there are no sprites, it's all just blits now, and I can do them whenever I need to, and let the flickering algorithm works its magic drawing the rest of the entities. Exciting times!

Re: Mode 3 Scrolling Game Engine Demo (WIP)

Posted: Fri Jan 27, 2023 7:22 am
by mapes
Reading your progress on the unicorn game and the sprite/blitting progress you are making is great! I could see this used in the future by the community and could definitely open the doors for more retro game developers for the 644 and bring about more nes like brawlers or similar action games.

One of these days I need to finish my dalaks game that I started along time ago "based on an Dr. Who".

Re: Mode 3 Scrolling Game Engine Demo (WIP)

Posted: Fri Jan 27, 2023 1:39 pm
by Artcfox
Thanks mapes!

It makes me happy knowing that I'm not just speaking out into the void. :lol:

I am really trying to go all out with this, and I am learning so much in the process. Now when I re-read other posts from people, I understand a lot more what they were talking about with the blitters, and they way the video modes work under the hood. I am hoping that I have learned enough to understand and try out some of the new video modes that Jubatian wrote. The high resolution and 256 color support of Mode 3 is a big draw to me though.

I just wish that mode 3 could access the SD card during HSYNC. I might need to explore adding that in order to not waste so many cycles accessing it during user code, even if it is to just finish off reading the extra junk bytes after I am done reading the data I need.

Ah yes, I do remember you talking about that game before! Now is as good of a time as any to finish it, so go for it! I had started this in 2018, and then got involved in other things, but it is fun to be back and working on Uzebox stuff again.

Re: Mode 3 Scrolling Game Engine Demo (WIP)

Posted: Tue Jan 31, 2023 8:38 am
by Artcfox
I haven't posted an update in a while, but I've still been making improvements.

Since my last post, I have:
  • Reduced RAM usage (and the uzem crash) by converting my RAM tile striping code to use maths, rather than a RAM-based LUT.
  • Added an optional stack painter/counter as a debug option, so I can see the high-water mark for how exactly how much stack memory the game has used in the worst case.
  • Added an automatic spawn eviction system, so as you move around the level if it wants to spawn something new in, but you are out of memory and the purely distance-based despawner hasn't freed up an entity slot, it will try to automatically despawn an entity to make room if there is one meeting the criteria. That criteria is: the entity is not marked as "never despawn", its spawn point is currently outside of VRAM, and it is the farthest one away from the player. Errors are still printed to the console if too many spawns are concentrated in one area, rather than silently removing entities that violate the criteria so when testing, you can be sure the level is working properly.
  • Added extra parameters to BlitMyBlipMap, so it can do blit swaps across all poses. Used this to make it so the unicorn's tongue appears when it gulps lollipops, no matter which animation/pose it is using.
  • Added horizontal moving platforms that smoothly accelerate up to speed! Debugging all of the associated jitter (sub-pixel and otherwise) was a huge, huge pain. There was sub-pixel jitter between the player's blit position with the tile position of the moving platform that sometimes manifested as moving back and forth on the platform. There was also jitter caused by the camera position tracking the player when on a moving platform, and finally jitter involved if the player clipped a wall while on a moving platform. But now with all the jitter removed, they look good at any speed, even zipping you across the level at warp speed. Each moving platform takes up an entity slot, but they require zero RAM tiles. :o
  • Bugfixes, code cleanup.

Re: Mode 3 Scrolling Game Engine Demo (WIP)

Posted: Thu Feb 02, 2023 5:22 am
by mapes
I'm either terrible at timing, or those nefarious bugs are really deadly and I have to start over! I tried 7 times and I couldn't beat level 1... so close, but fell into a bug. :lol: I got to say is game looks great and is extremely smooth to play using cuzebox. The 'some where over the rainbow' sound track and disappearing clouds is a nice touch.

I thought I'd give Daleks another look, but when I compile Daleks, it stalls when run... I couldn't figure it out... I then tried to compile mode 9 from the source (using AVR studio 4) but it did the same. Curious if anyone else has been able to compile and run the demo for mode 9.

Re: Mode 3 Scrolling Game Engine Demo (WIP)

Posted: Thu Feb 02, 2023 6:40 am
by Artcfox
mapes wrote: Thu Feb 02, 2023 5:22 am I'm either terrible at timing, or those nefarious bugs are really deadly and I have to start over! I tried 7 times and I couldn't beat level 1... so close, but fell into a bug. :lol: I got to say is game looks great and is extremely smooth to play using cuzebox. The 'some where over the rainbow' sound track and disappearing clouds is a nice touch.
Thank you! :D Are you using a USB gamepad with cuzebox? I can sit there and spend 15 minutes and still not beat it with a keyboard, but once I switch to the real hardware, or cuzebox with a USB gamepad, I can usually beat it a lot quicker (few minutes). You did hit the hotkey (F7) in cuzebox to turn off its frame merging right? I wish that defaulted to off.

I'm glad the soundtrack and dissolving clouds are appreciated. I originally sequenced Pink Fluffy Unicorns Dancing On Rainbows, but after about 20 seconds of having it in the game, it was too annoying, so I ditched it. Maybe I will bring it back as a non-repeating jingle for completing a level. See (listen to) the attachment for the Uzebox version I made.

When I look back and see how much code is needed to support the dissolving clouds, I just think to myself "Woah, I had really no idea what I was getting into when I started to add that feature!" Although, tonight I just figured out how to reduce the tile usage for the dissolving clouds by 6, which is good because it will take an extra 28 tiles to add vertically moving platforms (which I'm hoping to add next). I did draw out some actual rainbow tiles, but I think those might have to be stored on the SD card, and loaded into RAM tiles, or procedurally generated on the fly into RAM tiles, because they take up a lot of space. And it would be nice to use some RAM tile effects on the rainbow once implemented to make it look extra awesome.
mapes wrote: Thu Feb 02, 2023 5:22 am I thought I'd give Daleks another look, but when I compile Daleks, it stalls when run... I couldn't figure it out... I then tried to compile mode 9 from the source (using AVR studio 4) but it did the same. Curious if anyone else has been able to compile and run the demo for mode 9.
I can compile and run the mode 9 demo (with gcc). The demo is static though, it just does while(1); If you can't figure it out, start another thread and maybe we can help figure it out if you post your code.

Re: Mode 3 Scrolling Game Engine Demo (WIP)

Posted: Sat Feb 04, 2023 12:26 am
by Artcfox
Latest update:
  • Vertical moving platforms
  • Smoother, better looking unicorn animations/sprites
  • Fully dissolved clouds now indistinguishable from pure sky, dissolving clouds now use 6 fewer tiles
  • Enforced min jump height of 8 pixels (prevents bug in vertical moving platforms, also I've been wanting to add that for a while)
  • Bugfixes, code cleanup
So I implemented vertical moving platforms last night. The approach I took was similar to the horizontal moving platforms, and I thought it might be easier since I had the ordering of operations already sorted out from the horizontal moving platforms, so there shouldn't be any jitter. Since the tiles for a pixel to tile offset of 0 would be borrowed from the horizontal moving tiles, and the bottom halves of the vertical moving tiles were all the same I made things easier for myself and created two lookup tables in PROGMEM so I could pretend that all of the tiles with similar pixel to tile offsets were next to each other sequentially so I could use the same simple math when dealing with the offsets. Then I wrote the code to move the VMP entity up and down. Next I wrote the code to draw the correct tiles for the correct positions, and then the code to clean up any leftover tile gunk above and below from the previous draw.

Rather than looking at what tiles are in VRAM and figuring out where a moving platform is based on reading those tiles (that is how all of the other player collision detection code in this engine works), the Player_update code for moving platforms loops over all currently active moving platform entities, and it checks to see if it just crossed the invisible line on the top of a moving platform, and then pretends it hit a solid object and stops on it. It is just a programmed coincidence that I am drawing the tiles exactly where that invisible line is; as far as this collision code is concerned the tiles being rendered underneath that spot mean absolutely nothing, and are actually treated as 100% non-interacting sky tiles by the rest of the rendering engine.

The tricky part with the horizontal moving platforms (HMP) was the jitter. Once I determined the player is on an HMP, I just add the difference between the previous X and the current X position to the player's position. This allows the player's velocity to remain at zero while at rest on a moving platform, meaning when it does move, it's max speed won't be affected by the speed of the moving platform, and, like it should, it can run extra fast in the direction of motion, and if it runs backwards, it can match the speed of the moving platform and make it look like it is running in place.

With the vertical moving platforms (VMP) the tricky part was keeping the player locked to the platform. If the platform moved down faster than gravity, the player would separate and keep falling into it while it traveled down, and when it switched directions and traveled upwards the player would fall through it. I had to check against the platform's previous position to make sure it didn't pass over that invisible line without being noticed, and I had to assume that it was still on a moving platform if it was during the previous frame in order to keep it locked to the platform. Just when I was about to give up on VMPs, I stumbled upon those realizations and then there was no more jitter or weird behavior, and it worked as expected.

There is still a lot of room for optimizations for both moving platforms. Right now the VMPs use ~1800 clocks each, and HMPs use ~800 each, but maybe that's not terrible since they don't use the blitter at all, so they save tons of clock cycles there. I just wanted to get a baseline implementation working first, and then I could iterate on improvements later. Like the HMPs, I tested these all the way up to a speed value of WORLD_METER * 16, but the limiting factor becomes how fast the level needs to scroll when a player is position locked onto a fast moving platform. The scrolling code is only designed to handle loading a single column at a time, so you can't scoll by more than 8 pixels in a frame. That's not really a big deal, because you can't really play a game when things are scrolling by at 480 pixels per second anyway.

Re: Mode 3 Scrolling Game Engine Demo (WIP)

Posted: Sat Feb 04, 2023 1:00 am
by CunningFellow
nice. I am terrible at platform games but this is very very polished.