Yes, tokumaru, that makes sense to me. Thank you! ...I need to redo my attribute table code... cause it would save so much space if keep accessing the pallet values through my sister's metatile definitions. That will be challenging to accomplish... but space will be saved. It'll be a learning experience!tokumaru wrote:If you have a level, you should have a level map. Storing straight name/attribute table is not efficient at all, even if you compress each screen, because you don't take advantage of all the redundancy of blocks that repeat.unregistered wrote:Wat is the level map? What info does it hold other than the metatile index? I don't think I have a level map.
A level map is usually a 2D grid of metatiles, where each byte is a metatile index. The level map can be used both for rendering (read a row/column of metatiles from the map, use their indexes to load the tiles that make up the metatiles and write them to VRAM) and for collision (get the metatile index and use it to get the metatile's collision information).
Here's a straightforward representation of a level that uses 32x32-pixel (4x4 tiles) metatiles:See how everything connects? Whenever you want to check what is present at a certain map location, you convert the coordinates into an index which you can use to read a metatile index from the level map. Once you know the metatile's index, you can use it to get any information you want about it. If you are rendering the background, read from the MatatileTileXX tables and and write the tile indexes to the name tables, and the attributes to the attribute tables. If you're testing for collisions, read from the MetatileCollision table to know how the objects should react to the metatile.Code: Select all
;16 tiles for each of the 4 metatiles ;metatile: $00 $01 $02 $03 MatatileTile00: .db $00, $04, $08, $0c MatatileTile01: .db $00, $04, $09, $0c MatatileTile02: .db $00, $04, $0a, $0c MatatileTile03: .db $00, $04, $0a, $0c MatatileTile10: .db $00, $05, $0b, $0d MatatileTile11: .db $00, $05, $0a, $0c MatatileTile12: .db $00, $05, $0a, $0c MatatileTile13: .db $00, $05, $0b, $0d MatatileTile20: .db $00, $06, $06, $0e MatatileTile21: .db $00, $06, $06, $0c MatatileTile22: .db $00, $06, $06, $0c MatatileTile23: .db $00, $06, $06, $0e MatatileTile30: .db $00, $07, $07, $0f MatatileTile31: .db $00, $07, $07, $0f MatatileTile32: .db $00, $07, $07, $0f MatatileTile33: .db $00, $07, $07, $0f MatatileAttributes: ;4 palette indexes for each metatile .db %00000000 ;palettes for metatile $00 .db %01010101 ;palettes for metatile $01 .db %01010000 ;palettes for metatile $02 .db %11000011 ;palettes for metatile $03 MetatileCollision: ;%00 = air, %01 = solid, %10 = water, %11 = hazard .db %00000000 ;metatile $00 is all air .db %01010101 ;metatile $01 is all solid .db %01010000 ;only the top of metatile $02 is solid .db %11110101 ;the top of metatile $03 hursts the player, the bottom is solid LevelMap01: ;this small level is 16x8 metatiles (512x256 pixels) large .db $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00 .db $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00 .db $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00 .db $00, $00, $00, $00, $00, $02, $02, $00, $00, $00, $00, $00, $00, $00, $00, $00 .db $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $02, $02 .db $00, $00, $00, $01, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00 .db $01, $01, $01, $01, $00, $00, $00, $00, $00, $01, $01, $01, $03, $03, $01, $01 .db $01, $01, $01, $01, $00, $00, $01, $01, $01, $01, $01, $01, $01, $01, $01, $01
Se how one thing points to another, that points to another and so on? This is how you manage to reuse your level data effectively compressing it to much less than it would be if you had raw name/attribute table data. The data above is just 200 bytes. If I were to represent that in uncompressed form, this 2-screen wide level would have been 2048 bytes large.
Anyway, this is not a lesson about compression (although the maps are indeed compressed), this is to show you one of the possible ways in which a level can be represented, and how you can access everything on it. Does this make sense to you?
8x16 and whatever else unreg wants to know
Moderator: Moderators
-
- Posts: 1318
- Joined: Thu Apr 23, 2009 11:21 pm
- Location: cypress, texas
Re: 8x16 sprite is really a 16x32 pixel image?
Last edited by unregistered on Fri Sep 28, 2012 3:43 pm, edited 3 times in total.
Re: 8x16 sprite is really a 16x32 pixel image?
In your metatile stream, you can use the top 2 bits of the unused metatile data for the attribute info.
Re: 8x16 sprite is really a 16x32 pixel image?
Mine was just a straightforward example of how a bare bones metatile system works. Details such as metatile dimensions, types of collision, palette selection, bit ordering, etc. should be defined by the programmer according to the needs of their game.
-
- Posts: 1318
- Joined: Thu Apr 23, 2009 11:21 pm
- Location: cypress, texas
Re: 8x16 sprite is really a 16x32 pixel image?
Sorry for editing my post up there 3 times! It's how it should be? With all of tokumaru's post copied from the bottom of the previous page? It could be bad for slow internet connections... but it kind of helps someone from having too much trouble going to the bottom of the previous page... in my opinion. Thanks 3gengames. And thanks, tokumaru, that helps me too.
-
- Posts: 1318
- Joined: Thu Apr 23, 2009 11:21 pm
- Location: cypress, texas
Re: 8x16 sprite is really a 16x32 pixel image?
Ok... so we represent a screen with 240 bytes if we have 16x16 metatiles... cause it takes less space! But to move a player around in a screen it would require 240 x 4 = 960 bytes? Right? So there needs to be a routine that would create the screen in 960 bytes of memory. Then collision detection would be possible for me, I guess. I cant overcome the want of 960 bytes per screen.
Re: 8x16 sprite is really a 16x32 pixel image?
You don't need to unpack metatile data of the whole screen to do collision, you can just 'unpack' certain tile in a given position when you need to check it.
Re: 8x16 sprite is really a 16x32 pixel image?
That depends on how large you want your maps to be... Ideally, you will not store the whole map in RAM unless you're using extra WRAM, because the measly 2KB the NES has isn't enough for large levels, specially considering that this memory has to be used for many other things besides levels. If you are mot applying any other kind of compression on top of the metatiles, the obvious choice would be to just leave the maps in ROM. If you do decide to keep the maps in RAM, you'll need at least 3 screens worth of data that you can reload as the screen scrolls.unregistered wrote:But to move a player around in a screen it would require 240 x 4 = 960 bytes?
As long as you can read data from the map, which you need to do for rendering the tiles to the name tables, for example, you can just as easily check for the solidity of the metatiles.Then collision detection would be possible for me, I guess.
Again, they don't need to be in RAM. You can access level maps from ROM just fine.I cant overcome the want of 960 bytes per screen.
-
- Posts: 1318
- Joined: Thu Apr 23, 2009 11:21 pm
- Location: cypress, texas
Re: 8x16 sprite is really a 16x32 pixel image?
I'm so glad I asked that question! THANK YOU SHIRU! That's a great name for my method 'unpack'! It is going to replace my collisionU method!Shiru wrote:You don't need to unpack metatile data of the whole screen to do collision, you can just 'unpack' certain tile in a given position when you need to check it.
Itokumaru wrote:As long as you can read data from the map, which you need to do for rendering the tiles to the name tables, for example, you can just as easily check for the solidity of the metatiles.unregistered wrote:Then collision detection would be possible for me, I guess.
I'm so excited... right now about trying collision without having to create a table/map correctly first!! It's so much good news and happiness for me, Thank you so much Shiru and tokumaru!!!!
---
3gengames, I understand now why you didn't understand my need for a method that would allow either an increment by 2 or an increment by 32. I don't need it anymore!! I don't have to create a perfect table copy first because I already have access to the info.
edit
-
- Posts: 1318
- Joined: Thu Apr 23, 2009 11:21 pm
- Location: cypress, texas
Re: 8x16 sprite is really a 16x32 pixel image?
I'm working on GRAVITY now.... Our girl character falls down to the bottom of the screen. And keeps falling... through four screens... and then stops above the two metatile rows of solid ground. Why does she fall four screens before stopping?unregistered wrote:I'm so glad I asked that question! THANK YOU SHIRU! That's a great name for my method 'unpack'! It is going to replace my collisionU method!Shiru wrote:You don't need to unpack metatile data of the whole screen to do collision, you can just 'unpack' certain tile in a given position when you need to check it.
Ok, she stays on the same screen... she falls from top to the bottom like I want it to, but she keeps falling through the top of the screen and goes down toward the bottom. Sorry, it's lunch time right now and so I hope to return to an answer... if not I'll figure it out myself, i think.
Code: Select all
0C3AF unpack:
0C3AF
0C3AF ;128 64 32 16 8 4 2 1
0C3AF ;$80 $40 $20 $10 $08 $04 $02 $01
0C3AF A9 26 lda #<MetatileRhombus
0C3B1 85 0C sta rhombusCollision_low
0C3B3 A9 C8 lda #>MetatileRhombus
0C3B5 85 0D sta rhombusCollision_high
0C3B7
0C3B7 A9 05 lda #<MetatileCollision
0C3B9 85 1A sta UCollision_low
0C3BB A9 C9 lda #>MetatileCollision
0C3BD 85 1B sta UCollision_high
0C3BF
0C3BF A5 04 lda oX ;getting ready to divide my X coordinate by 8
0C3C1 4A lsr a
0C3C2 4A lsr a
0C3C3 4A lsr a
0C3C4 AA tax
0C3C5 ;sta tC+0
0C3C5
0C3C5
0C3C5 A5 05 lda oY
0C3C7 4A lsr a ;divide my Y coordinate by (2 * 2 * 2) = 8
0C3C8 4A lsr a ;<----------------------------*
0C3C9 4A lsr a ;<--------------------------------*
0C3CA A8 tay
0C3CB 85 32 sta tC+1
0C3CD
0C3CD ;x*16=row
0C3CD ;row+y=LINEAR_POSITION
0C3CD
0C3CD 8A txa
0C3CE 0A asl a ;multiply x by (2 * 2 * 2 * 2) = 16
0C3CF 0A asl a ;<----------------*
0C3D0 0A asl a ;<--------------------*
0C3D1 0A asl a ;<------------------------*
0C3D2 85 33 sta linearly
0C3D4
0C3D4 18 clc
0C3D5 98 tya
0C3D6 65 33 adc linearly
0C3D8 85 33 sta linearly
0C3DA A8 tay
0C3DB
0C3DB ;GRAVITY
0C3DB C6 31 dec tC
0C3DD 10 02 bpl +skip
0C3DF
0C3DF B1 0C lda (rhombusCollision_low), y
0C3E1 +skip
0C3E1 ;if the metatile is not solid
0C3E1 0A asl a ;<pushes bit #7 into carry.
0C3E2 90 09 bcc +skip
0C3E4
0C3E4 ;then fall to the metatile below...
0C3E4
0C3E4 B1 1A lda (UCollision_low), y
0C3E6 A5 05 lda oY
0C3E8 18 clc
0C3E9 69 08 adc #8
0C3EB 85 05 sta oY
0C3ED +skip
0C3ED
0C3ED E6 31 inc tC
0C3EF
0C3EF 60 rts ;end of unpack and end of daprg-collisionU
Re: 8x16 sprite is really a 16x32 pixel image?
There must be something wrong with the way you're reading the map... I can't make sense of the formula you're using to read metatiles... is it (x / 8) * 16 + (y / 8)? I have no idea what you're trying to do with that.
The exact formula obviously depends on how your maps are stored, but the typical way to convert pixel coordinates into map offsets is more like (y / MetatilesWidth) * NumberOfMetatilesPerRow + (x / MetatileHeight). So if your metatiles are 16x16 pixels the formula is (y / 16) * 16 + (x /16). If your metatiles are 32x32 pixels, it's (y / 32) * 8 + (x / 32). Once you have that offset, you can read a the index of the metatile at that location of the map and check it's collision information.
EDIT: Damn 8) smiley!
The exact formula obviously depends on how your maps are stored, but the typical way to convert pixel coordinates into map offsets is more like (y / MetatilesWidth) * NumberOfMetatilesPerRow + (x / MetatileHeight). So if your metatiles are 16x16 pixels the formula is (y / 16) * 16 + (x /16). If your metatiles are 32x32 pixels, it's (y / 32) * 8 + (x / 32). Once you have that offset, you can read a the index of the metatile at that location of the map and check it's collision information.
EDIT: Damn 8) smiley!
Last edited by tokumaru on Fri Oct 05, 2012 1:54 pm, edited 1 time in total.
-
- Posts: 1318
- Joined: Thu Apr 23, 2009 11:21 pm
- Location: cypress, texas
Re: 8x16 sprite is really a 16x32 pixel image?
YEAY THANK YOU SO MUCH TOKUMARU! Ok... ... My idea was pulled out of this post:tokumaru wrote:There must be something wrong with the way you're reading the map... I can't make sense of the formula you're using to read metatiles... is it (x / 8) * 16 + (y / 8)? I have no idea what you're trying to do with that.
I guess the part that was kind of confusing to me was when Bregalad said, "There is 7 rows so, multiply the row index by 7." There aren't 7 rows!!! More like 3 rows I think. 4 rows numbered 0 through 3. So that was difficult for me.Bregalad[color=#BFFF40], in a link from [url=http://forums.nesdev.com/viewtopic.php?p=83391#p83391]tokumaru's post on page 19[/url],[/color] wrote:You have a table arranged like this (I put random characters in it) :
A X U W O L T
S Z W K Y M Q
S U E U W P E
Q S G E I S L
Now you want to got the P that is in the third row and the sixth column.
You have two indexes, one is 2 (the count starts from zero, so the third row is number 2), and 5 (also start from zero).
The problem is that all the table is stored lineary in your ROM, so there it would be :
A X U W O L T S Z W K Y M Q S U E U W P E Q S G E I S L
The formula to know witch letter is the one you're looking for is :
There is 7 rows so, multiply the row index by 7. Scince the last letter of row 0 is at column 6, the first letter of the second row will be just after it, so effectively it's number 7.
Then, just add the column index to get the final index number.
In that case, 2*7=14; 14+5=19
Looking in the list above, the 20th letter is effectively P. It's number 19, but it's the 20th because the index starts from zero.
Yes, ok I'm going to try this out, tokumaru, right now...tokumaru wrote:The exact formula obviously depends on how your maps are stored, but the typical way to convert pixel coordinates into map offsets is more like (y / MetatilesWidth) * NumberOfMetatilesPerRow + (x / MetatileHeight). So if your metatiles are 16x16 pixels the formula is (y / 16) * 16 + (x /16). If your metatiles are 32x32 pixels, it's (y / 32) * 8 + (x / 32). Once you have that offset, you can read a the index of the metatile at that location of the map and check it's collision information.
-
- Posts: 1318
- Joined: Thu Apr 23, 2009 11:21 pm
- Location: cypress, texas
Re: 8x16 sprite is really a 16x32 pixel image?
Isn't that equal to y + (x/16) ? That seems crazy to me.tokumaru wrote:The exact formula obviously depends on how your maps are stored, but the typical way to convert pixel coordinates into map offsets is more like (y / MetatilesWidth) * NumberOfMetatilesPerRow + (x / MetatileHeight). So if your metatiles are 16x16 pixels the formula is (y / 16) * 16 + (x /16).
edit: that's quite a lot to think about... I have to go mow now. So I'll have to come back to this...
Re: 8x16 sprite is really a 16x32 pixel image?
Bregalad's formula is correct, for when there are 7 items per row. The formula for reading data from a 2D array which is stored in memory linearly is always Y * ElementsPerRow + X, that doesn't change. But you also have to take into consideration that the base unit is the type of element you are accessing, in this case, metatiles. If you have pixel coordinates, you have to first convert them to metatile coordinates, hence the need to divide both X and Y by the dimensions of your metatiles before applying that formula.
Another thing that will affect how you apply the formula is how your levels are stored in RAM/ROM. If you store it screen by screen, then ElementsPerRow will always be the same, because the number of metatiles per screen doesn't change. If you don't divide your level in screens, then ElementsPerRow will be the length of the entire level, and it will vary from level to level. IMO, things are easier if you divide the levels into screens, because the metatile offsets will always fit in 8 bits, and the multiplications/divisions can be easily done with shifts.
Here comes another (untested) example of data arrangement (and how to access it):
Now you can do whatever you want with that information. Want to know if that metatile is solid? Do something like this:
Want to know what is the top left tile of that metatile?
Another thing that will affect how you apply the formula is how your levels are stored in RAM/ROM. If you store it screen by screen, then ElementsPerRow will always be the same, because the number of metatiles per screen doesn't change. If you don't divide your level in screens, then ElementsPerRow will be the length of the entire level, and it will vary from level to level. IMO, things are easier if you divide the levels into screens, because the metatile offsets will always fit in 8 bits, and the multiplications/divisions can be easily done with shifts.
Here comes another (untested) example of data arrangement (and how to access it):
Code: Select all
LevelMap:
;first screen:
.db $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00
.db $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00
;(another 12 rows here)
.db $10, $10, $10, $10, $10, $10, $10, $10, $10, $10, $10, $10, $10, $10, $10, $10
;second screen:
.db $00, $00, $00, $00, $00, $10, $10, $00, $00, $00, $00, $00, $00, $00, $00, $00
.db $00, $00, $00, $00, $00, $10, $10, $00, $00, $00, $00, $00, $00, $00, $00, $00
;(another 12 rows here)
.db $10, $10, $20, $20, $20, $20, $20, $20, $20, $21, $22, $23, $00, $00, $00, $00
;third screen:
.db $22, $00, $22, $00, $22, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00
.db $22, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00
;(another 12 rows here)
.db $20, $20, $20, $20, $10, $10, $10, $10, $10, $10, $10, $10, $10, $10, $10, $10
;(keep going for as many screens as you want)
;Reads the index of the metatile at position (PointX, PointY) in the level map
ReadMetatile:
;get the index of the screen
ldy PointX+1
;multiply it by 240 (using a look-up table) and add it to the base
;address of the level to create a pointer to the screen we need
clc
lda ScreenOffsetLo, y
adc LevelMap+0
sta ScreenPointer+0
lda ScreenOffsetHi, y
adc LevelMap+1
sta ScreenPointer+1
;calculate the index of the metatile: (Y / 16) * 16 + (X / 16)
lda PointX+0
lsr
lsr
lsr
lsr
sta Temp
lda PointY+0
and #%11110000
ora Temp
tay
;read the metatile
lda (ScreenPointer), y
;return
rts
Code: Select all
tax
lda MetatileCollision, x
and #BITTHATINDICATESWHETHERAMETATILEISSOLID
beq +NotSolid
Code: Select all
tax
lda MetatileTopLeft, x
Re: 8x16 sprite is really a 16x32 pixel image?
Not really, because the lower 4 bits are cleared in the process of shifting right and back left. So as a shortcut you can just do AND #%11110000 instead of LSR LSR LSR LSR ASL ASL ASL ASL. It works the same. Stop thinking about math as you learned in school for a minute and try to see it as the 6502 sees it (which is much simpler than what you learned in school, IMO).unregistered wrote:Isn't that equal to y + (x/16) ? That seems crazy to me.(y / 16) * 16 + (x /16).
-
- Posts: 1318
- Joined: Thu Apr 23, 2009 11:21 pm
- Location: cypress, texas
Re: 8x16 sprite is really a 16x32 pixel image?
WOW!!!!!tokumaru wrote:Not really, because the lower 4 bits are cleared in the process of shifting right and back left. So as a shortcut you can just do AND #%11110000 instead of LSR LSR LSR LSR ASL ASL ASL ASL. It works the same. Stop thinking about math as you learned in school for a minute and try to see it as the 6502 sees it (which is much simpler than what you learned in school, IMO).unregistered wrote:Isn't that equal to y + (x/16) ? That seems crazy to me.(y / 16) * 16 + (x /16).
A
G
edited once more: aaaah... tokumaru, thank you very very much!
edits #2: Good morning! Goodness... i was really sleepy... sorry. THANK YOU SO VERY VERY VERY MUCH TOKUMARU!!