SNES Programing Help 2

Discussion of hardware and software development for Super NES and Super Famicom.

Moderator: Moderators

Forum rules
  • For making cartridges of your Super NES games, see Reproduction.
Post Reply
User avatar
Drew Sebastino
Formerly Espozo
Posts: 3496
Joined: Mon Sep 15, 2014 4:35 pm
Location: Richmond, Virginia

SNES Programing Help 2

Post by Drew Sebastino »

I don't know if I should have just resurrected the old thread, but I figured I would ask more "advanced" questions this time. I was wondering how everyone makes an object go to different pieces of code that correspond to a different action, like if the object is jumping or if it is on the ground. I suppose you could use a jump table, but I guess there isn't really a "definitive" way to do these kind of things. I wrote a code where the player spawns a bullet, and when I spawned the bullet, I wrote a certain number to a register in the bullets object slot. When the bullet code gets jumped to, I load the special register and I offset a jump table by the register which jumps to a certain piece of code that works for the direction of the bullet. I was thinking I could just change the bullet's velocity and check all sides of the screen instead of just the one the bullet is traveling, but the bullet's graphics would be the same and so would everything else including the hit box, so I really don't know.

If it helps, here is the code:

Code: Select all

;====================================================================================
;Bullet
;====================================================================================

.proc bullet
  rep #$30                         ; A=16, X/Y=16
  ldx ObjectTable+6,y
  jmp (BulletIdentificationTable,x)	;jump to the code that corresponds with the object

.endproc

;====================================================================================

BulletIdentificationTable:
  .word bulletright,bulletleft

;====================================================================================

.proc bulletright
  lda ObjectTable+2,y
  cmp #256
  bcs terminate_bullet
  clc
  adc #$0A
  sta ObjectTable+2,y


  lda Bullet1MetaspriteTableSize   ; Load number of Metasprites
  sta MetaspriteCount              ; ...and store it in MetaspriteCount

  ldx #Bullet1MetaspriteTable      ; Offset into MetaspriteTable
  stx MetaspriteTableOffset

  lda ObjectTable+2,y
  sta MetaspriteXPosition
  lda ObjectTable+4,y
  sta MetaspriteYPosition

  stz MetaspriteDirection

  jsr start_metasprite      ; jump to start_metasprite to build metasprites
  rts

terminate_bullet:
  lda #$0000
  sta ObjectTable,y
  rts

.endproc

;====================================================================================

.proc bulletleft
  lda ObjectTable+2,y
  cmp #256
  bcs terminate_bullet
  sec
  sbc #$0A
  sta ObjectTable+2,y


  lda Bullet1MetaspriteTableSize   ; Load number of Metasprites
  sta MetaspriteCount              ; ...and store it in MetaspriteCount

  ldx #Bullet1MetaspriteTable      ; Offset into MetaspriteTable
  stx MetaspriteTableOffset

  lda ObjectTable+2,y
  sta MetaspriteXPosition
  lda ObjectTable+4,y
  sta MetaspriteYPosition

  lda #$0001
  sta MetaspriteDirection

  jsr start_metasprite      ; jump to start_metasprite to build metasprites
  rts

terminate_bullet:
  lda #$0000
  sta ObjectTable,y
  rts

.endproc
User avatar
Khaz
Posts: 314
Joined: Thu Dec 25, 2014 10:26 pm
Location: Canada

Re: SNES Programing Help 2

Post by Khaz »

Code: Select all

  lda ObjectTable+2,y
  sta MetaspriteXPosition
  lda ObjectTable+4,y
  sta MetaspriteYPosition

  stz MetaspriteDirection

  jsr start_metasprite      ; jump to start_metasprite to build metasprites
Why not just have start_metasprite reference the X and Y positions in the object variables directly, instead of copying? Unless you plan to use start_metasprite outside of your object loop...

Regarding how you set this section up, I think the use of a jump table for left and right is a bit convoluted when you can just test something and branch. You don't even need to duplicate this section at all if you store their velocity in a variable and set it when they're initialized, then just add that every time.

Also, are you using direct page? I guess if your object list is anywhere other than the first $2000 of RAM you can't (err, can you use direct page in bank 7F to access another $2000 of RAM? Haven't tried using it outside bank 0 yet), but it would speed things up on all the indexed-by-y instructions. I'm not sure if you're using it some other way, it's just an idea if you haven't messed with direct page yet. Better to get it set up now before you go and write several thousand lines of code you'll need to change later.

Others are free to correct me because I am new.
User avatar
Drew Sebastino
Formerly Espozo
Posts: 3496
Joined: Mon Sep 15, 2014 4:35 pm
Location: Richmond, Virginia

Re: SNES Programing Help 2

Post by Drew Sebastino »

Khaz wrote:Why not just have start_metasprite reference the X and Y positions in the object variables directly, instead of copying? Unless you plan to use start_metasprite outside of your object loop...
X and Y are both already being used in the metasprite routine, that is why. I don't plan on jumping to the routine outside of it.
Khaz wrote:Regarding how you set this section up, I think the use of a jump table for left and right is a bit convoluted when you can just test something and branch.
I plan on adding a bunch of different angles, so I figured I'd just make a jump table so I wouldn't have to have a bunch of cmp,beq's.
Khaz wrote:You don't even need to duplicate this section at all if you store their velocity in a variable and set it when they're initialized, then just add that every time.
How would "negative velocity" (up or right) work? Also, you would need to have it to where the object is flipped around still, so you couldn't just add the velocity to the object position.

A little random for me to ask this, but is there some sort of instruction that can "flip" bits around? I mean like if you had 11111010 and you wrote 00000011 then the result would be 11111001.
User avatar
Khaz
Posts: 314
Joined: Thu Dec 25, 2014 10:26 pm
Location: Canada

Re: SNES Programing Help 2

Post by Khaz »

Espozo wrote:X and Y are both already being used in the metasprite routine, that is why. I don't plan on jumping to the routine outside of it.
In that case, that's the beautiful part of my earlier suggestion of using direct page. Once you get that set up, you can just have metasprite routine call the X and Y positions from the current direct page, without having to touch your X and Y registers.
Espozo wrote:I plan on adding a bunch of different angles, so I figured I'd just make a jump table so I wouldn't have to have a bunch of cmp,beq's.
If they're all still travelling in straight lines, you can still have one generic update routine that adds your X and Y velocities to your positions for all of them. If you plan to have them all behave significantly differently then your approach starts to make sense.
Espozo wrote:How would "negative velocity" (up or right) work? Also, you would need to have it to where the object is flipped around still, so you couldn't just add the velocity to the object position.

A little random for me to ask this, but is there some sort of instruction that can "flip" bits around? I mean like if you had 11111010 and you wrote 00000011 then the result would be 11111001.
Negative binary numbers in general all work the same way: $0000 rolls over into $FFFF, which is -1. So if your velocity is negative $10 then it's just $FFF0. The highest bit determines whether the number is positive or negative. To add that is the same whether it's positive or negative: clc, adc.

And yes, there is a flip bits instruction: EOR (Exclusive OR). This is also useful with negative numbers: To flip the sign on a number, you EOR it with $FFFF (flip all bits), then add one (because you have to add one after flipping all the bits to make it equal because math. Just remember: If you do $0000 EOR $FFFF, you get $FFFF so you have to add one to make it zero again.).

EDIT: Also, you meant to say "negative velocity (up or LEFT)", right?
93143
Posts: 1371
Joined: Fri Jul 04, 2014 9:31 pm

Re: SNES Programing Help 2

Post by 93143 »

Khaz wrote:(err, can you use direct page in bank 7F to access another $2000 of RAM? Haven't tried using it outside bank 0 yet)
Yes. Direct page always accesses bank 0, regardless of the value of the data bank register.
User avatar
koitsu
Posts: 4203
Joined: Sun Sep 19, 2004 9:28 pm
Location: A world gone mad

Re: SNES Programing Help 2

Post by koitsu »

Khaz wrote:Also, are you using direct page? I guess if your object list is anywhere other than the first $2000 of RAM you can't (err, can you use direct page in bank 7F to access another $2000 of RAM? Haven't tried using it outside bank 0 yet) ...
Direct page is hard-coded to bank $00.

Direct page is, in essence, the exact same as 6502/65c02 zero page: instructions are 2 bytes long (e.g. lda $12), with a range of 256 bytes ($00-FF). The difference is that instead of the effective address being $0000 to $00FF like with zero page, the D register (e.g. lda #$1000 / tcd) allows you to relocate the "base offset" for direct page reads/writes so you're no longer limited to $00xx.

If you need to be able to access memory in other banks, you need to either use absolute addressing and change B around dynamically (lda #$7f / pha / plb / lda $1000) or use long addressing (lda $7f1000). There's also absolute long indexed X (lda $7f1000,x) and direct page indirect long indexed Y (e.g. lda [$12],y), but I won't go into those here because it's outside of scope.

Equally important: the SNES has MMIO registers $2180 (WMDATA) and $2181 through $2183 (WMADDL/WMADDM/WMADDH) that allows a way to access to WRAM (memory $7E0000 to $7FFFFF). You might wonder (like I did at one point) what the purpose of those registers is if you can already access the same memory natively: the answer is general-purpose DMA (since it can read/write from an MMIO register, which makes this the fastest way to move large amounts of memory in/out of WRAM).
User avatar
Drew Sebastino
Formerly Espozo
Posts: 3496
Joined: Mon Sep 15, 2014 4:35 pm
Location: Richmond, Virginia

Re: SNES Programing Help 2

Post by Drew Sebastino »

Khaz wrote:
Espozo wrote:I plan on adding a bunch of different angles, so I figured I'd just make a jump table so I wouldn't have to have a bunch of cmp,beq's.
If they're all still travelling in straight lines, you can still have one generic update routine that adds your X and Y velocities to your positions for all of them. If you plan to have them all behave significantly differently then your approach starts to make sense.
The bullet isn't circular though, so velocity isn't the only thing changing.
Khaz wrote:Negative binary numbers in general all work the same way: $0000 rolls over into $FFFF, which is -1. So if your velocity is negative $10 then it's just $FFF0. The highest bit determines whether the number is positive or negative. To add that is the same whether it's positive or negative: clc, adc.
But how can it tell if it is -16 or if it is 65528? (I think the second number is correct)
Khaz wrote:And yes, there is a flip bits instruction: EOR (Exclusive OR). This is also useful with negative numbers: To flip the sign on a number, you EOR it with $FFFF (flip all bits), then add one (because you have to add one after flipping all the bits to make it equal because math. Just remember: If you do $0000 EOR $FFFF, you get $FFFF so you have to add one to make it zero again.).
Thank you! :) I've known of eor's existence but I had now idea as to what it did.
Khaz wrote:EDIT: Also, you meant to say "negative velocity (up or LEFT)", right?
Yeah. :oops: (I'm really not feeling it today.)
93143 wrote:Khaz wrote:(err, can you use direct page in bank 7F to access another $2000 of RAM? Haven't tried using it outside bank 0 yet)Yes. Direct page always accesses bank 0, regardless of the value of the data bank register.
Is there a good place to learn all this "direct page" stuff? I've never really understood or ever really even attempted to understand it. I've always wondered, how are you loading a 24bit address?
User avatar
Khaz
Posts: 314
Joined: Thu Dec 25, 2014 10:26 pm
Location: Canada

Re: SNES Programing Help 2

Post by Khaz »

Espozo wrote:The bullet isn't circular though, so velocity isn't the only thing changing.
What else is changing?
Espozo wrote:But how can it tell if it is -16 or if it is 65528? (I think the second number is correct)
It is both. Just try adding both of those to a two byte number and you'll get the same result.
Espozo wrote:Is there a good place to learn all this "direct page" stuff?
There really doesn't seem to be, you just kind of have to start trying to use it and once it works you'll get it. The idea is that you set your D register to a base address, then when you perform instructions with a one-byte-address operand, say "LDA $10", it will automatically add that value in D to the address, and quickly.

EDIT: Regarding "what's changing"... For the different sprites, simply load the index of the metasprite into the object's internal variables when it spawns, and have your metasprite routine draw whatever is in that variable every time.
User avatar
Drew Sebastino
Formerly Espozo
Posts: 3496
Joined: Mon Sep 15, 2014 4:35 pm
Location: Richmond, Virginia

Re: SNES Programing Help 2

Post by Drew Sebastino »

Khaz wrote:Espozo wrote:But how can it tell if it is -16 or if it is 65528? (I think the second number is correct)It is both. Just try adding both of those to a two byte number and you'll get the same result.
Oh yeah, because it wraps around... (Today's really not my day. :oops: )
Khaz wrote:
Espozo wrote:The bullet isn't circular though, so velocity isn't the only thing changing.
What else is changing?
The sprite tile number (changes based on where the bullet tile data got loaded into vram) and the tile data that gets uploaded to vram for it, (yet to be implemented, as I currently just have all the bullet tile data at a fixed spot in vram that gets loaded from a different code) and the direction, which is a register that the metasprite code flips the entire metasprite around if it is set, meaning it is not 0.
Khaz wrote:EDIT: Regarding "what's changing"... For the different sprites, simply load the index of the metasprite into the object's internal variables when it spawns, and have your metasprite routine draw whatever is in that variable every time.
:| ? What do you mean?
User avatar
Khaz
Posts: 314
Joined: Thu Dec 25, 2014 10:26 pm
Location: Canada

Re: SNES Programing Help 2

Post by Khaz »

What I mean is, store the address of the metasprite table as a variable inside the object. Every frame, have your build metasprite routine look up the metasprite table at that address and draw it. This way you don't have to decide every frame which metasprite to draw. You just update that address when you want it to change, and it will happen automatically.

Even if the bullets are animated, you could still arrange the metasprite tables sequentially for each animation and have your framely routine just increment the "metasprite table address" variable by the size of each table in a set pattern. This way you can have completely unique animations running in all different directions using the exact same framely-update routine.
User avatar
Drew Sebastino
Formerly Espozo
Posts: 3496
Joined: Mon Sep 15, 2014 4:35 pm
Location: Richmond, Virginia

Re: SNES Programing Help 2

Post by Drew Sebastino »

Okay, this is a small thing, but I just started trying to implement the velocity thing and I wrote "adc #-$0A" and ca65 is telling me
Objects.asm(290): Error: Range error (-10 not in [0..65535])
Does it not understand that it wraps around? Do I have to figure the number out manually?
lidnariq
Posts: 10677
Joined: Sun Apr 13, 2008 11:12 am
Location: Seattle

Re: SNES Programing Help 2

Post by lidnariq »

At some point recently, the previous ca65 maintainer decided that there was "no right way" to DTRT with mixing signed and unsigned integers. So as a result you have to forcefully cast it to 8 bits.

This is bunk, but we haven't managed(/tried?) to persuade the new maintainer to fix it.
User avatar
tokumaru
Posts: 12106
Joined: Sat Feb 12, 2005 9:43 pm
Location: Rio de Janeiro - Brazil

Re: SNES Programing Help 2

Post by tokumaru »

Espozo wrote:Do I have to figure the number out manually?
Would this work?

Code: Select all

adc #$100-$0A (8-bit)
adc #$10000-$0A (16-bit)
UnDisbeliever
Posts: 77
Joined: Mon Mar 02, 2015 1:11 am
Location: Australia (PAL)
Contact:

Re: SNES Programing Help 2

Post by UnDisbeliever »

Espozo wrote:Okay, this is a small thing, but I just started trying to implement the velocity thing and I wrote "adc #-$0A" and ca65 is telling me
Objects.asm(290): Error: Range error (-10 not in [0..65535])
Does it not understand that it wraps around? Do I have to figure the number out manually?
The ca65 maintainer removed that functionality a while ago, apparently its to force you to think when coding 16 bit signed math on a 8 bit 6502.

Instead you can use one of 5 functions to preform casts to 8 or 16 bit values.
  • .loword - bits 0 - 15 (lowest 16 bits) of the argument.
  • .hiword - bits 16 - 31 of the argument.
  • .lobyte - bits 0 - 7 (lowest 8 bits) of the argument
  • .hibyte - bits 8 - 15 (second byte) of the argument.
  • .bankbyte - bits 16 - 23 (third byte) of the argument.


For example.

16 bit values:

Code: Select all

    LDA #.loword(-10)
And 8 bit values:

Code: Select all

    LDA #.lobyte(-10)
My code is full of them.
User avatar
thefox
Posts: 3139
Joined: Mon Jan 03, 2005 10:36 am
Location: Tampere, Finland
Contact:

Re: SNES Programing Help 2

Post by thefox »

lidnariq wrote:At some point recently, the previous ca65 maintainer decided that there was "no right way" to DTRT with mixing signed and unsigned integers. So as a result you have to forcefully cast it to 8 bits.

This is bunk, but we haven't managed(/tried?) to persuade the new maintainer to fix it.
The official explanation was that it was a bug/oversight, and was never supposed to work like that in the first place.

No need to persuade anybody though, you can use...

Code: Select all

.feature force_range
...to disable all range checks. (There's also an --feature command line option that does the same thing.)
http://cc65.github.io/doc/ca65.html#ss11.42

For people who say that ca65 should be able to "DTRT" for signed and unsigned integers, do you know of any other assembler that actually treats them separately?
Download STREEMERZ for NES from fauxgame.com! — Some other stuff I've done: fo.aspekt.fi
Post Reply