Nerdy-Nights Week 5 (controller and sprites) Questions

Discuss technical or other issues relating to programming the Nintendo Entertainment System, Famicom, or compatible systems.

Moderator: Moderators

Post Reply
justin-rwx
Posts: 3
Joined: Sun Jan 10, 2021 1:12 pm

Nerdy-Nights Week 5 (controller and sprites) Questions

Post by justin-rwx » Sun Jan 10, 2021 2:01 pm

I'm slowly working my way through the Nerdy-Nights tutorial as I begin my journey on NES development and I had a few questions.

In the controller introduction of the tutorial, Nerdy-Nights suggests trying to edit the code to move all 4 sprites that are displayed on the screen. See the instructions highlighted below

Image

Currently I am brute-forcing the code by calling out all 4 sprites of Mario individually in the ReadLeft loop.

Code: Select all

ReadLeft:
  lda $4016			;player 1-left
  AND #%00000001  ; only look at bit 0
  BEQ ReadADone   ; branch to ReadADone if button is NOT pressed (0)
             		; add instructions here to do something when button IS pressed (1)
  lda $0203      	; load sprite X position
  sec             	; make sure the carry flag is true
  sbc #$01       	; A = A - 1
  sta $0203       	; save sprite X position
  lda $0207      	; load sprite X position
  sec             	; make sure the carry flag is true
  sbc #$01       	; A = A - 1
  sta $0207       	; save sprite X position
  lda $020b      	; load sprite X position
  sec             	; make sure the carry flag is true
  sbc #$01       	; A = A - 1
  sta $020b       	; save sprite X position
  lda $020f      	; load sprite X position
  sec             	; make sure the carry flag is true
  sbc #$01       	; A = A - 1
  sta $020f       	; save sprite X position
ReadLeftDone:        	; handling this button is done
These sprite X positions are stored as shown below.

Code: Select all

sprites:
     ;vert tile attr horiz
  .db $80, $32, $00, $80   ;sprite 0
  .db $80, $33, $00, $88   ;sprite 1
  .db $88, $34, $00, $80   ;sprite 2
  .db $88, $35, $00, $88   ;sprite 3
Is there a better way of moving all of Mario's tiles at once with a button? I do see the pattern that after the first horizontal position ($80), adding 4 to the memory location is the next horizontal position. Would some sort of 'for' loop make sense? I'm just imagining that this could get tedious if other sprites are made up of more than 4 tiles.

Another question regarding the tutorial, Week 5. I noticed that Nerdy-Nights puts the controller polling logic in the NMI portion of the code. After browsing the forums a bit, it's my understanding that NMI should only be used for updating graphics and that polling for controller logic should be handled elsewhere. I tried putting the controller polling logic into the Forever loop (as shown below), but when I run the program, Mario's tiles become scattered and just run scroll across the screen uncontrollably, and much faster, than they did when the logic was in the NMI. Why would the same controller logic in the Forever loop act differently than if it was in the NMI portion?

Code: Select all

Forever:
	;LatchController:
  	LDA #$01
  	STA $4016
  	LDA #$00
  	STA $4016       ; tell both the controllers to latch buttons

  	lda $4016 ;Read A
	lda $4016 ;Read B
	lda $4016 ;Read Select
	lda $4016; Read Start
	lda $4016 ;Read Up
	lda $4016; Read Down
    ReadLeft:
  	lda $4016			;player 1-left
  	AND #%00000001  ; only look at bit 0
  	BEQ ReadLeftDone   ; branch to ReadADone if button is NOT pressed (0)
             		; add instructions here to do something when button IS pressed (1)
  	lda $0203      	; load sprite X position
  	sec             	; make sure the carry flag is tru
  	sbc #$01       	; A = A - 1
  	sta $0203       	; save sprite X position
  	lda $0207      	; load sprite X position
  	sec             	; make sure the carry flag is tru
  	sbc #$01       	; A = A - 1
  	sta $0207       	; save sprite X position
  	lda $020b      	; load sprite X position
  	sec             	; make sure the carry flag is tru
  	sbc #$01       	; A = A - 1
  	sta $020b       	; save sprite X position
  	lda $020f      	; load sprite X position
  	sec             	; make sure the carry flag is tru
  	sbc #$01       	; A = A - 1
  	sta $020f       	; save sprite X position
    ReadLeftDone:        	; handling this button is done

    ReadRight: 
  	lda $4016       ; player 1 - right
  	AND #%00000001  ; only look at bit 0
  	BEQ ReadRightDone   ; branch to ReadBDone if button is NOT pressed (0)
                  ; add instructions here to do something when button IS pressed (1)
  	lda $0203      	; load sprite X position
  	clc             	; make sure the carry flag is clear
  	adc #$01       	; A = A + 1
  	sta $0203       	; save sprite X position
  	lda $0207      	; load sprite X position
  	clc             	; make sure the carry flag is clear
  	adc #$01       	; A = A + 1
  	sta $0207       	; save sprite X position
  	lda $020b      	; load sprite X position
  	clc             	; make sure the carry flag is clear
  	adc #$01       	; A = A + 1
  	sta $020b       	; save sprite X position
  	lda $020f      	; load sprite X position
  	clc             	; make sure the carry flag is clear
  	adc #$01       	; A = A + 1
  	sta $020f       	; save sprite X position
    ReadRightDone:        ; handling this button is done
JMP Forever     ;jump back to Forever, infinite loop

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
	RTI

User avatar
tokumaru
Posts: 11938
Joined: Sat Feb 12, 2005 9:43 pm
Location: Rio de Janeiro - Brazil

Re: Nerdy-Nights Week 5 (controller and sprites) Questions

Post by tokumaru » Sun Jan 10, 2021 2:59 pm

This is a very simplified version of how sprite movement works in a game. In an actual game, you don't normally move OAM entries directly, you instead control objects, and then you generate OAM entries for those objects, in a loop, like you mentioned.

An object is a collection of the attributes that describe a game entity. Each object has a position, a hit box, horizontal and vertical speeds, and so on. Instead of modifying the X and Y OAM fields directly, you could try creating two variables, PlayerX and PlayerY, and make the controller change those.

Then, once the position of the player is final, you can generate 4 OAM entries with coordinates relative to your player's position. Something like this:

Code: Select all

  ldy #$00 ;first byte of first sprite
  ldx #$00 ;output offset

WriteSprite:

  ;calculate Y position
  clc
  lda MarioStanding, y
  adc PlayerY
  sta $0200, x
  iny
  inx

  ;copy tile index
  lda MarioStanding, y
  sta $0200, x
  iny
  inx

  ;copy attributes
  lda MarioStanding, y
  sta $0200, x
  iny
  inx

  ;calculate X position
  clc
  lda MarioStanding, y
  adc PlayerX
  sta $0200, x
  iny
  inx

  ;test for the end
  cpx #$10
  bne WriteSprite
As for logic in the main loop, your problem is that you're lacking any form of timing control... As soon as your logic finishes, it runs again. In the NMI we have automatic timing control because the NMI is called once every frame, but if you're running logic in the main thread you have to count frames manually. One of the simplest ways to do this is to increment a counter in the NMI (e.g. INC FrameCounter), and then in the main thread you only advance to the next iteration of the game loop once this counter changes:

Code: Select all

  lda FrameCounter
WaitFrame:
  cmp FrameCounter
  beq WaitFrame
  jmp Forever

Pokun
Posts: 1692
Joined: Tue May 28, 2013 5:49 am
Location: Hokkaido, Japan

Re: Nerdy-Nights Week 5 (controller and sprites) Questions

Post by Pokun » Sun Jan 10, 2021 3:48 pm

Coding an object system is a bit too complicated this early on though, so I think moving OAM entries directly is perfectly fine for now. It works fine in Pong since it only has 3 objects anyway (2 paddles and 1 ball).

The technique you are looking for is called metaspriting. A metasprite is a larger "sprite" made up of several hardware sprites (the "objects" Tokumaru were talking about are also kind of metasprites but they may include game-mechanic parameters as well). Instead of moving all 4 sprites with the controller, you move only one of the sprites (I call this the "parent sprite"), and have the other three (I call them "child sprites") update their position to always be relative to the parent sprite (add the relevant sprite width/height to their positions so that they show up in the right place). This way you save a lot of code and simplifies the program.


The Nerdy Night tutorial is doing the NMI only approach like Super Mario Bros does. Once you are done with Nerdy Nights you might want to try the more common "NMI and main" approach where you have a main logic thread in the RESET handler (inside the "forever" loop) and a draw/sound thread in the NMI handler. There are a number of things that needs to be done differently in that case as Tokumaru explains. When you want to do that you should read this article.

User avatar
tokumaru
Posts: 11938
Joined: Sat Feb 12, 2005 9:43 pm
Location: Rio de Janeiro - Brazil

Re: Nerdy-Nights Week 5 (controller and sprites) Questions

Post by tokumaru » Sun Jan 10, 2021 10:51 pm

Pokun wrote:
Sun Jan 10, 2021 3:48 pm
Coding an object system is a bit too complicated this early on though
Definitely, which's why I only showed the very first step towards "objectifying" game entities, which is giving the player a position independent from its sprites. When you play Super Mario Bros. you're not controlling Mario's sprites, you're controlling Mario himself, and the game does you the "favor" of drawing a visual representation of him (along with all the other characters) on the screen so you know where he is what he's doing, otherwise you wouldn't be able to play the game.

Mario has coordinates indicating where he is in the level, a bounding box indicating how much space he occupies, and a bunch of other properties necessary for him to exist in his world and do the things he does in response to the player's input, all in RAM and completely independent from his graphics. None of those other properties are important right now, I agree, but by implementing just the position of your game entity in RAM, independently from any sprites, you'll already be miles closer to how a real game works in comparison to awkwardly manipulating a meaningless lump of sprites to move together.
The Nerdy Night tutorial is doing the NMI only approach like Super Mario Bros does.
They do it the wrong way though, with the game logic taking place before the video updates, which means that the entire program has to run during vblank in order to work. This is one of the biggest flaws of Nerdy Nights IMO... I'm not sure if they fix this later on, but starting out with this program structure is a very good way to put newbies on the wrong path.

Keeping everything in the NMI handler is indeed a valid approach to structuring NES programs (which has its own set of pitfalls, of course), but it must be done the right way.

Post Reply