Page 1 of 7
SNES Programing Help 2
Posted: Thu Mar 05, 2015 5:18 pm
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
Re: SNES Programing Help 2
Posted: Thu Mar 05, 2015 6:49 pm
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.
Re: SNES Programing Help 2
Posted: Thu Mar 05, 2015 7:05 pm
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.
Re: SNES Programing Help 2
Posted: Thu Mar 05, 2015 7:17 pm
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?
Re: SNES Programing Help 2
Posted: Thu Mar 05, 2015 7:31 pm
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.
Re: SNES Programing Help 2
Posted: Thu Mar 05, 2015 7:34 pm
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).
Re: SNES Programing Help 2
Posted: Thu Mar 05, 2015 7:52 pm
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.

(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?
Re: SNES Programing Help 2
Posted: Thu Mar 05, 2015 8:05 pm
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.
Re: SNES Programing Help 2
Posted: Thu Mar 05, 2015 9:22 pm
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.

)
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?
Re: SNES Programing Help 2
Posted: Thu Mar 05, 2015 11:06 pm
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.
Re: SNES Programing Help 2
Posted: Fri Mar 06, 2015 11:23 pm
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?
Re: SNES Programing Help 2
Posted: Fri Mar 06, 2015 11:28 pm
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.
Re: SNES Programing Help 2
Posted: Fri Mar 06, 2015 11:52 pm
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)
Re: SNES Programing Help 2
Posted: Sat Mar 07, 2015 1:28 am
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:
And 8 bit values:
My code is full of them.
Re: SNES Programing Help 2
Posted: Sat Mar 07, 2015 7:07 am
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...
...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?