09. Scrolling

Scrolling means moving the background around. It does not affect sprites.

The NES PPU has 1 scroll register, 2005. You write to it twice, first the X scroll, and then the Y scroll. This is another thing that needs to happen during v-blank, and is handled automatically by neslib. neslib has this function scroll(x,y), you pass it the shift amounts. Adding to X scroll, shifts the screen left. Adding to the Y scroll shifts the screen up.

But, I decided that I didn’t like the way it handled Y scrolling. Y scrolling is a bit odd anyway, since values 0-$ef are real positions, and $f0 – ff are treated as negative values, and not what you want. neslib subtracts $f0 if the Y value is > $ef, and assumes that you are going to manage the maximum at $1df.

So, long story short, I do things differently than everyone else using C. I made 2 functions called set_scroll_x(x) and set_scroll_y(y). You can pass the set_scroll_y any int value, and the high byte will tell you which nametable you are in. Even means top, odd means bottom. If you have 2 collision maps, you know even = use the first one, odd = use the second one. Simple. Well, not perfect.

Our code still has to skip over the $f0-ff region, because our screen is only 240 pixels high. Luckily, I wrote some functions to do this for us.

add_scroll_y(add, old y) to add to the y scroll.

sub_scroll_y(add, old y) to subtract from the y scroll.

Each returns a value, which will have to be passed to set_scroll_y(), to change the screen scroll.



scroll_y = 0xef, add 5. This returns 0x104

scroll_y = add_scroll_y(5, scroll_y);



scroll_y = 0x104, subtract 5. Returns 0xef

scroll_y = sub_scroll_y(5, scroll_y);

Again, skipping over the 0xf0 – 0xff invalid Y scroll values.


Horizontal scrolling (Vertical mirroring)

Remember from the intro page, I said that the NES only has enough VRAM for 2 nametables. If you set it to Vertical mirroring — the mirroring is set in the ines header in crt0.s, which is actually a linker symbol “NES_MIRRORING” found in the .cfg file. On a real cartridge they would have soldered one of the connections to permanently set it to H or V mirroring.

So with vertical mirroring the nametables are arranged like this.



With the lower 2 nametables being copies of the top 2.

This is good for sideways scrolling. If you scroll past the right screen, it will wrap back to the left. If you want a level that’s bigger than 2 screens wide, you have to change BG tiles as you go.

(The numbers on the screen are sprites. They don’t scroll with the background.)





Vertical scrolling (Horizontal mirroring)

is basically the same, except the right 2 nametables are copies of the left 2


This is good for vertical scrolling.





There is also 4 screen mode, which almost zero games used. It required a special cartridge with an extra RAM chip. Gauntlet and Rad Racer II, for example, use it.

This would be good for all direction scrolling. Most games just used the standard 2 screen layout, and had glitchy tiles at the edges. Old TVs tended to cut off the edges, so it usually wasn’t too noticeable.

There are special boards (mapper) that can change the mirroring layout. See Metroid, and it sometimes scrolls horizontally, and sometimes it scrolls vertically. Instead of being hardwired to one layout, it can alternate between them. But, that is a more advanced topic.


2 thoughts on “09. Scrolling

  1. I think there may be a bug in the nesdoug set_scroll_y. It seems to result in garbage being added to the top of the screen as the scroll amount nears the point of rolling back to 0.

    It can be seen even with the -1 case you use in your examples:


    I captured a short clip of it here (watch the top of the screen): https://imgur.com/a/MUAW965

    This does not happen with neslib “scroll()”.

    Liked by 1 person

    1. This is a known issue. The top line is actually the attribute table data being transmitted as tile data. I figured that 1 line of faulty graphics would be small enough to be ignored, and many actual NES games do the same thing, expecting that this would be hidden in the overscan region of the 1980s TVs.

      You could set_scroll_y() to 0x1ef to fix it (or using the scroll() function, y = 0x1df, since that manages scroll >= 0xf0 by subtracting 0xf0).

      Liked by 1 person

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s