SusiKette's project help thread

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

User avatar
tokumaru
Posts: 12427
Joined: Sat Feb 12, 2005 9:43 pm
Location: Rio de Janeiro - Brazil

Re: SusiKette's project help thread

Post by tokumaru »

Compressing to rows is cool because you can create a "library" of rows and reuse them throughout the levels, which will be fairly compressed when each row of 16 metatiles is replaced by a pointer to a compressed row. About 30 bytes per screen isn't bad.

If you use 256 or less unique rows per level you can cut the space they take by half, since you can use byte-sized indices to lookup the pointers to the compressed row data.
User avatar
rainwarrior
Posts: 8732
Joined: Sun Jan 22, 2012 12:03 pm
Location: Canada
Contact:

Re: SusiKette's project help thread

Post by rainwarrior »

tokumaru wrote:Compressing to rows is cool because you can create a "library" of rows and reuse them throughout the levels, which will be fairly compressed when each row of 16 metatiles is replaced by a pointer to a compressed row. About 30 bytes per screen isn't bad.

If you use 256 or less unique rows per level you can cut the space they take by half, since you can use byte-sized indices to lookup the pointers to the compressed row data.
Well, "metatiles" can be any shape, really. A 16x1 row can be effective, but so could, e.g. a 4x4 rectangle. Depends on the content of your game and what aligned shapes are most reusable.
User avatar
tokumaru
Posts: 12427
Joined: Sat Feb 12, 2005 9:43 pm
Location: Rio de Janeiro - Brazil

Re: SusiKette's project help thread

Post by tokumaru »

Rows are very good for vertical scrolling, so that's a plus. I do appreciate the use of multiple levels of metatiles too (in this case, 256x16 rows made of 16x16 blocks), it can really help improve compression without increasing the complexity too much.
User avatar
SusiKette
Posts: 147
Joined: Fri Mar 16, 2018 1:52 pm
Location: Finland

Re: SusiKette's project help thread

Post by SusiKette »

tokumaru wrote:If you use 256 or less unique rows per level you can cut the space they take by half, since you can use byte-sized indices to lookup the pointers to the compressed row data.
That would be really nice (and maybe a bit easier too), however it entirely depends on the stage's design. Maybe I should take care of that first.
Avatar is pixel art of Noah Prime from Astral Chain
User avatar
SusiKette
Posts: 147
Joined: Fri Mar 16, 2018 1:52 pm
Location: Finland

Re: SusiKette's project help thread

Post by SusiKette »

I've been trying to figure out a way to do the level data decoding but I haven't really been making much progress at all and I'm starting to feel like I need a bit more help on it. Maybe some example code or something?
Avatar is pixel art of Noah Prime from Astral Chain
User avatar
Kasumi
Posts: 1293
Joined: Wed Apr 02, 2008 2:09 pm

Re: SusiKette's project help thread

Post by Kasumi »

Arranged in smallest piece to largest piece:

Code: Select all

metatiletopleft:;Tile index of the top left corner of each 16x16 metatile
.db $00, $01, $02, $03
metatiletopright:;Tile index of the top right corner of each 16x16 metatile
.db $04, $05, $06, $07
metatilebottomleft:;Tile index of the bottom left corner of each 16x16 metatile
.db $08, $09, $0A, $0B
metatilebottomright:;Tile index of the bottom right corner of each 16x16 metatile
.db $0C, $0D, $0E, $0F
metatilepalette:;palette of each 16x16 metatile
.db 0, 1, 2, 3

;A 16x16 metatile is made of 8x8 tiles

row 0:;A row contains 16x16 tile indices.
.db 0, 1, 2, 3, 0, 3, 2, 1, 2, 0, 1, 3, 2, 0, 3, 1
row1:
.db 0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3

;A row is made of 16x16 metatiles

rowlow:;Half the address to access a specific row index
.db low(row0);If you're not using NESASM, you'll want .db <row0 instead
.db low(row1)
rowhigh:;The other half
.db high(row0);If you're not using NESASM, you'll want .db >row0 instead
.db high(row1)

;A list of rows is made up of references to rows

level0:
.db 0, 0, 1, 0, 1
;A level is made of row numbers
To load it, you go from largest to smallest.
The scroll will tell you which row in level to use.
The address from that row will give you access to the metatile indices that make it up.
The metatiles indices will give you access to tile numbers and attributes.

Code: Select all

;The scroll position will tell us what which row we're on.
;We divide it by 16 to get the row we need to draw
lda scrollylow
sta temp1
lda scrollyhigh
lsr a;shift the high byte into the carry
ror temp1;Shift the carry into the high byte of a copy of the low position of the scroll
;We have just done a 16bit divide by 2. 

lsr a
ror temp1
;now we've divided by 4

lsr a
ror temp1
;now we've divided by 8

lsr a
ror temp1
;now we've divided by 16

ldy temp1;This is the row we want to load.
lda level0,y;We get which row we need to draw at that position

tay
lda rowlow,y;We get the low byte of where that row is
sta temp1

lda rowhigh,y;We get the high byte of where that row is.
sta temp2

ldy #0
metatileloop:
lda [temp1],y;If you're not using NESASM, you'll want lda (temp1),y instead
;The above loads the first byte of the given address. So, the leftmost metatile. Let's just copy it in a loop for now
sta metatiledump,y
iny
cpy #16
bne metatileloop

ldy #0;Now we write the left and right side of the top of the metatile alternating
metadrawloop:
lda metatiledump,y
tax;Get the index of the metatile so we can read the table
lda metatiletopleft,x
sta $2007
lda metatiletopright,x
sta $2007
iny
cpy #16
bne metadrawloop
;Top half is copied. Now the bottom half
ldy #0
metadrawloop2:
lda metatiledump,y
tax
lda metatilebottomleft,x
sta $2007
lda metatilebottomright,x
sta $2007
iny
cpy #16
bne metadrawloop2
;Done
Two things are not shown.
One, how to set up attributes.
Two, how to get the proper nametable address for the row. (But hint, the scroll position also tells you this.)
The code is untested, but even if it's subtly broken it should at least give you an idea of how this works. Just large pieces made of small pieces. You use what you know to get the position in the largest piece, then work down using the references.

Edit: Hah. Well there was one mistake. I copied the top twice instead of the top and the bottom. Fixed now. I'll keep looking.
Edit2: Standard warning. You can't write to $2007 whenever, it has to be when rendering is disabled or early in your NMI. One way to make the above code work is to just move the part that reads from metatiledump and stores to $2007 to your NMI.
Edit3: Found a missing '#' symbol too. :wink: Fixed.
User avatar
SusiKette
Posts: 147
Joined: Fri Mar 16, 2018 1:52 pm
Location: Finland

Re: SusiKette's project help thread

Post by SusiKette »

What about screens? I'm using them as a part of the compression as well. It'd be easier to loop sections of the level that way when you can just repeat the same screen multiple times.

EDIT: As a reference my level data looks like this:

Code: Select all

MetatileList:
T00:			; blank tile
 .db $00,$00,$00,$00

T01:			; test tile
 .db $1D,$0E,$1C,$1D

T02:			; question mark tile
 .db $26,$26,$26,$26

T03:			; score part 1
 .db $90,$91,$00,$00

T04:			; score part 2
 .db $92,$93,$00,$00

T05:			; lives part 1
 .db $94,$95,$00,$00

T06:			; lives part 2
 .db $96,$97,$00,$00

T07:			; bombs part 1
 .db $98,$99,$00,$00

T08:			; bombs part 2
 .db $9A,$9B,$00,$00

RowList:
R00:			; blank row
 .dw T00,T00,T00,T00,T00,T00,T00,T00,T00,T00,T00,T00,T00,T00,T00,T00

R01:			; test row
 .dw T01,T01,T01,T01,T01,T01,T01,T01,T01,T01,T01,T01,T01,T01,T01,T01

R02:			; question mark row
 .dw T02,T02,T02,T02,T02,T02,T02,T02,T02,T02,T02,T02,T02,T02,T02,T02

R03:			; status row
 .dw T00,T00,T03,T04,T00,T00,T00,T05,T06,T00,T00,T00,T07,T08,T00,T00

ScreenList:
S00:			; test screen 1 
 .dw R00,R01,R00,R01,R00,R01,R00,R01,R00,R01,R00,R01,R00,R01,R00

S01:			; test screen 2
 .dw R02,R02,R02,R02,R02,R02,R02,R00,R02,R02,R02,R02,R02,R02,R02

S02:			; test screen 3
 .dw R00,R00,R03,R00,R00,R03,R00,R00,R03,R00,R00,R03,R00,R00,R03

LevelList:
L01:			; test level
 .dw S00,S01,S02,S00,S01,S02
Avatar is pixel art of Noah Prime from Astral Chain
User avatar
Kasumi
Posts: 1293
Joined: Wed Apr 02, 2008 2:09 pm

Re: SusiKette's project help thread

Post by Kasumi »

If you want screens, you add screens.

In my example, levels were made of rows. To load the rows, you found out where you were in the level, then loaded that row.
If you add screens, then levels are made of screens made of rows.

So you find where you are in the level, to get which screen, then find where you are in the screen to get that row.
Edit:

Code: Select all

lda scrollyhigh
asl a;Since you're storing both bytes of the screen together in your Levels. Multiply by 2 to get the offset for the start
tay;of the address.
lda L01,y;Get the low byte of the screen you're on
sta templo
iny
lda L01,y;Get the high byte of the screen you're on
sta temphi

lda scrollylow;Divide by 16 like before, except we don't need to do 16 bit because we already know what screen we're one
lsr a
lsr a
lsr a
lsr a
tay
lda (templo),y;Get the row.
unregistered
Posts: 1318
Joined: Thu Apr 23, 2009 11:21 pm
Location: cypress, texas

Re: SusiKette's project help thread

Post by unregistered »

Kasumi wrote: ;A row is made of 16x16 metatiles

rowlow:;Half the address to access a specific row index
.db low(row0);If you're not using NESASM, you'll want .db <row0 instead
.db low(row1)
rowhigh:;The other half
.db high(row0);If you're not using NESASM, you'll want .db >row0 instead
.db high(row1)
Small note: if someone is using asm6 they can just type:

Code: Select all

rowlow:
.dl row0, row1 ;dl writes the low byte of an address
rowhigh:
.dh row0, row1 ;dh writes the high byte of an address
That's really convenient and cool for me because we just have to type out the .dl line and then copy/paste it (and just change the new line's l to a h). :) Thank you Loopy! :mrgreen: :D
User avatar
tokumaru
Posts: 12427
Joined: Sat Feb 12, 2005 9:43 pm
Location: Rio de Janeiro - Brazil

Re: SusiKette's project help thread

Post by tokumaru »

I THINK you can use .EQU to create a symbol containing all the elements, so you don't have to copy & paste the list every time you change it. This might work (haven't tested it):

Code: Select all

rows .equ row0, row1, row2

rowlow:
  .dl rows
rowhigh:
  .dh rows
unregistered
Posts: 1318
Joined: Thu Apr 23, 2009 11:21 pm
Location: cypress, texas

Re: SusiKette's project help thread

Post by unregistered »

^that's really cool tokumaru! Thank you for that idea; it works wonderfully! :mrgreen: :D

edit: caution: this will make changing the file easier, like tokumaru said, but the characters used in the asm6 .lst file will be a little more than 1.5 times as many as needed for the copy/paste method. But, if filesize isn't an issue, go for it. :) Small changs to identical lines to avoid copy/paste isn't much of a problem for me. :)

edit2: When you consider that the section of each asm file you use this .equ optimization in will be about half as many characters in size, you actually save space, I think. :) 8 characters x 1.5 x 0.5 == 6 characters. Each of asm6's .lst lines has an added 32 characters so using .equ is a pretty even way of having less to change. Thanks again tokumaru! :)

final edit: Each line of characters saved would have to be at least 128 characters in length, I think, to break even with the 32 added characters appended to the front of each line in an asm6 .lst file. :)
Post Reply