Implementing simple abstraction

Are you new to 6502, NES, or even programming in general? Post any of your questions here. Remember - the only dumb question is the question that remains unasked.

Moderator: Moderators

Post Reply
User avatar
instantaphex
Posts: 30
Joined: Sat Sep 27, 2014 10:10 pm
Location: Houston, TX

Implementing simple abstraction

Post by instantaphex »

I'm wondering how I can improve on the code I've been working on over the past couple days and I've been reading a lot but making little progress. I'm wondering how I can make more of a generic system for handling hardware sprites and meta sprites. My current code is pretty ugly and I'm using constants in NESASM to refer to the RAM buffer for my player sprite like this:

Code: Select all

PLAYER_Y = $0200
PLAYER_TILE = $0201
PLAYER_ATTR = $0202
PLAYER_X = $0203

player_x .rs 1
player_y .rs 1
And then I've got a drawing function that is basically doing this:

Code: Select all

DrawPlayer:
  LDA player_x
  STA PLAYER_X
  LDA player_y
  STA PLAYER_Y
There's a little bit more math in there to calculate the offsets for the other tiles for my player's meta sprite as well.

Now this all works just fine but it's not the way I would approach it if I were coding in something like C or C++. I'm having a hard time wrapping my head around doing any abstraction when I'm coding in assembly. I see some people talking about using arrays and tables and things like that but I'm not quite sure how those are implemented in 6502 assembly.

I've added my sprites and nametables to a section of memory starting at $E000, which I understand to be cartridge rom. Is this the space that is typically used to store things like arrays and tables? For example if I wanted to create some simple sprite animations, would a typical approach be to do something like this:

Code: Select all

walk_anim_ptr .rs 1

Code: Select all

walk_animation_array:
  .db $03, walk1, walk2, walk3 ; length, 1, 2, 3

walk1:
  .db $80, $32, $00, $80 
  .db $80, $33, $00, $88 
  .db $88, $34, $00, $80
  .db $88, $35, $00, $88

walk2:
  .db $80, $32, $00, $80 
  .db $80, $33, $00, $88 
  .db $88, $34, $00, $80
  .db $88, $35, $00, $88

walk3:
  .db $80, $32, $00, $80 
  .db $80, $33, $00, $88 
  .db $88, $34, $00, $80
  .db $88, $35, $00, $88
Because the 6502 has an address bus width of 16, does that mean that my animation "data structure" if you could call it that, is 7 bytes long? Sorry for these simple questions. The tutorials I'm finding range from incredibly simple to very complex with little in between. I just want to make sure that I'm getting this right.

Are further abstractions possible here in terms of data structures? Could I for example implement something like a C struct? What would that look like? Or is that specific to the assembler that I'm using?

What would be a very simple next step from this approach I've outlined above to something that uses a bit more abstraction and possibly a more generalized approach?
User avatar
pubby
Posts: 555
Joined: Thu Mar 31, 2016 11:15 am

Re: Implementing simple abstraction

Post by pubby »

Do not hardcode the sprite locations. Instead, build the sprite buffer like a stack. Start with

Code: Select all

ldx #0
Then to add a sprite to the buffer write code like this:

Code: Select all

  lda player_x
  sta $203, x
  lda player_y
  sta $200, x
  lda #0
  sta $201, x
  sta $202, x
  inx
  inx
  inx
  inx
Once you've added all your sprites to the buffer you need to "clear" the remaining memory so that junk doesn't appear on the screen.

Code: Select all

    lda #$FF
clearOAMLoop:
    sta $200, x
    inx
    inx
    inx
    inx
    bne clearOAMLoop
Note that you can replace the four 'inx' instructions two: 'tax' followed by the unofficial instruction 'axs #252'.

The way you've implemented metasprites looks fine, but I would consider adding a length field to each metasprite so that the routine can be made generic. Also, one thing metasprite routines typically do is handle clipping with the edges of the screen, and doing so likely requires working with 16 bit coordinates.
User avatar
dougeff
Posts: 2876
Joined: Fri May 08, 2015 7:17 pm
Location: DIGDUG
Contact:

Re: Implementing simple abstraction

Post by dougeff »

.db $03, walk1, walk2, walk3

db is for 1 byte entries. You can't store an address in 1 byte (except for zero page RAM addresses, which these aren't).

nesasm uses .dw or .word for 2 byte addresses.

you can also store an address byte by byte using 2 db slots...

.db LOW[walk1], HIGH[walk1]
nesdoug.com -- blog/tutorial on programming for the NES
User avatar
instantaphex
Posts: 30
Joined: Sat Sep 27, 2014 10:10 pm
Location: Houston, TX

Re: Implementing simple abstraction

Post by instantaphex »

pubby wrote:Do not hardcode the sprite locations. Instead, build the sprite buffer like a stack. Start with

Code: Select all

ldx #0
Then to add a sprite to the buffer write code like this:

Code: Select all

  lda player_x
  sta $203, x
  lda player_y
  sta $200, x
  lda #0
  sta $201, x
  sta $202, x
  inx
  inx
  inx
  inx
Once you've added all your sprites to the buffer you need to "clear" the remaining memory so that junk doesn't appear on the screen.

Code: Select all

    lda #$FF
clearOAMLoop:
    sta $200, x
    inx
    inx
    inx
    inx
    bne clearOAMLoop
Note that you can replace the four 'inx' instructions two: 'tax' followed by the unofficial instruction 'axs #252'.

The way you've implemented metasprites looks fine, but I would consider adding a length field to each metasprite so that the routine can be made generic. Also, one thing metasprite routines typically do is handle clipping with the edges of the screen, and doing so likely requires working with 16 bit coordinates.
When I'm setting up my oam buffer, do I copy over all of the frames that I need for an animation or sprite flip and move them off screen when they aren't needed or do I reconstruct the oam buffer each frame? I'm assuming it would be quicker just to copy over what I need each frame right?

Let's say I have 16 bytes of memory for the meta sprite of the player facing right that I've copied to $0200 - $0204, but when left is pressed I want to switch to the left facing meta sprite, do I copy the left facing sprite to $0200 - $0204? Actually as I'm writing this it seems obvious that it is probably the correct way. I kept thinking for whatever reason that I would load all possible animation frames and everything into the oam buffer every frame.

Edit: I mean't 16 bytes with the first hardware sprite starting at $0200, not storing 16 bytes somehow into $0200 - $0204...

Actually now that I think about it, this is exactly what you told me in my sprite flipping thread. I think I'm finally getting what you were talking about.
Last edited by instantaphex on Sat Feb 03, 2018 2:01 pm, edited 1 time in total.
User avatar
instantaphex
Posts: 30
Joined: Sat Sep 27, 2014 10:10 pm
Location: Houston, TX

Re: Implementing simple abstraction

Post by instantaphex »

dougeff wrote:.db $03, walk1, walk2, walk3

db is for 1 byte entries. You can't store an address in 1 byte (except for zero page RAM addresses, which these aren't).

nesasm uses .dw or .word for 2 byte addresses.

you can also store an address byte by byte using 2 db slots...

.db LOW[walk1], HIGH[walk1]
Oh right, that makes sense. Thanks for the correction. That is going to save me a headache later.
Post Reply