Originally, my concept was to assign each tile number an attribute (or maybe rows of tiles an attribute ... $0-$0F= walkable, $10-$4F = solid, etc...) then use the grid position to determine what 'tile' was in the player's position to handle the result.
I am not sure what the most efficient way to handle this data would be. I know that the former would allow for more diversity while the latter seems might be easier to implement? But aside from that - I'm not completely sure the best way to store this data (something similar to loading nametable, perhaps?) or the best way to actually find the character's grid position (I can do this easily with multiplication and division in other environments, but trying to figure out the most efficient way to do it in ASM).
I have found a few methods. Are there any you guys would suggest? (for right now, simple top down engine, no screen scrolling).
Collision data in particular is often associated with metatiles. Very few games recognize individual tiles as entities of the level, tiles are almost exclusively a graphical element used to represent a portion of a metatile. That's probably because without an MMC5 the NES is unable to color individual tiles arbitrarily, so it makes sense to always process them in groups of 2x2, and that includes collisions.
One advantage of making the collision data part of the metatiles' attributes is that you can use the same code that decodes the level map to the screen to read the collision data. If you had a separate grid for collisions somewhere else you'd probably need different logic to access it.
Keep in mind that even if your collision data is arranged as a grid of 16x16-pixel blocks, you can have finer collision resolution if you store more than "solid" or "empty" for each block. Slopes for example are often done by specifying the height of each of the 16 columns in the block.
Since you don't seem to be working with metatiles yet, only (compressed?) name/attribute table data, and you don't scroll, maybe you'd benefit from a separate collision map for each screen. First you have to decide how many solidity states you have. If it's just "solid" vs. "empty", 1 bit per block will do. If you have to include "water", "hazard", and so on you'll obviously need more bits per block. Once you have figured out how many states you'll need, you have to group the bits into bytes and arrange them in a 2D array.
For example, if you have 4 solidity states you need 2 bits per block, meaning that 1 byte can hold the attributes of 4 blocks. If the collision blocks are 16x16 pixels, each screen has 16x15 = 240 blocks. Divided by 4 in each byte that's 60 bytes of collision date per screen.
You can manually define this data with .db statements, like this:
Code: Select all
Screen00Collision: .db %00000000, %00000000, %00000000, %00000000 ;1st row of collision blocks .db %00000000, %00000000, %00000000, %00000000 ;2nd row of collision blocks .db %00000000, %00000000, %00000000, %00000000 ;3rd row of collision blocks .db %00000000, %00000000, %00000000, %00000000 ;4th row of collision blocks .db %00000000, %00000000, %00000000, %00000000 ;5th row of collision blocks .db %00000000, %00000000, %00000000, %00000000 ;6th row of collision blocks .db %00000000, %00000000, %00111111, %11000000 ;7th row of collision blocks .db %00000000, %00000000, %00000000, %00000000 ;8th row of collision blocks .db %00000000, %00000000, %00000000, %00000000 ;9th row of collision blocks .db %00000000, %00000000, %00000000, %00000000 ;10th row of collision blocks .db %00000000, %00000000, %00000000, %00000000 ;11th row of collision blocks .db %11111111, %11111111, %11110000, %11111111 ;12th row of collision blocks .db %11111111, %11111111, %11110000, %11111111 ;13th row of collision blocks .db %11111111, %11111111, %11110000, %11111111 ;14th row of collision blocks .db %11111111, %11111111, %11110000, %11111111 ;15th row of collision blocks
Code: Select all
ByteIndex = (PixelY / 16) * 4 + (PixelX / 64);
Code: Select all
BlockIndex = (PixelX / 16) AND 3
This is the absolute simplest way to arrange/access collision data I can think of.
- Posts: 2157
- Joined: Sun Jun 05, 2005 2:04 pm
- Location: Minneapolis, Minnesota, United States
Like tokumaru said, when I'm decompressing the level map down to the 8x8 pixel tile level, I can grab the tile type and put it into RAM. I use 2 pages of RAM to house the collision data for a rolling 2-screen window of level data. It gets updated as the screen scrolls. This makes collision detection very easy.
The downside to this approach is that to keep the same graphic for a 2x2 tile but have it behave differently, you have to define a new 2x2 tile and make it a different type. 5 bytes is a small sacrifice, but depending on the complexity of your levels, sacrificing one of the available 2x2 tile IDs might be more of a problem.
I appreciate the response. I'm still having a hard time understanding the use of a pointer. With your help, I was able to implement a pointer for nametable data, but I'm still confused how to implement one for this purpose.
I completely get the screen collision table you have created. I also (think) I understand how to find the character's position (in terms of tiles...calculate how many rows he is down...x16...plus how many columns he is across sort of thing). However, I'm not sure how to create a pointer and then call a reference to that pointer or where to write pointer data in this case (please forgive me if my nomenclature is off). I think this is the main reason that my approach was to literally check the value of a tile since those values already exist, however the method your describing is what I definitely would rather pursue, it was sort of *next*. If you could help me understand how the pointer would work in this instance (and subsequent instances where one might be required), that would be awesome.
I'm sorry if this is vague, but thank you so much for your thought our and quick responses!
Examples of the few games that do use 8x8 primitives include Dig Dug, Tetris, Dr. Mario, Wit's, NESnake 2, Battle City, Rampart, and other (mostly non-scrolling) games.tokumaru wrote:Very few games recognize individual tiles as entities of the level, tiles are almost exclusively a graphical element used to represent a portion of a metatile.
An 8x8-oriented game separates its background into (usually predefined) "zones". For example, in the snake games and the falling block games, the playfield has its own color set and everything else uses other color sets. In Rampart, each player's side has its own color set. Dig Dug uses color sets to represent soil strata. Battle City has destructible areas in one color set and in another. Attribute Zone is somewhere in the middle because while it uses 8x8 pixel cells, the areas of influence are 2x2 cells each. The Legend of Zelda uses "vertical columns" (16x176 pixel areas) as its primitive in outdoor areas but allows certain things like rocks to be placed at half-metatile boundaries, so long as the area just above and below the rocks uses the same color set.That's probably because without an MMC5 the NES is unable to color individual tiles arbitrarily, so it makes sense to always process them in groups of 2x2, and that includes collisions.
With that said, let's go back to games using 16x16 pixels as the primitive:
There's a general principle of not repeating yourself. This means there should usually be a single version of the truth, where "the truth" is the map layout. You'll end up designing some sort of data model and then deriving various "views" of that model. For example, if metatile value $1B represents a "crate" (or "treasure chest" or "? block"), then there will be one table describing the collision properties for $1B and other tables describing its appearance. The collision code takes the $1B and looks it up in the collision table; the drawing code takes the $1B and looks it up in the appearance table.
Super Mario Bros. divides its 256 metatile numbers into four 64-metatile subspaces, and each subspace is hardcoded to one color set whose bits are the same as bits 7 and 6 of the metatile value. But this isn't redundant information because, say, values $04 and $44 represent completely unrelated metatiles.
Once stored, i can use the figuring of the coordinate system to find what metatile is there and then read its value to determine what to do, i get this too. I'm just trying to convert my logical understanding to ASM and the quirks of the NES.
I was able to cobble together something based on all of your great advice, my intuition from high language environments, and other resources that 'works'...likely less than efficient, but for the learning exercise that this is supposed to be, it'll do! Thanks so much!