Adventures in Scrolling: Mirroing Transitions

This is an archive of a topic from NESdev BBS, taken in mid-October 2019 before a server upgrade.
View original topic
Adventures in Scrolling: Mirroing Transitions
by on (#173835)
Hey guys,

I'm continuing my quest to gain more knowledge about scrolling and the associated techniques.

This time, I'm looking at various techniques I can use to mask the transition between vertical mirroring and horizontal mirroring. In games like Metroid and Mega Man, doors and ladders add a nice aesthetic to the game, but of course as developers, we know that they also mask the transition between scrolling axes.

Some games don't have any smooth transition, they just instantly change areas. Some of this is seen in parts of Air Fortress.

Can you guys think of any other techniques used to transition between h/v mirroring?
Re: Adventures in Scrolling: Mirroing Transitions
by on (#173836)
Some games just don't transition between mirrorings, and keep scrolling in four directions using the same setting.
Re: Adventures in Scrolling: Mirroing Transitions
by on (#173839)
I don't think Megaman ever transitions between directions, if I recall correctly all vertical motion is fast (which hides the mirroring issue). Vertical movement was only ever added in the post-NES games I think.

Most of the games that just swap between screens also happen to be using the nametable for the HUD, where keeping the HUD in place would interfere with thtat. Zelda is the only exception to the rule that comes to my mind (has both HUD and vertical motion), but vertical motion looks jerky (as it's done by just moving tiles instead of scrolling), especially in dungeons where it's slower.
Re: Adventures in Scrolling: Mirroing Transitions
by on (#173841)
Games which choose to change mirroring between H and V will do so when the scrolling is equal to (0, 0) so that they can change the mirroring without affecting the displayed screen.

Megaman 3-6 indeed change the mirroring when scrolling direction changes, however it's more to similate one-screen mirroring (only one nametable is used as the main one) rather than to hide scrolling glitches (which would require they to do it the oposite way).
Re: Adventures in Scrolling: Mirroing Transitions
by on (#173844)
I'm of the opinion that a solid 8-way scrolling engine is a much better investment than a clunky 4-way system that has to worry about a bunch of conditions and limitations. Make a good 8-way scrolling system and you can reuse it for practically any kind of scrolling NES game.

It's possible to get glitch-free 8-way scrolling on the NES using only the 2 built-in name tables, but you have to jump through some hoops to accomplish this, specially if you don't have mapper IRQs. For this reason, in my current project I decided to use 4 screens (i.e. no mirroring) and not worry about glitches or special tricks. Adding extra name table RAM to a cartridge is extremely simple if you use CHR-RAM, and is essentially free nowadays because 8KB RAM chips are harder to find than 16KB or 32KB ones, so the memory will often be in the cartridge anyway, and using the extra memory for name tables is just a matter of changing a single wire.
Re: Adventures in Scrolling: Mirroing Transitions
by on (#173850)
As someone who wrote a game relying on a "cluncky 4-way scrolling system", I have to disagree with your 1st statement. It really depends on the game. I think the majority of available NES games do not have free 8-way scrolling implemented in them, because this is complex, expensive in terms of memory/ressources, and very error prone. Especially with a status bar.
Re: Adventures in Scrolling: Mirroing Transitions
by on (#173851)
In reading on the various scrolling methods, I've noticed that there are always accommodations for the status bar. Why does the status bar complicate scrolling in any setting?
Re: Adventures in Scrolling: Mirroing Transitions
by on (#173854)
The map for a status bar requires its own video memory. In a typical 512x240 pixel (horizontal arrangement/vertical mirroring) or 256x480 pixel (vertical arrangement/horizontal mirroring) nametable layout, the visible region may cross the part of video memory devoted to the status bar. Workarounds:
  • Super Mario Bros. 3 levels that aren't vertical are 480 - 48 = 432 pixels tall, to leave room for the status bar at the bottom of a 256x480 pixel nametable.
  • A few games (Little Nemo and Kirby's Adventure) write all changes to both the first and second half of a 256x480 nametable, so that whenever the scroll is about to hit the status bar, it jumps to the other copy.
  • A few games use 256x480 and redraw the status bar between the bottom of one nametable to the bottom of the other nametable whenever the scroll is about to hit the status bar.
  • Most games developed by Rare use 1-screen mirroring, which divides video memory into two independent 256x240 pixel pages.
  • Crystalis uses 256x480 but uses an IRQ at the bottom of 240 pixels to wrap around to the top, simulating 1-screen mirroring mode.
Re: Adventures in Scrolling: Mirroing Transitions
by on (#173856)
tepples wrote:
The map for a status bar requires its own video memory. In a typical 512x240 pixel (horizontal arrangement/vertical mirroring) or 256x480 pixel (vertical arrangement/horizontal mirroring) nametable layout, the visible region may cross the part of video memory devoted to the status bar. Workarounds:
  • Super Mario Bros. 3 levels that aren't vertical are 480 - 48 = 432 pixels tall, to leave room for the status bar at the bottom of a 256x480 pixel nametable.
  • A few games (Little Nemo and Kirby's Adventure) write all changes to both the first and second half of a 256x480 nametable, so that whenever the scroll is about to hit the status bar, it jumps to the other copy.
  • A few games use 256x480 and redraw the status bar between the bottom of one nametable to the bottom of the other nametable whenever the scroll is about to hit the status bar.
  • Most games developed by Rare use 1-screen mirroring, which divides video memory into two independent 256x240 pixel pages.
  • Crystalis uses 256x480 but uses an IRQ at the bottom of 240 pixels to wrap around to the top, simulating 1-screen mirroring mode.

Hmm, I can see why using a status bar would complicate things.

Bregalad wrote:
Games which choose to change mirroring between H and V will do so when the scrolling is equal to (0, 0) so that they can change the mirroring without affecting the displayed screen.

Megaman 3-6 indeed change the mirroring when scrolling direction changes, however it's more to similate one-screen mirroring (only one nametable is used as the main one) rather than to hide scrolling glitches (which would require they to do it the oposite way).

Can you elaborate a bit on how MM3 uses the h/v mirroring to simulate one-screen mirroring?
Re: Adventures in Scrolling: Mirroing Transitions
by on (#173857)
MM3 (as well as 4, 5 and 6) uses horizontal mirroring when scrolling horizontally and vertical mirroring when scrolling vertically. In other term, they use vertical nametable arrangement when scrolling horizontally, and horizontal nametable arrangement when scrolling vertically. They only switch when the scroll is aligned with the screen, obviously.

This means they basically just use the same 32x30 tile map, and the second always remains unused. There is scrolling glitches in all cases, when scrolling horizontally and vertically. They use it for the menu in MM3 and for special effects and minibosses in MM4-6 (but only in some stages).

Doing the exact opposite (switch H/V in a way which reflects the direction the game is scrolling) allows to suppress all scrolling glitches. However, it doesn't allow for any special effects. It doesn't sound like suppressing scrolling glitches was Capcom's priority. Nor anyone else's really, they are standard on NES games, even of late in the console's life.
Re: Adventures in Scrolling: Mirroing Transitions
by on (#173859)
Bregalad wrote:
I think the majority of available NES games do not have free 8-way scrolling implemented in them

I didn't say that most games have it, I said that if you're gonna invest time into coding anything more complex than a side scroller, it's better to invest in an 8-way scrolling engine, because it's more versatile and can accommodate most types of scrolling.

Quote:
because this is complex

More than a side scroller, but not significantly more than a 4-way scroller.

Quote:
expensive in terms of memory/ressources

Depends on how you code it.

Quote:
and very error prone.

If you plan it will well and code it solidly, the result should be as stable as any other engine. Not being able to come up with code to do something doesn't make something "error prone", it just means the programmer didn't find a good way to implement it.

Quote:
Especially with a status bar.

Granted, a status bar will complicate things, but that's the case with any type of vertical scrolling, not just 8-way. If you want free scrolling and a status bar, the easiest solution is to use 1-screen mirroring, unfortunately that comes with artifacts on the sides...
Re: Adventures in Scrolling: Mirroing Transitions
by on (#173860)
Ok, I got to admit, I suck at writing 8-way scrolling engine. Both of my attempts failed, since they had glitches on some occasions when changing the scroll direction too much. Thanks god this is hardly necessary for most game styles.

Quote:
Granted, a status bar will complicate things, but that's the case with any type of vertical scrolling, not just 8-way.

Yep. But with 8-way it's updating the rows/column without each getting in the way of themselves which is complicated. Add in combining the status' bar shared attribute data, and it's a nightmare.
Re: Adventures in Scrolling: Mirroing Transitions
by on (#173861)
Bregalad wrote:
Both of my attempts failed, since they had glitches on some occasions when changing the scroll direction too much.

I have coded a number of 8-way scrolling engines, and based on previous experience, I'd guess this is because of handling one axis entirely before doing the other. For example, if you handle the horizontal scroll first, buffer the new column, and then handle the vertical scroll, you might have problems if you need a new row as well, because the column you prepared before will be vertically off by 1 unit relative to the final position of the camera, which could result in a wrong block overwriting a correct block or in a spot that needed a new block not being updated.

The trick is to move the camera both vertically and horizontally, validate both movements, and only then buffer rows and columns as necessary. This will guarantee that both the row and the column will be aligned to the final position of the camera, so you won't miss any spots or overwrite anything.

Handling one axis entirely before doing the other would be a perfectly valid solution in a system where you can update the map immediately, but since map updates are delayed until the vertical blank on the NES, you have to make sure that all map updates are based on the final state of the camera, to avoid inconsistencies.
Re: Adventures in Scrolling: Mirroing Transitions
by on (#173862)
xgamer wrote:
Hmm, I can see why using a status bar would complicate things.

There's one more, simple, way that tepples didn't mention: using sprites (although it's not really a "status bar" anymore at that point). A lot of 8-way scrolling Imagineering games did this (Bart vs the World, Swamp Thing, Home Alone 2, Bartman Meets Radioactive Man, ...)
Re: Adventures in Scrolling: Mirroing Transitions
by on (#173864)
The imagineering games also used DxROM instead of MMC3 since they didn't need IRQs.
Re: Adventures in Scrolling: Mirroing Transitions
by on (#173865)
Dwedit wrote:
The imagineering games also used DxROM instead of MMC3 since they didn't need IRQs.

Definitely not all of them. Bart vs the World (at least) uses IRQs to blank the bottom of the screen.
Re: Adventures in Scrolling: Mirroing Transitions
by on (#173867)
One of the things that trips me up from a pc dev perspective is how much of nes scrolling is hardware and which parts are software. For example, in the case of games that can scroll horizontally in both directions, how does the game know which columns and which tilemap to restore the nametable with when it gets overwritten? Is that hardware or software? If it's software, does the program have some sort of column register to keep track of which column is being drawn?
Re: Adventures in Scrolling: Mirroing Transitions
by on (#173869)
It's the same as on PC. A game has a "camera" object that tells how far the screen has scrolled. The game notices when the camera has moved by a certain number of pixels, writes the newly revealed area of the map to video memory, and then tells the PPU where in video memory to start drawing the map based on the camera's position.

Image
The process looks like this


Which 2D API are you familiar with from PC game programming? Allegro, SDL, something else? Or is it all OpenGL all the time?
Re: Adventures in Scrolling: Mirroing Transitions
by on (#173871)
Currently, I'm working with Allegro 4, so basically, I'm trying to build skyscrapers with sticks and stones. Scrolling on the nes is as simple as setting a register; the console has innate knowledge of what nametables are because its built into the 2C02. With a library like Allegro, you basically get access to keyboard, mouse, graphics card, and sound card, but that's it; nothing more is given. I have to build up to what the nes already knows how to do.
Re: Adventures in Scrolling: Mirroing Transitions
by on (#173873)
What the NES offers is still pretty low level. It's just a grid of tiles (name tables) and sprites, things you can easily simulate with bitmaps in Allegro or whatever. You can create a bitmap and call it a "name table", and your scroll registers are nothing more than the position of the screen where you draw the bitmap. If you draw it at (0, 0), the scroll is 0. Draw it at (-1, 0) and you have scrolled one pixel to the right. Just maintain a pair of variables for X and Y offsets and those will be your scroll registers. To handle mirroring, just have the offsets wrap around when they're larger than the name table's width/height, and draw 4 copies of the name table. As for sprites, those are nothing more than bitmaps drawn on top of (or below, if you're doing priorities) the name table. You can even easily implement multiple scroll planes this way, each one being a bitmap and its respective set of scroll offsets.

EDIT: A basic rendering loop could look like this:

Code:
clear screen using the background color;
draw background at (-ScrollX, -ScrollY);
draw background at (-ScrollX + BackgroundWidth, -ScrollY);
draw background at (-ScrollX, -ScrollY + BackgroundHeight);
draw background at (-ScrollX + BackgroundWidth, -ScrollY + BackgroundHeight);
for each sprite:
   draw sprite at (SpriteX, SpriteY);

And a system with sprite and background priorities could work like this:

Code:
clear screen using the background color;
for each level of priority:
   for each background with this priority:
      draw background at (-ScrollX, -ScrollY);
      draw background at (-ScrollX + BackgroundWidth, -ScrollY);
      draw background at (-ScrollX, -ScrollY + BackgroundHeight);
      draw background at (-ScrollX + BackgroundWidth, -ScrollY + BackgroundHeight);
   for each sprite with this priority:
      draw sprite at (SpriteX, SpriteY);

When setting/updating the scroll, just make sure the values are withing the range of a name table:

Code:
ScrollX = ScrollX % BackgroundWidth;
ScrollY = ScrollY % BackgroundHeight;
Re: Adventures in Scrolling: Mirroing Transitions
by on (#173874)
For anyone interested, here is the link to a demo of what I have working so far. I can scroll either horizontally or vertically, but not both (or at least not in a way that looks consistent); that's what I'm working on now.

And because I like to put the cart before the horse, I even have horizontal parallax scrolling.

Sorry for the sampling rate, but it does run at (about) 60fps.
Re: Adventures in Scrolling: Mirroing Transitions
by on (#173899)
You do not (and should not) have to copy how the NES does scrolling when making games for the PC.

On the NES, all graphics are represented as mutable state in VRAM which is persistent across frames, but on the PC, there is absolutely no need to make your video buffers persistent or mutable. Clear your buffers each frame and start anew; frames should not depend on the state of the previous frame. Do not bother trying to update individual columns of tiles offscreen as the NES does; that's bad design on modern PCs with all of their computing power.
Re: Adventures in Scrolling: Mirroing Transitions
by on (#173902)
pubby wrote:
You do not (and should not) have to copy how the NES does scrolling when making games for the PC.

Not even if you are trying to ensure 100 percent consistent logic between an NES game and its PC port?

Quote:
[Caching the composited tile map is] bad design on modern PCs with all of their computing power.

By "PC" do you mean x86-64 desktops and full-size laptops with a Core i series CPU, x86 tablets and netbooks with an Atom CPU, or also ARM devices such as Raspberry Pi? Would it be better to make 1000+ draw calls each frame, one for each tile on the screen?
Re: Adventures in Scrolling: Mirroing Transitions
by on (#173903)
tepples wrote:
Not even if you are trying to ensure 100 percent consistent logic between an NES game and its PC port

I guess if you're doing what rainwarrior does and coding a C++ version side-by-side with a NES version, then sure, it could make sense. But I don't think many people are doing this.

Quote:
By "PC" do you mean x86-64 desktops and full-size laptops with a Core i series CPU, x86 tablets and netbooks with an Atom CPU, or also ARM devices such as Raspberry Pi? Would it be better to make 1000+ draw calls each frame, one for each tile on the screen?

The Raspberry Pi seems way faster than needed for this. I don't understand where that 1000+ number is coming from. It only takes one glBufferSubData call to update the screen.

That first post of mine had some strongly worded opinions in it. I woke up in a bad mood today so sorry if I came off as standoffish.
Re: Adventures in Scrolling: Mirroing Transitions
by on (#173904)
pubby wrote:
I don't understand where that 1000+ number is coming from.

The number of tiles visible on the screen.
Re: Adventures in Scrolling: Mirroing Transitions
by on (#173906)
pubby wrote:
You do not (and should not) have to copy how the NES does scrolling when making games for the PC.

On the NES, all graphics are represented as mutable state in VRAM which is persistent across frames, but on the PC, there is absolutely no need to make your video buffers persistent or mutable. Clear your buffers each frame and start anew; frames should not depend on the state of the previous frame. Do not bother trying to update individual columns of tiles offscreen as the NES does; that's bad design on modern PCs with all of their computing power.

As a side not, this is also how sprites should properly be rendered on the NES. Too bad many newbies tends to uses the shadow OAM as part of the game state.

Quote:
I have coded a number of 8-way scrolling engines, and based on previous experience, I'd guess this is because of handling one axis entirely before doing the other.

It is quite possible that this is indeed the problem I have, I do not know. I do not need 8-directional scrolling for any of the games I'm planning to develop right now, so I don't really care at this point.
Re: Adventures in Scrolling: Mirroing Transitions
by on (#173907)
pubby wrote:
On the NES, all graphics are represented as mutable state in VRAM which is persistent across frames, but on the PC, there is absolutely no need to make your video buffers persistent or mutable. Clear your buffers each frame and start anew; frames should not depend on the state of the previous frame. Do not bother trying to update individual columns of tiles offscreen as the NES does; that's bad design on modern PCs with all of their computing power.

This is the common wisdom, but it gets me thinking that for 2D games with tilemaps, it may be better to simulate a nametable by rendering tiles to a texture and only updating it while needed. You're already going to need this if you want to avoid all the seam issues that would come from rendering separate quads (especially when tiles get scaled, which they will because you can't predict the output resolution), and you're also going to want it to reduce the amount of draw calls (only draw a few tiles instead of all of them).

But then again a lot of modern 2D games aren't even using tilemaps but instead everything is an object with an arbitrary shape (avoiding the issue altogether).
Re: Adventures in Scrolling: Mirroing Transitions
by on (#173908)
pubby wrote:
tepples wrote:
Would it be better to make 1000+ draw calls each frame, one for each tile on the screen?

I don't understand where that 1000+ number is coming from.

Tokumaru is right: 33 * 31 = 1023 entries in the tilemap that are at least partially visible. Even if you assume nominal NTSC overscan, that's still 33 * 29 = 957 entries. And before you say a modern game would use metatile-sized tiles instead of tiles the size of individual NES nametable entries, many newer platformers such as New Super Mario Bros. Wii tend to be zoomed out to levels that, if applied on NES or Super NES, would be reminiscent of Super Mario Land.

pubby wrote:
It only takes one glBufferSubData call to update the screen.

The manual states that this function "updates a subset of a buffer object's data store". Wouldn't one have to update pieces of the nametable through this call? Or are you recommending abandoning incremental updates and re-decoding the entire section of the map that falls within the camera's boundaries during every frame that the coarse scroll (X coordinate mod tile width and Y coordinate mod tile height) has changed from the previous frame?
Re: Adventures in Scrolling: Mirroing Transitions
by on (#173909)
tepples wrote:
Or are you recommending abandoning incremental updates and re-decoding the entire section of the map that falls within the camera's boundaries during every frame that the coarse scroll (X coordinate mod tile width and Y coordinate mod tile height) has changed from the previous frame?

Yep! That's what I was trying to convey.

tepples wrote:
The manual states that this function "updates a subset of a buffer object's data store".

It's like OAM DMA on the NES. Each frame you prepare a buffer of your vertex data in the CPU's address space, then use a single glBufferSubData to transfer it all to the GPU. The buffers will be rebuilt from scratch each frame.
Re: Adventures in Scrolling: Mirroing Transitions
by on (#173910)
The difference is that OAM has 64 objects, while a nametable has an order of magnitude more.