Is there an easier way to do this?
Moderator: Moderators
-
- Posts: 318
- Joined: Mon Jan 30, 2017 5:20 pm
- Location: Colorado USA
Is there an easier way to do this?
So I'm working on the first level of my game, and to actually have a level, you have to have a nametable. The way I'm writing mine is I have a separate header for each group of looping tiles. I have that increase the low byte until it's to the highest byte. I end up having so many headers it's confusing and I end up deleting all of my code and restarting from scratch. This is the fifteenth time I've done this. Is there an easier way to do nametables? I also have individual headers for pretty much every function in the entire game, a header for each comparison, move, and so on. After a while programming this gets really stressful and I occasionally lose all hope. Is there any easier way?
Re: Is there an easier way to do this?
I don't understand what you mean by "header". Can you show us a piece of the code that illustrates what you mean?
-
- Posts: 318
- Joined: Mon Jan 30, 2017 5:20 pm
- Location: Colorado USA
Re: Is there an easier way to do this?
Here. The reason I have this ">" after each comment is because Notepad++ makes you have to have something to close a comment.
What I meant by "header" were things like this
Code: Select all
BeginNametableZone:
LDA #0 ;Sets NametableLoop to zero>
STA NametableLoop ;Stores NametableLoop to the correct memory address.>
LDY #$40 ;Loads Y with low byte>
LDX #$23 ;Loads X with high byte>
JMP WriteNametableZone ;Jumps to begin writing the nametable>
WriteNametableZone:
LDX #$23
STX $2007 ;Stores high byte>
STY $2007 ;Stores low byte>
STA $2006 ;Stores tile number>
INY ;Increments low byte>
CPY #$9F ;Checks if it's okay to end the loop>
BNE WriteNametableZone ;If not finished, repeat>
LDY #$20
LDA #$2A
NametableZoneGrass:
LDX #$23 ;Reset X>
STX $2007 ;Writes high byte>
STY $2007 ;Writes low byte>
STA $2006 ;Store tile number>
INY ;Increases low byte>
CPY #$3E ;Checks if it's okay to end cycle>
BNE NametableZoneGrass ;If not, repeat>
LDA #$2D ;Load tile number>
LDY #$3F ;Load low byte>
STX $2007 ;Stores high byte>
STY $2007 ;Stores low byte>
STA $2006 ;Stores tile number>
LDY #$1F ;Loads Y with low byte>
STX $2007 ;Stores high byte>
STY $2007 ;Stores low byte>
STA $2006 ;Stores tile number>
Code: Select all
NametableZoneGrass:
- FrankenGraphics
- Formerly WheelInventor
- Posts: 2064
- Joined: Thu Apr 14, 2016 2:55 am
- Location: Gothenburg, Sweden
- Contact:
Re: Is there an easier way to do this?
Super Mario Bros. has a number of draw level routines, a bit like drawing shapes in ms paint or indesign. This tends to become a very rule bound way of designing levels, but it can still be interesting.
Metroid builds levels by linking reusable rooms. A room contains a number of reusable structures with assigned coordinates and drawing order per instance. A structure contains a number of reusable metatiles organized in a 2d array. A metatile is, of course, made from tiles. Tiles have solidity properties applied to them.
A third method is just doing a complete level in raw data and mitigate the much larger size by using a compression algorithm.
I hope i got your question right?
EDIT: I thought you by header meant some sort of metadata tied to the content. Those you have there are labels, for the sake of clarity.
Metroid builds levels by linking reusable rooms. A room contains a number of reusable structures with assigned coordinates and drawing order per instance. A structure contains a number of reusable metatiles organized in a 2d array. A metatile is, of course, made from tiles. Tiles have solidity properties applied to them.
A third method is just doing a complete level in raw data and mitigate the much larger size by using a compression algorithm.
I hope i got your question right?
EDIT: I thought you by header meant some sort of metadata tied to the content. Those you have there are labels, for the sake of clarity.
-
- Posts: 318
- Joined: Mon Jan 30, 2017 5:20 pm
- Location: Colorado USA
Re: Is there an easier way to do this?
But can I avoid using headers? It gets really confusing, even when I comment what every single command is doing!
-
- Posts: 318
- Joined: Mon Jan 30, 2017 5:20 pm
- Location: Colorado USA
Re: Is there an easier way to do this?
I mean labels.DementedPurple wrote:But can I avoid using headers? It gets really confusing, even when I comment what every single command is doing!
- FrankenGraphics
- Formerly WheelInventor
- Posts: 2064
- Joined: Thu Apr 14, 2016 2:55 am
- Location: Gothenburg, Sweden
- Contact:
Re: Is there an easier way to do this?
Bit of a sidenote, but you can fix this issue by selecting assembly as language in notepad++ (or by defining your own language).The reason I have this ">" after each comment is because Notepad++ makes you have to have something to close a comment.
Re: Is there an easier way to do this?
Let's say you want to load an entire, uncompressed nametable from the ROM to PPU (this is a bad idea in the long run, but maybe good practice for a beginner).
I would have 2 bytes of indirect addresses in the Zero Page RAM...lets use $00 and $01, for simplicity. Zero page indirect, uses the y register...but that would only let us count to 256. We need to count to 1024. For that I will use the X register, and another Zero Page address.
With rendering off, set address in PPU...
LDA #$20
STA $2006
LDA #$00
STA $2006 ;PPU now set to $2000
Then, load the address of the data, to RAM 00 and 01.
LDA #<data ; the low byte of a 2 byte address
STA $00
LDA #>data ; the high byte of a 2 byte address
STA $01
;now set up the loop
LDY #0 ; remains zero
LDX #0 ; counts to 256
LDA #4 ; loop 4 times
STA $03 ; using this address as a counter
loop:
LDA ($00), y ; looks at the address pushed here
STA $2007
INC $00 ; +1 the low byte
BNE +
INC $01 ; if low byte zero, inc high byte
+
DEX ; count down from 0...255...254...
BNE loop ; jump to loop unless X now zero
DEC $03 ; count down 4...3...2...1
BNE loop
And, of course, have a label called 'data:' with an .incbin after it, to a file holding your binary data.
Personally, I just use NES screen tool to make backgrounds, compress the name table as an RLE, and use the UnRLE code to push the nametable to the PPU. (Provided with NES screen tool).
I would have 2 bytes of indirect addresses in the Zero Page RAM...lets use $00 and $01, for simplicity. Zero page indirect, uses the y register...but that would only let us count to 256. We need to count to 1024. For that I will use the X register, and another Zero Page address.
With rendering off, set address in PPU...
LDA #$20
STA $2006
LDA #$00
STA $2006 ;PPU now set to $2000
Then, load the address of the data, to RAM 00 and 01.
LDA #<data ; the low byte of a 2 byte address
STA $00
LDA #>data ; the high byte of a 2 byte address
STA $01
;now set up the loop
LDY #0 ; remains zero
LDX #0 ; counts to 256
LDA #4 ; loop 4 times
STA $03 ; using this address as a counter
loop:
LDA ($00), y ; looks at the address pushed here
STA $2007
INC $00 ; +1 the low byte
BNE +
INC $01 ; if low byte zero, inc high byte
+
DEX ; count down from 0...255...254...
BNE loop ; jump to loop unless X now zero
DEC $03 ; count down 4...3...2...1
BNE loop
And, of course, have a label called 'data:' with an .incbin after it, to a file holding your binary data.
Personally, I just use NES screen tool to make backgrounds, compress the name table as an RLE, and use the UnRLE code to push the nametable to the PPU. (Provided with NES screen tool).
nesdoug.com -- blog/tutorial on programming for the NES
Re: Is there an easier way to do this?
Your code has lots of oddities.
It's not clear what NametableLoop is, but it's not being used in the rest of your code.
2 writes to $2006 sets an address. Writes to $2007 pushes a byte to the PPU. Usually, you would see...
Code: Select all
LDA #0
STA NametableLoop
Code: Select all
LDX #$23
STX $2007 ;Stores high byte>
STY $2007 ;Stores low byte>
STA $2006 ;Stores tile number>
Code: Select all
Lda something...
Sta $2006
Lda something
Sta $2006
Loop:
Lda something
Sta $2007
Some loop count
Branch to Loop
nesdoug.com -- blog/tutorial on programming for the NES
-
- Posts: 318
- Joined: Mon Jan 30, 2017 5:20 pm
- Location: Colorado USA
Re: Is there an easier way to do this?
Well, when I started my code, I was going to use NametableLoop to keep track of how many times the same tile has been written to a different location, but I later realized that I could just use the low byte.
Re: Is there an easier way to do this?
You can't avoid having labels, you need to specify the address for branches, jumps and interrupt handlers. And labels are the way to do it to avoid hard coding the addresses. However you can avoid having to make up unique names on every single label by using local labels. A local label only works in between the normal global labels.
In asm6 and ca65 local labels begin with @ and in Nesasm they begin with a full stop "." character.
Local labels are very useful for simple things like loops and skipping certain code. I usually make one descriptive global label before a routine and then keep all the labels inside the routine local. Usually I use names like: @loop, @skip, @exit, @outerloop, @nestedloop and so on. If there are multiple labels that needs the same name I usually solve that by adding numbers: @loop1, @loop2 and so on.
Examples:
In asm6 and ca65 local labels begin with @ and in Nesasm they begin with a full stop "." character.
Local labels are very useful for simple things like loops and skipping certain code. I usually make one descriptive global label before a routine and then keep all the labels inside the routine local. Usually I use names like: @loop, @skip, @exit, @outerloop, @nestedloop and so on. If there are multiple labels that needs the same name I usually solve that by adding numbers: @loop1, @loop2 and so on.
Examples:
Code: Select all
init_ram:
ldx #$00
txa
@loop:
sta $0000,x ;clear each RAM page
sta $0100,x
sta $0200,x
sta $0300,x
sta $0400,x
sta $0500,x
sta $0600,x
sta $0700,x
inx
bne @loop
Code: Select all
input_handler:
@up:
lda button_stat+0
and #CON_UP
beq @down
;UP button action
@down:
lda button_stat+0
and #CON_DOWN
beq @left
;DOWN button action
@left:
lda button_stat+0
and #CON_LEFT
beq @right
;LEFT button action
@right:
lda button_stat+0
and #CON_RIGHT
beq @exit
;RIGHT button action
@exit:
The x86 assembly isn't very fun to use for 6502 programming though, so you might want to download one with 65x highlightings. I uploaded one I made in the attachment. It has row comments for ";" and no need to close them.FrankenGraphics wrote:Bit of a sidenote, but you can fix this issue by selecting assembly as language in notepad++ (or by defining your own language).The reason I have this ">" after each comment is because Notepad++ makes you have to have something to close a comment.
- Attachments
-
- 65x.7z
- All the official mnemonics for NES, SNES and PC Engine programming (and some more). Hopefully I didn't make any mistakes.
- (2.37 KiB) Downloaded 113 times
Re: Is there an easier way to do this?
I would again encourage looking into macros. They can be used to make your source code much shorter and more clear, while still assembling exactly (in most cases) the same machine code for the processor to execute. (Actually, one way that it may not be exactly the same is that since it's easier to see what you're doing, you won't have as many bugs to find and fix!) You can even extend their use to program structures and get rid of a lot of the labels. A topic about this is at viewtopic.php?f=10&t=15088 .
So the source code above could be modified to:
and the assembled code would be indistinguishable from what came from the original version. There are no branch labels in the source code though.
For this kind of thing, I would normally use a CASE structure; but here, we have the AND to contend with. If you don't think you would use a special macro with the AND enough to make it worth taking the time to write it, perhaps a better way would be to use BIT# if you have a CMOS 6502 (65c02), otherwise BIT <abs>, so you don't have to keep loading button_stat again for each test.
If you do use this construction enough to justify a little extra time to write the extra macro (which isn't much once you see how it works internally), you could do:
and again get a machine-code result that's just as efficient as you would have done by hand, but without labels. Actually, this one would normally be slightly different in that if the case was found to be true, subsequent ones would not get executed. If the code for a particular case does not end in something like RTS or JMP, the END_OF will do a jump to the END_CASE. You'll notice there's an extra _ after the last END_OF. That prevents it from adding the jump to what's next anyway. If previous ones end in something like JMP or RTS, you can use END_OF_ (with the final _) there too, to avoid assembling an unused jump instruction.
The explanations are in main article on this subject at http://wilsonminesco.com/StructureMacros/, with additional explanation of what the assembler does at assembly time in the "assembling program structures" chapter in the 6502 stacks treatise, at http://wilsonminesco.com/stacks/pgmstruc.html .
So the source code above could be modified to:
Code: Select all
input_handler:
LDA button_stat
AND #CON_UP ; Is the UP button being pressed?
IF_NOT_ZERO ; If so,
<UP button action> ; do this stuff.
END_IF
LDA button_stat
AND #CON_DOWN ; Is the DOWN button being pressed?
IF_NOT_ZERO ; If so,
<DOWN button action> ; do this stuff.
END_IF
LDA button_stat
AND #CON_LEFT ; Is the LEFT button being pressed?
IF_NOT_ZERO ; If so,
<LEFT button action> ; do this stuff.
END_IF
LDA button_stat
AND #CON_RIGHT ; Is the RIGHT button being pressed?
IF_NOT_ZERO ; If so,
<RIGHT button action> ; do this stuff.
END_IF
For this kind of thing, I would normally use a CASE structure; but here, we have the AND to contend with. If you don't think you would use a special macro with the AND enough to make it worth taking the time to write it, perhaps a better way would be to use BIT# if you have a CMOS 6502 (65c02), otherwise BIT <abs>, so you don't have to keep loading button_stat again for each test.
If you do use this construction enough to justify a little extra time to write the extra macro (which isn't much once you see how it works internally), you could do:
Code: Select all
input_handler:
LDA button_stat
CASE_Accum ; We will be testing the accumulator below.
CASE_OF_BIT CON_UP, SET ; In the case of the UP bit being set,
<UP button action> ; do this stuff, then
END_OF ; jump to the end of the structure.
CASE_OF_BIT CON_DOWN, SET ; In the case of the UP bit being set,
<DOWN button action> ; do this stuff, then
END_OF ; jump to the end of the structure.
CASE_OF_BIT CON_LEFT, SET ; In the case of the UP bit being set,
<LEFT button action> ; do this stuff, then
END_OF ; jump to the end of the structure.
CASE_OF_BIT CON_RIGHT, SET ; In the case of the UP bit being set,
<RIGHT button action> ; do this stuff.
END_OF_ ; (No need to jump to the end here, unless you
; add default actions at the end to jump over.)
END_CASE
The explanations are in main article on this subject at http://wilsonminesco.com/StructureMacros/, with additional explanation of what the assembler does at assembly time in the "assembling program structures" chapter in the 6502 stacks treatise, at http://wilsonminesco.com/stacks/pgmstruc.html .
http://WilsonMinesCo.com/ lots of 6502 resources
-
- Posts: 1318
- Joined: Thu Apr 23, 2009 11:21 pm
- Location: cypress, texas
Re: Is there an easier way to do this?
Kasumi taught me something like this...
Make each nametable with your tiles like this (we have lots of screens like this... and my sister put them all in one file):
That is enough bytes for one entire screen... the commented numbers on top help us to quickly get to a specific 16x16 metatile.
We chose to do it this way because we can really easily change the screens if we want to... more easily than using a binary file as dougeff recommended, IMO.
I start my assigning nametable code out something like this:
Now that is better because the loop is smaller and quicker than dougeff's... his works... but he missed the importance of y in the lda (pointer), y. Instead of incrementing the pointer... you can keep the pointer the same until you are ready to load another screen... you can increment y and stop the loop after it reaches 00 again. This loop will work if your PPU is set to increment by 1 after each write to $2007, I think...
lda (pointer), y adds y to the pointer... just like lda address, x adds x to the address.
Since you are using asm6 you can use labels like - and +...
Restrictions:
- can only be used to branch to a lower address
+ can only be used to branch to a higher address
Benifits:
They are much better than the local @ labels, IMO, because you can use them over and over like: The + labels are exactly the same, but that is ok because when it branches to a + label it always branches to the first + label it finds... and asm6 does this perfectly every time. If you need another type of + label you can use ++ or +something. Same with the - lables. And you can do this to branch to the same line from either direction:
both bnes will branch to the lda #$01 if the Z flag is not set... cause when looking at the .lst files every new line is the same address until an instruction is found, then the next line will have a new address.
edit.
Make each nametable with your tiles like this (we have lots of screens like this... and my sister put them all in one file):
Code: Select all
screen_C_1:
; 0 1 2 3 4 5 6 7
.db $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF
.db $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF
.db $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF
.db $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF
.db $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF
.db $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF
.db $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF
.db $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF
.db $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF
.db $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF
.db $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF
.db $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF
.db $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF
.db $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF
.db $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF
We chose to do it this way because we can really easily change the screens if we want to... more easily than using a binary file as dougeff recommended, IMO.
I start my assigning nametable code out something like this:
Code: Select all
ldx #$00 ;screen 0
lda lvl1_floorC_lo, x
sta $10
lda lvl1_floorC_hi, x
sta $11
ldy #$00
;from dougeff....
lda #$20
sta $2006
lda #$00
sta $2006
;^good code :)
- lda ($10), y
sta $2007
iny
bne -
lda (pointer), y adds y to the pointer... just like lda address, x adds x to the address.
Code: Select all
lvl1_floorC_lo:
.dl screen_C_1, screen_C_2, screen_C_3 ;.dl gets the low byte value
lvl1_floorC_hi:
.dh screen_C_1, screen_C_2, screen_C_3 ;.dh gets the high byte value
Restrictions:
- can only be used to branch to a lower address
+ can only be used to branch to a higher address
Benifits:
They are much better than the local @ labels, IMO, because you can use them over and over like:
Code: Select all
iny
bpl +
;some code that only runs when y is negative
+ bmi +
;some code that only runs when the N flag is not set
+ ;more code
Code: Select all
bne +
;some code
-
+ lda #$01
;some code
;edit: note: the code here better be able to change the flags or you'll have an infinite loop
bne -
edit.
Re: Is there an easier way to do this?
Yes those are called nameless labels because they are nameless (just a colon) in other assemblers. But in asm6 they have names starting with + or - but are still called nameless labels because they work the same way, they are just more useful in asm6 than in the other assemblers.
I don't think they are more useful than local labels though, they have a slightly different use and I use them for shorter loops and such. For bigger routines, local labels are more useful because of their longer range.
I don't think they are more useful than local labels though, they have a slightly different use and I use them for shorter loops and such. For bigger routines, local labels are more useful because of their longer range.
-
- Posts: 1318
- Joined: Thu Apr 23, 2009 11:21 pm
- Location: cypress, texas
Re: Is there an easier way to do this?
They are more useful for me because + is only one character wide and you can use it over and over between local and normal lables... all local lables in a section have to be different so @ can only be used once in that section. And when I'm looking at a piece of code with nameless lables a bne +++ helps me to only have to search below for the first +++ label. I was looking at the famitone code trying to pad it so that all the branches end up on the same page and it took a while because shiru uses local labels.Pokun wrote:Yes those are called nameless labels because they are nameless (just a colon) in other assemblers. But in asm6 they have names starting with + or - but are still called nameless labels because they work the same way, they are just more useful in asm6 than in the other assemblers.
I don't think they are more useful than local labels though, they have a slightly different use and I use them for shorter loops and such. For bigger routines, local labels are more useful because of their longer range.
Why does a local label have a longer range than a nameless lable? Local lables and nameless lables both assemble the same way... at least I think so. A bne + and a bne @ both assemble to two bytes. I bet Loopy (asm6 creator) would have made nameless labels work the same as local labels. Please help me to understand what I'm missing Pokun, thanks for helping me!
edit: + labels don't have to have numbers in them; that is very helpful for me too... the less numbers I have to look at on the screen makes programming assembly easier for me.