Is there an easier way to do this?

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

DementedPurple
Posts: 318
Joined: Mon Jan 30, 2017 5:20 pm
Location: Colorado USA

Is there an easier way to do this?

Post by DementedPurple »

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?
User avatar
tokumaru
Posts: 12427
Joined: Sat Feb 12, 2005 9:43 pm
Location: Rio de Janeiro - Brazil

Re: Is there an easier way to do this?

Post by tokumaru »

I don't understand what you mean by "header". Can you show us a piece of the code that illustrates what you mean?
DementedPurple
Posts: 318
Joined: Mon Jan 30, 2017 5:20 pm
Location: Colorado USA

Re: Is there an easier way to do this?

Post by DementedPurple »

Here. The reason I have this ">" after each comment is because Notepad++ makes you have to have something to close a comment.

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>
What I meant by "header" were things like this

Code: Select all

NametableZoneGrass:
User avatar
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?

Post by FrankenGraphics »

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. :!:
DementedPurple
Posts: 318
Joined: Mon Jan 30, 2017 5:20 pm
Location: Colorado USA

Re: Is there an easier way to do this?

Post by DementedPurple »

But can I avoid using headers? It gets really confusing, even when I comment what every single command is doing!
DementedPurple
Posts: 318
Joined: Mon Jan 30, 2017 5:20 pm
Location: Colorado USA

Re: Is there an easier way to do this?

Post by DementedPurple »

DementedPurple wrote:But can I avoid using headers? It gets really confusing, even when I comment what every single command is doing!
I mean labels.
User avatar
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?

Post by FrankenGraphics »

The reason I have this ">" after each comment is because Notepad++ makes you have to have something to close a comment.
Bit of a sidenote, but you can fix this issue by selecting assembly as language in notepad++ (or by defining your own language).
User avatar
dougeff
Posts: 3078
Joined: Fri May 08, 2015 7:17 pm

Re: Is there an easier way to do this?

Post by dougeff »

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).
nesdoug.com -- blog/tutorial on programming for the NES
User avatar
dougeff
Posts: 3078
Joined: Fri May 08, 2015 7:17 pm

Re: Is there an easier way to do this?

Post by dougeff »

Your code has lots of oddities.

Code: Select all

LDA #0
STA NametableLoop
It's not clear what NametableLoop is, but it's not being used in the rest of your code.

Code: Select all

LDX #$23
STX $2007 ;Stores high byte>
STY $2007 ;Stores low byte>
STA $2006 ;Stores tile number>
2 writes to $2006 sets an address. Writes to $2007 pushes a byte to the PPU. Usually, you would see...

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
DementedPurple
Posts: 318
Joined: Mon Jan 30, 2017 5:20 pm
Location: Colorado USA

Re: Is there an easier way to do this?

Post by DementedPurple »

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.
Pokun
Posts: 2675
Joined: Tue May 28, 2013 5:49 am
Location: Hokkaido, Japan

Re: Is there an easier way to do this?

Post by Pokun »

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:

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:
FrankenGraphics wrote:
The reason I have this ">" after each comment is because Notepad++ makes you have to have something to close a comment.
Bit of a sidenote, but you can fix this issue by selecting assembly as language in notepad++ (or by defining your own language).
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.
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
Garth
Posts: 246
Joined: Wed Nov 30, 2016 4:45 pm
Location: Southern California
Contact:

Re: Is there an easier way to do this?

Post by Garth »

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:

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
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:

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
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 .
http://WilsonMinesCo.com/ lots of 6502 resources
unregistered
Posts: 1318
Joined: Thu Apr 23, 2009 11:21 pm
Location: cypress, texas

Re: Is there an easier way to do this?

Post by unregistered »

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):

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
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:

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 -
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.

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
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:

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
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:

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 -  
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.
Pokun
Posts: 2675
Joined: Tue May 28, 2013 5:49 am
Location: Hokkaido, Japan

Re: Is there an easier way to do this?

Post by Pokun »

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.
unregistered
Posts: 1318
Joined: Thu Apr 23, 2009 11:21 pm
Location: cypress, texas

Re: Is there an easier way to do this?

Post by unregistered »

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.
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.

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! :D :)

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. :)
Post Reply