Saturday, October 5, 2013

Camera Scrolling

This is the second part of the TileMap in depth explanation.

A preface first: I want to say that in retrospect, I should have probably created a Camera class rather than rely on the TileMap to be the camera. The scrolling concept remains the same though.

I just remembered, I created an image explanation for scrolling.



The basic concept is that there are two different coordinates: the game coordinates, and the screen coordinates. Whenever I draw to the screen, I first have to translate everything over from game position to screen position.

So instead of drawing at say:

g.drawImage(image, x, y, null);

I draw at:

g.drawImage(image, x - cameraX, y - cameraY, null);  

If anyone's ever done OpenGL, it's similar to applying the (model)view matrix to transform game coordinates to camera space.

TileMap in depth

I didn't really explain how my TileMap class works, so I'll go into more detail here. If you want to follow along, I'm using the TileMap class from: https://dl.dropboxusercontent.com/u/59779278/Dragon%20Tale%20Tutorial%20P02.rar

First, I'll go over the fields.

// position
private double x;
private double y;

This is self-explanatory. These two variables indicate the top left corner of the TileMap. As the player moves to the right, the TileMap moves to the left, ie: x decreases.

// bounds
private int xmin;
private int ymin;
private int xmax;
private int ymax; 

These are the minimum and maximum bounds of the map. The position (x, y) cannot go further than these limits. This prevents the "camera" from scrolling past the edges of the TileMap.

private double tween;

This is a factor between 0 and 1 that indicates the speed in which the TileMap's position changes. A tween of 1 means that the TileMap will move to a given position instantly, where as a tween of 0.1 means that the TileMap will move 10% towards a given position. This gives a smoother scrolling effect.

// map
rivate int map[][];
private int tileSize;
private int numRows;
private int numCols;
private int width;
private int height;

The map is simply a 2d array of integers, where each integer represents a tile from the tileset. The tileSize is the size of a single tile in pixels. numRows and numCols are the number of rows and columns of the map. width and height are the dimensions of the map in pixels.

// tileset
private BufferedImage tileset;
private int numTilesAcross;
private Tile[][] tiles;

The tileset is the entire tileset image. From that, I read each individual tile subimage, and store it in the tiles array. The numTilesAcross is used to calculate the "id number" of the tiles.






// drawing
private int rowOffset;
private int colOffset;
private int numRowsToDraw;
private int numColsToDraw;

These are for drawing. They indicate the row and column that drawing should start, and how many rows and columns to draw. This is so that only part of the TileMap on the screen gets drawn, rather than the entire TileMap. (With respect to drawing, these variables aren't important because the Graphics object has a device clip, which prevents any drawing from happening outside of the Graphics' device's visible region. But from a complexity point of view, it does reduce the time from O(n^2).)



 The constructor is simple. It sets the tileSize, the tween, and the number of rows and columns to draw.

The loadTiles(String s) method reads in the tileset and cuts out each individual tile to put into the tiles array. My TileMap class isn't very scalar, in that it has a specific way of handling tilesets. The tileset image must always have 2 rows of tiles. The top row are all the non-blocked tiles, and the bottom row are all the blocked tiles.

 The loadMap(String s) methods fills in all the field variables under // map. The map file contains the number of rows and columns of the map, and the map itself, a 2d array of integers. I get the numRows, numCols, width, height, and bounds of the map.

Then there are a few getters and setters. Simple.

Next is the setPosition(double x, double y) method. This method tries to move the TileMap's (x, y) position to the given destination (x, y). This is where tween kicks in to smoothly move the TileMap to the destination. Then I make sure the position doesn't go past the bounds using fixBounds() and then calculate the row and column offsets to know where to start drawing.

The last thing is the draw(Graphics2D g) method. This is a straight forward 2d array loop. I loop through the map array starting with the rowOffset and colOffset, up to the specified numRowsToDraw and numColsToDraw. Now remember the map array contains integers of tileset "id numbers." I have to calculate, say, the id number 24 into row 2 column 4 (see tileset image above.) Since I know the numTilesAcross of the tileset, I can easily translate the id number using:

 int idNumber = map[row][col];
int translatedRow = idNumber / numTilesAcross;
int translatedCol = idNumber % numTilesAcross;

Then I just draw the given tile at the correct position and that's it. 

Hopefully, this answers some questions about the TileMap. I'll make a separate post about the "camera" scrolling.