package sg.atom2d.tools.map; import java.awt.Graphics; import java.awt.*; import java.util.*; /** * A Map is a three layered tile map. * Layer 1 is the base layer. If a tile in this layer is null, it is * replaced by the base tile (the first tile in the tile set).<p> * Layer 2 is a special layer in which tiles are rendered in front of or behind * any sprites, depending on its location.<p> * Layer 3 is rendered last, over the top of everything else.<p> * * Tiles do not have to be the same size. A standard tile is 32 by 32 * pixels. If a tile is larger, it will be rendered in place, with the origin * at the bottom-right corner of the image. This means a large tile will * extend above and to the left over the top of other tiles behind it.<p> * * the map is only responsble for drawing unchanging tilesets. All interactive * stuff usually happens in the "Scene", using sprites, although the map does * support changing tiles on the fly if for example a switch were to move a wall. * * */ public class Map { /* change the below two numbers if you want the grid size to be different. * individual tile images may be any size. */ int tileWidth = 32; int tileHeight = 32; int zoomWidth = 32; int zoomHeight = 32; int viewWidth = 400; int viewHeight = 400; GraphicsBank gfx; ArrayList changeListeners; final static int LAYERS = 3; Tile[][][] tiles; //ground layer /** * Maps are constructed with a width and height, originally * having all null tiles. */ public Map(int width, int height) { tiles = new Tile[width][height][LAYERS]; changeListeners = new ArrayList(); } /** * Maps are constructed with a width and height, originally * having all null tiles. * * You can also specify the base tile width and height. */ public Map(int width, int height, int tileWidth, int tileHeight) { this(width, height); this.tileWidth = tileWidth; this.tileHeight = tileHeight; zoomWidth = tileWidth; zoomHeight = tileHeight; } /** * set the tile at location x, y in layer z. * Layers are: * 0: ground level. * 1: on or just above ground. * 2: above ground (and everything else). * @param x the X-location of the tile * @param y the Y-location of the tile. * @param z the layer the tile is on. * @param t the new Tile to set. */ public void setTile(int x, int y, int z, Tile t) { tiles[x][y][z] = t; } void setZoom(float z) { zoomWidth = (int)(tileWidth * z); zoomHeight = (int)(tileHeight * z); } /** * used only by the game. Sets the width and height in pixels * to draw when rendering this map. */ public void setViewSize(int width, int height) { this.viewWidth = width; this.viewHeight = height; } /** * used by the game. Renders the map to the given graphics context * at the given offsets. */ public void render(Graphics g, int offsetX, int offsetY) { //System.out.println("Render Map"); int minX = Math.max(offsetX/zoomWidth, 0); int maxX = Math.min((offsetX+viewWidth)/zoomWidth, tiles.length); int minY = Math.max(offsetY/zoomHeight, 0); int maxY = Math.min((offsetY+viewHeight)/zoomHeight, tiles[0].length); for(int z=0; z<LAYERS; z++) { for(int x=minX; x<maxX; x++) { for(int y=minY; y<maxY; y++) { if(tiles[x][y][z]!= null) tiles[x][y][z].render(g, x*zoomWidth -offsetX, y*zoomHeight -offsetY); } } } } public void render(Graphics g, Camera c) { setViewSize(c.viewWidth, c.viewHeight); render(g, (int)(c.viewx - viewWidth/2), (int)(c.viewy - viewHeight/2)); } /** * used by the level editor, renders a portion of the map with the * given origin and visible dimension.<p> * The origin and size are needed to increase efficiency by * only rendering what is on screen. * * @param origin the top-left visible corner of the map. * @param size the size to render. */ public void render(Graphics g, Point origin, Dimension size) { //System.out.println("Render Map"); double minX = Math.max(origin.getX()/zoomWidth, 0); double maxX = Math.min((origin.getX()+size.getWidth())/zoomWidth, tiles.length); double minY = Math.max(origin.getY()/zoomHeight, 0); double maxY = Math.min((origin.getY()+size.getHeight())/zoomHeight, tiles[0].length); for(int z=0; z<LAYERS; z++) { for(int y=(int)minY; y<maxY; y++) { for(int x=(int)minX; x<maxX; x++) { if(tiles[x][y][z]!= null) { tiles[x][y][z].render(g, x*zoomWidth+zoomWidth, y*zoomHeight+zoomHeight); } } } } } public void render(Graphics g, Point origin, Dimension size, int layer) { //System.out.println("Render Map"); double minX = Math.max(origin.getX()/zoomWidth, 0); double maxX = Math.min((origin.getX()+size.getWidth())/zoomWidth, tiles.length); double minY = Math.max(origin.getY()/zoomHeight, 0); double maxY = Math.min((origin.getY()+size.getHeight())/zoomHeight, tiles[0].length); int z = layer; for(int y=(int)minY; y<maxY; y++) { for(int x=(int)minX; x<maxX; x++) { if(tiles[x][y][z]!= null) { tiles[x][y][z].render(g, x*zoomWidth+zoomWidth, y*zoomHeight+zoomHeight); } } } } /** * gets the width of the map in tiles */ public int getWidth() { return tiles.length; } /** *gets the height of the map in tiles. */ public int getHeight() { return tiles[0].length; } /** * gets the standard width of a tile in the map. */ public int getTileWidth() { return tileWidth; } /** * gets the standard height of a tile in the map. */ public int getTileHeight() { return tileHeight; } public int getZoomWidth() { return zoomWidth; } public int getZoomHeight() { return zoomHeight; } /** *gets the tile at the given location. */ public Tile getTile(int x, int y, int z) { return tiles[x][y][z]; } /** * Resize the map to newWidth, newHeight. * may clip the edges. **/ void resize(int newWidth, int newHeight) { resize(newWidth, newHeight, LAYERS); } /** * Resize with layers **/ void resize(int newWidth, int newHeight, int newLayers) { System.out.println("Call resize"); int w, h, l; newWidth = Math.max(1, newWidth); newHeight = Math.max(1, newHeight); Tile[][][] newTiles = new Tile[newWidth][newHeight][newLayers]; w = Math.min(newWidth, tiles.length); h = Math.min(newHeight, tiles[0].length); l = Math.min(newLayers, tiles[0][0].length); for(int x = 0; x < w; x++) { for(int y = 0; y < h; y++) { for(int j = 0; j < l; j++) { newTiles[x][y][j] = tiles[x][y][j]; } } } tiles = newTiles; } /** * Move the map tiles **/ void shift(int offX, int offY) { System.out.println("Shift to new offset " + offX + ", " + offY + "."); Tile[][][] newTiles = new Tile[tiles.length][tiles[0].length][LAYERS]; int xStart = Math.max(0, -offX); int yStart = Math.max(0, -offY); int xEnd = Math.min(tiles.length, tiles.length - offX); int yEnd = Math.min(tiles[0].length, tiles[0].length - offY); for(int x = xStart; x < xEnd; x++) { for(int y = yStart; y < yEnd; y++) { for(int l = 0; l < LAYERS; l++) { newTiles[x + offX][y + offY][l] = tiles[x][y][l]; } } } tiles = newTiles; } void clear() { for(int x = 0; x < tiles.length; x++) { for(int y = 0; y < tiles[0].length; y++) { for(int l = 0; l < LAYERS; l++) { tiles[x][y][l] = null; } } } } /** * Provides a no-nonsense integer array version of this map. * The numbers are the tile IDs. * The dimensions are: x, y, layer. **/ public int[][][] toIntArray() { int set[][][] = new int[tiles.length][tiles[0].length][tiles[0][0].length]; for(int x = 0; x < tiles.length; x++) { for(int y = 0; y < tiles[0].length; y++) { for(int l = 0; l < LAYERS; l++) { if(tiles[x][y][l] != null) { set[x][y][l] = tiles[x][y][l].number; } else { set[x][y][l] = 0; } } } } return set; } /** * Means of setting all the tiles on a map easily. * You can set a different GraphicsBank as well. **/ public void setAllTiles(int[][][] set, GraphicsBank bank) { gfx = bank; resize(tiles.length, tiles[0].length, tiles[0][0].length); /* if(set.length == tiles.length && set[0].length == tiles[0].length && set[0][0].length == tiles[0][0].length) { */ for(int x = 0; x < tiles.length; x++) { for(int y = 0; y < tiles[0].length; y++) { for(int l = 0; l < LAYERS; l++) { tiles[x][y][l] = bank.getTile(set[x][y][l]); } } } /* } else { System.out.println("Use resize() Before calling setAllTiles()."); throw new RuntimeException("The int array provided does not match the map dimensions."); } */ } /* Note: Behaviour unknown. */ public void setTileset(GraphicsBank gfx) { int[][][] set = this.toIntArray(); this.setAllTiles(set, gfx); } public void addChangeListener(MapChangeListener l) { changeListeners.add(l); } public void removeChangeListener(MapChangeListener l) { changeListeners.remove(l); } private void fireChangingEvent(boolean m) { Iterator i = changeListeners.iterator(); ((MapChangeListener)i.next()).mapChanging(m); } private void fireChangedEvent(boolean m) { Iterator i = changeListeners.iterator(); ((MapChangeListener)i.next()).mapChanged(m); } }