I just coded (which means I haven't tested it!) a sample fading system, which hopefully will give you some ideas. Fading is actually a sequence of events, and the first thing we need to take care of is the animation itself: Are we fading? In or out? Is it time to change the brightness? Here's the code that handles this part:
Code: Select all
lda #$10 ;assume we're fading in and the step is positive
ldx CurrentBrightness ;compare the current brightness...
cpx TargetBrightness ;...against the target brightness
beq DoneFading ;skip fading if the target was already reached
bmi UpdateDelay ;skip the next instruction if we're indeed fading in
lda #$f0 ;oops, we're actually fading out, make the step negative
UpdateDelay:
dec FadeDelay ;decrement the frame counter
bne DoneFading ;skip fading if the delay isn't over yet
clc ;prepare for addition
adc CurrentBrightness ;change the brightness level...
sta CurrentBrightness ;...according to the step value
jsr GenerateModifiedPalette ;generete the modified palette
lda #$04 ;reset the delay...
sta FadeDelay ;...to the value of your choice
DoneFading:
The nice thing about this system is that all you need to do in order to fade is set a new brightness target. Every time you change the target, this code will detect that it's different from the current brightness and will automatically animate towards the target. Call this once per frame. Obviously, you have to initialize CurrentBrightness, TargetBrightness and FadeDelay to the correct values on power up, but after that you only need to change TargetBrightness whenever you need to fade.
Note that this also allows you to fade to white, not only black. Here are the common values you'll use for different brightness settings (current and target):
Code: Select all
$C0 (i.e. -$40) - all black;
$00 - normal brightness;
$40 (i.e. +$40) - all white;
The next step is actually modifying the colors. The code above calls the "GenerateModifiedPalette" function, which is this:
Code: Select all
GenerateModifiedPalette:
lda BackgroundColor ;get the background color
jsr ModifyColor ;modify its brightness...
sta ModifiedBackgroundColor ;...and save
ldx #23 ;repeat the following for the other 24 colors
ModifyPalette:
lda Palette, x ;get the color
jsr ModifyColor ;modify its brightness...
sta ModifiedPalette, x ;...and save
dex ;move on to the next color
bpl ModifyPalette ;repeat if there are still colors left
rts ;return
This is just a loop to modify all 25 colors. Storing the palette like this is a personal choice of mine, because I find it silly to use 32 bytes for colors when the NES can only display 25. You can obviously modify this if you prefer the other way. Anyway, the actual color modification happens here:
Code: Select all
ModifyColor:
clc ;prepare for addition
adc CurrentBrightness ;change the brightness
cmp #$0d ;compare the result against the stupid "forbidden color"
beq UseBlack ;use black if the result is the "forbidden color"
cmp #$c0 ;check if the result is too dark
bcs UseBlack ;use black if the result is too dark
cmp #$40 ;check if the result is too light
bcs UseWhite ;use white if the result is too light
rts ;return the modified color
UseBlack:
lda #$0f ;use black
rts ;return black
UseWhite:
lda #$30 ;use white
rts ;return white
This is all there's to it. All that's left now is to copy the processed palette to VRAM during VBlank, but I'm sure everyone knows how to do this, so I won't make this post any longer with something so trivial.
Note that this system is pretty versatile. Just as easily as you can fade to black, normal or white, you can fade to the intermediary steps. For example, you could use a target brightness of $E0 to make a room darker but not quite black, for when characters destroy light bulbs or something like that. Another possibility would be to set both CurrentBrightness and TargetBrightness (so that the change is instantaneous, without any animation) to $30, and manually call GenerateModifiedPalette to make everything very bright but not quite white, for effects like lightning storms.
I used the very simple concept of "subtracting or adding $10" to modify the colors, but you could still use the same overall idea with other types of color modification if you wanted. Personally, I prefer something smoother, but the vast majority of games appear to settle for this basic method.
EDIT: Crap, just noticed a mistake. In the first block of code I trashed the status flags when loading the default step value. I fixed it now. Hopefully I didn't make any more mistakes.
EDIT: Opps, another little mistake. I used bpl instead of bmi. Fixed it.
EDIT: Tested everything in the 6502 simulator and it appears to work fine now.