Background and Sprite problems

This is an archive of a topic from NESdev BBS, taken in mid-October 2019 before a server upgrade.
View original topic
Background and Sprite problems
by on (#41513)
I just started trying to write code in NES assembly, and I have run into a problem. My problem is that instead of just drawing the background image, the rom also draws a sprite as part of the background. And when I try to switch to a different background map, it just erases the background tiles and redraws this mysterious sprite in a different color. So what I when I'm changing the background map, it is really changing something about the sprites. My actual sprite that I want to move around the screen still looks and works fine no matter what the background looks like, but I can't seem to get the background right. I used a program called map2bin.exe to create the background map file from a text file. I used Tile Layer Pro to create the sprite .spr file. I believe I correctly set the Sprite Pattern Table to $1000 and the Background Pattern Table to $0000, but they seem to be interfering with each other somehow. Any help would be greatly appeciated. I'd be glad to offer my code if anyone thinks it might help.
Re: Background and Sprite problems
by on (#41514)
I'm not sure I understand the problem you're having from your description. Could you post screenshots or something? Or maybe upload the ROM so we can see for ourselves?

My first thought was to check the pattern table assignment (set BG to use $0xxx and sprites to use $1xxx), but since you say you're doing that, I don't have any other ideas as to what could be wrong.

Source code would help. If you don't want to upload the full source code (which would be easiest for me/us), you can paste here smaller sections -- specifically stuff related to $2000/$2001 register writes.

by on (#41517)
.inesprg 1 ; /////INES Header/////
.ineschr 1
.inesmap 0
.inesmir 1

; ////////////////////////Bank 1: Interrupt Table/////////////////////////////

.bank 1
.org $FFFA
.dw VBlank_Routine ; address to execute on VBlank
.dw Start
.dw 0

; ////////////////////////Bank 0: Program Code////////////////////////////////

.bank 0
; /////Sprite DMA and other Variables/////

.org $0300 ; OAM copy location

Sprite1_Y: .db 0 ; sprite #1's Y value
Sprite1_T: .db 0 ; sprite #1's tile number
Sprite1_S: .db 0 ; sprite #1's special byte
Sprite1_X: .db 0 ; sprite #1's X value
VBlankOrNo: .db 0
BKGset: .db 0

.org $8000 ; program code location

VBlank_Routine: ; /////VBlank Routine/////
inc VBlankOrNo ; add 1 to VBlankOrNo (1 if VBlank, 0 if not)
rti ; return from interrupt

Start: ; /////Screen Setup/////

lda #50
sta Sprite1_Y
sta Sprite1_X
lda #2
sta Sprite1_S

lda #%10001000 ; screen pattern table = $0000, sprite pattern table = $1000, name table = $2000
sta $2000
lda #%00011110 ; BG = black, show sprites, screen on, don't clip anything, color display
sta $2001

; /////Palette/////

lda #$3F ; store the value $3F00 in location $2006 so that whatever
sta $2006 ; we load into $2007 will start at location $3F00
lda #$00
sta $2006

ldx #$00 ; reset X to zero
loadpal: ; load 32-color pallete
lda tilepal, x
sta $2007
inx
cpx #32
bne loadpal

; /////Background/////

lda #$20
sta $2006 ; give $2006 both parts of address $2020.
sta $2006

ldx #$00 ; reset X to zero
loadNames: ; load name table
lda ourMap, x ; load A with a byte from address (ourMap + X)
inx ; increase X by 1
sta $2007 ; store map byte from A into $2007
cpx #64 ; check if last byte = 64th
bne loadNames ; if not all 64 done, loop

; ------------------------Controller Loop Begin-------------------------------

infinite:

WaitForVBlank: ; /////VBlank/////
lda VBlankOrNo
cmp #1 ; check if A = 1 (VBlank)
bne WaitForVBlank ; if A = 0 loop
dec VBlankOrNo ; decrease VBlankOrNo by 1 (A = 0)

lda #3 ; /////Sprite Info Load/////
sta $4014

; /////Controller Setup/////

lda #$01 ; reset pad
sta $4016
lda #$00
sta $4016

; /////Test Button Status/////

lda $4016 ; read A status
and #1
bne AKEYdown
lda $4016 ; read B status
lda $4016 ; read SELECT status
lda $4016 ; read START status
lda $4016 ; read UP status
and #1 ; check if UP is pressed
bne UPKEYdown ; branch to UPKEYdown if pressed

lda $4016 ; read DOWN status
and #1 ; check if DOWN is pressed
bne DOWNKEYdown ; branch to DOWNKEYdown if pressed

lda $4016 ; read LEFT status
and #1 ; check if LEFT is pressed
bne LEFTKEYdown ; branch to LEFTKEYdown if pressed

lda $4016 ; read RIGHT status
and #1 ; check if RIGHT is pressed
bne RIGHTKEYdown ; branch to RIGHTKEYdown if pressed
jmp NOTHINGdown

; /////Button Press Actions/////

AKEYdown:
lda BKGset
cmp #0
bne BG1
lda #$20
sta $2006 ; give $2006 both parts of address $2020.
sta $2006

ldx #$00 ; reset X to zero
loadNames2: ; load name table
lda ourMap2, x ; load A with a byte from address (ourMap + X)
inx ; increase X by 1
sta $2007 ; store map byte from A into $2007
cpx #64 ; check if last byte = 64th
bne loadNames2 ; if not all 64 done, loop
lda #1
sta BKGset
jmp NOTHINGdown
BG1:
lda #$20
sta $2006 ; give $2006 both parts of address $2020.
sta $2006

ldx #$00 ; reset X to zero
loadNames3: ; load name table
lda ourMap, x ; load A with a byte from address (ourMap + X)
inx ; increase X by 1
sta $2007 ; store map byte from A into $2007
cpx #64 ; check if last byte = 64th
bne loadNames3 ; if not all 64 done, loop
lda #0
sta BKGset
jmp NOTHINGdown

UPKEYdown: ; code executed when UP is pressed
lda Sprite1_Y
clc
sbc #1 ; subtract 1 from Sprite1_Y
sta Sprite1_Y
jmp NOTHINGdown

DOWNKEYdown: ; code executed when DOWN is pressed
lda Sprite1_Y
clc
adc #1 ; add 1 to Sprite1_Y
sta Sprite1_Y
jmp NOTHINGdown

LEFTKEYdown: ; code executed when LEFT is pressed

lda Sprite1_S
and #%10111111
sta Sprite1_S
lda Sprite1_X
clc
sbc #1 ; subtract 1 from Sprite1_X
sta Sprite1_X
jmp NOTHINGdown

RIGHTKEYdown: ; code executed when RIGHT is pressed

lda Sprite1_S
ora #%01000000
sta Sprite1_S
lda Sprite1_X
clc
adc #1 ; add 1 to Sprite1_X
sta Sprite1_X

NOTHINGdown:
jmp infinite

; ------------------------Controller Loop End---------------------------------

; /////Include Binary Files/////

tilepal: .incbin "our.pal" ; include pallete and label its location "tilepal"
ourMap: .incbin "bkg12.map" ; include map files and label its location "ourMap"
ourMap2: .incbin "bkg21.map"

; ////////////////////////Bank 2: Sprite/Background Data//////////////////////

.bank 2
.org $0000
.incbin "our.bkg"

.org $1000
.incbin "our.spr"

by on (#41520)
I can tell you one problem right away. You're writing to PPU data with the screen turned on. Cut and paste the first writes to $2000 and $2001 before "Infinite" and where the writes previously were, put lda #$00 sta $2000 sta $2001.

by on (#41525)
I replaced the code you suggested with

lda #$00
sta $2000
sta $2001

and I pasted the other code just after "infinite":

lda #%10001000
sta $2000
lda #%00011110
sta $2001

next comes the WaitForVBlank bit

The rom still runs, but this didn't change anything about my problem. Any further help would be greatly appreciated.

by on (#41527)
Still don't really understand the problem you're having -- clarification and/or screenshots please.

In the meantime here are some other misc problems with your code. Some of them might be the cause of your problem -- but like I say I'm still not sure I understand your problem so I don't know:

1) You need to zero your variables. ".db 0" may reserve a space for them in memory but it does not set them to zero initially. Sprite1_T and VBlankOrNo might not be zero when you run your program which could be problematic.

1.5) In the same vein as #1, you're not clearing the sprite page ($03xx). So you'll have garbage sprites appearing onscreen. Fill the sprite page with $FF -- this will give unused sprites a Y coord which puts them offscreen, making it so they won't be rendered.

2) You're not waiting for the PPU to warm up. Do this when the program starts:

Code:
LDA #0
STA $2000
STA $2001  ; turn off PPU

wait1:
  LDA $2002   ; these following two loops ensure that at least 1 full
  BPL wait1    ; frame passes -- enough time for the PPU to warm up

wait2:
  LDA $2002
  BPL wait2


3) You should leave the PPU off (write 0 to both $2000 and $2001) until you finish drawing the palettes and background. Don't enable NMIs until you're ready for them to happen, and don't turn on the PPU until you're ready for it to be on (everything you want drawn is drawn). Personally I would leave the PPU off until after your first NMI so that you enable it during VBlank. Enabling it during the middle of a frame could result in the frame looking fugly.

4) You're not resetting the scroll. Write to $2000 once (to set the low two bits) and to $2005 twice in VBlank, after you finish your drawing (but since you're not doing any drawing in VBlank -- you're fine to do it just after the sprite DMA (the $4014 write) -- just before the controller stuff)

by on (#41531)
Don't put

lda #%10001000
sta $2000
lda #%00011110
sta $2001

right after "infinite", put it before. If it's after infinite, it'll infinitely write to $2000/$2001, which might be bad (I'm not exactly sure, but I would avoid infinitely writing to $2000/$2001).

by on (#41532)
Ok. I tried to fix the problems you pointed out, but I only think I understand how to deal with the 1, 1.5, and 2. I'm not sure how to alter my code to reflect your suggestions for 3 and 4. My problem is the same. I will include some pics in another post. Here is my updated code:


.inesprg 1 ; /////INES Header/////
.ineschr 1
.inesmap 0
.inesmir 1

; ////////////////////////Bank 1: Interrupt Table/////////////////////////////

.bank 1
.org $FFFA
.dw VBlank_Routine ; address to execute on VBlank
.dw Start
.dw 0

; ////////////////////////Bank 0: Program Code////////////////////////////////

.bank 0
.org $0000

VBlankOrNo: .db 0
BKGset: .db 0
; /////Sprite DMA and other Variables/////

.org $0300 ; OAM copy location

Sprite1_Y: .db 0 ; sprite #1's Y value
Sprite1_T: .db 0 ; sprite #1's tile number
Sprite1_S: .db 0 ; sprite #1's special byte
Sprite1_X: .db 0 ; sprite #1's X value
UnusedSprites: .db 0

lda #0
sta Sprite1_Y
sta Sprite1_T
sta Sprite1_S
sta Sprite1_X
sta VBlankOrNo
sta BKGset
sta UnusedSprites

lda #$FF
ldx #0
ClearSprites:
sta UnusedSprites, x
inx
cpx #252
bne ClearSprites

.org $8000 ; program code location

lda #0
sta $2000
sta $2001 ; turn the PPU off

wait1:
lda $2002
bpl wait1

wait2:
lda $2002
bpl wait2

VBlank_Routine: ; /////VBlank Routine/////
inc VBlankOrNo ; add 1 to VBlankOrNo (1 if VBlank, 0 if not)
rti ; return from interrupt

Start: ; /////Screen Setup/////

lda #50
sta Sprite1_Y
sta Sprite1_X
lda #2
sta Sprite1_S

; /////Palette/////

lda #$3F ; store the value $3F00 in location $2006 so that whatever
sta $2006 ; we load into $2007 will start at location $3F00
lda #$00
sta $2006

ldx #$00 ; reset X to zero
loadpal: ; load 32-color pallete
lda tilepal, x
sta $2007
inx
cpx #32
bne loadpal

; /////Background/////

lda #$20
sta $2006 ; give $2006 both parts of address $2020.
sta $2006

ldx #$00 ; reset X to zero
loadNames: ; load name table
lda ourMap, x ; load A with a byte from address (ourMap + X)
inx ; increase X by 1
sta $2007 ; store map byte from A into $2007
cpx #64 ; check if last byte = 64th
bne loadNames ; if not all 64 done, loop

; ------------------------Controller Loop Begin-------------------------------

infinite:

lda #%10001000 ; screen pattern table = $0000, sprite pattern table = $1000, name table = $2000
sta $2000
lda #%00011110 ; BG = black, show sprites, screen on, don't clip anything, color display
sta $2001

WaitForVBlank: ; /////VBlank/////
lda VBlankOrNo
cmp #1 ; check if A = 1 (VBlank)
bne WaitForVBlank ; if A = 0 loop
dec VBlankOrNo ; decrease VBlankOrNo by 1 (A = 0)




lda #3 ; /////Sprite Info Load/////
sta $4014

; /////Controller Setup/////

lda #$01 ; reset pad
sta $4016
lda #$00
sta $4016

; /////Test Button Status/////

lda $4016 ; read A status
and #1
bne AKEYdown
lda $4016 ; read B status
lda $4016 ; read SELECT status
lda $4016 ; read START status
lda $4016 ; read UP status
and #1 ; check if UP is pressed
bne UPKEYdown ; branch to UPKEYdown if pressed

lda $4016 ; read DOWN status
and #1 ; check if DOWN is pressed
bne DOWNKEYdown ; branch to DOWNKEYdown if pressed

lda $4016 ; read LEFT status
and #1 ; check if LEFT is pressed
bne LEFTKEYdown ; branch to LEFTKEYdown if pressed

lda $4016 ; read RIGHT status
and #1 ; check if RIGHT is pressed
bne RIGHTKEYdown ; branch to RIGHTKEYdown if pressed
jmp NOTHINGdown

; /////Button Press Actions/////

AKEYdown:
lda BKGset
cmp #0
bne BG1
lda #$20
sta $2006 ; give $2006 both parts of address $2020.
sta $2006

ldx #$00 ; reset X to zero
loadNames2: ; load name table
lda ourMap2, x ; load A with a byte from address (ourMap + X)
inx ; increase X by 1
sta $2007 ; store map byte from A into $2007
cpx #64 ; check if last byte = 64th
bne loadNames2 ; if not all 64 done, loop
lda #1
sta BKGset
jmp NOTHINGdown
BG1:
lda #$20
sta $2006 ; give $2006 both parts of address $2020.
sta $2006

ldx #$00 ; reset X to zero
loadNames3: ; load name table
lda ourMap, x ; load A with a byte from address (ourMap + X)
inx ; increase X by 1
sta $2007 ; store map byte from A into $2007
cpx #64 ; check if last byte = 64th
bne loadNames3 ; if not all 64 done, loop
lda #0
sta BKGset
jmp NOTHINGdown

UPKEYdown: ; code executed when UP is pressed
lda Sprite1_Y
clc
sbc #1 ; subtract 1 from Sprite1_Y
sta Sprite1_Y
jmp NOTHINGdown

DOWNKEYdown: ; code executed when DOWN is pressed
lda Sprite1_Y
clc
adc #1 ; add 1 to Sprite1_Y
sta Sprite1_Y
jmp NOTHINGdown

LEFTKEYdown: ; code executed when LEFT is pressed

lda Sprite1_S
and #%10111111
sta Sprite1_S
lda Sprite1_X
clc
sbc #1 ; subtract 1 from Sprite1_X
sta Sprite1_X
jmp NOTHINGdown

RIGHTKEYdown: ; code executed when RIGHT is pressed

lda Sprite1_S
ora #%01000000
sta Sprite1_S
lda Sprite1_X
clc
adc #1 ; add 1 to Sprite1_X
sta Sprite1_X

NOTHINGdown:
jmp infinite

; ------------------------Controller Loop End---------------------------------

; /////Include Binary Files/////

tilepal: .incbin "our.pal" ; include pallete and label its location "tilepal"
ourMap: .incbin "bkg12.map" ; include map files and label its location "ourMap"
ourMap2: .incbin "bkg21.map"

; ////////////////////////Bank 2: Sprite/Background Data//////////////////////

.bank 2
.org $0000
.incbin "our.bkg"

.org $1000
.incbin "our.spr"

by on (#41533)
This is a screenshot off my rom just as it loads. You can see the red eyes that are on top of the two background tiles at the top. This is where a sprite is being drawn that I don't want (it's just a white and red version of the blue sprite):

Image

This shows that I can move the sprite around with the directional keys, which is what I want.

Image

This shows what happens when I press A. I wanted it to redraw the same two background tiles a few rows down (I made another background map for this, bkg21.map. bkg12.map is the map for the initial tile placement). Instead of moving the tiles, it erases them and as you can see, just the unwanted sprite remains when I press A.

Image

by on (#41536)
Unwanted sprites at the top left corner are often the result of not clearing OAM (clearing the sprite page, like Disch said). Emulators usually set memory to 0, meaning that all sprites you do not touch will use tile 0 and be placed at coordinates (0, 0), which is the top left corner of the screen.

by on (#41538)
My Dad suggested that I move the code I've been messing with after "Start:" so i did that and now the unwanted sprite doesn't appear. I still don't get the effect I'm looking for though (moving background tiles instead of disappearing background tiles).

by on (#41539)
I can tell the problem right away, you should fit the OAM page with $f0 value to disable all sprites, and then write the sprite you want to show.
The best/more verstatile habbit is to empty that page each frame and have a routine that fills it as you want to (as we just mentionned stuff like that in another thread). At first it will seem crazy, but it's better not relying on any value inside the OAM for your calculations.

by on (#41542)
A couple things that will cause catastrophe; change:

Code:
.org $0300 ; OAM copy location

Sprite1_Y: .db 0 ; sprite #1's Y value
Sprite1_T: .db 0 ; sprite #1's tile number
Sprite1_S: .db 0 ; sprite #1's special byte
Sprite1_X: .db 0 ; sprite #1's X value
UnusedSprites: .db 0

lda #0
sta Sprite1_Y
sta Sprite1_T
sta Sprite1_S
sta Sprite1_X
sta VBlankOrNo
sta BKGset
sta UnusedSprites

lda #$FF
ldx #0
ClearSprites:
sta UnusedSprites, x
inx
cpx #252
bne ClearSprites

.org $8000 ; program code location


To

Code:
.org $0300 ; OAM copy location

Sprite1_Y: .db 0 ; sprite #1's Y value
Sprite1_T: .db 0 ; sprite #1's tile number
Sprite1_S: .db 0 ; sprite #1's special byte
Sprite1_X: .db 0 ; sprite #1's X value
UnusedSprites: .db 0


.org $8000 ; program code location
Start:
sei ;Just some stuff that's standard to do at reset.
cld
ldx #$FF
txs

lda #0
sta Sprite1_Y
sta Sprite1_T
sta Sprite1_S
sta Sprite1_X
sta VBlankOrNo
sta BKGset
sta UnusedSprites

lda #$FF
ldx #0
ClearSprites:
sta UnusedSprites, x
inx
cpx #252
bne ClearSprites



Also, this:

Code:
wait1:
lda $2002
bpl wait1

wait2:
lda $2002
bpl wait2

VBlank_Routine: ; /////VBlank Routine/////
inc VBlankOrNo ; add 1 to VBlankOrNo (1 if VBlank, 0 if not)
rti ; return from interrupt


CRASH. In your Reset routine, you'll execute an RTI, which is not good. At all. Move "Vblank_Routine" to a place where it will never be executed unless there is an NMI executed. So like right here:

Code:
NOTHINGdown:
jmp infinite


VBlank_Routine: ; /////VBlank Routine/////
inc VBlankOrNo ; add 1 to VBlankOrNo (1 if VBlank, 0 if not)
rti ; return from interrupt


would be just fine. Oh, and change this:

Code:
infinite:

lda #%10001000 ; screen pattern table = $0000, sprite pattern table = $1000, name table = $2000
sta $2000
lda #%00011110 ; BG = black, show sprites, screen on, don't clip anything, color display
sta $2001


To

Code:

lda #%10001000 ; screen pattern table = $0000, sprite pattern table = $1000, name table = $2000
sta $2000
lda #%00011110 ; BG = black, show sprites, screen on, don't clip anything, color display
sta $2001

infinite:


I don't think there's any need to do write to $2000/$2001 constantly because the value never changes.

by on (#41596)
This helped a lot with my problems. Thanks :D