Page 1 of 2
Need some direction with multiple backgrounds
Posted: Wed Sep 16, 2015 2:34 am
by log in
Hi guys,
I picked up nes programming again.
A long time ago i did the nerdy night tutorial and made a 1 screen pong game.
Now i want to ad multiple screens.
So i had a idea in my head for 2 screens. Push start screen and actual playing screen.
With the nerdy nights stuff ( mario.chr, and background.bat and NESASM3 )
I created this idea that i have not tested yet.
- load title screen
- program start button so that if pressed the screen is turned of and the game screen is loaded.
But i really need to learn how to programm more nes screens. I think this is done by adding more chr. But i'm not sure.
In other words i need more information about adding more backgrounds and switching between them.
I searched the forum but i can't find it . So any help would be welcome.
Re: Need some direction with multiple backgrounds
Posted: Wed Sep 16, 2015 6:34 am
by tepples
If the screens share the same background tile set, you can just disable rendering (
LDA #$00 STA $2001), load in the new data through $2006 and $2007, and then reenable rendering (
LDA #$1E STA $2001)
If they have different tile sets, you have three choices:
- Stick with NROM (no mapper) but use $2000 to switch the background tile start address between PPU $0000 and $1000.
- Use a CHR ROM mapper to switch a different tile set into $2000.
- Use a CHR RAM board and load a new tile set into PPU $0000-$1FFF.
Re: Need some direction with multiple backgrounds
Posted: Wed Sep 16, 2015 6:54 am
by tokumaru
log in wrote:I think this is done by adding more chr.
Not necessarily. You can have thousands of screens sharing the same 256 (or 512) tiles. Look at SMB or any other NROM game.
The basic idea behind multiple screens is that each screen (I like the names "mode" and "state" better, since one of these parts can have more than 1 screen because of scrolling and such) is its own isolated program. Each program mode should have its own initialization code, main loop, and even a PPU update code, if you choose not to make a generic vblank handler that serves all program modes.
In other words i need more information about adding more backgrounds and switching between them.
You can keep the basic structure you have from Nerdy Nights, but you have add different initialization code for each mode and select the proper logic block and PPU update block depending on the current program mode.
The system initialization can be left intact, but you should put a label marking the start of the code that sets up your very first screen, so you can jump to it whenever you want to go back to that mode. Then you can do the same for how many more program modes you want. Then, when the NMI hits, you have to jump to the proper handler for the current program mode, instead of always running the same handler. It goes something like this (BTW, this is not NESASM syntax... I believe you have to use [] in the indirect JMP and LOW()/HIGH() to get the individual bytes of an address):
Code: Select all
;(REGULAR INITIALIZATION GOES HERE)
;****************************************************************
TitleScreenInitialize:
;(TURN NMIs OFF)
;(INITIALIZE VARIABLES, BACKGROUND, ETC. FOR THE TITLE SCREEN)
lda #<TitleScreenNMI
sta ProgramModeNMI+0
lda #>TitleScreenNMI
sta ProgramModeNMI+1
;(TURN NMIs ON)
TitleScreenInfiniteLoop:
jmp TitleScreenInfiniteLoop
TitleScreenNMI:
;(UPDATE THE PPU FOR THE TITLE SCREEN)
;(RUN LOGIC FOR THE TITLE SCREEN)
rti
;****************************************************************
MainGameInitialize:
;(TURN NMIs OFF)
;(INITIALIZE VARIABLES, BACKGROUND, ETC. FOR THE MAIN GAME)
lda #<MainGameNMI
sta ProgramModeNMI+0
lda #>MainGameNMI
sta ProgramModeNMI+1
;(TURN NMIs ON)
MainGameInfiniteLoop:
jmp MainGameInfiniteLoop
MainGameNMI:
;(UPDATE THE PPU FOR THE MAIN GAME)
;(RUN LOGIC FOR THE MAIN GAME)
rti
;****************************************************************
GameOverInitialize:
;(TURN NMIs OFF)
;(INITIALIZE VARIABLES, BACKGROUND, ETC. FOR THE GAME OVER SCREEN)
lda #<GameOverNMI
sta ProgramModeNMI+0
lda #>GameOverNMI
sta ProgramModeNMI+1
;(TURN NMIs ON)
GameOverInfiniteLoop:
jmp GameOverInfiniteLoop
GameOverNMI:
;(UPDATE THE PPU FOR THE GAME OVER SCREEN)
;(RUN LOGIC FOR THE GAME OVER SCREEN)
rti
;****************************************************************
NMI:
jmp (ProgramModeNMI)
With this, you can JMP from mode to mode whenever necessary. For example, if during the main game logic you detect that the player was hit and he lost all of his lives, you can just
jmp GameOverInitialize to switch from the main game to the game over screen. The same goes for the transition between the title screen and the main game, only you JMP when start is pressed.
This is just one possible solution (the simplest one I can think of based on what I know from the Nerdy Nights tutorials), but there are literally hundreds of ways of doing this. When you get more acquainted with NES development you will understand the pros and cons of the various methods and you will certainly tweak the code to whatever works best for your game. You could for example use generic PPU update code for all program modes, and only then JMP to the different frame logic handlers, that could save some ROM space.
Re: Need some direction with multiple backgrounds
Posted: Wed Sep 16, 2015 2:50 pm
by Sogona
If you dont want the screen to scroll and just have a new screen be loaded when something exits the screen (Like in battle kid), it's actually very simple. Just have an address table of screens
Code: Select all
Screen0:
;nametable for the first screen goes here
Screen1:
;nametable for second screen goes here
Screens:
.dw Screen0,Screen1
Then have what's called a pointer, which is basically just a variable that takes up 2 bytes. The only restriction is that it needs to be in zero page.
Then have a variable that keeps track of which screen you're in, and when your player exits the boundaries of one screen, you can load the correct next screen based on where he or she left.
Note that in order to draw an entire new screen, the ppu needs to be turned off. This is as simple as writing #%00000110 to $2001, and seting a variable NMI_enabled to 0 (At the start of NMI, just RTI immediately if its at 0.)
Then this code will load the correct screen
Code: Select all
lda screen_number
asl ;multiply by 2 to use as an index to a table of words
tax
lda Screens,x ;get the LOW byte of the address of your nametable
sta screen_pointer
lda Screens+1,x ;get the HIGH byte of the nametable address
sta screen_pointer+1
;tell ppu its time to draw
lda $2002
lda #$20
sta $2006
lda #$00
sta $2006
ldx #$04 ;gotta copy 256 × 4 tiles
ldy #$00
@loop:
lda (screen_pointer),y ;This combines the 2 bytes in the pointer to make a new address. The value of whatever address that was is then loaded into the accumulator. (You have to use y)
sta $2007
iny
bne @loop
inc screen_pointer+1 ;load the next 256 bytes
dex
bne @loop
After that, just add your attributes, set NMI_enabled back to 1, turn on the PPU again by writing #%00011110 to $2001 (You should do this in the NMI), and you should be all set.
If you do want to do scrolling, I haven't tried to tackle that yet so I can't really help you there. But it's a LOT more complicated. Not trying to steer you the wrong way though.
I typed this all out on a phone, so I appologize if there are any typos.
Re: Need some direction with multiple backgrounds
Posted: Wed Sep 16, 2015 3:34 pm
by tokumaru
Yeah, when we talk about "multiple screens" we can actually mean 2 things... I described how to handle different types of screens, while Sogona explained how to have multiple screens of the same type. You'll often have to combine both things, by using pointers to load data for a particular screen in the initialization phase of a particular screen type. The details will obviously vary depending on the type of game you're making.
Re: Need some direction with multiple backgrounds
Posted: Fri Sep 18, 2015 12:27 pm
by log in
Thanks for all the replies guys.
I Came up with this idea ( that's not tested yet !!!! ) And i haven't read the last 2 posts !!
* Inesheader
* .rsset With the variables TITLESCREEN HI+LOW and MAINGAME HI+LOW
* bank 0 + reset
* vblank 1 / clrmem/ vblank 2
* load palettes + loop
^ regulair stuff except the 4 variables
LDA $2002
LDA #$20
STA $2006 ; write the high byte of $2000 address
LDA #$00
STA $2006 ; write the low byte of $2000 address
LDA #low(background)
STA TITLESCREENLow
LDA #high(background)
STA TITLESCREENHigh
LDX #$04 ; Loop X 4 times
LDY #$00 ; Loop Y 256 times
LoadBackgroundsLoop:
LDA [TITLESCREENLow],y
STA $2007
INY
BNE LoadBackgroundsLoop
; Outer loop
INC TITLESCREENHigh ; increment high byte of address backg to next 256 byte chunk
DEX ; one chunk done so X = X - 1.
BNE LoadBackgroundsLoop ; if X isn't zero, do again
LDA $2002 ; Reset Scroll
LDA #$00
STA $2005
STA $2005
STA $2006
STA $2006
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
LatchController:
LDA #$01
STA $4016
LDA #$00
STA $4016 ; tell both the controllers to latch buttons
ReadStartButton:
LDA $4016 ; player 1 - A
AND #%00000001 ; only look at bit 0
BEQ ReadStartButtonDone ; branch to ReadStatButtonDone if button is NOT pressed (0)
; add instructions here to do something when button IS pressed (1)
LDX #$00
STX $2000 ; disable NMI
STX $2001 ; disable rendering
JMP Maingame ; jumps to maingame
ReadStartButtonDone: ; handling this button is done
LDA #%10010000 ; enable NMI, sprites from Pattern Table 0, background from Pattern Table 1
STA $2000
LDA #%00011110 ; enable sprites, enable background, no clipping on left side
STA $2001
Forever:
JMP Forever ;jump back to Forever, infinite loop, waiting for NMI ;titlescreen will keep looping untill start is pressed !?
NMI:
LDA #$00
STA $2003 ; set the low byte (00) of the RAM address
LDA #$02
STA $4014 ; set the high byte (02) of the RAM address, start the transfer
MAINGAME:
LDA $2002
LDA #$20
STA $2006 ; write the high byte of $2000 address
LDA #$00
STA $2006 ; write the low byte of $2000 address
LDA #low(background)
STA MAINGAMELow
LDA #high(background)
STA MAINGAMEHigh
rest of the code .........
Re: Need some direction with multiple backgrounds
Posted: Fri Sep 18, 2015 12:43 pm
by dougeff
I see you're taking the "all in the NMI" approach, yet i see no button reads in the NMI, and so this will almost certainly never read the Start button.
Re: Need some direction with multiple backgrounds
Posted: Fri Sep 18, 2015 12:45 pm
by tokumaru
That will not work (EDIT: I'm talking about log in's code here), because you're reading the controller only once, during the initialization of the title screen. You need to read it every frame, either inside the forever loop or in the NMI handler (which fits the Nerdy Nights way better), places where the frame logic can be. Apparently you created different initialization steps for the different program modes (title screen, main game), but you didn't do anything about the logic or vblank handling (besides moving the title screen frame logic to its initialization code, which is wrong).
The vblank handling can be unified for all program modes if you want, if you use generic VRAM update lists and the like. The frame logic however is different for each mode (in the title screen, the logic is to read the controller and change modes once start is pressed, in the main game, the logic is to move the game objects, check for collisions, etc.), and you must run the appropriate logic block for the current program mode every frame. My suggestion was to set up a pointer to the respective frame logic code in the initialization code of each program mode, and JMP to the the address indicated by that pointer every frame, after the common PPU updates are done.
Re: Need some direction with multiple backgrounds
Posted: Fri Sep 18, 2015 2:01 pm
by dougeff
tokumaru, that second paragraph isn't 100% clear. Maybe you could write some pseudo-code to better demonstrate what you mean?
Re: Need some direction with multiple backgrounds
Posted: Fri Sep 18, 2015 2:16 pm
by tokumaru
dougeff wrote:tokumaru, that second paragraph isn't 100% clear. Maybe you could write some pseudo-code to better demonstrate what you mean?
Just to clarify, when I said "That will not work" I was referring to log in's post, not yours.

Sorry for not making that clear.
What I just described is exactly what's in the
code I posted before though. The Initialization code of each program mode sets up a pointer to the vblank handler and frame logic of that program mode, and the NMI handler simply JMPs to that address. Of course you could do some tasks that are common to all program modes before JMPing to the custom NMI code if you wanted to, like a generic vblank handler. In that case, the custom code would be just the frame logic.
Re: Need some direction with multiple backgrounds
Posted: Sat Sep 19, 2015 2:41 pm
by log in
Thanks again guys.
Seems like i got a couple of nmi methods mixed up.
So i'm going with the nerdy nights set up that looks like what tokumaru told.
variables :gamestate / backgound high+low
constands : statetitle / stateplaying /stategameover
;;; regulair setup
after the palettesloop
; get starting gamestate
LDA satetitle
STA gamestate
2000 and 2001 set up
JMP Forever
NMI
ppu cleanup 2000, 2001 and 2005
read controller
game engine:
Titlestate
code comes here
playingstate
code comes here
gameoverstate
code comes here
RTI
I think this will work and btw this is a rough code.
The only thing that i can't figure out is the background.
do i really have to start that backgroundloop every time ? And how does the code know what is background 1 and 2.
Another question i have is how many backgrounds can my code handle ? The rows of .db $24 that is.
Re: Need some direction with multiple backgrounds
Posted: Sat Sep 19, 2015 4:43 pm
by dougeff
how does the code know what is background 1 and 2.
You give each data set a label. The label translates to a starting address. You can then have a list of pointers to the background labels. Then you would have a variable that keeps track of the current game state (title, game, end). So you would index with this variable to the list of pointers, and write them to a zero page # and #+1, then you use that as an indirect index to the data set. (#), y.
The 'load background' routine itself would be a generic one that can load every background, and called when needed.
Re: Need some direction with multiple backgrounds
Posted: Sat Sep 19, 2015 10:21 pm
by tokumaru
Now you got the right idea.
log in wrote:do i really have to start that backgroundloop every time ?
Do you mean having multiple loops for drawing the background because you have different backgrounds? No, you can have a single subroutine to do this, and index the different backgrounds, like dougeff suggested. Then you can just JSR to this routine whenever you need, passing the index of the background you want to draw as a parameter.
And how does the code know what is background 1 and 2.
Indexing something means giving it a number. When we're talking about blocks of data that can be read, that means building a table containing the addresses of all the different blocks of data. Like this:
Code: Select all
BackgroundPointer:
.dw TitleScreenBackground ;0
.dw GameOverBackground ;1
.dw GameplayCaveBackground ;2
.dw GameplaySnowBackground ;3
.dw GameplayWaterBackground ;4
(...)
Then you can simply do this when you need to draw the cave background:
Code: Select all
lda #2 ;index of the cave background
jsr DrawBackground
The subroutine that draws backgrounds will then look something like this:
Code: Select all
DrawBackground:
asl ;multiply the index by 2 because each address is 2 bytes
tax
lda BackgroundPointers+0, x ;copy the pointer to zero page
sta TempPointer+0
lda BackgroundPointers+1, x
sta TempPointer+0
lda #$20 ;set the destination address
sta $2006
lda #$00
sta $2006
ldx #$04 ;copy 4 x 256 = 1024 bytes
ldy #$00
CopyByte:
lda (TempPointer), y
sta $2007
iny
bne CopyByte
inc TempPointer+1
dex
bne CopyByte
rti ;return
Another question i have is how many backgrounds can my code handle ? The rows of .db $24 that is.
A full NT + AT is 1024 bytes, or 1KB. If you store this data raw, without any compression, an NROM (mapper 0) cartridge, which has at most 32KB of PRG-ROM, would be able to store 32 screens, if you didn't have anything else in the ROM (which is obviously not the case). Yes, raw backgrounds need a lot of space, which is why people normally resort to compression.
To compress backgrounds, you have to create a tool (or use someone else's) to proccess the data and include the resulting binary file in your program, instead of using .db statements. Alternatively, you could use a simple compression format like RLE or meta tiles, that are simple enough to still be encoded by hand, even though that's a bit tedious, and keep using .db. Then you need to modify the DrawScreen function to decompress the data while copying it to VRAM.
Re: Need some direction with multiple backgrounds
Posted: Sun Sep 20, 2015 3:59 am
by dougeff
Double check your code, tokumaru.
One of those "sta TempPointer+0" looks wrong.
Re: Need some direction with multiple backgrounds
Posted: Sun Sep 20, 2015 6:07 am
by tokumaru
Oh, your right. I'll just pretend it was intentional... you know, to prevent mindless copying & pasting.
BTW, I like to use "+0" like this when working with 16-bit variables, even though it doesn't do anything, just to make it clear that it's a 2-byte variable. This is particularly helpful when you manipulate only the lower byte, for whatever reason.