2D Platformer Camera

Started by
6 comments, last by Onigiri Flash 8 years, 8 months ago

I'm having a bit of trouble programming the camera view for a 2D platformer. Essentially, I want to scroll the camera in a tile-based level when the player moves in a particular direction. I'm storing the entire level in an array like so:


// Just an example

var level = [ 
  0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
  0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
  0, 0, 0, 1, 0, 0, 0, 0, 0, 0,
  1, 1, 1, 1, 0, 1, 1, 1, 1, 1
];

// 0 = empty, 1 = tile

I know how to scroll the camera by altering its (X,Y) coordinates within the overworld width and height, but how do I figure out which tiles to draw on the canvas (only the tiles within the camera's view, including partial tiles)?

Advertisement

I know how to scroll the camera by altering its (X,Y) coordinates within the overworld width and height, but how do I figure out which tiles to draw on the canvas (only the tiles within the camera's view, including partial tiles)?


TBH, I don't think its worth it to try to "cull" the tiles

I'm not sure what framework / lib you are using, but I am assuming since this is a 2D game you are using a sprite batcher to draw stuff. Internally, I would think that the sprite batcher would cull off things that are not on the screen. Basically speaking in terms of the GPU, sprites added to a batch that are actually off screen wont make it past the fragment shader stage. Saving you some GPU time.

However, they are not being culled off on the CPU side of things. In order to cull them off on the CPU side of things, this would require some kind of check. Such as:


for(int i = 0; i < tiles.length; ++i)
{
   if(tile[i].Intersects(ScreenBox)
      tile[i].Draw(spriteBatch);
}

BUT the reason I say its not worth it is because this check still costs CPU time. So in the end you are probably going to end up spending more CPU time just doing the check, then just telling the sprite batcher to draw it and have the GPU deal with culling it.

The way I have done this in the past is to find the tile furthest to the left in the view. That is, find the x-coord that is all the way to the left in your view and divide that by the tile size. Then find how many tiles goes across the screen. This is ofc only computed once.
Lastly, do the same on the y-axis, and tada!
Now you can start drawing from the tile you found in the top left corner (assuming top left origin) of your view, and draw however many tiles you need.

However, I'm sure noodleBowl is correct, and that the batcher will handle this for you :)
Programmer of HolyPoly games.

Tile culling logic for this type of game is so trivial that there's no reason why you shouldn't do it. Not only will you avoid sending throwaway data to the rendering engine, but knowing which tiles are on-screen can allow for certain gameplay benefits and optimizations.

Just transform the upper-left screen coordinate and lower-right screen coordinate to tile-space (you can also go directly from view-space coordinates to save a transformation). Truncate to integers, and that's your upper-left and lower-right visible tile bounds.

Thank you so much for responding, everyone! I forgot to mention that I'm using HTML5 canvas and Javascript for this game (no engine, just my own implementation). This is a drawing of what I wish to achieve (Mario as a reference):

example.png super_mario_allstars_4.jpg

The red tiles represent partial tiles that become visible when the camera scrolls. The camera represents a bounding box within the level than contains the viewport.

So I would have to store an array of static bounding boxes for each tile and check for collision with the camera's bounding box. Combined with Istarnion's method, I think this will work well.

Does your 2D camera or any of the tiles rotate? If not, your camera and all tiles are axis-aligned bounding boxes (AABBs). For a simple culling test, you could just loop through every tile in your level, and do a simple AABB intersection test. Looping through each tile in your scene can also be a bottleneck if you have a lot of tiles. A better solution would be to use a scene graph at that point. You'd pre-calculate them into a quadtree, or whatever scene graph you'd like. Then, loop through the scene graph's nodes recursively, and add all bottom-level nodes' tiles to the batch buffer for that frame.

So I would have to store an array of static bounding boxes for each tile and check for collision with the camera's bounding box. Combined with Istarnion's method, I think this will work well.

No.
You only use what Istarnion mentioned.
Calculating the start and end indices already instantly gives you the correct horizontal and vertical ranges of tiles to draw. Adding AABB tests on top of that is completely superfluous, not to mention poor in performance and costly in memory. There is absolutely no reason to be discussing AABB’s here at all.

Assume tile sizes are 32×32.

unsigned int uiLeft = camPos.x / 32; 				// 32 = Tile Width.
unsigned int uiRight = (camPos.x + camPos.width - 1) / 32; 	// 32 = Tile Width.
unsigned int uiTop = camPos.y / 32; 				// 32 = Tile Height.
unsigned int uiBottom = (camPos.y + camPos.height - 1) / 32; 	// 32 = Tile Height.
for ( unsigned int Y = uiTop; Y <= uiBottom; ++Y ) {
	for ( unsigned int X = uiLeft; X <= uiRight; ++X ) {
		DrawTile( X, Y );
	}
}
It is trivial.


L. Spiro

I restore Nintendo 64 video-game OST’s into HD! https://www.youtube.com/channel/UCCtX_wedtZ5BoyQBXEhnVZw/playlists?view=1&sort=lad&flow=grid

So I would have to store an array of static bounding boxes for each tile and check for collision with the camera's bounding box. Combined with Istarnion's method, I think this will work well.

No.
You only use what Istarnion mentioned.
Calculating the start and end indices already instantly gives you the correct horizontal and vertical ranges of tiles to draw. Adding AABB tests on top of that is completely superfluous, not to mention poor in performance and costly in memory. There is absolutely no reason to be discussing AABB’s here at all.

Assume tile sizes are 32×32.


unsigned int uiLeft = camPos.x / 32; 				// 32 = Tile Width.
unsigned int uiRight = (camPos.x + camPos.width - 1) / 32; 	// 32 = Tile Width.
unsigned int uiTop = camPos.y / 32; 				// 32 = Tile Height.
unsigned int uiBottom = (camPos.y + camPos.height - 1) / 32; 	// 32 = Tile Height.
for ( unsigned int Y = uiTop; Y <= uiBottom; ++Y ) {
	for ( unsigned int X = uiLeft; X <= uiRight; ++X ) {
		DrawTile( X, Y );
	}
}

Thank you very much for this! I understand how this works now.


It is trivial.

I'm sorry. happy.png I've never programmed a [platformer] tilemap before...

This topic is closed to new replies.

Advertisement