I'm working on 4 way scrolling right now. Working with X axis is nice and enjoyable experience, working with Y makes me cry in despair. I use 16-bit number for X where high 8-bit would be indicator of new nametable to load (or fill with $00 tile if not found). For Y, scroll must wrap at 240 otherwise I will have teared picture. So what I do is use scroll x/y (copies in RAM, that put in PPU at NMI) as lower 8 bits. So my 16-bit Y coordinate has to wrap at 240 for low byte as well.
I don't have division (I know I can use loop and substract 240 until value left is less or equal to 239), and having high byte to be level number, makes it easier to work with.
Problem arises when I need to compare what is the difference between camera coordinates, and loaded coordinates. If I scroll Y-1 and loaded Y - camera Y == 8, I load new row. At certain point I will have loaded Y=$XX00 and camera Y=$XXE8, and I want to get 8 as the result. I am not going to show any code, as all of my attempt have bugs and I'm still not very comfortable with assembler.
Does any one knows good approach to deal with this, or is there better way to deal with Y coordinate?
For scrolling, you probably don't need to scroll fast enough to need more than just an increment at a time, something like this:
Code:
low_y += scroll_rate_y
if low_y >= 240
low_y -= 240
high_y += 1
...and something similar for subtracting.
If you need to do a big jump, you can change that "if" into some sort of "while", perhaps, but what I'm getting at is that is that for moving short distances you don't need to do generic division. You can just accomplish division as a repeated subtraction, probably with more efficiency than a more generic division routine.
If you occasionally need to do a big jump, probably it won't matter on that particular frame if you take a few extra cycles. Even then, a subtraction loop is probably OK. How tall are your levels? 4 screens? 16 screens? 1000 screens? If the problem is small scale, a small scale solution may be sufficient.
For things that aren't scrolling but somehow need to be mapped to a screen, you can probably just keep the "real" coordinate of the camera position, and do a subtraction from that to find its position on the current screen. (Or at most they'll be one or two screens off, adjacently.)
I scroll 1px/frame and that is pretty much what I do and I will have at most 16x16 screens. Problem is not wrapping, but math after that. I want to know that we scrolled for 8 pixels, and 0-232!=8. But you are probably right, I should have an additional two variables to store scroll difference, and then use wrapped ones to find row to load.
I need to keep low byte wrapped, because if I have [0-255]:[0-240], high(scrollY) will be Y (+-1 for adjacent) for nametable (32 by 30) and low(scrollY)>>3 will be the row number to load from.
Keeping separate variables for "top of valid portion of tilemap in VRAM space" and "top of valid portion of tilemap in world space" can help. When you scroll, add 8 to or subtract 8 from both, and wrap world space mod 256 and VRAM space mod 240. Every time you update the tilemap or set the hardware scroll position, use additions and subtractions to convert world space to VRAM space.
yaros wrote:
I need to keep low byte wrapped, because if I have [0-255]:[0-240], high(scrollY) will be Y (+-1 for adjacent) for nametable (32 by 30) and low(scrollY)>>3 will be the row number to load from.
What I was suggesting is to keep both at once for your camera. A "world" coordinate to subtract from everything else, and the wrapped "screen" coordinate for mapping to the screen.
Edit: tepples made the same suggestion while I was typing.
The trick is to not to use the same value to calculate the row number to load from and the one to write to. Instead, use 2 separate scroll values, one relative to the level map (wraps normally at 256), and the other relative to the name tables (wraps at 240). Just update them in sync (always add/subtract the same amounts to both) and handle the special wrapping accordingly.
When you need to do operations relative to the level map, such as loading rows/columns or keeping the camera from going past the edges of the level, use the scroll that's relative to the map. When you need to do operations relative to the screen, such as calculating the target NT address for rows/columns or setting the PPU scroll, use the scroll that's relative to the name tables.
During initialization, the separate scroll values don't even need to be aligned to each other in any particular way when it comes to map rows. That is, there's no need to divide the normal scroll by 240 to find the initial value of the special scroll to find out what it'd be both started at 0 and scrolled down. The row where the special scroll starts makes literally no difference, since both scrolls wrap around independently from each other. Only the "pixel" part of the scroll values must match. For example, say that your metatiles are 16x16 pixels, and the initial vertical scroll is 1317. The remainder of that by 16 is 5, so you can simply initialize the special scroll to 5, but everything would work just as well if it were 21, 37, 53, and so on (increments of 16 pixels).
EDIT: Basically what the guys above said.
The only restriction on the scroll position in VRAM space and world space is that their difference has to be a multiple of 16. Allowing them to become unaligned recently bit me.
Yeah, I may not have worded it very well when I said that "the pixel part must match", but that's what I meant. They need to be pixel aligned within the specific metatile they're in, but the NT space coordinate can point to any metatile row at the beginning.
Thank you all. I made it working now.
Edit: smaller gif
I keep expecting the Skifree monster to show up in that gif, lol.
Happy New Year everyone!
tepples wrote:
The only restriction on the scroll position in VRAM space and world space is that their difference has to be a multiple of 16. Allowing them to become unaligned recently bit me.
Care to say what kind of issue it causes? I'm curious, because I can't figure out the problem.
Once the lower 4 bits of the VRAM and world space coordinates become misaligned, the attribute grid no longer lines up, and parts of your background start having the wrong palette.