Game project help and progress thread

A place where you can keep others updated about your NES-related projects through screenshots, videos or information in general.

Moderator: Moderators

User avatar
Alp
Posts: 223
Joined: Mon Oct 06, 2014 12:37 am

Re: Game project help and progress thread

Post by Alp »

Tsutarja wrote:Is there a syntax guide for NESASM somewhere? I haven't been able to find one. I feel like there might be some functions in NESASM that I don't know about.
Depending on where you downloaded NESASM, you may or may not have this text file, that's included.
I've attached it to this post, for your convenience. (You may need to open it in a programming utility.)
Attachments
usage.txt
(13 KiB) Downloaded 139 times
User avatar
Tsutarja
Posts: 123
Joined: Sun Oct 12, 2014 11:06 am
Location: Finland

Re: Game project help and progress thread

Post by Tsutarja »

I've been trying to get the palette stuff working, but every time I come up with a idea on how to do it, I run into some problem that I can't solve... I may need some example code to further explain the whole fading thing.
UP SIDE DOWN A B A B B A B A Hidari migi
L R L R STOP & DASH & UP & TALK Ijou nashi
User avatar
tokumaru
Posts: 12427
Joined: Sat Feb 12, 2005 9:43 pm
Location: Rio de Janeiro - Brazil

Re: Game project help and progress thread

Post by tokumaru »

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.
User avatar
Tsutarja
Posts: 123
Joined: Sun Oct 12, 2014 11:06 am
Location: Finland

Re: Game project help and progress thread

Post by Tsutarja »

Hmm... The fading itself works, but the colours and scrolling positions get messed up. I have no idea what causes this.

Code:
http://pastebin.com/uZVR2379
UP SIDE DOWN A B A B B A B A Hidari migi
L R L R STOP & DASH & UP & TALK Ijou nashi
User avatar
tokumaru
Posts: 12427
Joined: Sat Feb 12, 2005 9:43 pm
Location: Rio de Janeiro - Brazil

Re: Game project help and progress thread

Post by tokumaru »

The only thing obviously wrong I see is the way the palette is being copied to VRAM. It looks like you first copy the background color, and then copy the remaining colors in groups of 3, but incrementing X 4 times each iteration. You are compensating for the fact that you have only 25 colors instead of 32 the wrong way. You're skipping one entry in the array every 3, causing a misalignment with the hardware palette and not filling all of it. What I do in this case is write the background color again every 3 writes. Something like this:

Code: Select all

LDA PPUStatus
 LDA #$3F
 STA PPUAddr
 LDA #$00
 STA PPUAddr
 LDX #$00
 LDY bg_colour_modified

NMIPalLoop:
 STY PPUData
 LDA palette_modified, x
 STA PPUData
 INX
 LDA palette_modified, x
 STA PPUData
 INX
 LDA palette_modified, x
 STA PPUData
 INX
 CPX #$18
 BNE NMIPalLoop
 LDA #$00
 STA <palette_update_flag
I didn't notice anything that could be affecting the scroll (see EDIT below). Let's see what happens when you fix this.

I did notice a few unnecessary things. For example, I don't see any need to initialize the modified palettes with the same colors as the unmodified palette, considering that in the beginning the modified palette will most likely be all black, so you're filling it just to have it overwritten with blacks soon after. It would make sense to manually call BrightnessModify as part of the palette initialization process though, to make sure that the correct palette will be properly sent to the PPU as soon as possible:

Code: Select all

 ;initialize the brightness
 LDA #$C0
 STA <brightness_current
 LDA #$00
 STA <brightness_target

 ;initialize speed and delay
 LDA #$02
 STA <fade_speed
 STA <fade_delay

 ;generate the data for the VRAM update
 JSR BrightnessModify

 ;request the VRAM update
 LDA #$01
 STA <palette_update_flag
If you don't do something like this, you'll end up with an unitialized palette on screen for a few frames, instead of the first brightness level. After this, the system is ready to function on it's own.

BTW, making the delay configurable was a nice touch. Makes the fading system even more versatile! =)

EDIT: The "ScrollUpdate" is before the palette update. Setting the scroll should be the very last PPU operation in the NMI, because anything you do with $2006/$2007 will mess up the scroll. You're also missing a $2000 write to select which name table rendering should start in. This write should be near (before or after doesn't matter) the $2005 writes.
User avatar
Tsutarja
Posts: 123
Joined: Sun Oct 12, 2014 11:06 am
Location: Finland

Re: Game project help and progress thread

Post by Tsutarja »

Got that one working now. I'm not sure if the fade looks correct now (at least to me it does seem odd somehow)

https://www.youtube.com/watch?v=S28xVlw ... e=youtu.be

I probably should next make the background data compression/decompression system, graphics buffer and background update part for my NMI.
UP SIDE DOWN A B A B B A B A Hidari migi
L R L R STOP & DASH & UP & TALK Ijou nashi
User avatar
tokumaru
Posts: 12427
Joined: Sat Feb 12, 2005 9:43 pm
Location: Rio de Janeiro - Brazil

Re: Game project help and progress thread

Post by tokumaru »

Looks good to me (especially combined with the wobbling effect!), except for the gray(?) screen at the very beginning. Did you do the initialization as I suggested, with forced palette generation and update?
I probably should next make the background data compression/decompression system, graphics buffer and background update part for my NMI.
Are you going for a generic NMI that will be used throughout the whole program? Just keep an eye on how much CPU time you're spending on it. For larger data transfers, you should consider some degree of loop unrolling.
User avatar
Tsutarja
Posts: 123
Joined: Sun Oct 12, 2014 11:06 am
Location: Finland

Re: Game project help and progress thread

Post by Tsutarja »

tokumaru wrote: Looks good to me (especially combined with the wobbling effect!), except for the gray(?) screen at the very beginning. Did you do the initialization as I suggested, with forced palette generation and update?
Yeah I did try it, but I couldn't get it to work. I don't know whether its that I did it wrong, or that I don't do it early enough (that's why I posted from the beginning to the palette initialization)

Code: Select all

RESET:
 SEI
 CLD
 LDX #$40
 STX $4017
 LDX #$FF
 TXS
 INX
 STX PPUCtrl			; Disable NMI
 STX PPUMask			; Disable rendering
 STX DCMIRQ			; Disable DPCM

VBwait1:			; First PPU warm up wait
 BIT PPUStatus
 BPL VBwait1

ClearMem:			; Clear internal memory
 LDA #$FF
 STA $0200, x			; Set OAM to #$FF to render sprites off screen
 LDA #$00
 STA $0000, x			; Clear Zero Page
 STA $0100, x			; Clear Stack
 STA $0300, x			; Clear Sound Engine RAM
 STA $0400, x			; Clear Graphics Buffer RAM
 STA $0500, x
 STA $0600, x
 STA $0700, x
 DEX
 CPX #$00
 BNE ClearMem

 LDA #LOW(BgTitle00)
 STA <pointerLo
 LDA #HIGH(BgTitle00)
 STA <pointerHi

VBwait2:			; Second PPU warm up wait
 BIT PPUStatus
 BPL VBwait2

 LDX #$00
 LDA BgColour, x
 STA bg_colour_original

PaletteLoad:
 LDA Palette, x
 STA palette_original, x
 INX
 CPX #$17
 BNE PaletteLoad

 LDA PPUStatus
 LDA #$3F
 STA PPUAddr
 LDA #$00
 STA PPUAddr
 LDX #$00
 LDY bg_colour_original

PalInit:
 STY PPUData
 LDA palette_original, x
 STA PPUData
 INX
 LDA palette_original, x
 STA PPUData
 INX
 LDA palette_original, x
 STA PPUData
 INX
 CPX #$18
 BNE PalInit
tokumaru wrote: Are you going for a generic NMI that will be used throughout the whole program? Just keep an eye on how much CPU time you're spending on it. For larger data transfers, you should consider some degree of loop unrolling.
Yeah, I will use the same NMI throughout the whole game. I will probably do a tiles to metatiles and metatiles to screens type of compression. That probably doesn't take that much time since many games use it (?). I probably don't need to update more than one or two vertical background rows at once (and that probably split up over few frames) plus few tiles caused by other objects. During transitions I'll probably just turn off the PPU and load the background during CPU time and turn on the PPU when done. I've read that some people do that, but I'm not sure if that's recommend or not.
UP SIDE DOWN A B A B B A B A Hidari migi
L R L R STOP & DASH & UP & TALK Ijou nashi
User avatar
koitsu
Posts: 4201
Joined: Sun Sep 19, 2004 9:28 pm
Location: A world gone mad

Re: Game project help and progress thread

Post by koitsu »

You're missing a bit PPUStatus prior to VBWait1. Reference: http://wiki.nesdev.com/w/index.php/PPU_power_up_state

Small optimisation: get rid of cpx #$00 -- it serves no purpose. dex will set the zero flag when the register/effective address value decremented becomes zero. 65xxx CPUs are fantastic for this sort of thing. :-)

Off-the-cuff guess: maybe you get a grey screen is because you're spending a lot of CPU time (memory initialisation, etc.) without having pre-set all palette entries screen to black (and nametable + attribute tables to something that will use such a palette, e.g. $00 or maybe $FF for everything). That's my guess anyway. Some emulators choose to show things as "grey" until that's done. You might try setting $2006 to $0000 and $2005 as well, to see if maybe that settles things a bit.

That said: I doubt this is the full program. I do not see anywhere where you enable background or sprites by adjusting PPUMASK, so I can't even tell what is going on between the point where you turn off the screen and where you start manipulating the palette in real-time.

It should be very easy to find out what is taking up all the time ("grey screen") using an emulator like FCEUX. Step through your code gradually piece by piece and you'll figure it out.
User avatar
tokumaru
Posts: 12427
Joined: Sat Feb 12, 2005 9:43 pm
Location: Rio de Janeiro - Brazil

Re: Game project help and progress thread

Post by tokumaru »

Tsutarja wrote:Yeah I did try it, but I couldn't get it to work. I don't know whether its that I did it wrong, or that I don't do it early enough (that's why I posted from the beginning to the palette initialization)
Well, this code is not what I suggested at all. This is just uploading the original palette to the PPU. Why would you do this, if you want to start out all black? What I suggested was: copy the original palette from ROM to RAM (this part is OK), then initialize all fading variables (current brightness, target brightness, delay and speed) then call BrightnessModify to force the generation of the processed palette and finally set the flag that requests a palette update, to force the modified palette to be uploaded the next VBlank.

These last 2 operations have to be forced because normally they only happen when the delay expires and the system advances to the next brightness level, but you need to get the very first palette up somehow. The alternative would be to initialize the delay to 1 instead of the full value, and call BrightnessControl, so the delay expires immediately (triggering the generation of the modified palette and the request for an update). For that to work you'd also have to initialize the current brightness to one level before than what you really want to start with (in this case that would be $b0), otherwise you'll never see the first palette.
I will probably do a tiles to metatiles and metatiles to screens type of compression. That probably doesn't take that much time since many games use it (?).
Depending on the size of your metatiles, this could still be a lot of data. If they are the common 16x16-pixel kind, that means each screen will take 240 bytes. An 8KB PRG-ROM bank would only be able to hold 34 such screens. Larger metatiles, such as the ones used in Mega Man (32x32 pixels), are much better in terms of compression. There's always metatiles of metatiles, but the complexity this adds to map decoding sometimes scares people off!
During transitions I'll probably just turn off the PPU and load the background during CPU time and turn on the PPU when done. I've read that some people do that, but I'm not sure if that's recommend or not.
That's OK as long as the NMI handler is well structured enough to not get in the way of big updates. It's not advisable to turn NMIs off, mainly because of music, so the NMI handler should be able to detect that it interrupted a large PPU operation and that's it's not supposed to touch the PPU at all in that case.
User avatar
Tsutarja
Posts: 123
Joined: Sun Oct 12, 2014 11:06 am
Location: Finland

Re: Game project help and progress thread

Post by Tsutarja »

Okay, I managed to get the "grey screen time" at the startup down to 2 frames, which seems to be what many games have.
tokumaru wrote:There's always metatiles of metatiles, but the complexity this adds to map decoding sometimes scares people off!
Another question is: How much more time does it cost to decode over just one metatile compression, and is it worth it? I think there was a article about data compression and data buffering somewhere, but I can't remember where I found it.

By the way, how are attributes stored? Are they in the background data or are they in a separate table?
UP SIDE DOWN A B A B B A B A Hidari migi
L R L R STOP & DASH & UP & TALK Ijou nashi
User avatar
tokumaru
Posts: 12427
Joined: Sat Feb 12, 2005 9:43 pm
Location: Rio de Janeiro - Brazil

Re: Game project help and progress thread

Post by tokumaru »

Tsutarja wrote:Okay, I managed to get the "grey screen time" at the startup down to 2 frames, which seems to be what many games have.
That's probably because of the VBlanks we're required to wait so the PPU can warm up. What you could do is separate the gray frames and the fade in with a few intentional black frames. Half a second (30 frames), maybe. That should help players dissociate the initial flash from the actual fading effect.
Another question is: How much more time does it cost to decode over just one metatile compression, and is it worth it?
That depends entirely on how you're doing it. The absolute simplest way would be to decode it all to RAM beforehand, in which case speed would be completely irrelevant. This is only an option if you have extra RAM in the cartridge, though.

If you're decoding the data in real-time, then the amount of optimization in the code will make a big difference. Unrolled code would probably be fast enough to use in real-time without problems.

I think this is something you're gonna have to do the math and decide what the best course of action is yourself. Are your levels really big? If not, it might make more sense to use more PRG-ROM and keep the data formats simple.
By the way, how are attributes stored? Are they in the background data or are they in a separate table?
This is completely up to you, but to me it makes sense to keep palette attributes as part of the metatile, along with collision information and all other attributes.
User avatar
Tsutarja
Posts: 123
Joined: Sun Oct 12, 2014 11:06 am
Location: Finland

Re: Game project help and progress thread

Post by Tsutarja »

I think I have now come up with a decent idea for a graphics buffer.
Every time data is written to the buffer, bg_update_flag will be incremented. Data is written in "length, start address, data" order. Every time a byte is written to the buffer, buffer_offset will be incremented. During NMI bg_update_flag is compared to #$00. If true, graphics updates are skipped. If false, data buffer will be read. Byte indicating length is copied to RAM and decremented after every write to the PPU. At the same time buffer_offset_nmi is incremented. When length reaches zero, bg_update_flag is decremented and compared to #$00. Reading the buffer will continue until bg_update_flag reaches zero. At this point both buffer offsets are reset to zero and graphics updates end.

Do you think that this would be a good way to do this? I haven't thought of the data compression, metatiles, etc. much yet, since the data is uncompressed in the buffer anyway. I will get to that once the buffer itself works.
UP SIDE DOWN A B A B B A B A Hidari migi
L R L R STOP & DASH & UP & TALK Ijou nashi
User avatar
tokumaru
Posts: 12427
Joined: Sat Feb 12, 2005 9:43 pm
Location: Rio de Janeiro - Brazil

Re: Game project help and progress thread

Post by tokumaru »

Tsutarja wrote:Do you think that this would be a good way to do this?
Sounds like a good system, just a bit slow. If the data transfer loop is like this:

Code: Select all

CopyByte:
  lda Buffer, x ;4 cycles
  sta $2007 ;4 cycles
  dex ;2 cycles
  bne CopyByte ;3 cycles
...it will take 13 CPU cycles to copy each byte. If you were doing only this during VBlank, you'd be able to transfer about 170 bytes. Since this is NOT the only thing you'll be doing (there's aways the sprite DMA, setting the scroll and other tasks), you can probably transfer around 100 bytes per VBlank. If that's enough for you (and it should be unless you're scrolling at ridiculous speeds or animating CHR-RAM), no worries. Just keep in mind that you have to store data in the buffer backwards so you can count down and index data using the same register, otherwise the copy loop would be slower than 13 cycles per iteration.

If you are however animating CHR-RAM, then that's definitely too slow, since 100 bytes are less than 7 tiles. In this case you'd need more advanced unrolling techniques.
I haven't thought of the data compression, metatiles, etc. much yet, since the data is uncompressed in the buffer anyway. I will get to that once the buffer itself works.
Yeah, ideally these things are completely separate. As long as the decompression routines output data in the format you've chosen for the buffers, everything will connect just fine.
User avatar
Tsutarja
Posts: 123
Joined: Sun Oct 12, 2014 11:06 am
Location: Finland

Re: Game project help and progress thread

Post by Tsutarja »

If it's not too complex, I could try to use faster method for reading the buffer. I don't know if I'm going to animate CHR-RAM, but I guess I should make the code fast enough in case I need to use it.
UP SIDE DOWN A B A B B A B A Hidari migi
L R L R STOP & DASH & UP & TALK Ijou nashi
Post Reply