Skip navigation
NintendoAge
Welcome, Guest! Please Login or Join
Loading...

Nerdy Nights week 5 multiple sprites, reading controllers, more instructions

May 8, 2008 at 3:02:03 AM
bunnyboy (81)
avatar
(Funktastic B) < Master Higgins >
Posts: 7704 - Joined: 02/28/2007
California
Profile
Previous Week - Palettes, Sprites


This Week:  one sprite is boring, so now we add many more!  Also move that sprite around using the controller.

Multiple Sprites

Last time there was only 1 sprite loaded so we just used a few LDA/STA pairs to load the sprite data.  This time we will have 4 sprites on screen.  Doing that many load/stores just takes too much writing and code space.  Instead a loop will be used to load the data, like was used to load the palette before.  First the data bytes are set up using the .db directive:

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


There are 4 bytes per sprite, each on one line.  The bytes are in the correct order and easily changed.    This is only the starting data, when the program is running the copy in RAM can be changed to move the sprite around.

Next you need the loop to copy the data into RAM.  This loop also works the same way as the palette loading, with the X register as the loop counter.

LoadSprites:

  LDX #$00              ; start at 0

LoadSpritesLoop:

  LDA sprites, x        ; load data from address (sprites + x)

  STA $0200, x          ; store into RAM address ($0200 + x)

  INX                   ; X = X + 1

  CPX #$10              ; Compare X to hex $10, decimal 16

  BNE LoadSpritesLoop   ; Branch to LoadSpritesLoop if compare was Not Equal to zero

                        ; if compare was equal to 16, continue down


If you wanted to add more sprites, you would add lines into the sprite .db section then increase the CPX compare value.  That will run the loop more times, copying more bytes.

Once the sprites have been loaded into RAM, you can modify the data there.  





Controller Ports

The controllers are accessed through memory port addresses $4016 and $4017.  First you have to write the value $01 then the value $00 to port $4016.  This tells the controllers to latch the current button positions.  Then you read from $4016 for first player or $4017 for second player.  The buttons are sent  one at a time, in bit 0.  If bit 0 is 0, the button is not pressed.  If bit 0 is 1, the button is pressed.

Button status for each controller is returned in the following order: A, B, Select, Start, Up, Down, Left, Right.

  LDA #$01

  STA $4016

  LDA #$00

  STA $4016     ; tell both the controllers to latch buttons


  LDA $4016     ; player 1 - A

  LDA $4016     ; player 1 - B

  LDA $4016     ; player 1 - Select

  LDA $4016     ; player 1 - Start

  LDA $4016     ; player 1 - Up

  LDA $4016     ; player 1 - Down

  LDA $4016     ; player 1 - Left

  LDA $4016     ; player 1 - Right


  LDA $4017     ; player 2 - A

  LDA $4017     ; player 2 - B

  LDA $4017     ; player 2 - Select

  LDA $4017     ; player 2 - Start

  LDA $4017     ; player 2 - Up

  LDA $4017     ; player 2 - Down

  LDA $4017     ; player 2 - Left

  LDA $4017     ; player 2 - Right



AND Instruction

Button information is only sent in bit 0, so we want to erase all the other bits.  This can be done with the AND instruction.  Each of the 8 bits is ANDed with the bits from another value.  If the bit from both the first AND second value is 1, then the result is 1.  Otherwise the result is 0.

0 AND 0 = 0

0 AND 1 = 0

1 AND 0 = 0

1 AND 1 = 1


For a full random 8 bit value:

      01011011

AND   10101101

--------------

      00001001



We only want bit 0, so that bit is set and the others are cleared:

      01011011    controller data

AND   00000001    AND value

--------------

      00000001    only bit 0 is used, everything else erased



So to erase all the other bits when reading controllers, the AND should come after each read from $4016 or $4017:

  LDA $4016       ; player 1 - A

  AND #%00000001


  LDA $4016       ; player 1 - B

  AND #%00000001


  LDA $4016       ; player 1 - Select

  AND #%00000001



BEQ instruction

The BNE instruction was used earlier in loops to Branch when Not Equal to a compared value.  Here BEQ will be used without the compare instruction to Branch when EQual to zero.  When a button is not pressed, the value will be zero, so the branch is taken.  That skips over all the instructions that do something when the button is pressed:

ReadA: 

  LDA $4016       ; player 1 - A

  AND #%00000001  ; erase everything but bit 0

  BEQ ReadADone   ; branch to ReadADone if button is NOT pressed (0)


                  ; add instructions here to do something when button IS pressed (1)


ReadADone:        ; handling this button is done



CLC/ADC instructions

For this demo we will use the player 1 controller to move the Mario sprite around.  To do that we need to be able to add to values.  The ADC instruction stands for Add with Carry.  Before adding, you have to make sure the carry is cleared, using CLC.  This sample will load the sprite position into A, clear the carry, add one to the value, then store back into the sprite position:

  LDA $0203   ; load sprite X (horizontal) position

  CLC         ; make sure the carry flag is clear

  ADC #$01    ; A = A + 1

  STA $0203   ; save sprite X (horizontal) position


SEC/SBC instructions

To move the sprite the other direction, a subtract is needed.  SBC is Subtract with Carry.  This time the carry has to be set before doing the subtract:

  LDA $0203   ; load sprite position

  SEC         ; make sure carry flag is set

  SBC #$01    ; A = A - 1

  STA $0203   ; save sprite position



Putting It All Together

Download and unzip the controller.zip sample files.  All the code above is in the controller.asm file.  Make sure that file, mario.chr, and controller.bat is in the same folder as NESASM, then double click on controller.bat.  That will run NESASM and should produce controller.nes.  Run that NES file in FCEUXD SP to see small Mario.  Press the A and B buttons on the player 1 controller to move one sprite of Mario.  The movement will be one pixel per frame, or 60 pixels per second on NTSC machines.  If Mario isn't moving, make sure your controls are set up correctly in the Config menu under Input...  If you hold both buttons together, the value will be added then subtracted so no movement will happen.

Try editing the ADC and SBC values to make him move faster.  The screen is only 256 pixels across, so too fast and he will just jump around randomly!  Also try editing the code to move all 4 sprites together.

Finally try changing the code to use the dpad instead of the A and B buttons.  Left/right should change the X position of the sprites, and up/down should change the Y position of the sprites.


NEXT WEEK:  Backgrounds, attribute table




Edited: 05/17/2008 at 04:09 PM by bunnyboy

May 17, 2008 at 5:40:09 AM
KennyB (14)
avatar
(No Go ogle result) < Eggplant Wizard >
Posts: 466 - Joined: 09/06/2006
Belgium
Profile
need some help:

1) I can make the sprite move to left and right (with the left and right buttons),
but going to the left goes twice as fast.
For going left I used "SBC #$01" and for going right I used "ADC #$01"
If I change ADC to ADC #$02, the speeds are equal.
EDIT: Same problem for goin up and down. Up goes twice as fast as down.
Anyone got an idea what I'm doing wrong ?

2) And to move up, I need to subtract ? (and vica versa) Is this right ?

3) Can someone give me some extra info about including the 3 other sprites ? Can't seem to find it.

-------------------------
 


Edited: 05/17/2008 at 06:05 AM by KennyB

May 17, 2008 at 6:13:17 AM
bunnyboy (81)
avatar
(Funktastic B) < Master Higgins >
Posts: 7704 - Joined: 02/28/2007
California
Profile
Thats where the CLC and SEC instructions right before the add and subtract come in.

ADC stands for ADd with Carry. It is actually doing A + value + carry. Before doing the add you CLear Carry (CLC) so it does A + value + 0.

SBC is SuBtract with Carry, A - value - opposite of carry. Before doing the subtract you SEt Carry (SEC) to 1. Opposite of carry will now be 0, so it does A - value - 0.

When going left you are probably doing CLC before the SBC. That clears the carry, so the subtract is doing A - value - 1. Thats where the extra speed comes in because you end up subtracting 2 instead of 1.

May 17, 2008 at 6:49:18 AM
KennyB (14)
avatar
(No Go ogle result) < Eggplant Wizard >
Posts: 466 - Joined: 09/06/2006
Belgium
Profile
Originally posted by: bunnyboy

Thats where the CLC and SEC instructions right before the add and subtract come in.

ADC stands for ADd with Carry. It is actually doing A + value + carry. Before doing the add you CLear Carry (CLC) so it does A + value + 0.

SBC is SuBtract with Carry, A - value - opposite of carry. Before doing the subtract you SEt Carry (SEC) to 1. Opposite of carry will now be 0, so it does A - value - 0.

When going left you are probably doing CLC before the SBC. That clears the carry, so the subtract is doing A - value - 1. Thats where the extra speed comes in because you end up subtracting 2 instead of 1.


That solved my speed problem, thx !

But I still have the opposite direction problem. If I press up, it goes down.
Here is my up and down code. Is there something wrong with it ?



code 

ReadUp:
  LDA $4016    
  AND #%00000001
  BEQ ReadUpDone
  LDA $0200      
  CLC            
  ADC #$01  
  STA $0200 
ReadUpDone:

ReadDown:
  LDA $4016      
  AND #%00000001 
  BEQ ReadDownDone 
  LDA $0200
  SEC            
  SBC #$01    
  STA $0200   

ReadDownDone:      



-------------------------
 

May 17, 2008 at 8:33:47 AM
Zzap (47)
avatar
(James ) < King Solomon >
Posts: 3301 - Joined: 05/01/2007
Australia
Profile
Need to reverse up and down. Add to go down, subtract to go up. 0,0 is the top left corner of the screen.

-------------------------

Chunkout for iPhone, iPad and iTouch out now!
Chunkout Games: FaceBook | Web

May 17, 2008 at 10:26:44 AM
KennyB (14)
avatar
(No Go ogle result) < Eggplant Wizard >
Posts: 466 - Joined: 09/06/2006
Belgium
Profile
Originally posted by: Zzap

Need to reverse up and down. Add to go down, subtract to go up. 0,0 is the top left corner of the screen.



Thx, got it now (actually, I had it already but now I know the reason why it shoud be reversed)

-------------------------
 

May 17, 2008 at 10:56:40 AM
Zzap (47)
avatar
(James ) < King Solomon >
Posts: 3301 - Joined: 05/01/2007
Australia
Profile
Easily done, I was doing some calculations with some angles the other day and it looked great on paper until I coded it and everything was heading in the wrong directions. Turned out to be the same problem, I had my Y axis around the wrong way

-------------------------

Chunkout for iPhone, iPad and iTouch out now!
Chunkout Games: FaceBook | Web

Jun 30, 2008 at 9:56:33 PM
Kizul Emeraldfire (0)
avatar
(Kizul Emeraldfire) < Tourian Tourist >
Posts: 42 - Joined: 06/30/2008
United States
Profile
I have a different problem; I can get the whole sprite to move around left/right/up/down, but I can't figure out how to map the movement to buttons other than A and B. >.>

Anyone have any hints/suggestions? xD;

-------------------------
 

Jun 30, 2008 at 11:01:13 PM
Zzap (47)
avatar
(James ) < King Solomon >
Posts: 3301 - Joined: 05/01/2007
Australia
Profile
The buttons are determined in the set order you read from $4016. Each time you read from $4016 after latching the buttons, it will be for a different button. See below and add your own updates for the different buttons.

LDA $4016 ; player 1 - A
AND #%00000001
BNE NotA

; do stuff for A button

NotA:
LDA $4016 ; player 1 - B
AND #%00000001
BNE NotB

; Do stuff for B button

NotB:
LDA $4016 ; player 1 - Select
AND #%00000001
BNE NotSelect

; Do stuff for Select button

NotSelect:
LDA $4016 ; player 1 - Start
AND #%00000001
BNE NotStart

; Stuff for Start button

NotStart:
LDA $4016 ; player 1 - Up
AND #%00000001
BNE NotUp

; Stuff for Up button

NotUp:
LDA $4016 ; player 1 - Down
AND #%00000001
BNE NotDown

; Stuff for Down button

NotDown:
LDA $4016 ; player 1 - Left
AND #%00000001
BNE NotLeft

; Stuff for Left button

NotLeft:
LDA $4016 ; player 1 - Right
AND #%00000001
BNE NotRight

; Stuff for Right button

NotRight:


-------------------------

Chunkout for iPhone, iPad and iTouch out now!
Chunkout Games: FaceBook | Web

Jul 1, 2008 at 2:07:12 AM
Kizul Emeraldfire (0)
avatar
(Kizul Emeraldfire) < Tourian Tourist >
Posts: 42 - Joined: 06/30/2008
United States
Profile
Yay, it worked! Thanks, Zzap!

-------------------------
 

Jul 16, 2008 at 7:44:08 AM
dutch (0)
This user has been banned -- click for more information.
(bert n) < Meka Chicken >
Posts: 589 - Joined: 10/02/2007
Netherlands
Profile
I have a really strange problem!
If i open the controller file in the fceuxd sp i see the small mario BUT.. it wont move.
If i open the controller file in another emulator jnes ,i see mario Bigger then in the fceuxd and it moves BUT... i have to use the d button for moving left and the f button for moving right??

-------------------------

LOOKING FOR GARAGE CART,pm me with buy or trading offer


Jul 17, 2008 at 4:30:43 AM
dutch (0)
This user has been banned -- click for more information.
(bert n) < Meka Chicken >
Posts: 589 - Joined: 10/02/2007
Netherlands
Profile
Made my code longer,aded up and down . That Strangly enough is converted to A and S on my keyboard? Remember is use jnes as emulator because fceuxdsp doesn't react at all.

I tried to be smart and changed a and b by left and right(and didn't changed anything else of that code)4 errors right away,hahaha

Can change the speed of the sprite ,but how do i ad the other sprites with the controls??

-------------------------

LOOKING FOR GARAGE CART,pm me with buy or trading offer


Jul 17, 2008 at 7:36:00 AM
collectinisgame (40)
avatar
(troy wages) < Lolo Lord >
Posts: 1802 - Joined: 03/03/2008
South Carolina
Profile
does anyone have a link and a list of programs needed to make a homebrew?

-------------------------
 

 

Jul 17, 2008 at 8:02:47 AM
Zzap (47)
avatar
(James ) < King Solomon >
Posts: 3301 - Joined: 05/01/2007
Australia
Profile
See previous week links above

-------------------------

Chunkout for iPhone, iPad and iTouch out now!
Chunkout Games: FaceBook | Web

Jul 17, 2008 at 8:10:37 AM
dutch (0)
This user has been banned -- click for more information.
(bert n) < Meka Chicken >
Posts: 589 - Joined: 10/02/2007
Netherlands
Profile
Originally posted by: collectinisgame

does anyone have a link and a list of programs needed to make a homebrew?




Its not as simple as that. You need to read ALOT  of stuff.

There is no easy way

Try reading programming that 8bit beast of power,the nes

You can read and print this on the nesdev site.


-------------------------

LOOKING FOR GARAGE CART,pm me with buy or trading offer


Jul 18, 2008 at 5:47:25 PM
dutch (0)
This user has been banned -- click for more information.
(bert n) < Meka Chicken >
Posts: 589 - Joined: 10/02/2007
Netherlands
Profile
can somebody please tell me what part of the code needs to be updated to make all the sprites move together?

Can't seem to figure it out

-------------------------

LOOKING FOR GARAGE CART,pm me with buy or trading offer


Jul 18, 2008 at 9:19:57 PM
Zzap (47)
avatar
(James ) < King Solomon >
Posts: 3301 - Joined: 05/01/2007
Australia
Profile
When you add or subtract to the sprite position for up, down, left, right etc you'll need to add and subtract for each of the sprites. So instead of just:

LDA $0203 ; load sprite position
SEC ; make sure carry flag is set
SBC #$01 ; A = A - 1
STA $0203 ; save sprite position

You'll need to:

LDA $0203 ; load sprite 1 position
SEC ; make sure carry flag is set
SBC #$01 ; A = A - 1
STA $0203 ; save sprite 1 position
LDA $0207 ; load sprite 2 position
SEC ; make sure carry flag is set
SBC #$01 ; A = A - 1
STA $0207 ; save sprite 2 position
LDA $020B ; load sprite 3 position
SEC ; make sure carry flag is set
SBC #$01 ; A = A - 1
STA $020B ; save sprite 3 position
LDA $020F ; load sprite 4 position
SEC ; make sure carry flag is set
SBC #$01 ; A = A - 1
STA $020F ; save sprite 4 position

Note, I think I've got those sprite memory location's right, but I did rush through them a bit

-------------------------

Chunkout for iPhone, iPad and iTouch out now!
Chunkout Games: FaceBook | Web

Jul 19, 2008 at 11:53:16 AM
dutch (0)
This user has been banned -- click for more information.
(bert n) < Meka Chicken >
Posts: 589 - Joined: 10/02/2007
Netherlands
Profile
Thanks zzap,

changed my asm file and it works.
Even made up and down go right.

just added 4 up each time 00 04 08 0c this works but i have no idea where these numbers come from i just saw the pattern of left and right.
And figured out i had to do +4. So where do you get the code from?

Also i assume that the code for controller 2 4017 is the same(except you use $4017)
I dont play pc games so how do you test player 2 controlls btw???????????????

-------------------------

LOOKING FOR GARAGE CART,pm me with buy or trading offer


Jul 20, 2008 at 5:15:40 AM
Zzap (47)
avatar
(James ) < King Solomon >
Posts: 3301 - Joined: 05/01/2007
Australia
Profile
Have a look in the options or preferences section of your emulator. There should be a setting in there somewhere to configure the second gamepad

-------------------------

Chunkout for iPhone, iPad and iTouch out now!
Chunkout Games: FaceBook | Web

Oct 26, 2009 at 4:28:36 PM
resynthesize (0)

(brandon t) < Cherub >
Posts: 7 - Joined: 10/19/2009
United States
Profile
Hi,

I was able to modify the code to update all four sprites that comprised mario at the same time.  However, when I try to generalize the update with a loop, it doesn't work.  Here is the code I came up with:




ReadRight:
  lda $4016          ; player 1 - A
  and #%00000001     ; only look at bit 0
  beq ReadRightDone
  ldx #$03           ; x position of sprite starts at $0203

moveloop:
  lda $2000, x       ; load sprite X position ($2000 + x)
  clc                ; make sure the carry flag is clear
  adc #$02           ; A = A + 1
  sta $2000, x       ; save sprite X position
  inx                ; increment X 4 times to get to next sprite
  inx                ; x position
  inx
  inx
  cpx #$0F           ; if x = $0f, all 4 sprites have been moved
  bne moveloop       ; otherwise keep going.

ReadRightDone:

I think the problem may be in my understanding of lda/sta. When I debug the code, the accumulator has a value of 0 after the "lda $2000, x" statement. If I use four sequential lda/sta blocks with the sprite offset hardcoded, it works fine and the accumulator contains the correct x position or the sprite. Any ideas where I am going wrong?



As an aside, is there a better way to add 4 to X than doing the four INX statements? It appears ADC only operates on the accumulator.

-------------------------
 

Oct 26, 2009 at 4:48:18 PM
Mario's Right Nut (352)
avatar
(Cunt Punch) < Bowser >
Posts: 6634 - Joined: 11/21/2008
Texas
Profile
Not sure what the issue you're having is, but try it this way.

LDA $0200 ; load sprite Y position
CLC ; make sure the carry flag is clear
ADC #$02 ; A = A + 2
STA $0200 ; save sprite Y position
STA $0204
CLC
ADC #$08
STA $020C
STA $0208

LDA $0203 ; load sprite X position
STA $020B
CLC
ADC #$08
STA $0207
STA $020F

-------------------------

This is my shiny thing, and if you try to take it off me, I may have to eat you.

Check out my dev blog.


Oct 26, 2009 at 4:58:17 PM
bunnyboy (81)
avatar
(Funktastic B) < Master Higgins >
Posts: 7704 - Joined: 02/28/2007
California
Profile
Your error is the $2000 part, you likely meant $0200.

Yup 4x inx is the fastest way. Can't do general math on X or Y.

Oct 26, 2009 at 5:03:03 PM
resynthesize (0)

(brandon t) < Cherub >
Posts: 7 - Joined: 10/19/2009
United States
Profile
Man,

Without fail, as soon as I spend the time to type up a message on a forum, the solution jumps out to me a few minutes later.  The problem was that I was using the wrong address for the sprites!  It is supposed to be $0200, not $2000.  After I changed that, it worked OK except the last sprite did not update.  That was because the "cpx #$0F" was branching before the last sprite was moved.  Here is the finished code that can move the entire sprite.  I wanted to write the code this way so that moving an arbitrarily large sprite wouldn't need a ton of repetitive code:

 
  ldx #$03
moveloop:
  lda $0200, x ;load sprite X position
  clc          ; make sure the carry flag is clear
  adc #$02      
  sta $0200, x ; save sprite X position
  inx
  inx
  inx
  inx
  cpx #$13
  bne moveloop   

I'm still wondering if there is a better way to "add 4 to x" instead of using 4 inx statements.

-------------------------
 

Oct 26, 2009 at 5:06:43 PM
resynthesize (0)

(brandon t) < Cherub >
Posts: 7 - Joined: 10/19/2009
United States
Profile
Originally posted by: Mario's Right Nut

Not sure what the issue you're having is, but try it this way.

LDA $0200 ; load sprite Y position
CLC ; make sure the carry flag is clear
ADC #$02 ; A = A + 2
STA $0200 ; save sprite Y position
STA $0204
CLC
ADC #$08
STA $020C
STA $0208

LDA $0203 ; load sprite X position
STA $020B
CLC
ADC #$08
STA $0207
STA $020F


ah, that is clever as well   Thanks.

-------------------------
 

Jan 3, 2010 at 6:25:48 PM
RevEng (0)

(James Cooper) < Cherub >
Posts: 3 - Joined: 01/03/2010
Saskatchewan
Profile
Originally posted by: resynthesize

I'm still wondering if there is a better way to "add 4 to x" instead of using 4 inx statements.

I know this was posted long ago, but here's an answer for posterity's sake.

Since you can only do math on the A register (accumulator), you'll want to copy the value of X into A, do the math, then copy it back to X.  The following code will do that:

TXA
CLC
ADC #$04
TAX

This code can replace the 4 INX statements.  Note that it wrecks what's currently in the accumulator.  If you still need what's in the accumulator, you can push it on to the stack and pull it back off when you are done:

PHA
TXA
CLC
ADC #$04
TAX
PLA

As you can see, that's a lot of code just to add a number to the X register.  This is why it's best to avoid doing math (other than increment/decrement) on these registers.  For X+4, it's actually less code (and less CPU cycles) to do INX 4 times than to use the code above.

-------------------------
Programmer and Electrical Engineer.  Familiar with several assembly languages.  Starting to learn the NES for game development.  First major planned project: making a version of the SNES game Zoop! for the NES.  But first, baby steps.