Celius, this is how I plan on reading a single metatile out of my level map, which is compressed somewhat similarly to yours:
Code: Select all
;-- SUBROUTINE --------------------------------------------------
; Reads a single metatile from the level map.
; A: index of the metatile inside the screen;
; X: Y coordinate of the screen;
; Y: X coordinate of the screen;
; A: index of the metatile;
;DESTROYS: A, Y;
lda #(>ScreenLarge0) >> 2
lda #(>LargeMedium0) >> 2
lda #(>MediumSmall0) >> 2
lda #(>SmallMeta0) >> 2
;Set addresses of the data to read based on the coordinates of the metatile
;Get the index of the screen map
;Y must be zero because it's not used to index data
;Read from structure to structure until reaching the metatile
lda (LargeBlock0), y
lda (MediumBlock0), y
lda (SmallBlock0), y
lda (Metatile0), y
Obviously, in order to find the ID (I usually call this "index") of a specific metatile inside the level I must have it's coordinates. They conveniently fit into 24 bits, so I use the three 6502 registers to hold this information:
This makes it possible to point to any metatile in the level. Registers X and Y hold the highest bits of each coordinate, so they are directly used to fetch the screen ID. This would be the call to "ReadScreenMap" which just uses a pre-calculated table (which has pointers to each row of the level map) to read a screen from the level map. This happens pretty quick.
After I got the screen ID, I must read from structure to structure until I reach the metatile. To do that as quickly as possible, I set up various pointers to the data I need, and have these pointers modified by the coordinates of the metatile, because they are what in fact dictate what I want to read. These are the structures I have, all the way to the metatile:
Screen = 2x2 large blocks (256x256 pixels)
Large block = 2x2 medium blocks (128x128 pixels)
Medium block = 2x2 small blocks (64x64 pixels)
Small block = 2x2 metatiles (32x32 pixels)
Metatile = 2x2 tiles (16x16 pixels)
Since each of these structures is composed by 4 smaller structures there are 4 possibilities for each pointer. I first initialize the pointers with a base value, without the bits that decide which of the 4 possible areas are going to be read. Then I just shift the coordinate bits out of the accumulator into the appropriate pointers. The lower byte of the pointers is always the ID of the previous structure.
Now that I think of it, it would probably be faster to transfer that index to Y and always keep the lower byte of the pointers as 0. But the idea is the same. I haven't tested this specific piece of code yet, because I didn't get to the point of reading individual metatiles, so I don't even know how long it takes to execute.
I'm also rewriting much of my code now, after the switch to MMC3, and I do see some room for improvement here, so keep in mind that this isn't *exactly* what I'll be using in the final game.
For reading rows and columns I use a completely different piece of code (which does work) that is optimized to read a series of metatiles at once. It needs twice as many pointers, because 2 structures are read from each structure this time, but the main idea of the pointers being modified by the coordinates is still there. It is a big unrolled loop, so it's pretty quick.
I think it is absolutely possible to have your levels decoded in real time if you have it encoded like this. I can say for sure it works well when scrolling. I may not have tested the single metatile code yet, but you don't usually need to read that many single metatiles. You often just need to read about 5 of them for the main character collision, 1 or 2 for each enemy and that's probably it. So this part may not need to be extra fast, I guess.
Of course you need to have your data interleaved for this to work. Some of you may have noticed that I reeeaaally like interleaved data, because it's usually easier (and faster) to read. It's not as easy to define though, but that should not be a concern. I believe that data should be stored in the best possible way for the program, not for the programmer. If something is too difficult for humans to understand, you can always write simple convertion apps to do the work for you.
Anyway, by "interleaved" (I'm not sure if this is the best word for this, so I feel like I should explain) I mean that the top left tile of all metatiles are stored sequentially, then come all the top right tiles, then the bottom left tiles, and so on. Grouping similar entities together makes it simpler to index them (for example, you can read all the tiles of a metatile by using the index of the metatile, without having to increment it after each read tile). It surely is possible to manipulate pointers if you have you data arranged in other ways, but it would probably not be as fast, as the bits of interest might then not be placed so conveniently.