Efficient game maps and Scrolling

No. The code BP posted always draws the character at the center of the screen. StartX is the left edge of your view and is computed by taking the player’s position and subtracting 1/2 of the screen width.

I foresee an easy optimization in your future. :slight_smile:

I’ll post something better than that contrived example here in a bit, a little more concrete.

I might give it a shot, but I just use a simple 3D int array that I iterate though, so it runs pretty fast. The only time it ever does anything beyong the basic iteration is if it detects X/Y is within the viewport and has a value that isnt 0.

I would really appreciate that, I’m still having a bit of trouble, but i got the general idea.

Yay! Inspiration.

Heres the very basic idea of drawing a scrolling tile map in kind of pseudocode.


for (int x = 0; x < 18; x++){
for (int y = 0; y < 14; y++){
    Tile.draw(tileSprites[map[x+cameraX][y+cameraY]], (x+cameraX)*TileSize, (y+cameraY)*TileSize);
}
}

simply change the cameraX and cameraY with the arrow keys and it will move around.
thats the first challenge, after that works you can get it to draw the correct amount of tiles to fill the screen, limit the camera so it doesnt try to draw tiles which are off the edge of the map, then add in your entities, which fit in quite easily, depending on how you stored them.

Sorry but some of that confuses me. I should probably look into using 2d arrays.

I’d still be worried about the impacts on caching, but any technique that provides acceptable results is a good technique. 8)

well what i have there is a 1d array called tileSprites[] which stores each type of sprite
then i have a 2d array called map[][] which stores an index for which sprite to use from tileSprites.

taking into consideration all the code posted here, I would suggest doing something like I did to setup your tile engine:

Here’s how my system works (generally speaking):

  • it has a 3D int array, for X, Y and Layer coordinates.
  • The entire tileset is stored in 1 or more spritesheets.
  • The sprite sheets are iterated through, assigning each image on each sheet a global tileID (IE: 1 = dirt, 2 = grass, etc) and sticks them in a HashMap<Integer, Image>. (Where Integer is the tileID of that tile and Image is the sprite, not the entire sheet)
  • When rendering, you just iterate through the 3D int array (or 2D if you dont care about layers)
  • Every time a tileID is found, go to a HashMap and ask for the value (the image) at the integer key(the global tile ID) and render it.

My map engine is a lot more complicated than that, but thats the super basic basics on how I fetch images off sprite sheets without making a 2D/3D image array or something silly. The end result is your entire map is just a ton of ints in an array.

Can you show some psuedocode for this. I get what your saying with the IDs, but I want to see how to apply this and find that ID.

Got it. ;D

hmm, I’ll try. This is assuming you already have a spritesheet and image class of some kind that can grab the variables for you. Since I don’t know if you’re using Slick, Lib, LWJGL or even just J2D you’ll have to plug that stuff in yourself.

Step one: Load the images into a HashSet,assigning them a tileID in the order you stick them in:


	private HashMap<Integer, Image> tiles = new HashMap<Integer, Image>();
	private YourSpriteSheetClass tileSpriteSheet = new //Your sprite sheet.

	private void loadTiles(){
		
		int tileSheetH = tileSpriteSheet.getHorizontalCount(); //Get your horizontal sheet count (how many sprites wide the sheet is)
		int tileSheetV = tileSpriteSheet.getVerticalCount(); //Get your vertical sheet count (how many sprite tall the sheet is)
		
		for (int x = 0; x < tileSheetH; x++) {
			for (int y = 0; y < tileSheetV; y++) {
				tiles.put((x*tileSheetV)+y, tileSheets.get(tileSheet).getSprite(x, y));
			}
		}
	}

	//Getter you'll need for the other class later to fetch the images.
	public THashMap<Integer, Image> getTiles(){return tiles;}

Step two: Render the map by fetching the tile IDs and grabbing the image from the HashMap:


//if you don't care about layers, make it int[][] and remove mapLayers.
private int mapHeight = //your map height
private int mapWidth = //your map width
private int mapLayers = //How many layers you have, if any.
private int[][][] mapArray; = new int[mapHeight][mapWidth][mapLayers];

//NOTE::: You also need a method to build the actual mapArray with values!

private void render(){
	for (int x = 0; x < mapWidth; x++) {
		for (int y = 0; y < mapHeight; y++) {
			//Where you're seeing if X/Y is even in the viewport, if it's not, don't bother rendering.
			if (((x*tileWidth)+mapX > tileWidth*-1) && ((x*tileWidth)+mapX < displayWidth+tileWidth) && ((y*tileHeight)+mapY > tileHeight*-1) && ((y*tileHeight)+mapY < displayHeight+tileHeight)){ 
				for (int l = 0; l < mapLayers; l++) {
					//Assuming you store tiles by a tileID and tileID 0 is "no tile", you just skip attempting to render it.
					if (!(mapArray[x][y][l] == 0){ 
						//Draw the tile! Depending on what libraries you use, everything after .get may need modified to work with your image class.
						tileLoaderClassFromStepOne.getTiles().get(mapArray[x][y][l]).draw(mapX+(x*tileWidth), mapY+(y*tileHeight));
					}
				}
			}
		}
	}
}

I think that covers it for the basics.

(EDIT: Remember, this is still using my technically-not-as-good-as-BP’s loop, if you want to squeeze some performance out, use his loop.)

Here’s an updated version of the rendering code, using BP’s method.


private void render(){
	//The + and - 2 is to push the rendering bounds slightly outside the
	//viewport, that way you have a slight buffer of extra off-screen tiles being rendered.
	int startX = getMapRenderTileLeftX()-2;
	int startY = getMapRenderTileLeftY()-2;
	int endX = getMapRenderTileRightX()+2;
	int endY = getMapRenderTileRightY()+2;
	
	for(int x = startX; x < endX; x++) {
		for(int y = startY; y < endY; y++) {
			for (int l = 0; l < mapLayers; l++) {
				//So you dont try to fetch illegal values in your array you need these 2 if statements.
				if ((!(x < 0)) && (!(x > mapWidth-1))){
					if ((!(y < 0)) && (!(y > mapHeight-1))){
						//Assuming you store tiles by a tileID and tileID 0 is "no tile",
						//you just skip attempting to render it.
						if (!(mapArray[x][y][l] == 0){ 
							tileLoaderClassFromStepOne.getTiles().get(mapArray[x][y][l]).draw(mapX+(x*tileWidth), mapY+(y*tileHeight));
						}
					}
				}
			}
		}
	}
}

	public int getMapRenderTileLeftX(){return (int)(mapX*-1)/tileWidth;}
	public int getMapRenderTileLeftY(){return (int)(mapY*-1)/tileHeight;}
	public int getMapRenderTileRightX(){return (int)((mapX*-1)+displayWidth)/tileWidth;}
	public int getMapRenderTileRightY(){return (int)((mapY*-1)+displayHeight)/tileHeight;}

if (!(mapArray[x][y][l] == 0){

What is the 1 and 0 in those parameters?

EDIT: I’m probably going to use Slick2D because I don’t know how to animate with LWJGL, so you can refer to Slick2D.

[icode]mapArray[x][y][LETTER L][/icode] :stuck_out_tongue:

Yes, I’m one of those nutbags who actually uses the lowercase L. :slight_smile:

If you’re using slick, my code should be pretty easy to plug in play, just use slick’s image and spritesheet classes.

EDIT: The == 0 is checking if the tileID there is “0”, where “0” should be “no tile exists” and to not bother rendering anything.

Ha, okay, I think I got this. I’ll have to look into it in the morning because my brain is powering down now. Just to clarify, this code would only render what’s within the screen width and height right?

Alright. I’m going to bed (hopefully) so I can’t really answer questions until tomorrow, but here’s the prototype:

(ignore the GIF color-merge junk)

Full source: http://pastebin.java-gaming.org/8eaf049789a

Do what you want with it, but I wouldn’t want to use it as the base engine for an actual game, it’s just an example.
Also the input controls are a bit wonky, didn’t feel like sorting them out.
Read the comments for stuff you can tweak (like the clipping trim in paint() )

Did you spend a lot of time working on that for me? :’( I feel so honered, thanks! I also will check it out in the morning.

Not too much, mostly spent trying to pull J2D crap back from the long forgotten corners of my brain.