Your case is not so bad because you can have a "break" between the status bar and the gameplay area where to do the dirty work, you just need to time things really well if you want to prevent visual glitches. There's a correct time to turn sprites off, a correct time to modify the VRAM address, a correct time to turn rendering back on... It has to be very well planned and executed in order to look right.pwnskar wrote:If I opt for not having any scrolling at all, perhaps this could be a feature I would be able to pull off?
Well, sprites are delayed by one scanline (an OAM Y coordinate of 45 will cause the sprite to show on scanline 46), and the new scroll value should only be set for the scanline after the one where the hit happens, so 2 scanlines of difference sounds about right.I've managed to set the coarse Y scroll right by using the sprite-0 Y value divided by 8, though I seem to have to adjust it a bit by subtracting #2 before dividing.
You do 4 register writes, like this:I also tried applying some fine Y scroll and it worked just as you explained. How would one go about solving the issue of that missing bit? Is it impossible on a standard UNROM mapper?
Code: Select all
$2006: ****NN** (name table)
$2005: YYYYYyyy (coarse and fine Y scroll)
$2005: XXXXXxxx (coarse and fine X scroll)
$2006: YYYXXXXX (mix of coarse X and Y)
Then you set the Y and X scroll via $2005, almost as you'd do during vblank except that Y goes first, then X. This is because $2006 and $2005 share the toggle that selects between the first and the second write, and the $2006 write we did before changed that toggle, so the PPU thinks we're writing to $2005 the second time, so Y goes first. The toggle changes, and then the PPU expects the X scroll.
Finally, the second $2006 write works as I explained before. Even though X and Y have already been set, the scroll is still in a temporary PPU internal register, and the second $2006 write has the important task of copying that to the actual address register, otherwise the new scroll won't take effect until the next frame. So you need to combine the coarse X and Y scroll correctly for that last write to work.
As for timing, this can be done at any time when rendering is off, but when rendering is on (e.g. scroll splits for parallax effects) the first 2 writes can happen during the scanline, and the last 2 as close to the beginning of hblank as possible, so you may want to buffer those last 2 values in advance so you can store them in quick succession when hblank starts.
You count cycles starting from an event that happens at a known time. In this case, the reference event is the sprite zero hit, because you know when that happens. You have to count the cycles of each instruction from that point on to predict when things will happen as the PPU runs in parallel.Oh, and how do I know when I'm in h-blank, so that I can make sure to only do my palette updates at that time?
Each pixel is 1 PPU cycle, the PPU is 3 times faster than the CPU in NTSC, 3.2 times in PAL. This means that in the time it takes for the lda #$20 instruction to run (2 CPU cycles), the PPU will draw 6 pixels. An scanline has 256 visible pixels, plus 85 "pixels" of hblank (about 28.3 CPU cycles in NTSC). The PPU is still doing work during hblank when rendering is on though, so only some of that time is available for specific tasks when that's the case.