This page is a mirror of Tepples' nesdev forum mirror (URL TBD).
Last updated on Oct-18-2019 Download

Best practise to avoid scanline limitations

Best practise to avoid scanline limitations
by on (#148746)
Hello,
I am studying Shirus NESLib at the moment and try to write a simple Snake clone. Now I am facing scanline issues when the snake grows and more than 8 body-sprites are displayed per scanline (see attached screenshot). I know this is a common issue of the NES and since this problem is well known, I am searching for the best workaround for handling this problem. Is there really no other way than to transfer snake´s body-parts into the nametable and update it every single frame?
Re: Best practise to avoid scanline limitations
by on (#148750)
Realistically, what any developer would have done is make the snake into a background object and draw new tiles for its body as it grows. If you wish to make the snake appear as if it is moving, you can add sprites at the beginning and end of the snake to hide the blocks from randomly disappearing. Those energy beams in Quick Man's stage were achieved doing just that, drawing a sprite moving forward while the background draws the background tiles in. And ironically enough, those giant snakes in Battletoads do something very similar to that.
Re: Best practise to avoid scanline limitations
by on (#148755)
Yeah, just draw the body parts on the background. In fact this is probably the one kind of game you can get away without sprites at all.
Re: Best practise to avoid scanline limitations
by on (#148757)
He could have the food items be sprites. Otherwise, they'd have to be 16x16, because of the palette grid being that.
Re: Best practise to avoid scanline limitations
by on (#148758)
Thanks for your suggestions. I will do the suggested changes and come back to you with my experiences.
Re: Best practise to avoid scanline limitations
by on (#148759)
How do you plan on having it to where the body parts are following the snake? The way I thought of would be to have a long table where the first slot represents the head, the second slot being the next body segment, the third slot being the next one and so on. Every time the snake changes position, the positions of the different segments are shifted down the table to where body segment 3 is now using body segment 2's position. The last entry is deleted and a new one is created for the head based on where the player moves. There will be a counter that is the number of body segments that will increase whenever the snake eats something new.
Re: Best practise to avoid scanline limitations
by on (#148762)
If you randomize the order in which you draw segments, you'll get flicker but all the segments will be seen.

If you want to use the nametable, you don't necessarily have to update the whole thing every frame, just what changes each frame (head and tail).

You could even use a sprite for the head and tail, and use a flipbook sequence of CHR tiles to animate motion of segments in the nametable. CHR banking would make that easy, but since you probably only need 4 tile types (4 corners, 2 straight) with CHR-RAM you should have enough time to swap in 6 new tiles very vblank.
Re: Best practise to avoid scanline limitations
by on (#166138)
Picked this project up again, after a long time. I still struggle with updating the background. After some researches, I think I have to use function flush_ram_update. Sadly I cannot find a starters-tutorial which explains using this function. Can someone provide some sample code which explains updating the background punctually?
Re: Best practise to avoid scanline limitations
by on (#166144)
I'm not familiar with NESLib at all, but the basic idea is, in psuedocode, something like this:

Code:
Draw blank tile where snake's tail is (draw_tile(blank, tail_x, tail_y)
Run snake's move routine
Draw head tile where snake's head is (draw_tile(snake_head, head_x, head_y))


That's only two writes to the table per frame. It should be adequately quick. You can store the snake's "previous tail position' before moving the snake, to allow you to remove the game logic from your render code.
Re: Best practise to avoid scanline limitations
by on (#166157)
If you go the background tiles way, you have to do all the processing of updating in the VBLANK period. In Neslib, that means using a update list. There are examples on how to use it, and you can see it in action in Chase (if memory serves me right).

If you go the sprites way, do as suggested: Just randomize the order you send your sprites to the OAM. That can be achieved in several ways. I use a fixed length list and a interator with an increment which is prime to the list, incrementing the start index every frame. That way the order you send the sprites to the OAM is different each frame, so each frame a different set of sprites are not rendered when the PPU hits the limitation. What the player gets is flickering, but at least no parts of the snake are disappearing completely.

For example, if you have your snake to be composed of 32 sprites at maximum, you can use the value "9" to iterate the list of 32 values. Iterating 32 times adding 9 every time gets you the sequence 0, 9, 18, 27, 4, 13, 22, 31, 8, 17, 26, 3, 12, 21, 30, 7, 16, 25, 2, 11, 20, 29, 6, 15, 24, 1, 10, 19, 28, 5, 14, 23. Next time, start in "1" and you get a different sequence. Next time start in 2. When you get to the value of the prime, start over and start in "0" again.

Pseudocode:

Code:
do {
    // Game logics.

    index = start_index;
    for (i = 0 to LIST_SIZE) {
        // Create sprite in OAM copy for tail piece # index.
        ...
        // Increment index by prime (with modulus)
        index = (index + PRIME) % LIST_SIZE;
    }
    start_index ++; if (start_index == PRIME) start_index = 0;

    PPU_wait_nmi ();
    //etc
}


Remember that if LIST_SIZE is a power of two, that % LIST_SIZE can be performed as a & (LIST_SIZE - 1) which is a tad faster.
Re: Best practise to avoid scanline limitations
by on (#166158)
How come you're only using start indices from 0 to PRIME-1 instead of 0 to LIST_SIZE-1? They should all be valid starting points.
Re: Best practise to avoid scanline limitations
by on (#166159)
You could, yes, and benefit of the power of two there as well. Plus you get a more coplex pattern. You are right.

In fact I'm not sure why I was doing it that way :oops: :wink: :o
Re: Best practise to avoid scanline limitations
by on (#166167)
Thanks for the reply. I understand the way to do this with sprites and the reason to send those sprites in random order to OAM, to avoid a complete disabling of the same sprites each frame.

But unfortunately I have to be honest and admit, that I have serious problems understanding the VRAM-way to avoid the flickering. I think my problem is to understand, how to create the update-list which will be given to function flush_ram_update.

na_th_an is right, Shiru is using VRAM-update in his Chase example game. I tried to analyze and understand it, but failed :/

At the moment I am able to calculate the exact tile-numbers which should be changed but I don't know how to use this information in combination with function flush_vram_update. Somehow those information need to be put into the update-list parameter.
Re: Best practise to avoid scanline limitations
by on (#166238)
I don't know about that function.

You just need an array big enough. The worst case scenario needs three bytes per tile changed, that is, two for the address, one for the tile #.

Imagine your snake will be at most 32 tiles long, so you have to:

Code:
unsigned char update_list [32 * 3 + 1]; // +1 is needed for the terminator


Before your game loop starts, you instruct neslib to use your array as an update list:

Code:
set_vram_update (update_list);


And after your game loop, don't forget to disable it!

Code:
set_vram_update (0);


When an update list is active, the code in the NMI will read it until it reaches a special value defined in the constant NT_UPD_EOF, so you might crash your program if you are not careful.

As the data in your update list is dynamic and will change in each frame, I would do as follows:

You need a pointer to unsigned char (define it as a global in the ZP area):

Code:
unsigned char *ul;


Then, your game loop should look like this:

Code:
while (!finished) { // or whatever
    // Point ul to the beginning of the update list
    ul = update_list;

    // Your game logic. Update your snake.
    // *For each* body part, add it to the update list this way:
    // addr = NAMETABLE_A + x + (y << 5);
    // *ul ++ = MSB (addr);
    // *ul ++ = LSB (addr);
    // *ul ++ = tile_number;

    // Now we are finished, so we add the "end of update list" marker:
    *ul = NT_UPD_EOF;

    // And now we are ready to...
    ppu_wait_nmi ();
}


That way you are writing new data in the update list each frame and making sure the update list is properly terminated before the NMI code, which will parse it during VBLANK sending the data to the VRAM.
Re: Best practise to avoid scanline limitations
by on (#166337)
Thanks, that worked for me :) I have to add, that you need to initialize the update-list with NT_UPD_EOF, before the first game-loop, instead you will only see a flickering screen.

Next problem I have is, that somehow I am not able to calculate target tile-no. of body-elements (using the x- and y-coordinates of body elements) properly. The coordinates are correct, since I used them in my sprite-based version.

I tried the above mentioned version:
Code:
NAMETABLE_A + x + (y << 5)


As well as the version neslib provides:
Code:
#define NTADR_A(x,y)   (NAMETABLE_A|(((y)<<5)|(x)))


But in both cases, the body tiles are not aligned properly to the snakes' head.
Re: Best practise to avoid scanline limitations
by on (#166343)
-Basti- wrote:
The coordinates are correct, since I used them in my sprite-based version.

Don't forget that sprite coordinates are in pixel units while tile coordinates are in tile units. Since each tile is 8x8 pixels:

Code:
TileX = PixelX >> 3
TileY = PixelY >> 3
Re: Best practise to avoid scanline limitations
by on (#166345)
-Basti- wrote:
Thanks, that worked for me :) I have to add, that you need to initialize the update-list with NT_UPD_EOF, before the first game-loop, instead you will only see a flickering screen.


You don't, if you do things right. I mean, that flickering screen is because you are calling ppu_wait_nmi with an un-terminated update-list. The section in the NMI code which deals with update lists just interprets the update buffer array until it finds the value NT_UPD_EOF (which is a number). Most likely, the NMI code is sending everything in RAM to the VRAM so things end in a very awful crash.

So most likely you are enabling the update list too early. Maybe there's a ppu_wait_nmi before the first loop.

In my example, you are not calling ppu_wait_nmi until NT_UPD_EOF has been written to the update buffer, so there's no need to initialize anything. NT_UPD_EOF is written when it's going to be needed: during the execution of ppu_wait_nmi.

Always make sure you enable the update buffer right before your loop, and that you write NT_UPD_EOF to it right before your ppu_wait_nmi call, and you won't have problems.

What you did is most likely a patch because you have stuff around you are not completely in control. It may float your boat and save your day, but my advice is that you should address the problem, find the real cause, get things properly in control, and remove all patches.
Re: Best practise to avoid scanline limitations
by on (#166658)
tokumaru wrote:
-Basti- wrote:
The coordinates are correct, since I used them in my sprite-based version.

Don't forget that sprite coordinates are in pixel units while tile coordinates are in tile units. Since each tile is 8x8 pixels:

Code:
TileX = PixelX >> 3
TileY = PixelY >> 3


That was the problem. Thanks for the hint :)