Hey guys,
I am really trying to implement this for a PC game written in C++, but I can't imagine the abstraction to be any different than if applying it to the NES.
So I have implemented a tilemap-based background scheme and sprites (similar to the NES). For my collision detection (CD), I am scanning through several hotspots near my character sprite to see if they are touching any neighboring solid tiles. So no problems with collision detection in one tilemap.
The problem comes when I try to scroll (horizontally for now). Actually, CD works fine until the right edge of the character's bounding box goes into the next tilemap (the area I'm scrolling into). How does collision detection work when the character is on the boundary of two separate maps? How is this achieved on the NES?
You should probably have your tilemap be larger than the display area. This makes it easier to work with- you just have to work with larger coordinate fields.
If it's a screen transition (like a Metroid door or a Megaman ladder) then you're generally not colliding during transition, because the player doesn't have control.
I see what you mean, but this isn't exactly transition screens but just different sections of the same level.
Right now, my collision detection algo only looks for collisions with tiles in one section. When my character is in between sections, how do I handle that?
If I understand the question correctly, usually how this is done in games (as I understand it) is by having the "map region" or ("tile map area") be larger than what's visually on screen (by a substantial amount; commonly an entire extra screen horizontally and vertically). You then end up tracking your sprites' X/Y offsets internally (i.e. further than what's visually on screen). For example, say your visual screen is 256x256 pixels; you'd make your "map region" 512x512, and therefore your sprites X/Y coordinates (pretend all sprites are 8x8 pixels) can range from 0 to 511. I suppose you could also do it with signed numbers (ex. -255 to 255) if that's easier.
You have to keep track of X/Y coordinates of all your sprites/etc. anyway, right? Those are what you'd compare against, regardless of what's actually visually on screen (or rather: anything that's "not rendered visually on screen" would therefore not be considered for collision -- though it's up to you). Hence, the "between sections" aspect becomes moot. Does that make sense?
Take my comment with a grain of salt, though -- I haven't done actual gamedev. I look forward to seeing all the responses. Things like this have always been difficult for me to solve (smooth screen scrolling is another common one for me).
Ah. Well, there are several options. Here are a few:
- Force a clean transition by ignoring collision and taking control away from player when triggered (see: Castlevania or Metroid, where there's a door)
- Load the new map's collision into its own space in RAM much as you load the new map's graphics into RAM, making a sort of "collision camera region" which is agnostic to map segments that is use
- Have a special case in your collision routine for when you're transiting between sections that collides the player against both maps
Think Quick's level editor forced the edges of screens to have the same solidity as each other, so that any open map-edge was always traversable, and you couldn't get stuck in a wall nor have an edge that is mysteriously solid. This may be something you want, but if the regions are freely-scrolling into each other, then it won't be an important consideration.
The normal thing to do is to compute everything in world coordinates. Just forget about the screen for a moment, and think only about the game world. The level starts at X coordinate 0 and can be up to 65536 pixels wide, if you use 16-bit coordinates (not considering the fractional part), and everything should move within this space. Collisions would be handled exactly like you'd handle them in a single screen have, except now you have a really large "screen", as big as the entire level.
After everything has been updated, objects have been moved and collisions have been properly detected and processed, you can convert the world coordinates into screen coordinates so you can render the game's display. This is done by subtracting the camera's coordinates from the objects' coordinates. For example, if an object is at X coordinate 540 and the camera is at 520, the screen position for this object is 540 - 520 = 20, because the object is only 20 pixels past the left edge of the camera, so it will be displayed 20 pixels past the left edge of the screen.
The trick is to not scroll anything per se, and instead maintain a stationary world where objects can move freely. Then, just draw a small section of this large world on the screen, converting coordinates as necessary. Scrolling will then simply be a natural consequence of you gradually moving this viewport through the level over time.
All of these options make perfect sense. I'll test some of these out and hopefully be back soon. Thanks, guys.
I'm back guys,
So while the idea of making a world area that's larger than the viewable area was great, it doesn't eliminate the problem, but prolongs the inevitable.
My window area is 25x25 tiles, so I made a 50x50 master region where everything happens (arranged like 2x2, similar to NES nametables). Scrolling and collision detection works great, but if I wanted horizontally long levels like in SMB, I still have to load the next section of the level into one of the unused tilemaps and check for collisions across maps.
I guess an analogous problem in the NES realm would be if your character is on the border of nametables $2C00 and $2800 and you have to check for collisions in both tables.
Super Mario Bros. caches the collision map centered around the player in a 32x13-metatile circular buffer, internally organized as two 16x13-byte arrays.
On a 32-bit or larger system, you can just give the collision map a get(x, y) method that uses a modulo operator to handle wrapping around the side of the 50x50-metatile sliding window. Then run all accesses to the collision map through this method.